Merge pull request #1539 from MyCryptoHQ/develop

1.0.0 RC
This commit is contained in:
Daniel Ternyak 2018-04-16 20:33:32 -05:00 committed by GitHub
commit 6f32bdc216
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
227 changed files with 21054 additions and 1299 deletions

5
.gitignore vendored
View File

@ -58,4 +58,7 @@ v8-compile-cache-0/
package-lock.json
# Favicon cache
.wwp-cache
.wwp-cache
# MacOSX
.DS_Store

View File

@ -1,4 +1,4 @@
# MyCrypto Beta (VISIT [MyCryptoHQ/mycrypto.com](https://github.com/MyCryptoHQ/mycrypto.com) for the current site)<br/>Just looking to download? Grab our [latest release](https://github.com/MyCryptoHQ/MyCrypto/releases)
# MyCrypto Beta RC (VISIT [MyCryptoHQ/mycrypto.com](https://github.com/MyCryptoHQ/mycrypto.com) for the current site)<br/>Just looking to download? Grab our [latest release](https://github.com/MyCryptoHQ/MyCrypto/releases)
[![Greenkeeper badge](https://badges.greenkeeper.io/MyCryptoHq/MyCrypto.svg)](https://greenkeeper.io/)
[![Coverage Status](https://coveralls.io/repos/github/MyCryptoHQ/MyCrypto/badge.svg?branch=develop)](https://coveralls.io/github/MyCryptoHQ/MyCrypto?branch=develop)

View File

@ -10,9 +10,11 @@ import Swap from 'containers/Tabs/Swap';
import SignAndVerifyMessage from 'containers/Tabs/SignAndVerifyMessage';
import BroadcastTx from 'containers/Tabs/BroadcastTx';
import CheckTransaction from 'containers/Tabs/CheckTransaction';
import SupportPage from 'containers/Tabs/SupportPage';
import ErrorScreen from 'components/ErrorScreen';
import PageNotFound from 'components/PageNotFound';
import LogOutPrompt from 'components/LogOutPrompt';
import QrSignerModal from 'containers/QrSignerModal';
import { TitleBar } from 'components/ui';
import { Store } from 'redux';
import { pollOfflineStatus, TPollOfflineStatus } from 'actions/config';
@ -50,6 +52,7 @@ class RootClass extends Component<Props, State> {
public componentDidMount() {
this.props.pollOfflineStatus();
this.props.setUnitMeta(this.props.networkUnit);
this.addBodyClasses();
}
public componentDidCatch(error: Error) {
@ -84,6 +87,7 @@ class RootClass extends Component<Props, State> {
<Route path="/sign-and-verify-message" component={SignAndVerifyMessage} />
<Route path="/tx-status" component={CheckTransaction} exact={true} />
<Route path="/pushTx" component={BroadcastTx} />
<Route path="/support-us" component={SupportPage} exact={true} />
<RouteNotFound />
</Switch>
</CaptureRouteNotFound>
@ -95,18 +99,40 @@ class RootClass extends Component<Props, State> {
: BrowserRouter;
return (
<Provider store={store} key={Math.random()}>
<Router key={Math.random()}>
<React.Fragment>
{process.env.BUILD_ELECTRON && <TitleBar />}
{routes}
<LegacyRoutes />
<LogOutPrompt />
</React.Fragment>
</Router>
</Provider>
<React.Fragment>
<Provider store={store} key={Math.random()}>
<Router key={Math.random()}>
<React.Fragment>
{process.env.BUILD_ELECTRON && <TitleBar />}
{routes}
<LegacyRoutes />
<LogOutPrompt />
<QrSignerModal />
</React.Fragment>
</Router>
</Provider>
<div id="ModalContainer" />
</React.Fragment>
);
}
private addBodyClasses() {
const classes = [];
if (process.env.BUILD_ELECTRON) {
classes.push('is-electron');
if (navigator.appVersion.includes('Win')) {
classes.push('is-windows');
} else if (navigator.appVersion.includes('Mac')) {
classes.push('is-osx');
} else {
classes.push('is-linux');
}
}
document.body.className += ` ${classes.join(' ')}`;
}
}
const LegacyRoutes = withRouter(props => {

View File

@ -1,10 +1,15 @@
import * as interfaces from './actionTypes';
import { TypeKeys } from './constants';
export type TToggleOffline = typeof toggleOffline;
export function toggleOffline(): interfaces.ToggleOfflineAction {
export function setOnline(): interfaces.SetOnlineAction {
return {
type: TypeKeys.CONFIG_TOGGLE_OFFLINE
type: TypeKeys.CONFIG_SET_ONLINE
};
}
export function setOffline(): interfaces.SetOfflineAction {
return {
type: TypeKeys.CONFIG_SET_OFFLINE
};
}
@ -48,6 +53,14 @@ export function changeNodeIntent(payload: string): interfaces.ChangeNodeIntentAc
};
}
export type TChangeNodeIntentOneTime = typeof changeNodeIntentOneTime;
export function changeNodeIntentOneTime(payload: string): interfaces.ChangeNodeIntentOneTimeAction {
return {
type: TypeKeys.CONFIG_NODE_CHANGE_INTENT_ONETIME,
payload
};
}
export type TChangeNodeForce = typeof changeNodeForce;
export function changeNodeForce(payload: string): interfaces.ChangeNodeForceAction {
return {

View File

@ -2,9 +2,12 @@ import { TypeKeys } from './constants';
import { CustomNodeConfig, StaticNodeConfig } from 'types/node';
import { CustomNetworkConfig } from 'types/network';
/*** Toggle Offline ***/
export interface ToggleOfflineAction {
type: TypeKeys.CONFIG_TOGGLE_OFFLINE;
export interface SetOnlineAction {
type: TypeKeys.CONFIG_SET_ONLINE;
}
export interface SetOfflineAction {
type: TypeKeys.CONFIG_SET_OFFLINE;
}
export interface ToggleAutoGasLimitAction {
@ -36,6 +39,13 @@ export interface ChangeNodeIntentAction {
type: TypeKeys.CONFIG_NODE_CHANGE_INTENT;
payload: string;
}
/*** Change Node Onetime ***/
export interface ChangeNodeIntentOneTimeAction {
type: TypeKeys.CONFIG_NODE_CHANGE_INTENT_ONETIME;
payload: string;
}
/*** Force Change Node ***/
export interface ChangeNodeForceAction {
type: TypeKeys.CONFIG_NODE_CHANGE_FORCE;
@ -95,7 +105,8 @@ export type NodeAction =
export type MetaAction =
| ChangeLanguageAction
| ToggleOfflineAction
| SetOnlineAction
| SetOfflineAction
| ToggleAutoGasLimitAction
| PollOfflineStatus
| SetLatestBlockAction;

View File

@ -1,6 +1,9 @@
export enum TypeKeys {
CONFIG_LANGUAGE_CHANGE = 'CONFIG_LANGUAGE_CHANGE',
CONFIG_SET_ONLINE = 'CONFIG_SET_ONLINE',
CONFIG_SET_OFFLINE = 'CONFIG_SET_OFFLINE',
CONFIG_TOGGLE_OFFLINE = 'CONFIG_TOGGLE_OFFLINE',
CONFIG_TOGGLE_AUTO_GAS_LIMIT = 'CONFIG_TOGGLE_AUTO_GAS_LIMIT',
CONFIG_POLL_OFFLINE_STATUS = 'CONFIG_POLL_OFFLINE_STATUS',
@ -10,6 +13,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_INTENT_ONETIME = 'CONFIG_NODE_CHANGE_INTENT_ONETIME',
CONFIG_NODE_CHANGE_FORCE = 'CONFIG_NODE_CHANGE_FORCE',
CONFIG_ADD_CUSTOM_NODE = 'CONFIG_ADD_CUSTOM_NODE',

View File

@ -0,0 +1,117 @@
import {
SetTimeBountyFieldAction,
SetWindowSizeFieldAction,
SetWindowStartFieldAction,
SetScheduleTimestampFieldAction,
SetScheduleTypeAction,
SetSchedulingToggleAction,
SetScheduleTimezoneAction,
SetScheduleGasPriceFieldAction,
SetScheduleGasLimitFieldAction,
SetScheduleDepositFieldAction,
SetScheduleParamsValidityAction
} from '../actionTypes';
import { TypeKeys } from 'actions/schedule/constants';
type TSetTimeBountyField = typeof setTimeBountyField;
const setTimeBountyField = (
payload: SetTimeBountyFieldAction['payload']
): SetTimeBountyFieldAction => ({
type: TypeKeys.TIME_BOUNTY_FIELD_SET,
payload
});
type TSetWindowSizeField = typeof setWindowSizeField;
const setWindowSizeField = (
payload: SetWindowSizeFieldAction['payload']
): SetWindowSizeFieldAction => ({
type: TypeKeys.WINDOW_SIZE_FIELD_SET,
payload
});
type TSetWindowStartField = typeof setWindowStartField;
const setWindowStartField = (
payload: SetWindowStartFieldAction['payload']
): SetWindowStartFieldAction => ({
type: TypeKeys.WINDOW_START_FIELD_SET,
payload
});
type TSetScheduleTimestampField = typeof setScheduleTimestampField;
const setScheduleTimestampField = (
payload: SetScheduleTimestampFieldAction['payload']
): SetScheduleTimestampFieldAction => ({
type: TypeKeys.SCHEDULE_TIMESTAMP_FIELD_SET,
payload
});
type TSetScheduleType = typeof setScheduleType;
const setScheduleType = (payload: SetScheduleTypeAction['payload']): SetScheduleTypeAction => ({
type: TypeKeys.SCHEDULE_TYPE_SET,
payload
});
type TSetSchedulingToggle = typeof setSchedulingToggle;
const setSchedulingToggle = (
payload: SetSchedulingToggleAction['payload']
): SetSchedulingToggleAction => ({
type: TypeKeys.SCHEDULING_TOGGLE_SET,
payload
});
type TSetScheduleTimezone = typeof setScheduleTimezone;
const setScheduleTimezone = (
payload: SetScheduleTimezoneAction['payload']
): SetScheduleTimezoneAction => ({
type: TypeKeys.SCHEDULE_TIMEZONE_SET,
payload
});
type TSetScheduleGasPriceField = typeof setScheduleGasPriceField;
const setScheduleGasPriceField = (payload: SetScheduleGasPriceFieldAction['payload']) => ({
type: TypeKeys.SCHEDULE_GAS_PRICE_FIELD_SET,
payload
});
type TSetScheduleGasLimitField = typeof setScheduleGasLimitField;
const setScheduleGasLimitField = (payload: SetScheduleGasLimitFieldAction['payload']) => ({
type: TypeKeys.SCHEDULE_GAS_LIMIT_FIELD_SET,
payload
});
type TSetScheduleDepositField = typeof setScheduleDepositField;
const setScheduleDepositField = (payload: SetScheduleDepositFieldAction['payload']) => ({
type: TypeKeys.SCHEDULE_DEPOSIT_FIELD_SET,
payload
});
type TSetScheduleParamsValidity = typeof setScheduleParamsValidity;
const setScheduleParamsValidity = (payload: SetScheduleParamsValidityAction['payload']) => ({
type: TypeKeys.SCHEDULE_PARAMS_VALIDITY_SET,
payload
});
export {
TSetWindowSizeField,
TSetWindowStartField,
TSetTimeBountyField,
TSetScheduleTimestampField,
TSetScheduleType,
TSetSchedulingToggle,
TSetScheduleTimezone,
TSetScheduleGasPriceField,
TSetScheduleGasLimitField,
TSetScheduleDepositField,
TSetScheduleParamsValidity,
setTimeBountyField,
setWindowSizeField,
setWindowStartField,
setScheduleTimestampField,
setScheduleType,
setSchedulingToggle,
setScheduleTimezone,
setScheduleGasPriceField,
setScheduleGasLimitField,
setScheduleDepositField,
setScheduleParamsValidity
};

View File

@ -0,0 +1,7 @@
export * from './fields';
export * from './scheduleTimestamp';
export * from './scheduleType';
export * from './schedulingToggle';
export * from './timeBounty';
export * from './windowSize';
export * from './windowStart';

View File

@ -0,0 +1,21 @@
import { TypeKeys } from 'actions/schedule';
import {
SetCurrentScheduleTimestampAction,
SetCurrentScheduleTimezoneAction
} from '../actionTypes/scheduleTimestamp';
export type TSetCurrentScheduleTimestamp = typeof setCurrentScheduleTimestamp;
export const setCurrentScheduleTimestamp = (
payload: SetCurrentScheduleTimestampAction['payload']
): SetCurrentScheduleTimestampAction => ({
type: TypeKeys.CURRENT_SCHEDULE_TIMESTAMP_SET,
payload
});
export type TSetCurrentScheduleTimezone = typeof setCurrentScheduleTimezone;
export const setCurrentScheduleTimezone = (
payload: SetCurrentScheduleTimezoneAction['payload']
): SetCurrentScheduleTimezoneAction => ({
type: TypeKeys.CURRENT_SCHEDULE_TIMEZONE_SET,
payload
});

View File

@ -0,0 +1,10 @@
import { TypeKeys } from 'actions/schedule';
import { SetCurrentScheduleTypeAction } from '../actionTypes/scheduleType';
export type TSetCurrentScheduleType = typeof setCurrentScheduleType;
export const setCurrentScheduleType = (
payload: SetCurrentScheduleTypeAction['payload']
): SetCurrentScheduleTypeAction => ({
type: TypeKeys.CURRENT_SCHEDULE_TYPE,
payload
});

View File

@ -0,0 +1,12 @@
import { SetCurrentSchedulingToggleAction } from '../actionTypes/schedulingToggle';
import { TypeKeys } from 'actions/schedule';
type TSetCurrentSchedulingToggle = typeof setCurrentSchedulingToggle;
const setCurrentSchedulingToggle = (
payload: SetCurrentSchedulingToggleAction['payload']
): SetCurrentSchedulingToggleAction => ({
type: TypeKeys.CURRENT_SCHEDULING_TOGGLE,
payload
});
export { TSetCurrentSchedulingToggle, setCurrentSchedulingToggle };

View File

@ -0,0 +1,12 @@
import { SetCurrentTimeBountyAction } from '../actionTypes/timeBounty';
import { TypeKeys } from 'actions/schedule';
type TSetCurrentTimeBounty = typeof setCurrentTimeBounty;
const setCurrentTimeBounty = (
payload: SetCurrentTimeBountyAction['payload']
): SetCurrentTimeBountyAction => ({
type: TypeKeys.CURRENT_TIME_BOUNTY_SET,
payload
});
export { setCurrentTimeBounty, TSetCurrentTimeBounty };

View File

@ -0,0 +1,12 @@
import { SetCurrentWindowSizeAction } from '../actionTypes/windowSize';
import { TypeKeys } from 'actions/schedule';
type TSetCurrentWindowSize = typeof setCurrentWindowSize;
const setCurrentWindowSize = (
payload: SetCurrentWindowSizeAction['payload']
): SetCurrentWindowSizeAction => ({
type: TypeKeys.CURRENT_WINDOW_SIZE_SET,
payload
});
export { setCurrentWindowSize, TSetCurrentWindowSize };

View File

@ -0,0 +1,12 @@
import { SetCurrentWindowStartAction } from '../actionTypes/windowStart';
import { TypeKeys } from 'actions/schedule';
type TSetCurrentWindowStart = typeof setCurrentWindowStart;
const setCurrentWindowStart = (
payload: SetCurrentWindowStartAction['payload']
): SetCurrentWindowStartAction => ({
type: TypeKeys.CURRENT_WINDOW_START_SET,
payload
});
export { setCurrentWindowStart, TSetCurrentWindowStart };

View File

@ -0,0 +1,5 @@
import { ScheduleFieldAction } from './fields';
export * from './fields';
export type ScheduleAction = ScheduleFieldAction;

View File

@ -0,0 +1,116 @@
import { TypeKeys } from 'actions/schedule/constants';
import { Wei } from 'libs/units';
interface SetTimeBountyFieldAction {
type: TypeKeys.TIME_BOUNTY_FIELD_SET;
payload: {
raw: string;
value: Wei | null;
};
}
interface SetWindowSizeFieldAction {
type: TypeKeys.WINDOW_SIZE_FIELD_SET;
payload: {
raw: string;
value: Wei | null;
};
}
interface SetWindowStartFieldAction {
type: TypeKeys.WINDOW_START_FIELD_SET;
payload: {
raw: string;
value: number | null;
};
}
interface SetScheduleTimestampFieldAction {
type: TypeKeys.SCHEDULE_TIMESTAMP_FIELD_SET;
payload: {
raw: string;
value: Date;
};
}
interface SetScheduleTypeAction {
type: TypeKeys.SCHEDULE_TYPE_SET;
payload: {
raw: string;
value: string | null;
};
}
interface SetSchedulingToggleAction {
type: TypeKeys.SCHEDULING_TOGGLE_SET;
payload: {
value: boolean;
};
}
interface SetScheduleTimezoneAction {
type: TypeKeys.SCHEDULE_TIMEZONE_SET;
payload: {
raw: string;
value: string;
};
}
interface SetScheduleGasPriceFieldAction {
type: TypeKeys.SCHEDULE_GAS_PRICE_FIELD_SET;
payload: {
raw: string;
value: Wei | null;
};
}
interface SetScheduleGasLimitFieldAction {
type: TypeKeys.SCHEDULE_GAS_LIMIT_FIELD_SET;
payload: {
raw: string;
value: Wei | null;
};
}
interface SetScheduleDepositFieldAction {
type: TypeKeys.SCHEDULE_DEPOSIT_FIELD_SET;
payload: {
raw: string;
value: Wei | null;
};
}
interface SetScheduleParamsValidityAction {
type: TypeKeys.SCHEDULE_PARAMS_VALIDITY_SET;
payload: {
value: boolean;
};
}
type ScheduleFieldAction =
| SetTimeBountyFieldAction
| SetWindowSizeFieldAction
| SetWindowStartFieldAction
| SetScheduleTimestampFieldAction
| SetScheduleTypeAction
| SetSchedulingToggleAction
| SetScheduleGasPriceFieldAction
| SetScheduleGasLimitFieldAction
| SetScheduleDepositFieldAction
| SetScheduleTimezoneAction
| SetScheduleParamsValidityAction;
export {
ScheduleFieldAction,
SetTimeBountyFieldAction,
SetWindowSizeFieldAction,
SetWindowStartFieldAction,
SetScheduleTimestampFieldAction,
SetScheduleTypeAction,
SetSchedulingToggleAction,
SetScheduleGasPriceFieldAction,
SetScheduleGasLimitFieldAction,
SetScheduleDepositFieldAction,
SetScheduleTimezoneAction,
SetScheduleParamsValidityAction
};

View File

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

View File

@ -0,0 +1,17 @@
import { TypeKeys } from 'actions/schedule';
/* user input */
interface SetCurrentScheduleTimestampAction {
type: TypeKeys.CURRENT_SCHEDULE_TIMESTAMP_SET;
payload: string;
}
type CurrentAction = SetCurrentScheduleTimestampAction;
interface SetCurrentScheduleTimezoneAction {
type: TypeKeys.CURRENT_SCHEDULE_TIMEZONE_SET;
payload: string;
}
export { SetCurrentScheduleTimestampAction, CurrentAction, SetCurrentScheduleTimezoneAction };

View File

@ -0,0 +1,12 @@
import { TypeKeys } from 'actions/schedule';
/* user input */
interface SetCurrentScheduleTypeAction {
type: TypeKeys.CURRENT_SCHEDULE_TYPE;
payload: string;
}
type CurrentAction = SetCurrentScheduleTypeAction;
export { SetCurrentScheduleTypeAction, CurrentAction };

View File

@ -0,0 +1,12 @@
import { TypeKeys } from 'actions/schedule';
/* user input */
interface SetCurrentSchedulingToggleAction {
type: TypeKeys.CURRENT_SCHEDULING_TOGGLE;
payload: string;
}
type CurrentAction = SetCurrentSchedulingToggleAction;
export { SetCurrentSchedulingToggleAction, CurrentAction };

View File

@ -0,0 +1,12 @@
import { TypeKeys } from 'actions/schedule';
/* user input */
interface SetCurrentTimeBountyAction {
type: TypeKeys.CURRENT_TIME_BOUNTY_SET;
payload: string;
}
type CurrentAction = SetCurrentTimeBountyAction;
export { SetCurrentTimeBountyAction, CurrentAction };

View File

@ -0,0 +1,12 @@
import { TypeKeys } from 'actions/schedule';
/* user input */
interface SetCurrentWindowSizeAction {
type: TypeKeys.CURRENT_WINDOW_SIZE_SET;
payload: string;
}
type CurrentAction = SetCurrentWindowSizeAction;
export { SetCurrentWindowSizeAction, CurrentAction };

View File

@ -0,0 +1,12 @@
import { TypeKeys } from 'actions/schedule';
/* user input */
interface SetCurrentWindowStartAction {
type: TypeKeys.CURRENT_WINDOW_START_SET;
payload: string;
}
type CurrentAction = SetCurrentWindowStartAction;
export { SetCurrentWindowStartAction, CurrentAction };

View File

@ -0,0 +1,21 @@
export enum TypeKeys {
CURRENT_TIME_BOUNTY_SET = 'CURRENT_TIME_BOUNTY_SET',
CURRENT_WINDOW_SIZE_SET = 'CURRENT_WINDOW_SIZE_SET',
CURRENT_WINDOW_START_SET = 'CURRENT_WINDOW_START_SET',
CURRENT_SCHEDULE_TIMESTAMP_SET = 'CURRENT_SCHEDULE_TIMESTAMP_SET',
CURRENT_SCHEDULE_TIMEZONE_SET = 'CURRENT_SCHEDULE_TIMEZONE_SET',
CURRENT_SCHEDULE_TYPE = 'CURRENT_SCHEDULE_TYPE',
CURRENT_SCHEDULING_TOGGLE = 'CURRENT_SCHEDULING_TOGGLE',
TIME_BOUNTY_FIELD_SET = 'TIME_BOUNTY_FIELD_SET',
WINDOW_SIZE_FIELD_SET = 'WINDOW_SIZE_FIELD_SET',
WINDOW_START_FIELD_SET = 'WINDOW_START_FIELD_SET',
SCHEDULE_GAS_PRICE_FIELD_SET = 'SCHEDULE_GAS_PRICE_SET',
SCHEDULE_GAS_LIMIT_FIELD_SET = 'SCHEDULE_GAS_LIMIT_SET',
SCHEDULE_TIMESTAMP_FIELD_SET = 'SCHEDULE_TIMESTAMP_FIELD_SET',
SCHEDULE_TIMEZONE_SET = 'SCHEDULE_TIMEZONE_SET',
SCHEDULE_TYPE_SET = 'SCHEDULE_TYPE_SET',
SCHEDULING_TOGGLE_SET = 'SCHEDULING_TOGGLE_SET',
SCHEDULE_DEPOSIT_FIELD_SET = 'SCHEDULE_DEPOSIT_FIELD_SET',
SCHEDULE_PARAMS_VALIDITY_SET = 'SCHEDULE_PARAMS_VALIDITY_SET'
}

View File

@ -0,0 +1,3 @@
export * from './actionCreators';
export * from './constants';
export * from './actionTypes';

View File

@ -0,0 +1,92 @@
<?xml version="1.0"?>
<svg width="650" height="130" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<style type="text/css">.st0{fill:#EBFFFE;}
.st1{clip-path:url(#SVGID_2_);}
.st2{fill:none;stroke:#CCCCCC;stroke-miterlimit:10;}
.st3{fill:#52CACB;}
.st4{fill:#263238;}
.st5{fill:#FFFFFF;}
.st6{fill:#B3B3B3;}
.st7{fill:url(#SVGID_3_);}
.st8{fill:#2E3192;}
.st9{fill:url(#SVGID_4_);}
.st10{opacity:0.3;}
.st11{clip-path:url(#SVGID_6_);}
.st12{fill:none;stroke:#FFFFFF;stroke-width:0.25;stroke-miterlimit:10;}
.st13{fill:#8CC63F;}
.st14{fill:url(#SVGID_7_);}
.st15{fill:url(#SVGID_8_);}
.st16{clip-path:url(#SVGID_10_);}
.st17{fill:#36CBFF;}</style>
<g>
<title>Layer 1</title>
<g id="svg_2">
<g id="svg_3">
<g id="svg_4">
<path id="svg_5" d="m38,121.800003l-27,0c-2.1,-0.5 -3.4,-1.900002 -3.7,-4.100006l6.8,-2.5l22.1,0l1.799999,6.600006zm-30.5,-5.800003l2.2,-21.300003l1.8,-1.599998l4.4,4.900002l-1.599999,15.5l-6.8,2.5zm11.299999,-45l-1.599998,15.5l-5.500001,4.900002l-1.4,-1.599998l2.2,-21.300003l6.299999,2.5zm22.200001,-1.599998l-22,0l-6.3,-2.5c0.8,-2.200005 2.3,-3.5 4.500001,-4.100002l26.899998,0l-3.099998,6.600002zm-2.799999,19.599998l2.899998,3.300003l-3.599998,3.299995l-21.4,0l-2.900001,-3.299995l3.7,-3.300003l21.300001,0z" class="st3"/>
<path id="svg_6" d="m61.599998,69.400002l-6.299999,-2.5c0.799999,-2.200005 2.299999,-3.5 4.5,-4.100002l26.100002,0c2.099998,0.5 3.400002,1.899998 3.699997,4.100002l-6.799995,2.5l-21.200005,0zm8,23.699997l2.900002,3.300003l-2.199997,21.299995c-0.800003,2.200005 -2.300003,3.5 -4.5,4.100006l-1.800003,-6.5l2,-18.800003l3.599998,-3.400002zm-2.699997,-4.900002l1.799995,-17.199997l6.5,0l-1.799995,17.199997l-3.700005,3.300003l-2.799995,-3.300003z" class="st3"/>
<path id="svg_7" d="m101,98l-1.800003,17.199997l-3.099998,6.5c-2.099998,-0.5 -3.400002,-1.899994 -3.699997,-4.099998l2.400002,-22.900002l1.799995,-1.599998l4.400002,4.900002zm-4.400002,-5.699997l-1.400002,-1.600006l2.400002,-22.899994c0.800003,-2.200005 2.300003,-3.5 4.5,-4.100002l1.800003,6.499996l-1.800003,17.200005l-5.5,4.900002zm26.599998,-3.300003l2.900002,3.300003l-3.599998,3.299995l-21.300003,0l-2.899994,-3.299995l3.699997,-3.300003l21.199997,0zm4.5,4.099998l1.5,1.599998l-2.399994,22.900002c-0.800003,2.200005 -2.300003,3.5 -4.5,4.099998l-1.800003,-6.5l1.800003,-17.199997l5.399994,-4.900002zm-4.199997,-6.5l1.800003,-17.199997l3.099991,-6.5c2.100006,0.5 3.400009,1.900002 3.700012,4.099998l-2.400009,22.900002l-1.899994,1.599998l-4.300003,-4.900002z" class="st3"/>
<path id="svg_8" d="m156.300003,108.699997l-6.5,0l0.699997,-6.5l6.5,0l-0.699997,6.5zm2.800003,-26.199997l-6.5,0l0.699997,-6.5l6.5,0l-0.699997,6.5z" class="st3"/>
<path id="svg_9" d="m186.100006,98l-1.800003,17.199997l-3.099991,6.5c-2.100006,-0.5 -3.400009,-1.899994 -3.699997,-4.099998l2.399994,-22.900002l1.800003,-1.599998l4.399994,4.900002zm2.799988,-27l-1.600006,15.5l-5.5,4.900002l-1.399994,-1.599998l2.199997,-21.300003l6.300003,2.5zm19.800003,-1.599998l-19.599991,0l-6.300003,-2.5c0.800003,-2.200005 2.300003,-3.5 4.5,-4.100002l24.5,0l-3.100006,6.600002zm-0.399994,19.599998l2.899994,3.300003l-3.599991,3.299995l-21.300003,0l-2.900009,-3.299995l3.700012,-3.300003l21.199997,0zm4.399994,4.099998l1.5,1.599998l-2.399994,22.900002c-0.800003,2.200005 -2.300003,3.5 -4.5,4.099998l-1.800003,-6.5l1.800003,-17.199997l5.399994,-4.900002zm-4.099991,-6.5l1.800003,-17.199997l3.100006,-6.5c2.099991,0.5 3.399994,1.900002 3.699997,4.099998l-2.400009,22.900002l-1.899994,1.599998l-4.300003,-4.900002z" class="st3"/>
<path id="svg_10" d="m228.600006,98l-1.800003,17.199997l-3.099991,6.5c-2.100006,-0.5 -3.400009,-1.899994 -3.699997,-4.099998l2.399994,-22.900002l1.800003,-1.599998l4.399994,4.900002zm-4.200012,-6.5l-1.400009,-1.599998l2.400009,-22.900002c0.800003,-2.199997 2.300003,-3.5 4.5,-4.099998l1.800003,6.5l-1.800003,17.199997l-5.5,4.900002zm4.100006,23.699997l19.600006,0l6.299988,2.5c-0.799988,2.200005 -2.299988,3.5 -4.5,4.100006l-24.5,0l3.100006,-6.600006z" class="st3"/>
<path id="svg_11" d="m271.200012,98l-1.799988,17.199997l-3.100006,6.5c-2.100006,-0.5 -3.399994,-1.899994 -3.700012,-4.099998l2.399994,-22.900002l1.800018,-1.599998l4.399994,4.900002zm2.799988,-27l-1.600006,15.5l-5.5,4.900002l-1.399994,-1.599998l2.200012,-21.300003l6.299988,2.5zm19.799988,-1.599998l-19.600006,0l-6.299988,-2.5c0.799988,-2.200005 2.299988,-3.5 4.5,-4.100002l24.5,0l-3.100006,6.600002zm-0.399994,19.599998l2.899994,3.300003l-3.600006,3.299995l-21.299988,0l-2.899994,-3.299995l3.699982,-3.300003l21.200012,0zm4.399994,4.099998l1.5,1.599998l-2.399994,22.900002c-0.800018,2.200005 -2.300018,3.5 -4.5,4.099998l-1.800018,-6.5l1.800018,-17.199997l5.399994,-4.900002zm-4.099976,-6.5l1.799988,-17.199997l3.100006,-6.5c2.100006,0.5 3.399994,1.900002 3.700012,4.099998l-2.399994,22.900002l-1.900024,1.599998l-4.299988,-4.900002z" class="st3"/>
<path id="svg_12" d="m313.700012,98l-1.799988,17.199997l-3.100006,6.5c-2.100006,-0.5 -3.399994,-1.899994 -3.700012,-4.099998l2.399994,-22.900002l1.800018,-1.599998l4.399994,4.900002zm2.899994,-27l-1.600006,15.5l-5.5,4.900002l-1.399994,-1.599998l2.200012,-21.300003l6.299988,2.5zm19.799988,-1.599998l-19.600006,0l-6.299988,-2.5c0.799988,-2.200005 2.299988,-3.5 4.5,-4.100002l24.5,0l-3.100006,6.600002zm-0.399994,19.599998l2.899994,3.300003l-3.600006,3.299995l-21.299988,0l-2.899994,-3.299995l3.699982,-3.300003l21.200012,0zm-20.600006,8.199997l4.899994,0l19.100006,21.300003c-1.300018,2.199997 -3.600006,3.300003 -6.899994,3.300003l-17.600006,-19.600006l0.5,-5zm20.800018,-10.599998l1.799988,-17.199997l3.100006,-6.5c2.100006,0.5 3.399994,1.900002 3.700012,4.099998l-2.399994,22.900002l-1.900024,1.599998l-4.299988,-4.900002z" class="st3"/>
<path id="svg_13" d="m356.299988,98l-1.799988,17.199997l-3.100006,6.5c-2.100006,-0.5 -3.399994,-1.899994 -3.700012,-4.099998l2.399994,-22.900002l1.800018,-1.599998l4.399994,4.900002zm2.800018,-27l-1.600006,15.5l-5.5,4.900002l-1.399994,-1.599998l2.200012,-21.300003l6.299988,2.5zm19.799988,-1.599998l-19.600006,0l-6.299988,-2.5c0.799988,-2.200005 2.299988,-3.5 4.5,-4.100002l24.5,0l-3.100006,6.600002zm-7.799988,20.5l-3.699982,3.299995l-2.900024,-3.299995l2,-18.800003l6.5,0l-1.899994,18.800003zm11.799988,3.199997l1.5,1.599998l-2.399994,22.900002c-0.800018,2.200005 -2.300018,3.5 -4.5,4.099998l-1.800018,-6.5l1.800018,-17.199997l5.399994,-4.900002zm-4.100006,-6.5l1.799988,-17.199997l3.100006,-6.5c2.100006,0.5 3.399994,1.900002 3.700012,4.099998l-2.399994,22.900002l-1.899994,1.599998l-4.300018,-4.900002z" class="st3"/>
<path id="svg_14" d="m411.600006,108.699997l-6.600006,0l0.700012,-6.5l6.5,0l-0.600006,6.5zm2.699982,-26.199997l-6.5,0l0.700012,-6.5l6.5,0l-0.700012,6.5z" class="st3"/>
<path id="svg_15" d="m463.399994,121.800003l-27,0c-2.100006,-0.5 -3.399994,-1.900002 -3.700012,-4.100006l6.800018,-2.5l22.099976,0l1.800018,6.600006zm-30.5,-5.800003l2.199982,-21.300003l1.800018,-1.599998l4.399994,4.900002l-1.600006,15.5l-6.799988,2.5zm11.300018,-45l-1.600006,15.5l-5.5,4.900002l-1.399994,-1.599998l2.200012,-21.300003l6.299988,2.5zm22.299988,-1.599998l-22,0l-6.299988,-2.5c0.799988,-2.200005 2.299988,-3.5 4.5,-4.100002l26.899994,0l-3.100006,6.600002z" class="st3"/>
<path id="svg_16" d="m483.899994,98l-1.800018,17.199997l-3.099976,6.5c-2.100006,-0.5 -3.400024,-1.899994 -3.700012,-4.099998l2.399994,-22.900002l1.800018,-1.599998l4.399994,4.900002zm-4.299988,-6.5l-1.399994,-1.599998l2.399994,-22.900002c0.800018,-2.199997 2.300018,-3.5 4.5,-4.099998l1.800018,6.5l-1.800018,17.199997l-5.5,4.900002zm4.100006,23.699997l19.600006,0l6.299988,2.5c-0.799988,2.200005 -2.299988,3.5 -4.5,4.100006l-24.5,0l3.100006,-6.600006z" class="st3"/>
<path id="svg_17" d="m526.400024,98l-1.799988,17.199997l-2.700012,2.5l-3.700012,-4.099998l2,-18.799995l1.799988,-1.600006l4.400024,4.800003zm-4.200012,-6.5l-1.400024,-1.599998l2,-18.800003l4.5,-4.099998l2.200012,2.5l-1.799988,17.199997l-5.5,4.800003zm25,27l-3.599976,3.300003l-18,0l-2.900024,-3.300003l3.599976,-3.300003l18,0l2.900024,3.300003zm1.899963,-49.099998l-18,0l-2.900024,-3.300003l3.600037,-3.299999l18,0l2.899963,3.299999l-3.599976,3.300003zm4,23.699997l1.5,1.599998l-2,18.800003l-4.5,4.099998l-2.200012,-2.5l1.799988,-17.199997l5.400024,-4.800003zm-4.199951,-6.5l1.799988,-17.199997l2.700012,-2.5l3.700012,4.099998l-2,18.800003l-1.900024,1.599998l-4.299988,-4.800003z" class="st3"/>
<path id="svg_18" d="m591.099976,121.800003l-27,0c-2.099976,-0.5 -3.400024,-1.900002 -3.700012,-4.100006l6.799988,-2.5l22.100037,0l1.799988,6.600006zm-30.599976,-5.800003l2.200012,-21.300003l1.799988,-1.599998l4.5,4.900002l-1.599976,15.5l-6.900024,2.5zm11.299988,-45l-1.599976,15.5l-5.5,4.900002l-1.400024,-1.599998l2.200012,-21.300003l6.299988,2.5zm22.299988,-1.599998l-22,0l-6.299988,-2.5c0.799988,-2.200005 2.299988,-3.5 4.5,-4.100002l26.899963,0l-3.099976,6.600002z" class="st3"/>
<path id="svg_19" d="m611.5,98l-1.799988,17.199997l-3.100037,6.5c-2.099976,-0.5 -3.399963,-1.899994 -3.699951,-4.099998l2.399963,-22.900002l1.799988,-1.599998l4.400024,4.900002zm-4.200012,-6.5l-1.400024,-1.599998l2.400024,-22.900002c0.799988,-2.199997 2.299988,-3.5 4.5,-4.099998l1.799988,6.5l-1.799988,17.199997l-5.5,4.900002zm26.5,-2.5l2.900024,3.300003l-3.600037,3.299995l-21.299988,0l-2.900024,-3.299995l3.700012,-3.300003l21.200012,0zm-19,-6.5l21.700012,-19.599998c3.299988,0 5.299988,1.099998 6.200012,3.299995l-23.5,21.300003l-4.900024,0l0.5,-5zm23.400024,10.599998l1.5,1.599998l-2.400024,22.900002c-0.799988,2.200005 -2.299988,3.5 -4.5,4.099998l-1.799988,-6.5l1.799988,-17.199997l5.400024,-4.900002z" class="st3"/>
</g>
</g>
</g>
<g id="svg_20">
<g id="svg_21">
<path id="svg_22" d="m220.5,34.099998l-10.399994,0l-1.800003,10.600002l-3.300003,0l4.899994,-28l13.700012,0c3.599991,0 5.5,2.4 4.899994,6l-1,5.5c-0.600006,3.5 -3.399994,5.899998 -7,5.899998zm4.699997,-11.299999c0.400009,-2 -0.699997,-3.199999 -2.599991,-3.199999l-9.800003,0l-2.100006,11.6l9.800003,0c2,0 3.5,-1.200001 3.800003,-3.200001l0.899994,-5.200001z" class="st4"/>
<path id="svg_23" d="m238.899994,44.700001l-4.899994,0c-3.599991,0 -5.5,-2.400002 -4.899994,-6l1.599991,-8.900002c0.600006,-3.599998 3.400009,-6 7,-6l4.900009,0c3.599991,0 5.599991,2.400002 4.899994,6l-1.600006,8.900002c-0.599991,3.599998 -3.399994,6 -7,6zm5.500015,-14.900002c0.299988,-2 -0.600006,-3.199999 -2.600006,-3.199999l-4.199997,0c-2,0 -3.5,1.199999 -3.800003,3.199999l-1.600006,9c-0.399994,2 0.699997,3.200001 2.699997,3.200001l4.200012,0c2,0 3.399994,-1.299999 3.799988,-3.200001l1.500015,-9z" class="st4"/>
<path id="svg_24" d="m254.199997,44.700001l-2.799988,-20.800001l3,0l2.199997,17.000002l8.100006,-17.000002l3.099976,0l2.100006,17.000002l8.200012,-17.000002l3.200012,0l-10.200012,20.800001l-3.5,0l-2.200012,-16.1l-7.799988,16.1l-3.400009,0z" class="st4"/>
<path id="svg_25" d="m298.399994,38.099998l-0.099976,0.600002c-0.600006,3.599998 -3.400024,6 -7,6l-4.900024,0c-3.600006,0 -5.5,-2.400002 -4.899994,-6l1.600006,-8.900002c0.600006,-3.599998 3.399994,-6 7,-6l4.899994,0c3.600006,0 5.600006,2.400002 4.899994,6l-1,5.400002l-13.699982,0l-0.600006,3.5c-0.399994,2 0.700012,3.200001 2.700012,3.200001l4.199982,0c2,0 3.399994,-1.300003 3.800018,-3.200001l0.099976,-0.700001l3,0l0,0.099998zm-12.799988,-5.299999l10.600006,0l0.5,-3c0.299988,-2 -0.600006,-3.199999 -2.600006,-3.199999l-4.100006,0c-2,0 -3.5,1.199999 -3.799988,3.199999l-0.600006,3z" class="st4"/>
<path id="svg_26" d="m316.799988,26.6c-2,0 -2.899994,0.299999 -3.899994,1.199999l-5.599976,4.700001l-2.100006,12.200001l-3.100006,0l3.600006,-20.800001l2.399994,0l0.100006,3.4l-0.300018,1.800001l3.700012,-3.1c1.600006,-1.4 3.299988,-2 5.600006,-2l-0.400024,2.6z" class="st4"/>
<path id="svg_27" d="m333.600006,38.099998l-0.100006,0.600002c-0.599976,3.599998 -3.399994,6 -7,6l-4.899994,0c-3.599976,0 -5.5,-2.400002 -4.899994,-6l1.600006,-8.900002c0.600006,-3.599998 3.399994,-6 7,-6l4.899994,0c3.599976,0 5.599976,2.400002 4.899994,6l-1,5.400002l-13.699982,0l-0.600037,3.5c-0.399963,2 0.700012,3.200001 2.700012,3.200001l4.200012,0c2,0 3.399994,-1.300003 3.799988,-3.200001l0.100006,-0.700001l3,0l0,0.099998zm-12.800018,-5.299999l10.600006,0l0.5,-3c0.300018,-2 -0.599976,-3.199999 -2.599976,-3.199999l-4.200012,0c-2,0 -3.5,1.199999 -3.799988,3.199999l-0.500031,3z" class="st4"/>
<path id="svg_28" d="m351.5,39.5l-3.700012,3.200001c-1.699982,1.5 -3.299988,2 -5,2c-3.599976,0 -5.499969,-2.400002 -4.799988,-5.900002l1.600006,-8.9c0.600006,-3.6 3.399994,-6 7,-6l3,0c2.100006,0 3.700012,0.9 4.299988,2.6l2,-11.4l3.100006,0l-5.200012,29.6l-2.399994,0l-0.099976,-3.400002l0.199982,-1.799999zm1.700012,-9.9c0.199982,-1.9 -0.700012,-3.1 -2.600006,-3.1l-4.200012,0c-2,0 -3.399994,1.200001 -3.799988,3.200001l-1.600006,9c-0.399994,2 0.700012,3.200001 2.600006,3.200001c1,0 1.799988,-0.300003 2.899994,-1.200001l5.600006,-4.700001l1.100006,-6.4z" class="st4"/>
<path id="svg_29" d="m376.300018,29l3.699982,-3.200001c1.799988,-1.5 3.300018,-2 5,-2c3.600006,0 5.5,2.400002 4.899994,5.900002l-1.599976,8.899998c-0.600006,3.600002 -3.400024,6 -7,6l-3,0c-2.300018,0 -4,-1 -4.600006,-2.799999l-0.900024,2.799999l-2.399994,0l5.200012,-29.599998l3.100006,0l-2.399994,14zm-1.700012,9.799999c-0.299988,2 0.600006,3.200001 2.600006,3.200001l4.200012,0c2,0 3.5,-1.200001 3.799988,-3.200001l1.599976,-9c0.400024,-2 -0.699982,-3.199999 -2.599976,-3.199999c-1,0 -1.799988,0.299999 -2.899994,1.199999l-5.600006,4.799999l-1.100006,6.200001z" class="st4"/>
<path id="svg_30" d="m393,52.700001l4.5,-8l-0.199982,0l-4.300018,-20.800001l3.200012,0l3.599976,17.4l9.600006,-17.4l3.300018,0l-16.100006,28.800001l-3.600006,0z" class="st4"/>
<path id="svg_31" d="m444.700012,25.700001l0.5,-2.900002c0.399994,-2 -0.700012,-3.199999 -2.600006,-3.199999l-7.800018,0c-2,0 -3.499969,1.199999 -3.799988,3.199999l-2.799988,15.799999c-0.400024,2 0.699982,3.200001 2.599976,3.200001l7.800018,0c2,0 3.5,-1.299999 3.799988,-3.200001l0.5,-2.899998l3.300018,0l-0.600006,3c-0.600006,3.599998 -3.399994,6 -7,6l-9,0c-3.600006,0 -5.5,-2.400002 -4.899994,-6l2.799988,-16.1c0.600006,-3.6 3.399994,-6 7,-6l9,0c3.600006,0 5.5,2.4 4.899994,6l-0.5,3l-3.199982,0l0,0.1z" class="st4"/>
<path id="svg_32" d="m463.600006,44.700001l2.600006,-14.900002c0.399994,-2 -0.700012,-3.199999 -2.600006,-3.199999c-1,0 -1.799988,0.299999 -2.899994,1.199999l-5.600006,4.700001l-2.100006,12.200001l-3.100006,0l5.200012,-29.6l3.100006,0l-2.5,13.9l3.700012,-3.1c1.799988,-1.5 3.299988,-2 5,-2c3.599976,0 5.5,2.4 4.899994,5.9l-2.600006,14.900002l-3.100006,0z" class="st4"/>
<path id="svg_33" d="m486.600006,26.6c-2,0 -2.899994,0.299999 -3.899994,1.199999l-5.600006,4.700001l-2.100006,12.200001l-3.100006,0l3.600006,-20.800001l2.399994,0l0.100006,3.4l-0.299988,1.800001l3.699982,-3.1c1.600006,-1.4 3.300018,-2 5.600006,-2l-0.399994,2.6z" class="st4"/>
<path id="svg_34" d="m496.299988,44.700001l-4.899994,0c-3.600006,0 -5.5,-2.400002 -4.899994,-6l1.599976,-8.900002c0.600006,-3.599998 3.400024,-6 7,-6l4.900024,0c3.599976,0 5.599976,2.400002 4.899994,6l-1.600006,8.900002c-0.599976,3.599998 -3.399994,6 -7,6zm5.5,-14.900002c0.299988,-2 -0.600006,-3.199999 -2.600006,-3.199999l-4.199982,0c-2,0 -3.5,1.199999 -3.799988,3.199999l-1.600006,9c-0.399994,2 0.699982,3.200001 2.699982,3.200001l4.200012,0c2,0 3.399994,-1.299999 3.799988,-3.200001l1.5,-9z" class="st4"/>
<path id="svg_35" d="m520.799988,44.700001l2.600037,-14.900002c0.399963,-2 -0.700073,-3.199999 -2.600037,-3.199999c-1,0 -1.799988,0.299999 -2.900024,1.199999l-5.599976,4.700001l-2.100006,12.200001l-3.100006,0l3.600006,-20.800001l2.399994,0l0.099976,3.4l-0.299988,1.800001l3.700012,-3.1c1.700012,-1.5 3.299988,-2 5,-2c3.599976,0 5.5,2.4 4.900024,5.9l-2.600037,14.9l-3.099976,0l0,-0.099998z" class="st4"/>
<path id="svg_36" d="m539.600037,44.700001l-4.900024,0c-3.600037,0 -5.5,-2.400002 -4.899963,-6l1.599976,-8.900002c0.599976,-3.599998 3.400024,-6 7,-6l4.900024,0c3.599976,0 5.599976,2.400002 4.899963,6l-1.599976,8.900002c-0.600037,3.599998 -3.400024,6 -7,6zm5.499939,-14.900002c0.300049,-2 -0.599976,-3.199999 -2.599976,-3.199999l-4.199951,0c-2,0 -3.5,1.199999 -3.800049,3.199999l-1.599976,9c-0.400024,2 0.700012,3.200001 2.700012,3.200001l4.200012,0c2,0 3.399963,-1.299999 3.799988,-3.200001l1.499939,-9z" class="st4"/>
<path id="svg_37" d="m555.299988,16.700001l3.299988,0l-4.400024,25.099998l14.800049,0l-0.5,2.900002l-18.099976,0l4.899963,-28z" class="st4"/>
<path id="svg_38" d="m582.099976,44.700001l-4.899963,0c-3.599976,0 -5.5,-2.400002 -4.899963,-6l1.599976,-8.900002c0.599976,-3.599998 3.400024,-6 7,-6l4.900024,0c3.599976,0 5.599976,2.400002 4.899963,6l-1.600037,8.900002c-0.599976,3.599998 -3.399963,6 -7,6zm5.500061,-14.900002c0.299988,-2 -0.600037,-3.199999 -2.600037,-3.199999l-4.199951,0c-2,0 -3.5,1.199999 -3.800049,3.199999l-1.599976,9c-0.400024,2 0.699951,3.200001 2.699951,3.200001l4.200073,0c2,0 3.399963,-1.299999 3.799927,-3.200001l1.500061,-9z" class="st4"/>
<path id="svg_39" d="m607.099976,39.5l-3.699951,3.200001c-1.799988,1.5 -3.300049,2 -5,2c-3.599976,0 -5.5,-2.400002 -4.799988,-5.900002l1.599976,-8.9c0.600037,-3.6 3.400024,-6 7,-6l3,0c2.299988,0 4,1 4.5,2.800001l0.900024,-2.800001l2.399963,0l-4,22.800001c-0.599976,3.599998 -3.5,6 -7,6l-5,0c-3.599976,0 -5.599976,-2.400002 -4.900024,-6l0.100037,-0.600002l3.100037,0l-0.100037,0.700001c-0.399963,2 0.600037,3.200001 2.600037,3.200001l4.199951,0c2,0 3.5,-1.200001 3.800049,-3.200001l1.299927,-7.299999zm1.700012,-9.700001c0.299988,-2 -0.700012,-3.199999 -2.600037,-3.199999l-4.199951,0c-2,0 -3.399963,1.199999 -3.799988,3.199999l-1.599976,9c-0.400024,2 0.699951,3.200001 2.599976,3.200001c1,0 1.799988,-0.200001 2.899963,-1.200001l5.600037,-4.700001l1.099976,-6.299999z" class="st4"/>
<path id="svg_40" d="m618.200012,23.9l3.100037,0l-3.600037,20.800001l-3.099976,0l3.599976,-20.800001zm1.299988,-7.5l3.100037,0l-0.800049,4.5l-3.099976,0l0.799988,-4.5z" class="st4"/>
<path id="svg_41" d="m640.5,37.700001l-0.200012,1c-0.599976,3.599998 -3.400024,6 -7,6l-4.899963,0c-3.600037,0 -5.500061,-2.400002 -4.900024,-6l1.599976,-8.900002c0.600037,-3.599998 3.400024,-6 7,-6l4.900024,0c3.600037,0 5.600037,2.400002 4.899963,6l-0.199951,1l-3.099976,0l0.199951,-1.099998c0.299988,-2 -0.599976,-3.200001 -2.599976,-3.200001l-4.200012,0c-2,0 -3.5,1.200001 -3.799988,3.200001l-1.599976,9c-0.400024,2 0.699951,3.200001 2.699951,3.200001l4.200012,0c2,0 3.399963,-1.300003 3.799988,-3.200001l0.200012,-1.100002l3,0l0,0.100002z" class="st4"/>
</g>
</g>
<g id="svg_42">
<g id="svg_43">
<g id="svg_44">
<g id="svg_45">
<path id="svg_46" d="m38,121.800003l-27,0c-2.1,-0.5 -3.4,-1.900002 -3.7,-4.100006l6.8,-2.5l22.1,0l1.799999,6.600006zm-30.5,-5.800003l2.2,-21.300003l1.8,-1.599998l4.4,4.900002l-1.599999,15.5l-6.8,2.5zm11.299999,-45l-1.599998,15.5l-5.500001,4.900002l-1.4,-1.599998l2.2,-21.300003l6.299999,2.5zm22.200001,-1.599998l-22,0l-6.3,-2.5c0.8,-2.200005 2.3,-3.5 4.500001,-4.100002l26.899998,0l-3.099998,6.600002zm-2.799999,19.599998l2.899998,3.300003l-3.599998,3.299995l-21.4,0l-2.900001,-3.299995l3.7,-3.300003l21.300001,0z" class="st3"/>
<path id="svg_47" d="m61.599998,69.400002l-6.299999,-2.5c0.799999,-2.200005 2.299999,-3.5 4.5,-4.100002l26.100002,0c2.099998,0.5 3.400002,1.899998 3.699997,4.100002l-6.799995,2.5l-21.200005,0zm8,23.699997l2.900002,3.300003l-2.199997,21.299995c-0.800003,2.200005 -2.300003,3.5 -4.5,4.100006l-1.800003,-6.5l2,-18.800003l3.599998,-3.400002zm-2.699997,-4.900002l1.799995,-17.199997l6.5,0l-1.799995,17.199997l-3.700005,3.300003l-2.799995,-3.300003z" class="st3"/>
<path id="svg_48" d="m101,98l-1.800003,17.199997l-3.099998,6.5c-2.099998,-0.5 -3.400002,-1.899994 -3.699997,-4.099998l2.400002,-22.900002l1.799995,-1.599998l4.400002,4.900002zm-4.400002,-5.699997l-1.400002,-1.600006l2.400002,-22.899994c0.800003,-2.200005 2.300003,-3.5 4.5,-4.100002l1.800003,6.499996l-1.800003,17.200005l-5.5,4.900002zm26.599998,-3.300003l2.900002,3.300003l-3.599998,3.299995l-21.300003,0l-2.899994,-3.299995l3.699997,-3.300003l21.199997,0zm4.5,4.099998l1.5,1.599998l-2.399994,22.900002c-0.800003,2.200005 -2.300003,3.5 -4.5,4.099998l-1.800003,-6.5l1.800003,-17.199997l5.399994,-4.900002zm-4.199997,-6.5l1.800003,-17.199997l3.099991,-6.5c2.100006,0.5 3.400009,1.900002 3.700012,4.099998l-2.400009,22.900002l-1.899994,1.599998l-4.300003,-4.900002z" class="st3"/>
<path id="svg_49" d="m156.300003,108.699997l-6.5,0l0.699997,-6.5l6.5,0l-0.699997,6.5zm2.800003,-26.199997l-6.5,0l0.699997,-6.5l6.5,0l-0.699997,6.5z" class="st3"/>
<path id="svg_50" d="m186.100006,98l-1.800003,17.199997l-3.099991,6.5c-2.100006,-0.5 -3.400009,-1.899994 -3.699997,-4.099998l2.399994,-22.900002l1.800003,-1.599998l4.399994,4.900002zm2.799988,-27l-1.600006,15.5l-5.5,4.900002l-1.399994,-1.599998l2.199997,-21.300003l6.300003,2.5zm19.800003,-1.599998l-19.599991,0l-6.300003,-2.5c0.800003,-2.200005 2.300003,-3.5 4.5,-4.100002l24.5,0l-3.100006,6.600002zm-0.399994,19.599998l2.899994,3.300003l-3.599991,3.299995l-21.300003,0l-2.900009,-3.299995l3.700012,-3.300003l21.199997,0zm4.399994,4.099998l1.5,1.599998l-2.399994,22.900002c-0.800003,2.200005 -2.300003,3.5 -4.5,4.099998l-1.800003,-6.5l1.800003,-17.199997l5.399994,-4.900002zm-4.099991,-6.5l1.800003,-17.199997l3.100006,-6.5c2.099991,0.5 3.399994,1.900002 3.699997,4.099998l-2.400009,22.900002l-1.899994,1.599998l-4.300003,-4.900002z" class="st3"/>
<path id="svg_51" d="m228.600006,98l-1.800003,17.199997l-3.099991,6.5c-2.100006,-0.5 -3.400009,-1.899994 -3.699997,-4.099998l2.399994,-22.900002l1.800003,-1.599998l4.399994,4.900002zm-4.200012,-6.5l-1.400009,-1.599998l2.400009,-22.900002c0.800003,-2.199997 2.300003,-3.5 4.5,-4.099998l1.800003,6.5l-1.800003,17.199997l-5.5,4.900002zm4.100006,23.699997l19.600006,0l6.299988,2.5c-0.799988,2.200005 -2.299988,3.5 -4.5,4.100006l-24.5,0l3.100006,-6.600006z" class="st3"/>
<path id="svg_52" d="m271.200012,98l-1.799988,17.199997l-3.100006,6.5c-2.100006,-0.5 -3.399994,-1.899994 -3.700012,-4.099998l2.399994,-22.900002l1.800018,-1.599998l4.399994,4.900002zm2.799988,-27l-1.600006,15.5l-5.5,4.900002l-1.399994,-1.599998l2.200012,-21.300003l6.299988,2.5zm19.799988,-1.599998l-19.600006,0l-6.299988,-2.5c0.799988,-2.200005 2.299988,-3.5 4.5,-4.100002l24.5,0l-3.100006,6.600002zm-0.399994,19.599998l2.899994,3.300003l-3.600006,3.299995l-21.299988,0l-2.899994,-3.299995l3.699982,-3.300003l21.200012,0zm4.399994,4.099998l1.5,1.599998l-2.399994,22.900002c-0.800018,2.200005 -2.300018,3.5 -4.5,4.099998l-1.800018,-6.5l1.800018,-17.199997l5.399994,-4.900002zm-4.099976,-6.5l1.799988,-17.199997l3.100006,-6.5c2.100006,0.5 3.399994,1.900002 3.700012,4.099998l-2.399994,22.900002l-1.900024,1.599998l-4.299988,-4.900002z" class="st3"/>
<path id="svg_53" d="m313.700012,98l-1.799988,17.199997l-3.100006,6.5c-2.100006,-0.5 -3.399994,-1.899994 -3.700012,-4.099998l2.399994,-22.900002l1.800018,-1.599998l4.399994,4.900002zm2.899994,-27l-1.600006,15.5l-5.5,4.900002l-1.399994,-1.599998l2.200012,-21.300003l6.299988,2.5zm19.799988,-1.599998l-19.600006,0l-6.299988,-2.5c0.799988,-2.200005 2.299988,-3.5 4.5,-4.100002l24.5,0l-3.100006,6.600002zm-0.399994,19.599998l2.899994,3.300003l-3.600006,3.299995l-21.299988,0l-2.899994,-3.299995l3.699982,-3.300003l21.200012,0zm-20.600006,8.199997l4.899994,0l19.100006,21.300003c-1.300018,2.199997 -3.600006,3.300003 -6.899994,3.300003l-17.600006,-19.600006l0.5,-5zm20.800018,-10.599998l1.799988,-17.199997l3.100006,-6.5c2.100006,0.5 3.399994,1.900002 3.700012,4.099998l-2.399994,22.900002l-1.900024,1.599998l-4.299988,-4.900002z" class="st3"/>
<path id="svg_54" d="m356.299988,98l-1.799988,17.199997l-3.100006,6.5c-2.100006,-0.5 -3.399994,-1.899994 -3.700012,-4.099998l2.399994,-22.900002l1.800018,-1.599998l4.399994,4.900002zm2.800018,-27l-1.600006,15.5l-5.5,4.900002l-1.399994,-1.599998l2.200012,-21.300003l6.299988,2.5zm19.799988,-1.599998l-19.600006,0l-6.299988,-2.5c0.799988,-2.200005 2.299988,-3.5 4.5,-4.100002l24.5,0l-3.100006,6.600002zm-7.799988,20.5l-3.699982,3.299995l-2.900024,-3.299995l2,-18.800003l6.5,0l-1.899994,18.800003zm11.799988,3.199997l1.5,1.599998l-2.399994,22.900002c-0.800018,2.200005 -2.300018,3.5 -4.5,4.099998l-1.800018,-6.5l1.800018,-17.199997l5.399994,-4.900002zm-4.100006,-6.5l1.799988,-17.199997l3.100006,-6.5c2.100006,0.5 3.399994,1.900002 3.700012,4.099998l-2.399994,22.900002l-1.899994,1.599998l-4.300018,-4.900002z" class="st3"/>
<path id="svg_55" d="m411.600006,108.699997l-6.600006,0l0.700012,-6.5l6.5,0l-0.600006,6.5zm2.699982,-26.199997l-6.5,0l0.700012,-6.5l6.5,0l-0.700012,6.5z" class="st3"/>
<path id="svg_56" d="m463.399994,121.800003l-27,0c-2.100006,-0.5 -3.399994,-1.900002 -3.700012,-4.100006l6.800018,-2.5l22.099976,0l1.800018,6.600006zm-30.5,-5.800003l2.199982,-21.300003l1.800018,-1.599998l4.399994,4.900002l-1.600006,15.5l-6.799988,2.5zm11.300018,-45l-1.600006,15.5l-5.5,4.900002l-1.399994,-1.599998l2.200012,-21.300003l6.299988,2.5zm22.299988,-1.599998l-22,0l-6.299988,-2.5c0.799988,-2.200005 2.299988,-3.5 4.5,-4.100002l26.899994,0l-3.100006,6.600002z" class="st3"/>
<path id="svg_57" d="m483.899994,98l-1.800018,17.199997l-3.099976,6.5c-2.100006,-0.5 -3.400024,-1.899994 -3.700012,-4.099998l2.399994,-22.900002l1.800018,-1.599998l4.399994,4.900002zm-4.299988,-6.5l-1.399994,-1.599998l2.399994,-22.900002c0.800018,-2.199997 2.300018,-3.5 4.5,-4.099998l1.800018,6.5l-1.800018,17.199997l-5.5,4.900002zm4.100006,23.699997l19.600006,0l6.299988,2.5c-0.799988,2.200005 -2.299988,3.5 -4.5,4.100006l-24.5,0l3.100006,-6.600006z" class="st3"/>
<path id="svg_58" d="m526.400024,98l-1.799988,17.199997l-2.700012,2.5l-3.700012,-4.099998l2,-18.799995l1.799988,-1.600006l4.400024,4.800003zm-4.200012,-6.5l-1.400024,-1.599998l2,-18.800003l4.5,-4.099998l2.200012,2.5l-1.799988,17.199997l-5.5,4.800003zm25,27l-3.599976,3.300003l-18,0l-2.900024,-3.300003l3.599976,-3.300003l18,0l2.900024,3.300003zm1.899963,-49.099998l-18,0l-2.900024,-3.300003l3.600037,-3.299999l18,0l2.899963,3.299999l-3.599976,3.300003zm4,23.699997l1.5,1.599998l-2,18.800003l-4.5,4.099998l-2.200012,-2.5l1.799988,-17.199997l5.400024,-4.800003zm-4.199951,-6.5l1.799988,-17.199997l2.700012,-2.5l3.700012,4.099998l-2,18.800003l-1.900024,1.599998l-4.299988,-4.800003z" class="st3"/>
<path id="svg_59" d="m591.099976,121.800003l-27,0c-2.099976,-0.5 -3.400024,-1.900002 -3.700012,-4.100006l6.799988,-2.5l22.100037,0l1.799988,6.600006zm-30.599976,-5.800003l2.200012,-21.300003l1.799988,-1.599998l4.5,4.900002l-1.599976,15.5l-6.900024,2.5zm11.299988,-45l-1.599976,15.5l-5.5,4.900002l-1.400024,-1.599998l2.200012,-21.300003l6.299988,2.5zm22.299988,-1.599998l-22,0l-6.299988,-2.5c0.799988,-2.200005 2.299988,-3.5 4.5,-4.100002l26.899963,0l-3.099976,6.600002z" class="st3"/>
<path id="svg_60" d="m611.5,98l-1.799988,17.199997l-3.100037,6.5c-2.099976,-0.5 -3.399963,-1.899994 -3.699951,-4.099998l2.399963,-22.900002l1.799988,-1.599998l4.400024,4.900002zm-4.200012,-6.5l-1.400024,-1.599998l2.400024,-22.900002c0.799988,-2.199997 2.299988,-3.5 4.5,-4.099998l1.799988,6.5l-1.799988,17.199997l-5.5,4.900002zm26.5,-2.5l2.900024,3.300003l-3.600037,3.299995l-21.299988,0l-2.900024,-3.299995l3.700012,-3.300003l21.200012,0zm-19,-6.5l21.700012,-19.599998c3.299988,0 5.299988,1.099998 6.200012,3.299995l-23.5,21.300003l-4.900024,0l0.5,-5zm23.400024,10.599998l1.5,1.599998l-2.400024,22.900002c-0.799988,2.200005 -2.299988,3.5 -4.5,4.099998l-1.799988,-6.5l1.799988,-17.199997l5.400024,-4.900002z" class="st3"/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1023.44 246"><title>logo</title><path d="M131.9,2.82A120.18,120.18,0,1,0,252.08,123,120.18,120.18,0,0,0,131.9,2.82Zm43.46,159.47-30.92,31.33a17.23,17.23,0,0,1-24.57,0l-31-31.29a17.74,17.74,0,0,1,0-24.87h0a10.72,10.72,0,0,1,15.29,0l15.66,15.83a17.23,17.23,0,0,0,24.57,0l15.64-15.85a10.72,10.72,0,0,1,15.29,0h0A17.74,17.74,0,0,1,175.36,162.28Zm32.52-50.09h0a10.33,10.33,0,0,1-14.73.15L178.2,97.23a16.61,16.61,0,0,0-23.69.25L139.29,112.9a10.49,10.49,0,0,1-8.63,3.09,10.1,10.1,0,0,1-6.37-3L109.35,97.91a16.61,16.61,0,0,0-23.69.25L70.44,113.58a10.33,10.33,0,0,1-14.73.15h0a17.1,17.1,0,0,1,.22-24L86,59.27A16.61,16.61,0,0,1,109.7,59L132.1,81.67l22.78-23.08a16.61,16.61,0,0,1,23.69-.25L208.1,88.21A17.1,17.1,0,0,1,207.88,112.19Z" style="fill:#007896"/><path d="M415.29,84.35,382,127.22a1.06,1.06,0,0,1-.83.41h-4.07a1.06,1.06,0,0,1-.83-.4L342.94,84.7a1.06,1.06,0,0,0-1.89.65v79.76a1.06,1.06,0,0,1-1.06,1.06H326.43a1.06,1.06,0,0,1-1.06-1.06l.19-111.23a1.06,1.06,0,0,1,1.06-1.05h9.76a1.06,1.06,0,0,1,.83.41l41.16,52.92a1.06,1.06,0,0,0,1.67,0l41-52.92a1.06,1.06,0,0,1,.84-.41h9.76a1.06,1.06,0,0,1,1.06,1.06V165.11a1.06,1.06,0,0,1-1.06,1.06H418.24a1.06,1.06,0,0,1-1.06-1.06V85A1.06,1.06,0,0,0,415.29,84.35Z" style="fill:#007896"/><path d="M477.81,195.63H464a1,1,0,0,1-.94-1.47l14.8-32.11a1,1,0,0,0,0-.86L444.77,88.29a1,1,0,0,1,.94-1.46h14.7a1,1,0,0,1,1,.64L484,142.55a14.44,14.44,0,0,1,1.51,5.48h.76a18.26,18.26,0,0,1,1.12-5.44l0-.08,22.58-55a1,1,0,0,1,1-.64h13.59a1,1,0,0,1,.95,1.45L478.76,195A1,1,0,0,1,477.81,195.63Z" style="fill:#007896"/><path d="M633.4,153.73a1,1,0,0,1-.1,1.36l-.87.87a47.15,47.15,0,0,1-4.44,3.49,46.65,46.65,0,0,1-7.46,4.44A52.37,52.37,0,0,1,610,167.3a61.73,61.73,0,0,1-13.79,1.51q-25.12,0-41-17t-15.87-42.13q0-25.31,15.77-42.31t41.09-17A58.06,58.06,0,0,1,616.95,54q9.45,3.59,13.22,7.18l3.1,2.95a1,1,0,0,1,.12,1.38l-7.73,10.31a1,1,0,0,1-1.58.11l-.43-.39q-.85-.76-3.68-2.74a56.13,56.13,0,0,0-5.86-3.59,35.79,35.79,0,0,0-7.84-2.83,40,40,0,0,0-9.92-1.23q-17.95,0-29.47,12.85t-11.52,31.74q0,18.7,11.52,31.55t29.47,12.85a39.63,39.63,0,0,0,15.11-2.93q7.14-2.91,10.35-5.73l.07-.06,2.14-2.14a1,1,0,0,1,1.57.11Z" style="fill:#007896"/><path d="M690.48,98.25a1,1,0,0,1-1,1h-7.34a19.37,19.37,0,0,0-14.45,6.14,21.23,21.23,0,0,0-6,15.4v44.29a1,1,0,0,1-1,1H647.25a1,1,0,0,1-1-1V87.87a1,1,0,0,1,1-1h9.59a1,1,0,0,1,1,1l.41,12.4h.94a21.44,21.44,0,0,1,8.88-10,26.86,26.86,0,0,1,14-3.59h7.34a1,1,0,0,1,1,1Z" style="fill:#007896"/><path d="M731.49,195.63H717.74a1,1,0,0,1-.95-1.48l14.8-32.09a1,1,0,0,0,0-.87L698.46,88.3a1,1,0,0,1,.95-1.47h14.68a1,1,0,0,1,1,.65l22.59,55.08c1,2.27,2.41,4.09,2.41,5.48h-1c0-1.39,1.27-3.21,2-5.48l22.59-55.08a1,1,0,0,1,1-.65h13.57a1,1,0,0,1,1,1.46L732.45,195A1,1,0,0,1,731.49,195.63Z" style="fill:#007896"/><path d="M802.37,156.15v38.44a1,1,0,0,1-1,1H787.92a1,1,0,0,1-1-1V87.86a1,1,0,0,1,1-1h10.3a1,1,0,0,1,1,1l.09,10.49h.77a1,1,0,0,0,.86-.46l.64-1a26.55,26.55,0,0,1,3-3.21,29.86,29.86,0,0,1,5.48-4.16,33.81,33.81,0,0,1,8.22-3.21,41.84,41.84,0,0,1,11.05-1.42,34.54,34.54,0,0,1,26.45,11.43q10.58,11.43,10.58,30.13,0,18.32-10.48,29.94a33.58,33.58,0,0,1-26,11.62,45.53,45.53,0,0,1-10.86-1.23,26.8,26.8,0,0,1-7.84-3,41.82,41.82,0,0,1-4.82-3.5,15.84,15.84,0,0,1-2.71-2.79l-.06-.09-.73-1.27Zm6.23-8.78a23.27,23.27,0,0,0,19.12,7.81,22.13,22.13,0,0,0,14.32-6.1q8.69-8.38,8.69-22.58,0-12.47-6.89-20.59a21.93,21.93,0,0,0-17.47-8.12,23.11,23.11,0,0,0-17.76,7.84q-7.18,7.84-7.18,20.87T808.61,147.37Z" style="fill:#007896"/><path d="M884.9,85.77V63.7A1.05,1.05,0,0,1,886,62.65h13.38a1.05,1.05,0,0,1,1.05,1.05V85.77a1.05,1.05,0,0,0,1.05,1.05H922a1.05,1.05,0,0,1,1.05,1.05V98.05A1.05,1.05,0,0,1,922,99.1H901.45a1.05,1.05,0,0,0-1.05,1.05v42.77q0,11.9,11,11.9a17.55,17.55,0,0,0,5.2-.85,18.69,18.69,0,0,0,4.25-1.79l.51-.26a1.05,1.05,0,0,1,1.44.53l4,9.22a1,1,0,0,1-.31,1.24,19.85,19.85,0,0,1-2.12,1.36,28.6,28.6,0,0,1-5.09,2.07A33.89,33.89,0,0,1,907.73,168q-11.26-.35-16.88-6.59-6-6.61-6-18.32v-43a1.05,1.05,0,0,0-1.05-1.05H871.41a1.05,1.05,0,0,1-1.05-1.05V87.88a1.05,1.05,0,0,1,1.05-1.05h12.44A1.05,1.05,0,0,0,884.9,85.77Z" style="fill:#007896"/><path d="M1000,156.72q-11.71,11.33-28.71,11.33-17.19,0-28.71-11.33t-11.52-29.85q0-18.7,11.62-30.32t28.62-11.62A39,39,0,0,1,1000,96.65q11.71,11.71,11.71,30.22T1000,156.72Zm-11-9q7.18-7.84,7.18-21.06,0-13-7.18-21.16a22.76,22.76,0,0,0-17.76-8.12,23.16,23.16,0,0,0-17.85,8q-7.27,8-7.27,21.25t7.18,21.06a24.24,24.24,0,0,0,35.7,0Z" style="fill:#007896"/></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -25,12 +25,13 @@ export default class BetaAgreement extends React.PureComponent<{}, State> {
return (
<div className={`BetaAgreement ${isFading}`}>
<div className="BetaAgreement-content">
<h2>Welcome to the New MyCrypto Beta!</h2>
<h2>Welcome to the New MyCrypto Beta Release Candidate!</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.
You are about to use the new MyCrypto Beta Release Candidate. Although this is a release
candidate for production, we encourage caution while using this unreleased version of
MyCrypto.
</p>
<p>We hope to move this version of MyCrypto into production in the near future!</p>
<p>
Feedback and bug reports are greatly appreciated. You can file issues on our{' '}
<NewTabLink href="https://github.com/MyCryptoHQ/MyCrypto/issues">
@ -46,7 +47,7 @@ export default class BetaAgreement extends React.PureComponent<{}, State> {
className="BetaAgreement-content-buttons-btn is-continue"
onClick={this.doContinue}
>
Yes, continue to the Beta
Yes, continue to the Beta RC
</button>
<button className="BetaAgreement-content-buttons-btn is-reject" onClick={this.reject}>
No, take me to the production site

View File

@ -98,7 +98,7 @@ class CustomNodeModal extends React.Component<Props, State> {
const options = [...staticNetwrks, ...customNetwrks, CUSTOM];
return (
<Modal
title={translateRaw('NODE_Title')}
title={translateRaw('NODE_TITLE')}
isOpen={isOpen}
buttons={buttons}
handleClose={handleClose}
@ -125,7 +125,7 @@ class CustomNodeModal extends React.Component<Props, State> {
/>
</label>
<label className="col-sm-3 input-group">
<div className="input-group-header">Network</div>
<div className="input-group-header">{translate('CUSTOM_NETWORK')}</div>
<Dropdown
value={network}
options={options}

View File

@ -0,0 +1,2 @@
import CustomNodeModal from './CustomNodeModal';
export default CustomNodeModal;

View File

@ -0,0 +1,154 @@
@import 'common/sass/variables';
@import 'common/sass/mixins';
// Prefer pixels of rem in this file, Electron shouldn't be responsive in the
// same way the content is.
$branding-spacing-top: 12px;
$back-spacing: 10px;
.ElectronNav {
transition: transform 300ms ease;
&.is-panel-open {
transform: translateX(-100%);
}
&-branding {
position: -webkit-sticky;
position: sticky;
top: 0;
padding-top: $branding-spacing-top;
background: #FFF;
z-index: 1;
border-bottom: 1px solid $gray-lighter;
.is-osx & {
padding-top: $electron-osx-control-spacing + $branding-spacing-top - 5;
}
&-logo {
margin-left: -1px;
height: 35px;
margin-bottom: 10px;
background-image: url('~assets/images/logo-mycrypto-transparent.svg');
background-position: center;
background-repeat: no-repeat;
background-size: auto 100%;
}
&-beta {
text-align: center;
letter-spacing: 4px;
height: 20px;
line-height: 20px;
color: #FFF;
background: $brand-info;
text-transform: uppercase;
font-size: 8px;
opacity: 0.8;
pointer-events: none;
}
}
&-links {
padding: 0;
margin: 0 auto;
}
&-status {
margin: 12px 0;
padding-left: 10px;
}
&-controls {
margin: 10px 0;
padding: 0 10px;
&-btn {
@include reset-button;
display: flex;
align-items: center;
width: 100%;
color: $gray-light;
font-size: 12px;
padding: 6px 0;
&-icon {
font-size: 11px;
margin-left: 4px;
}
&:hover {
color: $link-hover-color;
}
}
}
&-panel {
position: absolute;
top: 0;
left: 100%;
width: 100%;
z-index: $zindex-navbar - 1;
&-back {
@include reset-button;
position: sticky;
top: 0;
display: flex;
align-items: center;
width: 100%;
text-align: left;
padding: $back-spacing 0 8px $back-spacing;
border-bottom: 1px solid $gray-lighter;
color: rgba($text-color, 0.4);
background: #FFF;
.is-osx & {
padding-top: $electron-osx-control-spacing + $back-spacing;
}
&-icon {
font-size: 12px;
margin-right: 4px;
}
&:hover {
color: $link-hover-color;
}
}
}
}
// Styling for common/components/NavigationLink, with custom classname
.ElectronNavLink {
display: block;
margin: 0;
@include ellipsis;
&-link {
display: block;
height: 48px;
line-height: 48px;
padding: 0 0 0 10px;
border-bottom: 1px solid $gray-lighter;
color: $text-color;
font-size: 14px;
font-weight: normal;
&.is-active {
color: $link-color;
background: $gray-lightest;
}
&:hover {
color: $link-hover-color;
}
&-icon {
font-size: 12px;
margin-left: 3px;
opacity: 0.8;
}
}
}

View File

@ -0,0 +1,103 @@
import React from 'react';
import classnames from 'classnames';
import translate from 'translations';
import { navigationLinks } from 'config';
import NavigationLink from 'components/NavigationLink';
import LanguageSelect from './LanguageSelect';
import NodeSelect from './NodeSelect';
import NetworkStatus from './NetworkStatus';
import './ElectronNav.scss';
interface State {
panelContent: React.ReactElement<any> | null;
isPanelOpen: boolean;
}
export default class ElectronNav extends React.Component<{}, State> {
public state: State = {
panelContent: null,
isPanelOpen: false
};
public render() {
const { panelContent, isPanelOpen } = this.state;
return (
<div
className={classnames({
ElectronNav: true,
'is-panel-open': isPanelOpen
})}
>
<div className="ElectronNav-branding">
<div className="ElectronNav-branding-logo" />
<div className="ElectronNav-branding-beta">Beta Release</div>
</div>
<ul className="ElectronNav-links">
{navigationLinks.map(link => (
<NavigationLink
key={link.to}
link={link}
isHomepage={link === navigationLinks[0]}
className="ElectronNavLink"
/>
))}
</ul>
<div className="ElectronNav-controls">
<button className="ElectronNav-controls-btn" onClick={this.openLanguageSelect}>
Change Language
<i className="ElectronNav-controls-btn-icon fa fa-arrow-circle-right" />
</button>
<button className="ElectronNav-controls-btn" onClick={this.openNodeSelect}>
Change Network
<i className="ElectronNav-controls-btn-icon fa fa-arrow-circle-right" />
</button>
</div>
<div className="ElectronNav-status">
<NetworkStatus />
</div>
<div className="ElectronNav-panel">
<button className="ElectronNav-panel-back" onClick={this.closePanel}>
<i className="ElectronNav-panel-back-icon fa fa-arrow-circle-left" />
{translate('MODAL_BACK')}
</button>
<div className="ElectronNav-panel-content">{panelContent}</div>
</div>
</div>
);
}
private openLanguageSelect = () => {
const panelContent = <LanguageSelect closePanel={this.closePanel} />;
this.setState({
panelContent,
isPanelOpen: true
});
};
private openNodeSelect = () => {
const panelContent = <NodeSelect closePanel={this.closePanel} />;
this.setState({
panelContent,
isPanelOpen: true
});
};
private closePanel = () => {
const { panelContent } = this.state;
// Start closing panel
this.setState({ isPanelOpen: false });
// Remove content when out of sight
setTimeout(() => {
if (this.state.panelContent === panelContent) {
this.setState({ panelContent: null });
}
}, 300);
};
}

View File

@ -0,0 +1,25 @@
@import 'common/sass/variables';
@import 'common/sass/mixins';
.LanguageSelect {
&-language {
@include reset-button;
display: block;
width: 100%;
height: 48px;
line-height: 48px;
padding: 0 10px;
color: $text-color;
border-bottom: 1px solid $gray-lighter;
text-align: left;
&:hover {
color: $link-hover-color;
}
&.is-selected {
color: $link-color;
background: $gray-lightest;
}
}
}

View File

@ -0,0 +1,58 @@
import React from 'react';
import classnames from 'classnames';
import { connect } from 'react-redux';
import { languages } from 'config';
import { TChangeLanguage, changeLanguage } from 'actions/config';
import { getLanguageSelection } from 'selectors/config';
import { AppState } from 'reducers';
import './LanguageSelect.scss';
interface OwnProps {
closePanel(): void;
}
interface StateProps {
languageSelection: string;
}
interface DispatchProps {
changeLanguage: TChangeLanguage;
}
type Props = OwnProps & StateProps & DispatchProps;
class LanguageSelect extends React.Component<Props> {
public render() {
const { languageSelection } = this.props;
return (
<div className="LanguageSelect">
{Object.entries(languages).map(lang => (
<button
key={lang[0]}
className={classnames({
'LanguageSelect-language': true,
'is-selected': languageSelection === lang[0]
})}
onClick={() => this.handleLanguageSelect(lang[0])}
>
{lang[1]}
</button>
))}
</div>
);
}
private handleLanguageSelect = (lang: string) => {
this.props.changeLanguage(lang);
this.props.closePanel();
};
}
export default connect(
(state: AppState): StateProps => ({
languageSelection: getLanguageSelection(state)
}),
{
changeLanguage
}
)(LanguageSelect);

View File

@ -0,0 +1,30 @@
@import 'common/sass/variables';
.NetworkStatus {
display: flex;
align-items: center;
&-icon {
width: 10px;
height: 10px;
margin-right: 4px;
border-radius: 100%;
&.is-online {
background: $brand-success;
}
&.is-offline {
background: $brand-danger;
}
&.is-connecting {
background: $brand-warning;
}
}
&-text {
color: $gray-dark;
font-size: 9px;
}
}

View File

@ -0,0 +1,49 @@
import React from 'react';
import { connect } from 'react-redux';
import translate from 'translations';
import { getNetworkConfig, getOffline, isNodeChanging } from 'selectors/config';
import { NetworkConfig } from 'types/network';
import { AppState } from 'reducers';
import './NetworkStatus.scss';
enum NETWORK_STATUS {
CONNECTING = 'NETWORK_STATUS_CONNECTING',
OFFLINE = 'NETWORK_STATUS_OFFLINE',
ONLINE = 'NETWORK_STATUS_ONLINE'
}
interface StateProps {
network: NetworkConfig;
isOffline: boolean;
isChangingNode: boolean;
}
const NetworkStatus: React.SFC<StateProps> = ({ isOffline, isChangingNode, network }) => {
let statusClass: string;
let statusText: string;
const $network = network.isCustom ? network.unit : network.name;
if (isChangingNode) {
statusClass = 'is-connecting';
statusText = NETWORK_STATUS.CONNECTING;
} else if (isOffline) {
statusClass = 'is-offline';
statusText = NETWORK_STATUS.OFFLINE;
} else {
statusClass = 'is-online';
statusText = NETWORK_STATUS.ONLINE;
}
return (
<div className="NetworkStatus">
<div className={`NetworkStatus-icon ${statusClass}`} />
<div className="NetworkStatus-text">{translate(statusText, { $network })}</div>
</div>
);
};
export default connect((state: AppState): StateProps => ({
network: getNetworkConfig(state),
isOffline: getOffline(state),
isChangingNode: isNodeChanging(state)
}))(NetworkStatus);

View File

@ -0,0 +1,31 @@
@import 'common/sass/variables';
@import 'common/sass/mixins';
.NodeSelect {
&-node {
@include reset-button;
display: block;
width: 100%;
height: 48px;
line-height: 48px;
padding: 0 10px;
color: $text-color;
border-bottom: 1px solid $gray-lighter;
border-left: 4px solid;
text-align: left;
@include ellipsis;
&:hover {
color: $link-hover-color;
}
&.is-active {
color: $link-color;
background: $gray-lightest;
}
small {
font-size: 12px;
}
}
}

View File

@ -0,0 +1,126 @@
import React from 'react';
import classnames from 'classnames';
import { connect } from 'react-redux';
import translate from 'translations';
import CustomNodeModal from 'components/CustomNodeModal';
import {
TChangeNodeIntent,
TAddCustomNode,
TRemoveCustomNode,
changeNodeIntent,
addCustomNode,
removeCustomNode,
AddCustomNodeAction
} from 'actions/config';
import {
isNodeChanging,
getNodeId,
CustomNodeOption,
NodeOption,
getNodeOptions
} from 'selectors/config';
import { AppState } from 'reducers';
import './NodeSelect.scss';
interface OwnProps {
closePanel(): void;
}
interface StateProps {
nodeSelection: AppState['config']['nodes']['selectedNode']['nodeId'];
isChangingNode: AppState['config']['nodes']['selectedNode']['pending'];
nodeOptions: (CustomNodeOption | NodeOption)[];
}
interface DispatchProps {
changeNodeIntent: TChangeNodeIntent;
addCustomNode: TAddCustomNode;
removeCustomNode: TRemoveCustomNode;
}
type Props = OwnProps & StateProps & DispatchProps;
interface State {
isAddingCustomNode: boolean;
}
class NodeSelect extends React.Component<Props, State> {
public state: State = {
isAddingCustomNode: false
};
public render() {
const { nodeSelection, nodeOptions } = this.props;
const { isAddingCustomNode } = this.state;
return (
<div className="NodeSelect">
{nodeOptions.map(node => (
<button
key={node.value}
className={classnames({
'NodeSelect-node': true,
'is-active': node.value === nodeSelection
})}
onClick={() => this.handleNodeSelect(node.value)}
style={{ borderLeftColor: node.color }}
>
{this.renderNodeLabel(node)}
</button>
))}
<button className="NodeSelect-node is-add" onClick={this.openCustomNodeModal}>
{translate('NODE_ADD')}
</button>
<CustomNodeModal
isOpen={isAddingCustomNode}
addCustomNode={this.addCustomNode}
handleClose={this.closeCustomNodeModal}
/>
</div>
);
}
private handleNodeSelect = (node: string) => {
this.props.changeNodeIntent(node);
this.props.closePanel();
};
private renderNodeLabel(node: CustomNodeOption | NodeOption) {
return node.isCustom ? (
<span>
{node.label.network} - {node.label.nodeName} <small>(custom)</small>
</span>
) : (
<span>
{node.label.network} - <small>({node.label.service})</small>
</span>
);
}
private openCustomNodeModal = () => {
this.setState({ isAddingCustomNode: true });
};
private closeCustomNodeModal = () => {
this.setState({ isAddingCustomNode: false });
};
private addCustomNode = (payload: AddCustomNodeAction['payload']) => {
this.props.addCustomNode(payload);
this.closeCustomNodeModal();
};
}
export default connect(
(state: AppState): StateProps => ({
isChangingNode: isNodeChanging(state),
nodeSelection: getNodeId(state),
nodeOptions: getNodeOptions(state)
}),
{
changeNodeIntent,
addCustomNode,
removeCustomNode
}
)(NodeSelect);

View File

@ -0,0 +1,2 @@
import ElectronNav from './ElectronNav';
export default ElectronNav;

View File

@ -3,14 +3,16 @@ import { Link } from 'react-router-dom';
import translate from 'translations';
import { NewTabLink } from 'components/ui';
import { BlockExplorerConfig } from 'types/network';
import { getTXDetailsCheckURL } from 'libs/scheduling';
import { etherChainExplorerInst } from 'config/data';
export interface TransactionSucceededProps {
txHash: string;
blockExplorer?: BlockExplorerConfig;
scheduling?: boolean;
}
const TransactionSucceeded = ({ txHash, blockExplorer }: TransactionSucceededProps) => {
const TransactionSucceeded = ({ txHash, blockExplorer, scheduling }: TransactionSucceededProps) => {
let verifyBtn: React.ReactElement<string> | undefined;
let altVerifyBtn: React.ReactElement<string> | undefined;
if (blockExplorer) {
@ -30,11 +32,21 @@ const TransactionSucceeded = ({ txHash, blockExplorer }: TransactionSucceededPro
);
}
let scheduleDetailsBtn: React.ReactElement<string> | undefined;
if (scheduling) {
scheduleDetailsBtn = (
<NewTabLink className="btn btn-xs" href={getTXDetailsCheckURL(txHash)}>
{translate('SCHEDULE_CHECK')}
</NewTabLink>
);
}
return (
<div>
<p>
{translate('SUCCESS_3')} {txHash}
</p>
{scheduleDetailsBtn}
{verifyBtn}
{altVerifyBtn}
<Link to={`/tx-status?txHash=${txHash}`} className="btn btn-xs">

View File

@ -1,22 +1,22 @@
import logo from 'assets/images/logo-mycrypto.svg';
import {
ledgerReferralURL,
trezorReferralURL,
ethercardReferralURL,
donationAddressMap,
VERSION,
knowledgeBaseURL,
discordURL
socialMediaLinks,
productLinks,
affiliateLinks,
partnerLinks
} from 'config';
import React from 'react';
import PreFooter from './PreFooter';
import DisclaimerModal from './DisclaimerModal';
import DisclaimerModal from 'components/DisclaimerModal';
import { NewTabLink } from 'components/ui';
import OnboardModal from 'containers/OnboardModal';
import './index.scss';
import { translateRaw } from 'translations';
const SocialMediaLink = ({ link, text }: Link) => {
const SocialMediaLink = ({ link, text }: { link: string; text: string }) => {
return (
<NewTabLink className="SocialMediaLink" key={link} href={link} aria-label={text}>
<i className={`sm-icon sm-logo-${text}`} />
@ -24,101 +24,6 @@ const SocialMediaLink = ({ link, text }: Link) => {
);
};
const SOCIAL_MEDIA: Link[] = [
{
link: 'https://twitter.com/mycrypto',
text: 'twitter'
},
{
link: 'https://www.facebook.com/MyCrypto/',
text: 'facebook'
},
{
link: 'https://medium.com/@mycrypto',
text: 'medium'
},
{
link: 'https://www.linkedin.com/company/mycrypto',
text: 'linkedin'
},
{
link: 'https://github.com/MyCryptoHQ',
text: 'github'
},
{
link: 'https://www.reddit.com/r/mycrypto/',
text: 'reddit'
},
{
link: discordURL,
text: 'discord'
}
];
const PRODUCT_INFO: Link[] = [
{
link:
'https://chrome.google.com/webstore/detail/etheraddresslookup/pdknmigbbbhmllnmgdfalmedcmcefdfn',
text: translateRaw('ETHER_ADDRESS_LOOKUP')
},
{
link:
'https://chrome.google.com/webstore/detail/ethersecuritylookup/bhhfhgpgmifehjdghlbbijjaimhmcgnf',
text: translateRaw('ETHER_SECURITY_LOOKUP')
},
{
link: 'https://etherscamdb.info/',
text: translateRaw('ETHERSCAMDB')
},
{
link: 'https://www.mycrypto.com/helpers.html',
text: translateRaw('FOOTER_HELP_AND_DEBUGGING')
},
{
link: 'mailto:press@mycrypto.com',
text: translateRaw('FOOTER_PRESS')
}
];
const AFFILIATES: Link[] = [
{
link: ledgerReferralURL,
text: translateRaw('LEDGER_REFERRAL_1')
},
{
link: trezorReferralURL,
text: translateRaw('TREZOR_REFERAL')
},
{
link: ethercardReferralURL,
text: translateRaw('ETHERCARD_REFERAL')
}
];
const FRIENDS: Link[] = [
{
link: 'https://metamask.io/',
text: 'MetaMask'
},
{
link: 'https://infura.io/',
text: 'Infura'
},
{
link: 'https://etherscan.io/',
text: 'Etherscan'
},
{
link: 'https://etherchain.org/',
text: 'Etherchain'
}
];
interface Link {
link: string;
text: string;
}
interface Props {
latestBlock: string;
}
@ -139,7 +44,7 @@ export default class Footer extends React.PureComponent<Props, State> {
<footer className="Footer" role="contentinfo" aria-label="footer">
<div className="Footer-links Footer-section">
<div className="Footer-links-social">
{SOCIAL_MEDIA.map((socialMediaItem, idx) => (
{socialMediaLinks.map((socialMediaItem, idx) => (
<SocialMediaLink
link={socialMediaItem.link}
key={idx}
@ -149,11 +54,14 @@ export default class Footer extends React.PureComponent<Props, State> {
</div>
<div className="Footer-links-links">
{PRODUCT_INFO.map((productInfoItem, idx) => (
<NewTabLink key={idx} href={productInfoItem.link}>
{productInfoItem.text}
{productLinks.map(link => (
<NewTabLink key={link.link} href={link.link}>
{link.text}
</NewTabLink>
))}
<NewTabLink href="mailto:press@mycrypto.com">
{translateRaw('FOOTER_PRESS')}
</NewTabLink>
</div>
</div>
@ -169,7 +77,7 @@ export default class Footer extends React.PureComponent<Props, State> {
</NewTabLink>
<div className="Footer-about-links">
<a href="https://mycrypto.com">MyCrypto.com</a>
<NewTabLink href="https://mycrypto.com">MyCrypto.com</NewTabLink>
<NewTabLink href={knowledgeBaseURL}>{translateRaw('FOOTER_SUPPORT')}</NewTabLink>
<NewTabLink href="https://about.mycrypto.com">
{translateRaw('FOOTER_TEAM')}
@ -192,7 +100,7 @@ export default class Footer extends React.PureComponent<Props, State> {
<div className="Footer-support Footer-section">
<h5 className="Footer-support-title">{translateRaw('FOOTER_AFFILIATE_TITLE')}</h5>
<div className="Footer-support-affiliates">
{AFFILIATES.map((link, i) => (
{affiliateLinks.map((link, i) => (
<NewTabLink key={i} href={link.link}>
{link.text}
</NewTabLink>
@ -214,7 +122,7 @@ export default class Footer extends React.PureComponent<Props, State> {
</div>
<div className="Footer-support-friends">
{FRIENDS.map((link, i) => (
{partnerLinks.map((link, i) => (
<NewTabLink key={i} href={link.link}>
{link.text}
</NewTabLink>

View File

@ -9,9 +9,14 @@ import { Input } from 'components/ui';
interface Props {
customLabel?: string;
disabled?: boolean;
hideGasCalculationSpinner?: boolean;
}
export const GasLimitField: React.SFC<Props> = ({ customLabel, disabled }) => (
export const GasLimitField: React.SFC<Props> = ({
customLabel,
disabled,
hideGasCalculationSpinner
}) => (
<GasLimitFieldFactory
withProps={({ gasLimit: { raw }, onChange, readOnly, gasEstimationPending }) => (
<div className="input-group-wrapper">
@ -19,7 +24,10 @@ export const GasLimitField: React.SFC<Props> = ({ customLabel, disabled }) => (
<div className="input-group-header">
{customLabel ? customLabel : translate('TRANS_GAS')}
<div className="flex-spacer" />
<InlineSpinner active={gasEstimationPending} text="Calculating" />
<InlineSpinner
active={!hideGasCalculationSpinner && gasEstimationPending}
text="Calculating"
/>
</div>
<Input
className={gasLimitValidator(raw) ? 'is-valid' : 'is-invalid'}

View File

@ -5,6 +5,7 @@ import { inputGasLimit, TInputGasLimit } from 'actions/transaction';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { sanitizeNumericalInput } from 'libs/values';
import { getSchedulingToggle } from 'selectors/schedule/fields';
const defaultGasLimit = '21000';
@ -18,16 +19,24 @@ export interface CallBackProps {
interface DispatchProps {
inputGasLimit: TInputGasLimit;
}
interface OwnProps {
gasLimit: string | null;
scheduling: boolean;
withProps(props: CallBackProps): React.ReactElement<any> | null;
}
type Props = DispatchProps & OwnProps;
class GasLimitFieldClass extends Component<Props, {}> {
class GasLimitFieldClass extends Component<Props> {
public componentDidMount() {
const { gasLimit } = this.props;
const { gasLimit, scheduling } = this.props;
if (scheduling) {
return;
}
if (gasLimit) {
this.props.inputGasLimit(gasLimit);
} else {
@ -45,7 +54,12 @@ class GasLimitFieldClass extends Component<Props, {}> {
};
}
const GasLimitField = connect(null, { inputGasLimit })(GasLimitFieldClass);
const GasLimitField = connect(
(state: AppState) => ({
scheduling: getSchedulingToggle(state).value
}),
{ inputGasLimit }
)(GasLimitFieldClass);
interface DefaultGasLimitFieldProps {
withProps(props: CallBackProps): React.ReactElement<any> | null;

View File

@ -42,7 +42,7 @@ interface OwnProps {
type Props = OwnProps & StateProps;
class GenerateTransactionFactoryClass extends Component<Props> {
export class GenerateTransactionFactoryClass extends Component<Props> {
public render() {
const {
walletType,
@ -61,6 +61,7 @@ class GenerateTransactionFactoryClass extends Component<Props> {
const isButtonDisabled =
!isFullTransaction || networkRequestPending || !validGasPrice || !validGasLimit;
return (
<React.Fragment>
<WithSigner

View File

@ -78,3 +78,69 @@
}
}
}
// Styling for common/components/NavigationLink.tsx, with custom class
.NavigationLink {
display: inline-block;
&-link {
color: darken($link-color, 15%);
display: block;
font-size: 16px;
font-weight: 300;
padding: 10px;
white-space: nowrap;
position: relative;
min-height: 2.75rem;
&-icon {
font-size: 0.7rem;
margin-left: 2px;
opacity: 0.8;
}
&:after {
content: '';
background: $brand-primary;
height: 2px;
width: 100%;
left: 0px;
position: absolute;
bottom: -1px;
transition: all 250ms ease 0s;
transform: scaleX(0);
}
&.is-active,
&:hover,
&:focus {
color: $brand-primary;
text-decoration: none;
transition: all 250ms ease 0s;
&:after {
transform: scaleX(1);
transition: all 250ms ease 0s;
}
}
&.is-disabled {
pointer-events: none;
opacity: 0.3;
}
}
}
#NAV_SWAP.NavigationLink-link:before {
content: '';
display: inline-block;
margin-top: -0.1rem;
width: 1.3rem;
height: 1.3rem;
background-image: url('~assets/images/logo-shapeshift-no-text.svg');
background-position: center;
background-repeat: no-repeat;
background-size: contain;
vertical-align: middle;
margin-right: 4px;
}

View File

@ -1,54 +1,8 @@
import React, { PureComponent } from 'react';
import NavigationLink from './NavigationLink';
import { knowledgeBaseURL } from 'config';
import NavigationLink from 'components/NavigationLink';
import { navigationLinks } from 'config';
import './Navigation.scss';
export interface TabLink {
name: string;
to: string;
external?: boolean;
}
const tabs: TabLink[] = [
{
name: 'NAV_VIEW',
to: '/account'
},
{
name: 'NAV_GENERATEWALLET',
to: '/generate'
},
{
name: 'NAV_SWAP',
to: '/swap'
},
{
name: 'NAV_CONTRACTS',
to: '/contracts'
},
{
name: 'NAV_ENS',
to: '/ens'
},
{
name: 'NAV_SIGN',
to: '/sign-and-verify-message'
},
{
name: 'NAV_TXSTATUS',
to: '/tx-status'
},
{
name: 'NAV_BROADCAST',
to: '/pushTx'
},
{
name: 'NAV_HELP',
to: `${knowledgeBaseURL}`,
external: true
}
];
interface Props {
color?: string | false;
}
@ -97,9 +51,14 @@ export default class Navigation extends PureComponent<Props, State> {
<div className="Navigation-scroll container">
<ul className="Navigation-links">
{tabs.map(link => {
return <NavigationLink key={link.name} link={link} isHomepage={link === tabs[0]} />;
})}
{navigationLinks.map(link => (
<NavigationLink
key={link.name}
link={link}
isHomepage={link === navigationLinks[0]}
className="NavigationLink"
/>
))}
</ul>
</div>

View File

@ -1,60 +0,0 @@
@import 'common/sass/variables';
.NavigationLink {
display: inline-block;
&-link {
color: darken($link-color, 15%);
display: block;
font-size: 16px;
font-weight: 300;
padding: 10px;
white-space: nowrap;
position: relative;
min-height: 2.75rem;
&:after {
content: '';
background: $brand-primary;
height: 2px;
width: 100%;
left: 0px;
position: absolute;
bottom: -1px;
transition: all 250ms ease 0s;
transform: scaleX(0);
}
&.is-active,
&:hover,
&:focus {
color: $brand-primary;
text-decoration: none;
transition: all 250ms ease 0s;
&:after {
transform: scaleX(1);
transition: all 250ms ease 0s;
}
}
&.is-disabled {
pointer-events: none;
opacity: 0.3;
}
}
}
#NAV_SWAP a:before {
content: '';
display: inline-block;
margin-top: -0.1rem;
width: 1.3rem;
height: 1.3rem;
background-image: url('~assets/images/logo-shapeshift-no-text.svg');
background-position: center;
background-repeat: no-repeat;
background-size: contain;
vertical-align: middle;
margin-right: 4px;
}

View File

@ -1,12 +1,14 @@
import {
TChangeLanguage,
TChangeNodeIntent,
TChangeNodeIntentOneTime,
TAddCustomNode,
TRemoveCustomNode,
TAddCustomNetwork,
AddCustomNodeAction,
changeLanguage,
changeNodeIntent,
changeNodeIntentOneTime,
addCustomNode,
removeCustomNode,
addCustomNetwork
@ -19,8 +21,8 @@ import { Link } from 'react-router-dom';
import { TSetGasPriceField, setGasPriceField } from 'actions/transaction';
import { ANNOUNCEMENT_MESSAGE, ANNOUNCEMENT_TYPE, languages } from 'config';
import Navigation from './components/Navigation';
import CustomNodeModal from './components/CustomNodeModal';
import OnlineStatus from './components/OnlineStatus';
import CustomNodeModal from 'components/CustomNodeModal';
import { getKeyByValue } from 'utils/helpers';
import { NodeConfig } from 'types/node';
import './index.scss';
@ -34,15 +36,21 @@ import {
CustomNodeOption,
NodeOption,
getNodeOptions,
getNetworkConfig
getNetworkConfig,
isStaticNodeId
} from 'selectors/config';
import { NetworkConfig } from 'types/network';
import { connect } from 'react-redux';
import { stripWeb3Network } from 'libs/nodes';
import { connect, MapStateToProps } from 'react-redux';
import translate from 'translations';
interface OwnProps {
networkParam: string | null;
}
interface DispatchProps {
changeLanguage: TChangeLanguage;
changeNodeIntent: TChangeNodeIntent;
changeNodeIntentOneTime: TChangeNodeIntentOneTime;
setGasPriceField: TSetGasPriceField;
addCustomNode: TAddCustomNode;
removeCustomNode: TRemoveCustomNode;
@ -50,6 +58,7 @@ interface DispatchProps {
}
interface StateProps {
shouldSetNodeFromQS: boolean;
network: NetworkConfig;
languageSelection: AppState['config']['meta']['languageSelection'];
node: NodeConfig;
@ -59,7 +68,11 @@ interface StateProps {
nodeOptions: (CustomNodeOption | NodeOption)[];
}
const mapStateToProps = (state: AppState): StateProps => ({
const mapStateToProps: MapStateToProps<StateProps, OwnProps, AppState> = (
state,
{ networkParam }
): StateProps => ({
shouldSetNodeFromQS: !!(networkParam && isStaticNodeId(state, networkParam)),
isOffline: getOffline(state),
isChangingNode: isNodeChanging(state),
languageSelection: getLanguageSelection(state),
@ -73,6 +86,7 @@ const mapDispatchToProps: DispatchProps = {
setGasPriceField,
changeLanguage,
changeNodeIntent,
changeNodeIntentOneTime,
addCustomNode,
removeCustomNode,
addCustomNetwork
@ -82,13 +96,17 @@ interface State {
isAddingCustomNode: boolean;
}
type Props = StateProps & DispatchProps;
type Props = OwnProps & StateProps & DispatchProps;
class Header extends Component<Props, State> {
public state = {
isAddingCustomNode: false
};
public componentDidMount() {
this.attemptSetNodeFromQueryParameter();
}
public render() {
const {
languageSelection,
@ -120,7 +138,7 @@ class Header extends Component<Props, State> {
...rest,
name: (
<span>
{stripWeb3Network(label.network)} <small>({label.service})</small>
{label.network} <small>({label.service})</small>
</span>
)
};
@ -161,7 +179,6 @@ class Header extends Component<Props, State> {
color="white"
/>
</div>
{console.log(nodeSelection)}
<div
className={classnames({
'Header-branding-right-dropdown': true,
@ -177,7 +194,7 @@ class Header extends Component<Props, State> {
value={nodeSelection || ''}
extra={
<li>
<a onClick={this.openCustomNodeModal}>Add Custom Node</a>
<a onClick={this.openCustomNodeModal}>{translate('NODE_ADD')}</a>
</li>
}
disabled={nodeSelection === 'web3'}
@ -221,6 +238,13 @@ class Header extends Component<Props, State> {
this.setState({ isAddingCustomNode: false });
this.props.addCustomNode(payload);
};
private attemptSetNodeFromQueryParameter() {
const { shouldSetNodeFromQS, networkParam } = this.props;
if (shouldSetNodeFromQS) {
this.props.changeNodeIntentOneTime(networkParam!);
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);

View File

@ -70,7 +70,6 @@ class LogOutPromptClass extends React.Component<Props, State> {
private onConfirm = () => {
const { nextLocation: next } = this.state;
this.props.resetWallet();
this.props.web3UnsetNode();
this.setState(
{
openModal: false,
@ -79,6 +78,7 @@ class LogOutPromptClass extends React.Component<Props, State> {
() => {
if (next) {
this.props.history.push(`${next.pathname}${next.search}${next.hash}`);
this.props.web3UnsetNode();
}
}
);

View File

@ -2,21 +2,20 @@ import classnames from 'classnames';
import React from 'react';
import { Link, withRouter, RouteComponentProps } from 'react-router-dom';
import translate, { translateRaw } from 'translations';
import { TabLink } from './Navigation';
import './NavigationLink.scss';
import { NavigationLink } from 'config';
interface Props extends RouteComponentProps<{}> {
link: TabLink;
link: NavigationLink;
isHomepage: boolean;
className: string;
}
class NavigationLink extends React.PureComponent<Props, {}> {
class NavigationLinkClass extends React.PureComponent<Props, {}> {
public render() {
const { link, location, isHomepage } = this.props;
const isExternalLink = link.to.includes('http');
const { link, location, isHomepage, className } = this.props;
let isActive = false;
if (!isExternalLink) {
if (!link.external) {
// isActive if
// 1) Current path is the same as link
// 2) the first path is the same for both links (/account and /account/send)
@ -27,7 +26,7 @@ class NavigationLink extends React.PureComponent<Props, {}> {
}
const linkClasses = classnames({
'NavigationLink-link': true,
[`${className}-link`]: true,
'is-disabled': !link.to,
'is-active': isActive
});
@ -43,6 +42,7 @@ class NavigationLink extends React.PureComponent<Props, {}> {
rel="noopener noreferrer"
>
{translate(link.name)}
<i className={`${className}-link-icon fa fa-external-link`} />
</a>
) : (
<Link className={linkClasses} to={(link as any).to} aria-label={linkLabel}>
@ -51,7 +51,7 @@ class NavigationLink extends React.PureComponent<Props, {}> {
);
return (
<li id={link.name} className="NavigationLink">
<li id={link.name} className={className}>
{linkEl}
</li>
);
@ -59,4 +59,4 @@ class NavigationLink extends React.PureComponent<Props, {}> {
}
// withRouter is a HOC which provides NavigationLink with a react-router location prop
export default withRouter<Props>(NavigationLink);
export default withRouter<Props>(NavigationLinkClass);

View File

@ -41,7 +41,7 @@ interface OwnProps {
type Props = StateProps & OwnProps;
class SendButtonFactoryClass extends Component<Props> {
export class SendButtonFactoryClass extends Component<Props> {
public render() {
const {
signing,

View File

@ -3,6 +3,10 @@
.SubTabs {
margin-top: $space-xs;
.is-electron & {
margin-top: 0;
}
&-tabs {
display: inline-block;
white-space: nowrap;

View File

@ -50,6 +50,7 @@ interface OwnProps {
disableToggle?: boolean;
advancedGasOptions?: AdvancedOptions;
className?: string;
scheduling?: boolean;
}
type Props = DispatchProps & OwnProps & StateProps;
@ -90,16 +91,19 @@ class TXMetaDataPanel extends React.Component<Props, State> {
}
public render() {
const { offline, disableToggle, advancedGasOptions, className = '' } = this.props;
const { offline, disableToggle, advancedGasOptions, className = '', scheduling } = this.props;
const { gasPrice } = this.state;
const showAdvanced = this.state.sliderState === 'advanced' || offline;
return (
<div className={`Gas col-md-12 ${className}`}>
<br />
{showAdvanced ? (
<AdvancedGas
gasPrice={gasPrice}
inputGasPrice={this.props.inputGasPrice}
options={advancedGasOptions}
scheduling={scheduling}
/>
) : (
<SimpleGas

View File

@ -1,6 +1,6 @@
import React from 'react';
import { translateRaw } from 'translations';
import FeeSummary from './FeeSummary';
import FeeSummary, { RenderData } from './FeeSummary';
import './AdvancedGas.scss';
import { TToggleAutoGasLimit, toggleAutoGasLimit } from 'actions/config';
import { AppState } from 'reducers';
@ -11,6 +11,8 @@ import { getAutoGasLimitEnabled } from 'selectors/config';
import { isValidGasPrice } from 'selectors/transaction';
import { sanitizeNumericalInput } from 'libs/values';
import { Input } from 'components/ui';
import { EAC_SCHEDULING_CONFIG } from 'libs/scheduling';
import { getScheduleGasPrice, getTimeBounty } from 'selectors/schedule';
export interface AdvancedOptions {
gasPriceField?: boolean;
@ -24,6 +26,9 @@ interface OwnProps {
inputGasPrice: TInputGasPrice;
gasPrice: AppState['transaction']['fields']['gasPrice'];
options?: AdvancedOptions;
scheduling?: boolean;
scheduleGasPrice: AppState['schedule']['scheduleGasPrice'];
timeBounty: AppState['schedule']['timeBounty'];
}
interface StateProps {
@ -54,8 +59,9 @@ class AdvancedGas extends React.Component<Props, State> {
};
public render() {
const { autoGasLimitEnabled, gasPrice, validGasPrice } = this.props;
const { gasPriceField, gasLimitField, nonceField, dataField, feeSummary } = this.state.options;
const { autoGasLimitEnabled, gasPrice, scheduling, validGasPrice } = this.props;
const { gasPriceField, gasLimitField, nonceField, dataField } = this.state.options;
return (
<div className="AdvancedGas row form-group">
<div className="AdvancedGas-calculate-limit">
@ -78,7 +84,7 @@ class AdvancedGas extends React.Component<Props, State> {
{translateRaw('OFFLINE_STEP2_LABEL_3')} (gwei)
</div>
<Input
className={!!gasPrice.raw && !validGasPrice ? 'is-invalid' : ''}
className={!!gasPrice.raw && !validGasPrice ? 'invalid' : ''}
type="number"
placeholder="40"
value={gasPrice.raw}
@ -91,7 +97,11 @@ class AdvancedGas extends React.Component<Props, State> {
{gasLimitField && (
<div className="AdvancedGas-gas-limit">
<GasLimitField customLabel={translateRaw('OFFLINE_STEP2_LABEL_4')} />
<GasLimitField
customLabel={translateRaw('OFFLINE_STEP2_LABEL_4')}
disabled={scheduling}
hideGasCalculationSpinner={scheduling}
/>
</div>
)}
{nonceField && (
@ -101,24 +111,62 @@ class AdvancedGas extends React.Component<Props, State> {
)}
</div>
{dataField && (
<div className="AdvancedGas-data">
<DataField />
</div>
)}
{!scheduling &&
dataField && (
<div className="AdvancedGas-data">
<DataField />
</div>
)}
{feeSummary && (
<div className="AdvancedGas-fee-summary">
<FeeSummary
gasPrice={gasPrice}
render={({ gasPriceWei, gasLimit, fee, usd }) => (
<span>
{gasPriceWei} * {gasLimit} = {fee} {usd && <span>~= ${usd} USD</span>}
</span>
)}
/>
</div>
)}
{this.renderFee()}
</div>
);
}
private renderFee() {
const { gasPrice, scheduleGasPrice } = this.props;
const { feeSummary } = this.state.options;
if (!feeSummary) {
return;
}
return (
<div className="AdvancedGas-fee-summary">
<FeeSummary
gasPrice={gasPrice}
scheduleGasPrice={scheduleGasPrice}
render={(data: RenderData) => this.printFeeFormula(data)}
/>
</div>
);
}
private printFeeFormula(data: RenderData) {
if (this.props.scheduling) {
return this.getScheduleFeeFormula(data);
}
return this.getStandardFeeFormula(data);
}
private getStandardFeeFormula({ gasPriceWei, gasLimit, fee, usd }: RenderData) {
return (
<span>
{gasPriceWei} * {gasLimit} = {fee} {usd && <span>~= ${usd} USD</span>}
</span>
);
}
private getScheduleFeeFormula({ gasPriceWei, scheduleGasLimit, fee, usd }: RenderData) {
const { scheduleGasPrice, timeBounty } = this.props;
return (
<div>
{timeBounty && timeBounty.value && timeBounty.value.toString()} + {gasPriceWei} *{' '}
{EAC_SCHEDULING_CONFIG.SCHEDULING_GAS_LIMIT.toString()} +{' '}
{scheduleGasPrice && scheduleGasPrice.value && scheduleGasPrice.value.toString()} * ({EAC_SCHEDULING_CONFIG.FUTURE_EXECUTION_COST.toString()}{' '}
+ {scheduleGasLimit}) =&nbsp;{fee}&nbsp;{usd && <span>~=&nbsp;${usd}&nbsp;USD</span>}
</div>
);
}
@ -136,6 +184,8 @@ class AdvancedGas extends React.Component<Props, State> {
export default connect(
(state: AppState) => ({
autoGasLimitEnabled: getAutoGasLimitEnabled(state),
scheduleGasPrice: getScheduleGasPrice(state),
timeBounty: getTimeBounty(state),
validGasPrice: isValidGasPrice(state)
}),
{ toggleAutoGasLimit }

View File

@ -9,3 +9,9 @@
text-align: center;
font-size: 14px;
}
.SchedulingFeeSummary {
font-size: 12px;
height: auto;
min-height: 42px;
}

View File

@ -2,17 +2,21 @@ import React from 'react';
import BN from 'bn.js';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import classNames from 'classnames';
import { getNetworkConfig, getOffline } from 'selectors/config';
import { getIsEstimating } from 'selectors/gas';
import { getGasLimit } from 'selectors/transaction';
import { UnitDisplay, Spinner } from 'components/ui';
import { NetworkConfig } from 'types/network';
import './FeeSummary.scss';
import { getScheduleGasLimit, getTimeBounty, getSchedulingToggle } from 'selectors/schedule';
import { calcEACTotalCost } from 'libs/scheduling';
interface RenderData {
export interface RenderData {
gasPriceWei: string;
gasPriceGwei: string;
gasLimit: string;
scheduleGasLimit: string;
fee: React.ReactElement<string>;
usd: React.ReactElement<string> | null;
}
@ -23,10 +27,15 @@ interface ReduxStateProps {
network: NetworkConfig;
isOffline: AppState['config']['meta']['offline'];
isGasEstimating: AppState['gas']['isEstimating'];
scheduleGasLimit: AppState['schedule']['scheduleGasLimit'];
timeBounty: AppState['schedule']['timeBounty'];
scheduling: AppState['schedule']['schedulingToggle']['value'];
}
interface OwnProps {
gasPrice: AppState['transaction']['fields']['gasPrice'];
scheduleGasPrice: AppState['schedule']['scheduleGasPrice'];
render(data: RenderData): React.ReactElement<string> | string;
}
@ -34,7 +43,16 @@ type Props = OwnProps & ReduxStateProps;
class FeeSummary extends React.Component<Props> {
public render() {
const { gasPrice, gasLimit, rates, network, isOffline, isGasEstimating } = this.props;
const {
gasPrice,
gasLimit,
rates,
network,
isOffline,
isGasEstimating,
scheduling,
scheduleGasLimit
} = this.props;
if (isGasEstimating) {
return (
@ -44,7 +62,7 @@ class FeeSummary extends React.Component<Props> {
);
}
const feeBig = gasPrice.value && gasLimit.value && gasPrice.value.mul(gasLimit.value);
const feeBig = this.getFee();
const fee = (
<UnitDisplay
value={feeBig}
@ -68,18 +86,56 @@ class FeeSummary extends React.Component<Props> {
/>
);
const feeSummaryClasses = classNames({
FeeSummary: true,
SchedulingFeeSummary: scheduling
});
return (
<div className="FeeSummary">
<div className={feeSummaryClasses}>
{this.props.render({
gasPriceWei: gasPrice.value.toString(),
gasPriceGwei: gasPrice.raw,
fee,
usd,
gasLimit: gasLimit.raw
gasLimit: gasLimit.raw,
scheduleGasLimit: scheduleGasLimit.raw
})}
</div>
);
}
private getFee() {
const { scheduling } = this.props;
if (scheduling) {
return this.calculateSchedulingFee();
}
return this.calculateStandardFee();
}
private calculateStandardFee() {
const { gasPrice, gasLimit } = this.props;
return gasPrice.value && gasLimit.value && gasPrice.value.mul(gasLimit.value);
}
private calculateSchedulingFee() {
const { gasPrice, scheduleGasLimit, scheduleGasPrice, timeBounty } = this.props;
return (
gasPrice.value &&
scheduleGasLimit.value &&
timeBounty.value &&
calcEACTotalCost(
scheduleGasLimit.value,
gasPrice.value,
scheduleGasPrice.value,
timeBounty.value
)
);
}
}
function mapStateToProps(state: AppState): ReduxStateProps {
@ -88,7 +144,10 @@ function mapStateToProps(state: AppState): ReduxStateProps {
rates: state.rates.rates,
network: getNetworkConfig(state),
isOffline: getOffline(state),
isGasEstimating: getIsEstimating(state)
isGasEstimating: getIsEstimating(state),
scheduling: getSchedulingToggle(state).value,
scheduleGasLimit: getScheduleGasLimit(state),
timeBounty: getTimeBounty(state)
};
}

View File

@ -1,7 +1,6 @@
import React from 'react';
import Slider, { createSliderWithTooltip } from 'rc-slider';
import translate from 'translations';
import FeeSummary from './FeeSummary';
import './SimpleGas.scss';
import { AppState } from 'reducers';
import {
@ -17,11 +16,15 @@ import { Wei, fromWei } from 'libs/units';
import { gasPriceDefaults } from 'config';
import { InlineSpinner } from 'components/ui/InlineSpinner';
import { TInputGasPrice } from 'actions/transaction';
import FeeSummary from './FeeSummary';
import { getScheduleGasPrice } from 'selectors/schedule';
const SliderWithTooltip = createSliderWithTooltip(Slider);
interface OwnProps {
gasPrice: AppState['transaction']['fields']['gasPrice'];
setGasPrice: TInputGasPrice;
inputGasPrice(rawGas: string): void;
}
@ -32,6 +35,7 @@ interface StateProps {
gasLimitPending: boolean;
isWeb3Node: boolean;
gasLimitEstimationTimedOut: boolean;
scheduleGasPrice: AppState['schedule']['scheduleGasPrice'];
}
interface ActionProps {
@ -68,7 +72,8 @@ class SimpleGas extends React.Component<Props> {
gasLimitEstimationTimedOut,
isWeb3Node,
noncePending,
gasLimitPending
gasLimitPending,
scheduleGasPrice
} = this.props;
const bounds = {
@ -114,6 +119,7 @@ class SimpleGas extends React.Component<Props> {
</div>
<FeeSummary
gasPrice={gasPrice}
scheduleGasPrice={scheduleGasPrice}
render={({ fee, usd }) => (
<span>
{fee} {usd && <span>/ ${usd}</span>}
@ -151,7 +157,8 @@ export default connect(
noncePending: nonceRequestPending(state),
gasLimitPending: getGasEstimationPending(state),
gasLimitEstimationTimedOut: getGasLimitEstimationTimedOut(state),
isWeb3Node: getIsWeb3Node(state)
isWeb3Node: getIsWeb3Node(state),
scheduleGasPrice: getScheduleGasPrice(state)
}),
{
fetchGasEstimates

View File

@ -1,5 +1,6 @@
import React from 'react';
import Markdown from 'react-markdown';
import NewTabLink from 'components/ui/NewTabLink';
interface Props {
source: string;
@ -11,7 +12,10 @@ const TranslateMarkdown = ({ source }: Props) => {
escapeHtml={true}
unwrapDisallowed={true}
allowedTypes={['link', 'emphasis', 'strong', 'code', 'root', 'inlineCode']}
renderers={{ root: React.Fragment }}
renderers={{
root: React.Fragment,
link: NewTabLink
}}
source={source}
/>
);

View File

@ -26,6 +26,8 @@ $speed: 500ms;
position: relative;
&-wallets {
margin: 0 -$space-md;
&-title {
@include decrypt-title;
}

View File

@ -0,0 +1,12 @@
import React from 'react';
import translate from 'translations';
const DeprecationWarning: React.SFC<{}> = () => {
if (process.env.BUILD_DOWNLOADABLE) {
return null;
}
return <div className="alert alert-warning">{translate('INSECURE_WALLET_DEPRECATION')}</div>;
};
export default DeprecationWarning;

View File

@ -24,7 +24,6 @@ const WALLETS_PER_PAGE = 5;
interface Props {
// Passed props
isOpen?: boolean;
walletType?: string;
dPath: string;
dPaths: DPath[];
publicKey?: string;
@ -87,16 +86,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
}
public render() {
const {
wallets,
desiredToken,
network,
tokens,
dPath,
dPaths,
onCancel,
walletType
} = this.props;
const { wallets, desiredToken, network, tokens, dPath, dPaths, onCancel } = this.props;
const { selectedAddress, customPath, page } = this.state;
const buttons: IButton[] = [
@ -115,7 +105,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
return (
<Modal
title={translateRaw(`DECRYPT_PROMPT_UNLOCK_${walletType}`)}
title={translateRaw('DECRYPT_PROMPT_SELECT_ADDRESS')}
isOpen={this.props.isOpen}
buttons={buttons}
handleClose={onCancel}
@ -209,15 +199,19 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
private getAddresses(props: Props = this.props) {
const { dPath, publicKey, chainCode, seed } = props;
if (dPath && ((publicKey && chainCode) || seed) && isValidPath(dPath)) {
this.props.getDeterministicWallets({
seed,
dPath,
publicKey,
chainCode,
limit: WALLETS_PER_PAGE,
offset: WALLETS_PER_PAGE * this.state.page
});
if (dPath && ((publicKey && chainCode) || seed)) {
if (isValidPath(dPath)) {
this.props.getDeterministicWallets({
seed,
dPath,
publicKey,
chainCode,
limit: WALLETS_PER_PAGE,
offset: WALLETS_PER_PAGE * this.state.page
});
} else {
console.error('Invalid dPath provided', dPath);
}
}
}
@ -274,7 +268,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
private renderDPathOption(option: Option) {
if (option.value === customDPath.value) {
return translate('ADD_Radio_5_PathCustom');
return translate('X_CUSTOM');
}
return (

View File

@ -4,6 +4,7 @@ import translate, { translateRaw } from 'translations';
import Spinner from 'components/ui/Spinner';
import { TShowNotification } from 'actions/notifications';
import { Input } from 'components/ui';
import DeprecationWarning from './DeprecationWarning';
export interface KeystoreValue {
file: string;
@ -43,7 +44,8 @@ export class KeystoreDecrypt extends PureComponent {
const unlockDisabled = !file || (passReq && !password);
return (
<form id="selectedUploadKey" onSubmit={this.unlock}>
<form onSubmit={this.unlock}>
<DeprecationWarning />
<div className="form-group">
<input
className="hidden"

View File

@ -9,6 +9,8 @@ import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { SecureWalletName, ledgerReferralURL } from 'config';
import { getPaths, getSingleDPath } from 'selectors/config/wallet';
import { getNetworkConfig } from 'selectors/config';
import { NetworkConfig } from 'types/network';
import './LedgerNano.scss';
interface OwnProps {
@ -18,6 +20,7 @@ interface OwnProps {
interface StateProps {
dPath: DPath | undefined;
dPaths: DPath[];
network: NetworkConfig;
}
interface State {
@ -54,6 +57,7 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
}
public render() {
const { network } = this.props;
const { dPath, publicKey, chainCode, error, isLoading, showTip } = this.state;
const showErr = error ? 'is-showing' : '';
@ -66,7 +70,7 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
<div className="LedgerDecrypt">
<div className="alert alert-danger">
Unlocking a Ledger hardware wallet is only possible on pages served over HTTPS. You can
unlock your wallet at <a href="https://mycrypto.com">MyCrypto.com</a>
unlock your wallet at <NewTabLink href="https://mycrypto.com">MyCrypto.com</NewTabLink>
</div>
</div>
);
@ -74,12 +78,6 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
return (
<div className="LedgerDecrypt">
{showTip && (
<p>
<strong>Tip: </strong>Make sure you're logged into the ethereum app on your hardware
wallet
</p>
)}
<button
className="LedgerDecrypt-decrypt btn btn-primary btn-lg btn-block"
onClick={this.handleNullConnect}
@ -101,6 +99,10 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
<div className={`LedgerDecrypt-error alert alert-danger ${showErr}`}>{error || '-'}</div>
{showTip && (
<p className="LedgerDecrypt-tip">{translate('LEDGER_TIP', { $network: network.unit })}</p>
)}
<div className="LedgerDecrypt-help">
<NewTabLink href="https://support.ledgerwallet.com/hc/en-us/articles/115005200009">
{translate('HELP_ARTICLE_1')}
@ -116,7 +118,6 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
onCancel={this.handleCancel}
onConfirmAddress={this.handleUnlock}
onPathChange={this.handlePathChange}
walletType={'LEDGER'}
/>
</div>
);
@ -144,13 +145,34 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
});
})
.catch((err: any) => {
let showTip;
let errMsg;
// Timeout
if (err && err.metaData && err.metaData.code === 5) {
this.showTip();
showTip = true;
errMsg = translateRaw('LEDGER_TIMEOUT');
}
// Wrong app logged into
if (err && err.includes && err.includes('6804')) {
showTip = true;
errMsg = translateRaw('LEDGER_WRONG_APP');
}
// Ledger locked
if (err && err.includes && err.includes('6801')) {
errMsg = translateRaw('LEDGER_LOCKED');
}
// Other
if (!errMsg) {
errMsg = err && err.metaData ? err.metaData.type : err.toString();
}
this.setState({
error: err && err.metaData ? err.metaData.type : err.toString(),
error: errMsg,
isLoading: false
});
if (showTip) {
this.showTip();
}
});
});
};
@ -180,7 +202,8 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
function mapStateToProps(state: AppState): StateProps {
return {
dPath: getSingleDPath(state, SecureWalletName.LEDGER_NANO_S),
dPaths: getPaths(state, SecureWalletName.LEDGER_NANO_S)
dPaths: getPaths(state, SecureWalletName.LEDGER_NANO_S),
network: getNetworkConfig(state)
};
}

View File

@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import { getSingleDPath, getPaths } from 'selectors/config/wallet';
import { TogglablePassword } from 'components';
import { Input } from 'components/ui';
import DeprecationWarning from './DeprecationWarning';
interface OwnProps {
onUnlock(param: any): void;
@ -49,7 +50,8 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
const isValidMnemonic = validateMnemonic(formattedPhrase);
return (
<div>
<React.Fragment>
<DeprecationWarning />
<div id="selectedTypeKey">
<div className="form-group">
<TogglablePassword
@ -91,9 +93,8 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
onCancel={this.handleCancel}
onConfirmAddress={this.handleUnlock}
onPathChange={this.handlePathChange}
walletType={'MNEMONIC'}
/>
</div>
</React.Fragment>
);
}

View File

@ -4,6 +4,7 @@ import React, { PureComponent } from 'react';
import translate, { translateRaw } from 'translations';
import { TogglablePassword } from 'components';
import { Input } from 'components/ui';
import DeprecationWarning from './DeprecationWarning';
export interface PrivateKeyValue {
key: string;
@ -54,6 +55,7 @@ export class PrivateKeyDecrypt extends PureComponent<Props> {
return (
<form id="selectedTypeKey" onSubmit={this.unlock}>
<DeprecationWarning />
<div className="input-group-wrapper">
<label className="input-group">
<TogglablePassword

View File

@ -93,7 +93,6 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
onCancel={this.handleCancel}
onConfirmAddress={this.handleUnlock}
onPathChange={this.handlePathChange}
walletType={'TREZOR'}
/>
</div>
);

View File

@ -0,0 +1,26 @@
@import 'common/sass/variables';
@import 'common/sass/mixins';
.ViewOnly {
&-recent {
text-align: left;
&-separator {
display: block;
margin: $space-sm 0;
text-align: center;
color: $gray-light;
}
.Select {
&-option {
@include ellipsis;
}
.Identicon {
display: inline-block;
margin-right: $space-sm;
}
}
}
}

View File

@ -1,63 +1,101 @@
import React, { PureComponent } from 'react';
import translate from 'translations';
import { donationAddressMap } from 'config';
import { connect } from 'react-redux';
import Select, { Option } from 'react-select';
import translate, { translateRaw } from 'translations';
import { isValidETHAddress } from 'libs/validators';
import { AddressOnlyWallet } from 'libs/wallet';
import { TextArea } from 'components/ui';
import { getRecentAddresses } from 'selectors/wallet';
import { AppState } from 'reducers';
import { Input, Identicon } from 'components/ui';
import './ViewOnly.scss';
interface Props {
interface OwnProps {
onUnlock(param: any): void;
}
interface StateProps {
recentAddresses: AppState['wallet']['recentAddresses'];
}
type Props = OwnProps & StateProps;
interface State {
address: string;
}
export class ViewOnlyDecrypt extends PureComponent<Props, State> {
class ViewOnlyDecryptClass extends PureComponent<Props, State> {
public state = {
address: ''
};
public render() {
const { recentAddresses } = this.props;
const { address } = this.state;
const isValid = isValidETHAddress(address);
const recentOptions = (recentAddresses.map(addr => ({
label: (
<React.Fragment>
<Identicon address={addr} />
{addr}
</React.Fragment>
),
value: addr
// I hate this assertion, but React elements definitely work as labels
})) as any) as Option[];
return (
<div id="selectedUploadKey">
<div className="ViewOnly">
<form className="form-group" onSubmit={this.openWallet}>
<TextArea
className={isValid ? 'is-valid' : 'is-invalid'}
{!!recentOptions.length && (
<div className="ViewOnly-recent">
<Select
value={address}
onChange={this.handleSelectAddress}
options={recentOptions}
placeholder={translateRaw('VIEW_ONLY_RECENT')}
/>
<em className="ViewOnly-recent-separator">{translate('OR')}</em>
</div>
)}
<Input
className={`ViewOnly-input ${isValid ? 'is-valid' : 'is-invalid'}`}
value={address}
onChange={this.changeAddress}
onKeyDown={this.handleEnterKey}
placeholder={donationAddressMap.ETH}
rows={3}
placeholder={translateRaw('VIEW_ONLY_ENTER')}
/>
<button className="btn btn-primary btn-block" disabled={!isValid}>
{translate('NAV_VIEWWALLET')}
<button className="ViewOnly-submit btn btn-primary btn-block" disabled={!isValid}>
{translate('VIEW_ADDR')}
</button>
</form>
</div>
);
}
private changeAddress = (ev: React.FormEvent<HTMLTextAreaElement>) => {
private changeAddress = (ev: React.FormEvent<HTMLInputElement>) => {
this.setState({ address: ev.currentTarget.value });
};
private handleEnterKey = (ev: React.KeyboardEvent<HTMLElement>) => {
if (ev.keyCode === 13) {
this.openWallet(ev);
}
private handleSelectAddress = (option: Option) => {
const address = option && option.value ? option.value.toString() : '';
this.setState({ address }, () => this.openWallet());
};
private openWallet = (ev: React.FormEvent<HTMLElement>) => {
private openWallet = (ev?: React.FormEvent<HTMLElement>) => {
if (ev) {
ev.preventDefault();
}
const { address } = this.state;
ev.preventDefault();
if (isValidETHAddress(address)) {
const wallet = new AddressOnlyWallet(address);
this.props.onUnlock(wallet);
}
};
}
export const ViewOnlyDecrypt = connect((state: AppState): StateProps => ({
recentAddresses: getRecentAddresses(state)
}))(ViewOnlyDecryptClass);

View File

@ -8,6 +8,7 @@ export * from './CurrentCustomMessage';
export * from './GenerateTransaction';
export * from './SendButton';
export * from './SigningStatus';
export * from '../containers/Tabs/ScheduleTransaction/components';
export { default as NonceField } from './NonceField';
export { default as Header } from './Header';
export { default as Footer } from './Footer';
@ -20,3 +21,4 @@ export { default as TogglablePassword } from './TogglablePassword';
export { default as GenerateKeystoreModal } from './GenerateKeystoreModal';
export { default as TransactionStatus } from './TransactionStatus';
export { default as ParityQrSigner } from './ParityQrSigner';
export { default as ElectronNav } from './ElectronNav';

View File

@ -13,7 +13,19 @@ interface IQueryResults {
[key: string]: string | null;
}
export type Param = 'to' | 'data' | 'readOnly' | 'tokenSymbol' | 'value' | 'gaslimit' | 'limit';
export type Param =
| 'to'
| 'data'
| 'readOnly'
| 'tokenSymbol'
| 'value'
| 'gaslimit'
| 'limit'
| 'windowSize'
| 'windowStart'
| 'scheduleTimestamp'
| 'timeBounty'
| 'network';
interface Props extends RouteComponentProps<{}> {
params: Param[];

View File

@ -5,13 +5,19 @@ import './Help.scss';
type Size = 'x1' | 'x2' | 'x3';
interface Props {
link: string;
link?: string;
size?: Size;
className?: string;
}
const Help = ({ size = 'x1', link }: Props) => {
const Help = ({ size = 'x1', link, className }: Props) => {
return (
<a href={link} className={`Help Help-${size}`} target="_blank" rel="noopener noreferrer">
<a
href={link}
className={`Help Help-${size} ${className}`}
target="_blank"
rel="noopener noreferrer"
>
<img src={icon} alt="help" />
</a>
);

View File

@ -78,7 +78,7 @@
padding: 0.5rem 0.75rem;
}
&::placeholder {
color: rgba(0, 0, 0, 0.3);
color: $input-color-placeholder;
}
&:not([disabled]):not([readonly]) {
&.invalid.has-blurred.has-value {

View File

@ -1,4 +1,5 @@
import React, { PureComponent } from 'react';
import { createPortal } from 'react-dom';
import ModalBody from './ModalBody';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import './index.scss';
@ -28,6 +29,7 @@ const Fade = ({ ...props }: any) => (
);
export default class Modal extends PureComponent<Props, {}> {
public modalParent: HTMLElement;
public modalBody: ModalBody;
public componentDidUpdate(prevProps: Props) {
@ -36,8 +38,20 @@ export default class Modal extends PureComponent<Props, {}> {
}
}
public componentDidMount() {
const modalEl = document.getElementById('ModalContainer');
if (modalEl) {
this.modalParent = document.createElement('div');
modalEl.appendChild(this.modalParent);
}
}
public componentWillUnmount() {
document.body.classList.remove('no-scroll');
const modalEl = document.getElementById('ModalContainer');
if (modalEl) {
modalEl.removeChild(this.modalParent);
}
}
public render() {
@ -52,7 +66,7 @@ export default class Modal extends PureComponent<Props, {}> {
const modalBodyProps = { title, children, modalStyle, hasButtons, buttons, handleClose };
return (
const modal = (
<TransitionGroup>
{isOpen && (
// Trap focus in modal by focusing the first element after the animation is complete
@ -65,5 +79,11 @@ export default class Modal extends PureComponent<Props, {}> {
)}
</TransitionGroup>
);
if (this.modalParent) {
return createPortal(modal, this.modalParent);
} else {
return modal;
}
}
}

View File

@ -31,8 +31,16 @@ export interface AAttributes {
interface NewTabLinkProps extends AAttributes {
href: string;
content?: React.ReactElement<any> | string | string[] | number;
children?: React.ReactElement<any> | string | string[] | number;
content?:
| React.ReactElement<any>
| string
| number
| (string | number | React.ReactElement<any>)[];
children?:
| React.ReactElement<any>
| string
| number
| (string | number | React.ReactElement<any>)[];
}
export class NewTabLink extends React.Component<NewTabLinkProps> {

View File

@ -0,0 +1,64 @@
$slider-radius: 26px;
$transition-time: .4s;
$toggle-color: #0C6482;
$travel-distance: 38px;
.Toggle {
$root: &;
position: relative;
display: inline-block;
width: 72px;
height: 34px;
margin: 5px 0;
&-input {
display: none;
&:checked + #{$root}-slider {
background-color: $toggle-color;
&::before {
-webkit-transform: translateX($travel-distance);
-ms-transform: translateX($travel-distance);
transform: translateX($travel-distance);
}
}
&:focus + #{$root}-slider {
box-shadow: 0 0 1px $toggle-color;
}
}
&-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: $transition-time;
transition: $transition-time;
&.round {
border-radius: 34px;
&::before {
border-radius: 50%;
}
}
&::before {
position: absolute;
content: "";
height: $slider-radius;
width: $slider-radius;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: $transition-time;
transition: $transition-time;
}
}
}

View File

@ -0,0 +1,16 @@
import React, { ChangeEvent } from 'react';
import './Toggle.scss';
interface Props {
checked: boolean;
onChangeHandler(event: ChangeEvent<HTMLInputElement>): any;
}
const Toggle: React.SFC<Props> = ({ checked, onChangeHandler }) => (
<label className="Toggle checkbox">
<input className="Toggle-input" type="checkbox" checked={checked} onChange={onChangeHandler} />
<span className="Toggle-slider round" />
</label>
);
export default Toggle;

View File

@ -70,7 +70,7 @@ const UnitDisplay: React.SFC<EthProps | TokenProps> = params => {
element = (
<span>
{formattedValue}
<span>{symbol ? ` ${symbol}` : ''}</span>
<span>{symbol ? <>&nbsp;{symbol}</> : ''}</span>
</span>
);
}

View File

@ -17,6 +17,7 @@ export { default as Input } from './Input';
export { default as TextArea } from './TextArea';
export { default as Address } from './Address';
export { default as CodeBlock } from './CodeBlock';
export { default as Toggle } from './Toggle';
export * from './ConditionalInput';
export * from './Expandable';
export * from './InlineSpinner';

View File

@ -9,7 +9,7 @@ export const languages = require('./languages.json');
export const discordURL = 'https://discord.gg/VSaTXEA';
// Displays in the footer
export const VERSION = `${packageJson.version} (BETA)`;
export const VERSION = `${packageJson.version} (Release Candidate)`;
export const N_FACTOR = 8192;
// Displays at the top of the site, make message empty string to remove.
@ -18,7 +18,7 @@ export const N_FACTOR = 8192;
export const ANNOUNCEMENT_TYPE = '';
export const ANNOUNCEMENT_MESSAGE = (
<React.Fragment>
This is a Beta version of MyCrypto. Please submit any bug reports to our{' '}
This is a Beta Release Candidate of the new MyCrypto. Please submit any bug reports to our{' '}
<NewTabLink href="https://github.com/MyCryptoHQ/MyCrypto/issues">GitHub</NewTabLink> and use{' '}
<NewTabLink href="https://hackerone.com/mycrypto">HackerOne</NewTabLink> for critical
vulnerabilities. Join the discussion on <NewTabLink href={discordURL}>Discord</NewTabLink>.
@ -65,6 +65,8 @@ export const bityReferralURL = 'https://bity.com/af/jshkb37v';
export const shapeshiftReferralURL = 'https://shapeshift.io';
export const ethercardReferralURL =
'https://ether.cards/?utm_source=mycrypto&utm_medium=cpm&utm_campaign=site';
export const keepkeyReferralURL = 'https://keepkey.go2cloud.org/aff_c?offer_id=1&aff_id=4086';
export const steelyReferralURL = 'https://stee.ly/2Hcl4RE';
export enum SecureWalletName {
WEB3 = 'web3',

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 = {
@ -86,5 +86,5 @@ export const EXTRA_PATHS = [ETH_SINGULAR];
// 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)'\/[0-9]+\'\/[0-9]+(\'+$|\'+(\/[0-1]+$))/;
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

@ -3,3 +3,5 @@ export * from './bity';
export * from './addressMessages';
export * from './helpArticles';
export * from './dpaths';
export * from './navigation';
export * from './links';

108
common/config/links.ts Normal file
View File

@ -0,0 +1,108 @@
import { translateRaw } from 'translations';
import {
discordURL,
ledgerReferralURL,
trezorReferralURL,
ethercardReferralURL,
keepkeyReferralURL,
steelyReferralURL
} from './data';
interface Link {
link: string;
text: string;
}
export const socialMediaLinks: Link[] = [
{
link: 'https://twitter.com/mycrypto',
text: 'twitter'
},
{
link: 'https://www.facebook.com/MyCrypto/',
text: 'facebook'
},
{
link: 'https://medium.com/@mycrypto',
text: 'medium'
},
{
link: 'https://www.linkedin.com/company/mycrypto',
text: 'linkedin'
},
{
link: 'https://github.com/MyCryptoHQ',
text: 'github'
},
{
link: 'https://www.reddit.com/r/mycrypto/',
text: 'reddit'
},
{
link: discordURL,
text: 'discord'
}
];
export const productLinks: Link[] = [
{
link:
'https://chrome.google.com/webstore/detail/etheraddresslookup/pdknmigbbbhmllnmgdfalmedcmcefdfn',
text: translateRaw('ETHER_ADDRESS_LOOKUP')
},
{
link:
'https://chrome.google.com/webstore/detail/ethersecuritylookup/bhhfhgpgmifehjdghlbbijjaimhmcgnf',
text: translateRaw('ETHER_SECURITY_LOOKUP')
},
{
link: 'https://etherscamdb.info/',
text: translateRaw('ETHERSCAMDB')
},
{
link: 'https://www.mycrypto.com/helpers.html',
text: translateRaw('FOOTER_HELP_AND_DEBUGGING')
}
];
export const affiliateLinks: Link[] = [
{
link: ledgerReferralURL,
text: translateRaw('LEDGER_REFERRAL_1')
},
{
link: trezorReferralURL,
text: translateRaw('TREZOR_REFERAL')
},
{
link: keepkeyReferralURL,
text: translateRaw('KEEPKEY_REFERRAL')
},
{
link: steelyReferralURL,
text: translateRaw('STEELY_REFERRAL')
},
{
link: ethercardReferralURL,
text: translateRaw('ETHERCARD_REFERAL')
}
];
export const partnerLinks: Link[] = [
{
link: 'https://metamask.io/',
text: 'MetaMask'
},
{
link: 'https://infura.io/',
text: 'Infura'
},
{
link: 'https://etherscan.io/',
text: 'Etherscan'
},
{
link: 'https://etherchain.org/',
text: 'Etherchain'
}
];

View File

@ -0,0 +1,53 @@
import { knowledgeBaseURL } from './data';
export interface NavigationLink {
name: string;
to: string;
external?: boolean;
disabled?: boolean;
}
export const navigationLinks: NavigationLink[] = [
{
name: 'NAV_VIEW',
to: '/account'
},
{
name: 'NAV_GENERATEWALLET',
to: '/generate'
},
{
name: 'NAV_SWAP',
to: '/swap'
},
{
name: 'NAV_CONTRACTS',
to: '/contracts'
},
{
name: 'NAV_ENS',
to: '/ens'
},
{
name: 'NAV_SIGN',
to: '/sign-and-verify-message'
},
{
name: 'NAV_TXSTATUS',
to: '/tx-status'
},
{
name: 'NAV_BROADCAST',
to: '/pushTx'
},
{
name: 'NAV_SUPPORT_US',
to: '/support-us',
disabled: !process.env.BUILD_ELECTRON
},
{
name: 'NAV_HELP',
to: knowledgeBaseURL,
external: true
}
].filter(link => !link.disabled);

File diff suppressed because it is too large Load Diff

View File

@ -29,11 +29,4 @@
}
}
}
@media screen and (min-width: $screen-sm) {
.Modal {
max-width: 800px;
width: 100%;
}
}
}

View File

@ -123,6 +123,7 @@ class OnboardModal extends React.Component<Props, State> {
<Modal
isOpen={isOpen}
buttons={buttons}
maxWidth={800}
handleClose={() => (slideNumber === NUMBER_OF_SLIDES ? this.closeModal : null)}
>
<div className="OnboardModal-stepper">

View File

@ -0,0 +1,46 @@
@import 'common/sass/variables';
.ElectronTemplate {
height: 100%;
display: flex;
flex-direction: row;
&-sidebar,
&-content {
position: relative;
overflow: auto;
}
&-sidebar {
width: $electron-sidebar-width;
overflow-x: hidden;
background: #FFF;
border-right: 1px solid $gray-lighter;
}
&-content {
padding: 10vh 30px;
flex: 1;
&-tab {
width: 100%;
max-width: 1200px;
margin: 0 auto;
}
}
&-draggable {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
height: 16px;
-webkit-app-region: drag;
// Only needed on OSX, Linux and Windows have title bars
.is-osx & {
display: block;
}
}
}

View File

@ -0,0 +1,48 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import Notifications from './Notifications';
import OfflineTab from './OfflineTab';
import { getOffline } from 'selectors/config';
import { ElectronNav } from 'components';
import './ElectronTemplate.scss';
interface StateProps {
isOffline: AppState['config']['meta']['offline'];
}
interface OwnProps {
isUnavailableOffline?: boolean;
children: string | React.ReactElement<string> | React.ReactElement<string>[];
}
type Props = OwnProps & StateProps;
class ElectronTemplate extends Component<Props, {}> {
public render() {
const { isUnavailableOffline, children, isOffline } = this.props;
return (
<div className="ElectronTemplate">
<div className="ElectronTemplate-sidebar">
<ElectronNav />
</div>
<div className="ElectronTemplate-content">
<div className="Tab ElectronTemplate-content-tab">
{isUnavailableOffline && isOffline ? <OfflineTab /> : children}
</div>
<Notifications />
</div>
<div className="ElectronTemplate-draggable" />
</div>
);
}
}
function mapStateToProps(state: AppState): StateProps {
return {
isOffline: getOffline(state)
};
}
export default connect(mapStateToProps, {})(ElectronTemplate);

View File

@ -0,0 +1,10 @@
.WebTemplate {
height: 100%;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
&-spacer {
flex-grow: 2;
}
}

View File

@ -0,0 +1,54 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { BetaAgreement, Footer, Header } from 'components';
import { AppState } from 'reducers';
import Notifications from './Notifications';
import OfflineTab from './OfflineTab';
import { getOffline, getLatestBlock } from 'selectors/config';
import { Query } from 'components/renderCbs';
import './WebTemplate.scss';
interface StateProps {
isOffline: AppState['config']['meta']['offline'];
latestBlock: AppState['config']['meta']['latestBlock'];
}
interface OwnProps {
isUnavailableOffline?: boolean;
children: string | React.ReactElement<string> | React.ReactElement<string>[];
}
type Props = OwnProps & StateProps;
class WebTemplate extends Component<Props, {}> {
public render() {
const { isUnavailableOffline, children, isOffline, latestBlock } = this.props;
return (
<div className="WebTemplate">
<Query
params={['network']}
withQuery={({ network }) => (
<Header networkParam={network && `${network.toLowerCase()}_auto`} />
)}
/>
<div className="Tab container">
{isUnavailableOffline && isOffline ? <OfflineTab /> : children}
</div>
<div className="WebTemplate-spacer" />
<Footer latestBlock={latestBlock} />
<Notifications />
<BetaAgreement />
</div>
);
}
}
function mapStateToProps(state: AppState): StateProps {
return {
isOffline: getOffline(state),
latestBlock: getLatestBlock(state)
};
}
export default connect(mapStateToProps, {})(WebTemplate);

View File

@ -1,47 +1,6 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { BetaAgreement, Footer, Header } from 'components';
import { AppState } from 'reducers';
import Notifications from './Notifications';
import OfflineTab from './OfflineTab';
import { getOffline, getLatestBlock } from 'selectors/config';
import ElectronTemplate from './ElectronTemplate';
import WebTemplate from './WebTemplate';
interface StateProps {
isOffline: AppState['config']['meta']['offline'];
latestBlock: AppState['config']['meta']['latestBlock'];
}
const template = process.env.BUILD_ELECTRON ? ElectronTemplate : WebTemplate;
interface OwnProps {
isUnavailableOffline?: boolean;
children: string | React.ReactElement<string> | React.ReactElement<string>[];
}
type Props = OwnProps & StateProps;
class TabSection extends Component<Props, {}> {
public render() {
const { isUnavailableOffline, children, isOffline, latestBlock } = this.props;
return (
<div className="page-layout">
<Header />
<div className="Tab container">
{isUnavailableOffline && isOffline ? <OfflineTab /> : children}
</div>
<div className="flex-spacer" />
<Footer latestBlock={latestBlock} />
<Notifications />
<BetaAgreement />
</div>
);
}
}
function mapStateToProps(state: AppState): StateProps {
return {
isOffline: getOffline(state),
latestBlock: getLatestBlock(state)
};
}
export default connect(mapStateToProps, {})(TabSection);
export default template;

View File

@ -1,6 +1,7 @@
import React from 'react';
import { generateMnemonic } from 'bip39';
import translate from 'translations';
import shuffle from 'lodash/shuffle';
import Word from './Word';
import FinalSteps from '../FinalSteps';
import Template from '../Template';
@ -10,8 +11,10 @@ import './Mnemonic.scss';
interface State {
words: string[];
confirmValues: string[];
confirmWords: WordTuple[][];
isConfirming: boolean;
isConfirmed: boolean;
isRevealingNextWord: boolean;
}
interface WordTuple {
@ -23,8 +26,10 @@ export default class GenerateMnemonic extends React.Component<{}, State> {
public state: State = {
words: [],
confirmValues: [],
confirmWords: [],
isConfirming: false,
isConfirmed: false
isConfirmed: false,
isRevealingNextWord: false
};
public componentDidMount() {
@ -32,63 +37,57 @@ export default class GenerateMnemonic extends React.Component<{}, State> {
}
public render() {
const { words, isConfirming, isConfirmed } = this.state;
let content;
const { words, confirmWords, isConfirming, isConfirmed } = this.state;
const defaultBtnClassName = 'GenerateMnemonic-buttons-btn btn btn-default';
const canContinue = this.checkCanContinue();
const [firstHalf, lastHalf] =
confirmWords.length === 0 ? this.splitWordsIntoHalves(words) : confirmWords;
if (isConfirmed) {
content = <FinalSteps walletType={WalletType.Mnemonic} />;
} else {
const canContinue = this.checkCanContinue();
const firstHalf: WordTuple[] = [];
const lastHalf: WordTuple[] = [];
words.forEach((word, index) => {
if (index < words.length / 2) {
firstHalf.push({ word, index });
} else {
lastHalf.push({ word, index });
}
});
const content = isConfirmed ? (
<FinalSteps walletType={WalletType.Mnemonic} />
) : (
<div className="GenerateMnemonic">
<h1 className="GenerateMnemonic-title">{translate('GENERATE_MNEMONIC_TITLE')}</h1>
content = (
<div className="GenerateMnemonic">
<h1 className="GenerateMnemonic-title">{translate('GENERATE_MNEMONIC_TITLE')}</h1>
<p className="GenerateMnemonic-help">
{isConfirming ? translate('MNEMONIC_DESCRIPTION_1') : translate('MNEMONIC_DESCRIPTION_2')}
</p>
<p className="GenerateMnemonic-help">
{isConfirming
? translate('MNEMONIC_DESCRIPTION_1')
: translate('MNEMONIC_DESCRIPTION_2')}
</p>
<div className="GenerateMnemonic-words">
{[firstHalf, lastHalf].map((ws, i) => (
<div key={i} className="GenerateMnemonic-words-column">
{ws.map(this.makeWord)}
</div>
))}
</div>
<div className="GenerateMnemonic-buttons">
{!isConfirming && (
<button
className="GenerateMnemonic-buttons-btn btn btn-default"
onClick={this.regenerateWordArray}
>
<i className="fa fa-refresh" /> {translate('REGENERATE_MNEMONIC')}
</button>
)}
<button
className="GenerateMnemonic-buttons-btn btn btn-primary"
disabled={!canContinue}
onClick={this.goToNextStep}
>
{translate('CONFIRM_MNEMONIC')}
</button>
</div>
<button className="GenerateMnemonic-skip" onClick={this.skip} />
<div className="GenerateMnemonic-words">
{[firstHalf, lastHalf].map((ws, i) => (
<div key={i} className="GenerateMnemonic-words-column">
{ws.map(this.makeWord)}
</div>
))}
</div>
);
}
<div className="GenerateMnemonic-buttons">
{!isConfirming && (
<button className={defaultBtnClassName} onClick={this.regenerateWordArray}>
<i className="fa fa-refresh" /> {translate('REGENERATE_MNEMONIC')}
</button>
)}
{isConfirming && (
<button
className={defaultBtnClassName}
disabled={canContinue}
onClick={this.revealNextWord}
>
<i className="fa fa-eye" /> {translate('REVEAL_NEXT_MNEMONIC')}
</button>
)}
<button
className="GenerateMnemonic-buttons-btn btn btn-primary"
disabled={!canContinue}
onClick={this.goToNextStep}
>
{translate('CONFIRM_MNEMONIC')}
</button>
</div>
<button className="GenerateMnemonic-skip" onClick={this.skip} />
</div>
);
return <Template>{content}</Template>;
}
@ -97,14 +96,6 @@ export default class GenerateMnemonic extends React.Component<{}, State> {
this.setState({ words: generateMnemonic().split(' ') });
};
private handleConfirmChange = (index: number, value: string) => {
this.setState((state: State) => {
const confirmValues = [...state.confirmValues];
confirmValues[index] = value;
this.setState({ confirmValues });
});
};
private goToNextStep = () => {
if (!this.checkCanContinue()) {
return;
@ -113,7 +104,13 @@ export default class GenerateMnemonic extends React.Component<{}, State> {
if (this.state.isConfirming) {
this.setState({ isConfirmed: true });
} else {
this.setState({ isConfirming: true });
const shuffledWords = shuffle(this.state.words);
const confirmWords = this.splitWordsIntoHalves(shuffledWords);
this.setState({
isConfirming: true,
confirmWords
});
}
};
@ -129,18 +126,79 @@ export default class GenerateMnemonic extends React.Component<{}, State> {
}
};
private makeWord = (word: WordTuple) => (
<Word
key={`${word.word}${word.index}`}
index={word.index}
word={word.word}
value={this.state.confirmValues[word.index] || ''}
isReadOnly={!this.state.isConfirming}
onChange={this.handleConfirmChange}
/>
);
private makeWord = (word: WordTuple) => {
const { words, confirmValues, isRevealingNextWord, isConfirming } = this.state;
const confirmIndex = words.indexOf(word.word);
const nextIndex = confirmValues.length;
const isNext = confirmIndex === nextIndex;
const isRevealed = isRevealingNextWord && isNext;
const hasBeenConfirmed = this.getWordConfirmed(word.word);
return (
<Word
key={`${word.word}${word.index}`}
index={word.index}
confirmIndex={confirmIndex}
word={word.word}
value={confirmValues[word.index] || ''}
showIndex={!isConfirming}
isNext={isNext}
isBeingRevealed={isRevealed}
isConfirming={isConfirming}
hasBeenConfirmed={hasBeenConfirmed}
onClick={this.handleWordClick}
/>
);
};
private handleWordClick = (_: number, value: string) => {
const { confirmValues: previousConfirmValues, words, isConfirming } = this.state;
const wordAlreadyConfirmed = previousConfirmValues.includes(value);
const activeIndex = previousConfirmValues.length;
const isCorrectChoice = words[activeIndex] === value;
if (isConfirming && !wordAlreadyConfirmed && isCorrectChoice) {
const confirmValues = previousConfirmValues.concat(value);
this.setState({ confirmValues });
}
};
private getWordConfirmed = (word: string) => this.state.confirmValues.includes(word);
private skip = () => {
this.setState({ isConfirmed: true });
};
private revealNextWord = () => {
const revealDuration = 400;
this.setState(
{
isRevealingNextWord: true
},
() =>
setTimeout(
() =>
this.setState({
isRevealingNextWord: false
}),
revealDuration
)
);
};
private splitWordsIntoHalves = (words: string[]) => {
const firstHalf: WordTuple[] = [];
const lastHalf: WordTuple[] = [];
words.forEach((word: string, index: number) => {
const inFirstColumn = index < words.length / 2;
const half = inFirstColumn ? firstHalf : lastHalf;
half.push({ word, index });
});
return [firstHalf, lastHalf];
};
}

View File

@ -16,21 +16,14 @@ $number-margin: 6px;
.MnemonicWord {
display: flex;
width: $width;
margin-bottom: $space-md;
margin-bottom: $space-xs;
&:last-child {
margin-bottom: 0;
}
&-number {
display: inline-block;
width: $number-width;
margin-right: $number-margin;
text-align: right;
font-size: 26px;
font-weight: 100;
line-height: 40px;
vertical-align: bottom;
& .input-group-addon {
margin-bottom: $space-md;
}
&-word {
@ -39,14 +32,31 @@ $number-margin: 6px;
&-input {
animation: word-fade 400ms ease 1;
animation-fill-mode: both;
transition: border 0.2s ease-in;
margin-bottom: 0;
}
}
&-toggle {
color: $gray-light;
&-button {
position: relative;
width: 300px;
margin-bottom: $space-sm;
&:hover {
color: $gray;
}
&-index {
position: absolute;
top: -4px;
left: -7px;
z-index: 1;
color: #fff;
width: 26px;
height: 26px;
border-radius: 100%;
background: linear-gradient(
to top,
lighten($brand-success, 10%),
lighten($brand-success, 5%)
);
line-height: 24px;
}
}

View File

@ -1,67 +1,98 @@
import React from 'react';
import classnames from 'classnames';
import { translateRaw } from 'translations';
import './Word.scss';
import { Input } from 'components/ui';
interface Props {
index: number;
confirmIndex: number;
word: string;
value: string;
isReadOnly: boolean;
onChange(index: number, value: string): void;
showIndex: boolean;
isNext: boolean;
isBeingRevealed: boolean;
isConfirming: boolean;
hasBeenConfirmed: boolean;
onClick(index: number, value: string): void;
}
interface State {
isShowingWord: boolean;
flashingError: boolean;
}
export default class MnemonicWord extends React.Component<Props, State> {
public state = {
isShowingWord: false
flashingError: false
};
public render() {
const { index, word, value, isReadOnly } = this.props;
const { isShowingWord } = this.state;
const readOnly = isReadOnly || isShowingWord;
const {
hasBeenConfirmed,
isBeingRevealed,
showIndex,
index,
isConfirming,
confirmIndex,
word
} = this.props;
const { flashingError } = this.state;
const btnClassName = classnames({
btn: true,
'btn-default': !(isBeingRevealed || flashingError),
'btn-success': isBeingRevealed,
'btn-danger': flashingError
});
const indexClassName = 'input-group-addon input-group-addon--transparent';
return (
<div className="input-group-wrapper MnemonicWord">
<label className="input-group input-group-inline ENSInput-name">
<span className="input-group-addon input-group-addon--transparent">{index + 1}.</span>
<Input
className={`MnemonicWord-word-input ${!isReadOnly && 'border-rad-right-0'}`}
value={readOnly ? word : value}
onChange={this.handleChange}
readOnly={readOnly}
/>
{!isReadOnly && (
<span
onClick={this.toggleShow}
aria-label={translateRaw('GEN_ARIA_2')}
role="button"
className="MnemonicWord-word-toggle input-group-addon"
{showIndex && <span className={indexClassName}>{index + 1}.</span>}
{hasBeenConfirmed && (
<span className="MnemonicWord-button-index">{confirmIndex + 1}</span>
)}
{isConfirming ? (
<button
className={`MnemonicWord-button ${btnClassName} ${
hasBeenConfirmed ? 'disabled' : ''
}`}
onClick={() => this.handleClick(word)}
>
<i
className={classnames(
'fa',
isShowingWord && 'fa-eye-slash',
!isShowingWord && 'fa-eye'
)}
/>
</span>
{word}
</button>
) : (
<Input className="MnemonicWord-word-input" value={word} readOnly={true} />
)}
</label>
</div>
);
}
private handleChange = (ev: React.FormEvent<HTMLInputElement>) => {
this.props.onChange(this.props.index, ev.currentTarget.value);
private handleClick = (value: string) => {
const { isNext, index, onClick } = this.props;
if (!isNext) {
this.flashError();
}
onClick(index, value);
};
private toggleShow = () => {
this.setState({ isShowingWord: !this.state.isShowingWord });
private flashError = () => {
const errorDuration = 200;
this.setState(
{
flashingError: true
},
() =>
setTimeout(
() =>
this.setState({
flashingError: false
}),
errorDuration
)
);
};
}

View File

@ -0,0 +1,72 @@
import { connect } from 'react-redux';
import React, { Component } from 'react';
import { AppState } from 'reducers';
import { setScheduleDepositField, TSetScheduleDepositField } from 'actions/schedule';
import { translateRaw } from 'translations';
import { Input, Tooltip } from 'components/ui';
import { getDecimal } from 'selectors/transaction';
import { getScheduleDeposit, isValidScheduleDeposit } from 'selectors/schedule/fields';
import { toWei } from 'libs/units';
import Help from 'components/ui/Help';
interface OwnProps {
decimal: number;
scheduleDeposit: any;
validScheduleDeposit: boolean;
}
interface DispatchProps {
setScheduleDepositField: TSetScheduleDepositField;
}
type Props = OwnProps & DispatchProps;
class ScheduleDepositFieldClass extends Component<Props> {
public render() {
const { scheduleDeposit, validScheduleDeposit } = this.props;
return (
<div className="input-group-wrapper">
<label className="input-group">
<div className="input-group-header">
<span className="ScheduleFields-field-title-wrap">
{translateRaw('SCHEDULE_DEPOSIT')}
<div className="ScheduleFields-field-title-tooltip">
<Tooltip>{translateRaw('SCHEDULE_DEPOSIT_TOOLTIP')}</Tooltip>
<Help className="ScheduleFields-field-title-help" />
</div>
</span>
</div>
<Input
className={!!scheduleDeposit.raw && !validScheduleDeposit ? 'invalid' : ''}
type="number"
placeholder="0.00001"
value={scheduleDeposit.raw}
onChange={this.handleDepositChange}
/>
</label>
</div>
);
}
private handleDepositChange = (ev: React.FormEvent<HTMLInputElement>) => {
const { decimal } = this.props;
const { value } = ev.currentTarget;
this.props.setScheduleDepositField({
raw: value,
value: value ? toWei(value, decimal) : null
});
};
}
export const ScheduleDepositField = connect(
(state: AppState) => ({
decimal: getDecimal(state),
scheduleDeposit: getScheduleDeposit(state),
validScheduleDeposit: isValidScheduleDeposit(state)
}),
{
setScheduleDepositField
}
)(ScheduleDepositFieldClass);

View File

@ -0,0 +1,67 @@
import { connect } from 'react-redux';
import React from 'react';
import { AppState } from 'reducers';
import { setScheduleGasLimitField, TSetScheduleGasLimitField } from 'actions/schedule';
import { translateRaw } from 'translations';
import { Input, InlineSpinner } from 'components/ui';
import { getGasEstimationPending } from 'selectors/transaction';
import { Wei } from 'libs/units';
import { EAC_SCHEDULING_CONFIG } from 'libs/scheduling';
import { getScheduleGasLimit, isValidScheduleGasLimit } from 'selectors/schedule/fields';
interface OwnProps {
gasEstimationPending: boolean;
scheduleGasLimit: any;
validScheduleGasLimit: boolean;
}
interface DispatchProps {
setScheduleGasLimitField: TSetScheduleGasLimitField;
}
type Props = OwnProps & DispatchProps;
class ScheduleGasLimitFieldClass extends React.Component<Props> {
public render() {
const { gasEstimationPending, scheduleGasLimit, validScheduleGasLimit } = this.props;
return (
<div className="input-group-wrapper">
<label className="input-group">
<div className="input-group-header">
{translateRaw('SCHEDULE_GAS_LIMIT')}
<div className="flex-spacer" />
<InlineSpinner active={gasEstimationPending} text="Calculating" />
</div>
<Input
className={!!scheduleGasLimit.raw && !validScheduleGasLimit ? 'invalid' : ''}
type="number"
placeholder={EAC_SCHEDULING_CONFIG.SCHEDULE_GAS_LIMIT_FALLBACK.toString()}
value={scheduleGasLimit.raw}
onChange={this.handleGasLimitChange}
/>
</label>
</div>
);
}
private handleGasLimitChange = (ev: React.FormEvent<HTMLInputElement>) => {
const { value } = ev.currentTarget;
this.props.setScheduleGasLimitField({
raw: value,
value: Wei(value)
});
};
}
export const ScheduleGasLimitField = connect(
(state: AppState) => ({
gasEstimationPending: getGasEstimationPending(state),
scheduleGasLimit: getScheduleGasLimit(state),
validScheduleGasLimit: isValidScheduleGasLimit(state)
}),
{
setScheduleGasLimitField
}
)(ScheduleGasLimitFieldClass);

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