Merge branch 'develop' into new-networks
This commit is contained in:
commit
a5ce56ba5f
|
@ -29,7 +29,7 @@ export function loadBityRatesSucceededSwap(
|
|||
|
||||
export type TLoadShapeshiftRatesSucceededSwap = typeof loadShapeshiftRatesSucceededSwap;
|
||||
export function loadShapeshiftRatesSucceededSwap(
|
||||
payload
|
||||
payload: interfaces.LoadShapeshiftRatesSucceededSwapAction['payload']
|
||||
): interfaces.LoadShapeshiftRatesSucceededSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_SUCCEEDED,
|
||||
|
|
|
@ -29,10 +29,14 @@ const repOptions = {
|
|||
name: 'Augur'
|
||||
};
|
||||
|
||||
export interface MappedRates {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function getAllRates() {
|
||||
const mappedRates = {};
|
||||
const mappedRates: MappedRates = {};
|
||||
return _getAllRates().then(bityRates => {
|
||||
bityRates.objects.forEach(each => {
|
||||
bityRates.objects.forEach((each: any) => {
|
||||
const pairName = each.pair;
|
||||
const from = { id: pairName.substring(0, 3) };
|
||||
const to = { id: pairName.substring(3, 6) };
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { checkHttpStatus, parseJSON } from './utils';
|
||||
import { Omit } from 'react-redux';
|
||||
|
||||
const MAX_GAS_FAST = 250;
|
||||
|
||||
|
@ -21,15 +22,29 @@ export interface GasEstimates {
|
|||
isDefault: boolean;
|
||||
}
|
||||
|
||||
interface GasExpressResponse {
|
||||
block_time: number;
|
||||
blockNum: number;
|
||||
fast: number;
|
||||
fastest: number;
|
||||
safeLow: number;
|
||||
standard: number;
|
||||
}
|
||||
|
||||
export function fetchGasEstimates(): Promise<GasEstimates> {
|
||||
return fetch('https://dev.blockscale.net/api/gasexpress.json', {
|
||||
mode: 'cors'
|
||||
})
|
||||
.then(checkHttpStatus)
|
||||
.then(parseJSON)
|
||||
.then((res: object) => {
|
||||
.then((res: GasExpressResponse) => {
|
||||
// Make sure it looks like a raw gas estimate, and it has valid values
|
||||
const keys = ['safeLow', 'standard', 'fast', 'fastest'];
|
||||
const keys: (keyof Omit<GasExpressResponse, 'block_time' | 'blockNum'>)[] = [
|
||||
'safeLow',
|
||||
'standard',
|
||||
'fast',
|
||||
'fastest'
|
||||
];
|
||||
keys.forEach(key => {
|
||||
if (typeof res[key] !== 'number') {
|
||||
throw new Error(
|
||||
|
|
|
@ -28,6 +28,44 @@ export const SHAPESHIFT_TOKEN_WHITELIST = [
|
|||
];
|
||||
export const SHAPESHIFT_WHITELIST = [...SHAPESHIFT_TOKEN_WHITELIST, 'ETH', 'ETC', 'BTC'];
|
||||
|
||||
interface IPairData {
|
||||
limit: number;
|
||||
maxLimit: number;
|
||||
min: number;
|
||||
minerFee: number;
|
||||
pair: string;
|
||||
rate: string;
|
||||
}
|
||||
|
||||
interface IExtraPairData {
|
||||
status: string;
|
||||
image: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface IAvailablePairData {
|
||||
[pairName: string]: IExtraPairData;
|
||||
}
|
||||
|
||||
interface ShapeshiftMarketInfo {
|
||||
rate: string;
|
||||
limit: number;
|
||||
pair: string;
|
||||
maxLimit: number;
|
||||
min: number;
|
||||
minerFee: number;
|
||||
}
|
||||
|
||||
interface TokenMap {
|
||||
[pairName: string]: {
|
||||
id: string;
|
||||
rate: string;
|
||||
limit: number;
|
||||
min: number;
|
||||
options: (IExtraPairData & { id: string })[];
|
||||
};
|
||||
}
|
||||
|
||||
class ShapeshiftService {
|
||||
public whitelist = SHAPESHIFT_WHITELIST;
|
||||
private url = SHAPESHIFT_BASE_URL;
|
||||
|
@ -36,13 +74,18 @@ class ShapeshiftService {
|
|||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
public checkStatus(address) {
|
||||
public checkStatus(address: string) {
|
||||
return fetch(`${this.url}/txStat/${address}`)
|
||||
.then(checkHttpStatus)
|
||||
.then(parseJSON);
|
||||
}
|
||||
|
||||
public sendAmount(withdrawal, originKind, destinationKind, destinationAmount) {
|
||||
public sendAmount(
|
||||
withdrawal: string,
|
||||
originKind: string,
|
||||
destinationKind: string,
|
||||
destinationAmount: number
|
||||
) {
|
||||
const pair = `${originKind.toLowerCase()}_${destinationKind.toLowerCase()}`;
|
||||
|
||||
return fetch(`${this.url}/sendamount`, {
|
||||
|
@ -81,7 +124,7 @@ class ShapeshiftService {
|
|||
return mappedRates;
|
||||
};
|
||||
|
||||
private getPairRates(marketInfo) {
|
||||
private getPairRates(marketInfo: ShapeshiftMarketInfo[]) {
|
||||
const filteredMarketInfo = marketInfo.filter(obj => {
|
||||
const { pair } = obj;
|
||||
const pairArr = pair.split('_');
|
||||
|
@ -97,7 +140,7 @@ class ShapeshiftService {
|
|||
return pairRates;
|
||||
}
|
||||
|
||||
private async checkAvl(pairRates) {
|
||||
private async checkAvl(pairRates: IPairData[]) {
|
||||
const avlCoins = await this.getAvlCoins();
|
||||
const mapAvl = pairRates.map(p => {
|
||||
const { pair } = p;
|
||||
|
@ -121,7 +164,8 @@ class ShapeshiftService {
|
|||
};
|
||||
}
|
||||
});
|
||||
return mapAvl;
|
||||
const filered = mapAvl.filter(v => v);
|
||||
return filered as (IPairData & IAvailablePairData)[];
|
||||
}
|
||||
|
||||
private getAvlCoins() {
|
||||
|
@ -130,7 +174,7 @@ class ShapeshiftService {
|
|||
.then(parseJSON);
|
||||
}
|
||||
|
||||
private getSinglePairRate(pair) {
|
||||
private getSinglePairRate(pair: string) {
|
||||
return fetch(`${this.url}/rate/${pair}`)
|
||||
.then(checkHttpStatus)
|
||||
.then(parseJSON);
|
||||
|
@ -142,12 +186,12 @@ class ShapeshiftService {
|
|||
.then(parseJSON);
|
||||
}
|
||||
|
||||
private isWhitelisted(coin) {
|
||||
private isWhitelisted(coin: string) {
|
||||
return this.whitelist.includes(coin);
|
||||
}
|
||||
|
||||
private mapMarketInfo(marketInfo) {
|
||||
const tokenMap = {};
|
||||
private mapMarketInfo(marketInfo: (IPairData & IAvailablePairData)[]) {
|
||||
const tokenMap: TokenMap = {};
|
||||
marketInfo.forEach(m => {
|
||||
const originKind = m.pair.substring(0, 3);
|
||||
const destinationKind = m.pair.substring(4, 7);
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 38 KiB |
|
@ -0,0 +1,97 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="81px" height="120px" viewBox="0 0 81 120" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Artboard</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<linearGradient x1="100.099854%" y1="-16.122731%" x2="44.1579795%" y2="77.6511455%" id="linearGradient-1">
|
||||
<stop stop-color="#2B415B" offset="13.45%"></stop>
|
||||
<stop stop-color="#3B5676" offset="37.62%"></stop>
|
||||
<stop stop-color="#54769E" offset="69.23%"></stop>
|
||||
<stop stop-color="#52749B" offset="79.01%"></stop>
|
||||
<stop stop-color="#4D6C92" offset="86.14%"></stop>
|
||||
<stop stop-color="#436082" offset="92.44%"></stop>
|
||||
<stop stop-color="#364F6C" offset="98.22%"></stop>
|
||||
<stop stop-color="#314863" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="99.7887646%" y1="61.4941149%" x2="-4.60729499%" y2="13.3460499%" id="linearGradient-2">
|
||||
<stop stop-color="#54769E" offset="0%"></stop>
|
||||
<stop stop-color="#53749C" offset="48.02%"></stop>
|
||||
<stop stop-color="#4F6F95" offset="68.78%"></stop>
|
||||
<stop stop-color="#486588" offset="84.23%"></stop>
|
||||
<stop stop-color="#435F80" offset="90.95%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="725.856983%" y1="50.0229457%" x2="-347.124022%" y2="50.0229457%" id="linearGradient-3">
|
||||
<stop stop-color="#20344C" offset="25.39%"></stop>
|
||||
<stop stop-color="#273D57" offset="40.72%"></stop>
|
||||
<stop stop-color="#395373" offset="67.33%"></stop>
|
||||
<stop stop-color="#54769E" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="-653.788268%" y1="50.0214562%" x2="455.494413%" y2="50.0214562%" id="linearGradient-4">
|
||||
<stop stop-color="#54769E" offset="25.39%"></stop>
|
||||
<stop stop-color="#4D6E93" offset="41.33%"></stop>
|
||||
<stop stop-color="#3C5777" offset="68.97%"></stop>
|
||||
<stop stop-color="#233850" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="50.0370086%" y1="-209.335792%" x2="49.9575834%" y2="235.965027%" id="linearGradient-5">
|
||||
<stop stop-color="#54769E" offset="0.6545247%"></stop>
|
||||
<stop stop-color="#507198" offset="19.93%"></stop>
|
||||
<stop stop-color="#466488" offset="45.02%"></stop>
|
||||
<stop stop-color="#354F6D" offset="73.18%"></stop>
|
||||
<stop stop-color="#21354D" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="163.859644%" y1="-95.5767241%" x2="-53.1396713%" y2="173.049483%" id="linearGradient-6">
|
||||
<stop stop-color="#54769E" offset="25.39%"></stop>
|
||||
<stop stop-color="#4D6E93" offset="41.02%"></stop>
|
||||
<stop stop-color="#3C5777" offset="68.13%"></stop>
|
||||
<stop stop-color="#22364E" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="-44.1116757%" y1="-60.1463542%" x2="137.687762%" y2="146.957292%" id="linearGradient-7">
|
||||
<stop stop-color="#54769E" offset="25.39%"></stop>
|
||||
<stop stop-color="#4D6E93" offset="41.02%"></stop>
|
||||
<stop stop-color="#3C5777" offset="68.13%"></stop>
|
||||
<stop stop-color="#22364E" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="-1.43584837%" y1="31.6007047%" x2="127.701745%" y2="103.798097%" id="linearGradient-8">
|
||||
<stop stop-color="#54769E" offset="26.64%"></stop>
|
||||
<stop stop-color="#425E7F" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="-71.276129%" y1="-65.3137535%" x2="149.174581%" y2="96.3311507%" id="linearGradient-9">
|
||||
<stop stop-color="#54769E" stop-opacity="0" offset="46.09%"></stop>
|
||||
<stop stop-color="#52739A" stop-opacity="0.2156" offset="56.99%"></stop>
|
||||
<stop stop-color="#4A698E" stop-opacity="0.4266" offset="67.64%"></stop>
|
||||
<stop stop-color="#3D597B" stop-opacity="0.6356" offset="78.2%"></stop>
|
||||
<stop stop-color="#2C435F" stop-opacity="0.8422" offset="88.63%"></stop>
|
||||
<stop stop-color="#1B2E45" offset="96.61%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="50.309375%" y1="295.997443%" x2="50.309375%" y2="-124.649242%" id="linearGradient-10">
|
||||
<stop stop-color="#54769E" offset="25.39%"></stop>
|
||||
<stop stop-color="#4D6E93" offset="41.02%"></stop>
|
||||
<stop stop-color="#3C5777" offset="68.13%"></stop>
|
||||
<stop stop-color="#22364E" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Artboard" fill-rule="nonzero">
|
||||
<g id="logo-shapeshift">
|
||||
<polygon id="Shape" fill="#273C51" points="75.9045553 29.0113736 81 0.0349956255 59.7045553 8.88888889 36.3006508 8.88888889 15.0052061 0 20.1357918 29.0113736 15.3214751 45.1793526 19.8546638 48.0139983 0 65.3718285 0 81.67979 22.7010846 112.825897 44.1019523 119.965004 44.1370933 120 60.8642082 111.461067 60.8993492 111.426072 60.8993492 98.7926509 51.4112798 93.6132983 51.4112798 93.6132983 51.4112798 93.6132983 66.3110629 54.1032371 66.2056399 54.1032371 80.683731 45.1793526"></polygon>
|
||||
<g id="Group">
|
||||
<polygon id="Shape" fill="url(#linearGradient-1)" points="34.2584416 35.3372093 0 65.2325581 47.7233766 92.5465116 47.8987013 36.244186"></polygon>
|
||||
<polygon id="Shape" fill="#466284" points="29.3844156 53.8255814 47.7584416 102.697674 47.8987013 56.5465116"></polygon>
|
||||
<polygon id="Shape" fill="#354D6A" points="66.1675325 54 47.7584416 102.697674 47.8987013 56.5465116"></polygon>
|
||||
<polygon id="Shape" fill="url(#linearGradient-2)" points="80.8246753 0.104651163 62.5558442 9.6627907 47.9337662 12.8372093 33.1714286 9.6627907 14.9727273 0.0697674419 21.2493506 28.9186047 15.2883117 45.1046512 36.187013 58.0116279 47.7935065 70.5697674 47.7935065 70.6744186 47.8636364 70.6046512 47.9337662 70.6744186 47.9337662 70.5697674 59.5402597 58.0116279 80.5090909 45.1046512 74.5480519 28.9534884"></polygon>
|
||||
<polygon id="Shape" fill="url(#linearGradient-3)" points="80.8246753 0.104651163 75.7402597 28.9883721 80.5090909 45.1046512 74.5480519 28.9534884"></polygon>
|
||||
<polygon id="Shape" fill="url(#linearGradient-4)" points="14.9727273 0.0697674419 20.0922078 28.9883721 15.2883117 45.1046512 21.2493506 28.9186047"></polygon>
|
||||
<polygon id="Shape" fill="url(#linearGradient-5)" points="14.9727273 0.0697674419 36.2220779 8.93023256 59.5753247 8.93023256 80.8246753 0.104651163 62.5558442 9.6627907 47.9337662 12.8372093 33.1714286 9.6627907"></polygon>
|
||||
<polygon id="Shape" fill="url(#linearGradient-6)" points="21.2493506 28.9186047 20.1974026 31.8139535 42.2532468 11.5813953"></polygon>
|
||||
<polygon id="Shape" fill="url(#linearGradient-7)" points="74.5480519 28.9534884 53.6493506 11.5813953 75.5649351 31.6744186"></polygon>
|
||||
<polygon id="Shape" fill="url(#linearGradient-8)" points="0 65.2325581 51.3 93.3837209 57.787013 109.534884 44.0415584 114.732558 22.6519481 112.534884 0 81.4883721"></polygon>
|
||||
<polygon id="Shape" fill="#FFFFFF" points="42.4636364 88.5348837 22.7220779 112.465116 22.6519481 112.534884 44.0064935 119.651163 44.0415584 119.686047 60.7324675 111.174419 60.7675325 111.139535 60.7675325 98.5465116"></polygon>
|
||||
<polygon id="Shape" fill="url(#linearGradient-9)" points="74.5480519 28.9534884 80.8246753 0.104651163 62.5558442 9.6627907 53.6493506 11.5813953"></polygon>
|
||||
<polygon id="Shape" fill="#FFFFFF" points="47.8987013 70.6744186 42.112987 64.3604651 47.8987013 58.0116279 53.7194805 64.3604651"></polygon>
|
||||
<polygon id="Shape" fill="url(#linearGradient-10)" points="47.3376623 12.6976744 47.9337662 12.8372093 48.4597403 12.7325581 47.8987013 49.5348837"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.1 KiB |
|
@ -1,36 +1,35 @@
|
|||
// Mixins
|
||||
// --------------------------------------------------
|
||||
// Utilities
|
||||
@import "mixins/hide-text.less";
|
||||
@import "mixins/opacity.less";
|
||||
@import "mixins/image.less";
|
||||
@import "mixins/labels.less";
|
||||
@import "mixins/reset-filter.less";
|
||||
@import "mixins/resize.less";
|
||||
@import "mixins/responsive-visibility.less";
|
||||
@import "mixins/size.less";
|
||||
@import "mixins/tab-focus.less";
|
||||
@import "mixins/reset-text.less";
|
||||
@import "mixins/text-emphasis.less";
|
||||
@import "mixins/text-overflow.less";
|
||||
@import "mixins/vendor-prefixes.less";
|
||||
@import 'mixins/hide-text.less';
|
||||
@import 'mixins/opacity.less';
|
||||
@import 'mixins/image.less';
|
||||
@import 'mixins/labels.less';
|
||||
@import 'mixins/reset-filter.less';
|
||||
@import 'mixins/resize.less';
|
||||
@import 'mixins/responsive-visibility.less';
|
||||
@import 'mixins/size.less';
|
||||
@import 'mixins/reset-text.less';
|
||||
@import 'mixins/text-emphasis.less';
|
||||
@import 'mixins/text-overflow.less';
|
||||
@import 'mixins/vendor-prefixes.less';
|
||||
// Components
|
||||
@import "mixins/alerts.less";
|
||||
@import "mixins/buttons.less";
|
||||
@import "mixins/panels.less";
|
||||
@import "mixins/pagination.less";
|
||||
@import "mixins/list-group.less";
|
||||
@import "mixins/nav-divider.less";
|
||||
@import "mixins/forms.less";
|
||||
@import "mixins/progress-bar.less";
|
||||
@import "mixins/table-row.less";
|
||||
@import 'mixins/alerts.less';
|
||||
@import 'mixins/buttons.less';
|
||||
@import 'mixins/panels.less';
|
||||
@import 'mixins/pagination.less';
|
||||
@import 'mixins/list-group.less';
|
||||
@import 'mixins/nav-divider.less';
|
||||
@import 'mixins/forms.less';
|
||||
@import 'mixins/progress-bar.less';
|
||||
@import 'mixins/table-row.less';
|
||||
// Skins
|
||||
@import "mixins/background-variant.less";
|
||||
@import "mixins/border-radius.less";
|
||||
@import "mixins/gradients.less";
|
||||
@import 'mixins/background-variant.less';
|
||||
@import 'mixins/border-radius.less';
|
||||
@import 'mixins/gradients.less';
|
||||
// Layout
|
||||
@import "mixins/clearfix.less";
|
||||
@import "mixins/center-block.less";
|
||||
@import "mixins/nav-vertical-align.less";
|
||||
@import "mixins/grid-framework.less";
|
||||
@import "mixins/grid.less";
|
||||
@import 'mixins/clearfix.less';
|
||||
@import 'mixins/center-block.less';
|
||||
@import 'mixins/nav-vertical-align.less';
|
||||
@import 'mixins/grid-framework.less';
|
||||
@import 'mixins/grid.less';
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
// Set the border and box shadow on specific inputs to match
|
||||
.form-control {
|
||||
border-color: @border-color;
|
||||
.box-shadow(inset 0 1px 1px rgba(0, 0, 0, .075)); // Redeclare so transitions work
|
||||
.box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.075)); // Redeclare so transitions work
|
||||
&:focus {
|
||||
border-color: darken(@border-color, 10%);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 3px rgba(@brand-primary, .5);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 3px rgba(@brand-primary, 0.5);
|
||||
}
|
||||
}
|
||||
// Set validation states also for addons
|
||||
|
@ -51,11 +51,10 @@
|
|||
// Example usage: change the default blue border and shadow to white for better
|
||||
// contrast against a dark gray background.
|
||||
.form-control-focus(@color: @input-border-focus) {
|
||||
@color-rgba: rgba(red(@color), green(@color), blue(@color), .6);
|
||||
@color-rgba: rgba(red(@color), green(@color), blue(@color), 0.6);
|
||||
&:focus {
|
||||
border-color: @color;
|
||||
outline: 0;
|
||||
.box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}");
|
||||
.box-shadow(~'inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
// WebKit-style focus
|
||||
|
||||
.tab-focus() {
|
||||
outline: thin dotted;
|
||||
outline-offset: 3px;
|
||||
}
|
|
@ -7,10 +7,6 @@
|
|||
position: absolute;
|
||||
left: 5px;
|
||||
top: 5px;
|
||||
&:hover,
|
||||
&:active {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ export const AmountField: React.SFC<Props> = ({
|
|||
<AmountFieldFactory
|
||||
withProps={({ currentValue: { raw }, isValid, onChange, readOnly }) => (
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group input-group-inline-dropdown">
|
||||
<label className="input-group input-group-inline">
|
||||
<div className="input-group-header">{translate('SEND_amount')}</div>
|
||||
<Input
|
||||
className={`input-group-input ${
|
||||
|
@ -37,5 +37,8 @@ export const AmountField: React.SFC<Props> = ({
|
|||
/>
|
||||
);
|
||||
|
||||
const isAmountValid = (raw, customValidator, isValid) =>
|
||||
customValidator ? customValidator(raw) : isValid;
|
||||
const isAmountValid = (
|
||||
raw: string,
|
||||
customValidator: ((rawAmount: string) => boolean) | undefined,
|
||||
isValid: boolean
|
||||
) => (customValidator ? customValidator(raw) : isValid);
|
||||
|
|
|
@ -8,7 +8,6 @@ import { chain, flatMap } from 'lodash';
|
|||
import { TokenBalance, getShownTokenBalances } from 'selectors/wallet';
|
||||
import { Balance } from 'libs/wallet';
|
||||
import './EquivalentValues.scss';
|
||||
import { Wei } from 'libs/units';
|
||||
import { AppState } from 'reducers';
|
||||
import { getNetworkConfig, getOffline } from 'selectors/config';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -16,6 +15,7 @@ import btcIco from 'assets/images/bitcoin.png';
|
|||
import ethIco from 'assets/images/ether.png';
|
||||
import repIco from 'assets/images/augur.png';
|
||||
import { NetworkConfig } from 'types/network';
|
||||
import BN from 'bn.js';
|
||||
|
||||
interface AllValue {
|
||||
symbol: string;
|
||||
|
@ -51,6 +51,14 @@ interface DispatchProps {
|
|||
fetchCCRates: TFetchCCRatesRequested;
|
||||
}
|
||||
|
||||
interface FiatSymbols {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
interface Rates {
|
||||
[rate: string]: number;
|
||||
}
|
||||
|
||||
type Props = StateProps & DispatchProps;
|
||||
|
||||
class EquivalentValues extends React.Component<Props, State> {
|
||||
|
@ -110,7 +118,7 @@ class EquivalentValues extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
public selectOption = equivalentValues => {
|
||||
public selectOption = (equivalentValues: Option) => {
|
||||
this.setState({ equivalentValues });
|
||||
};
|
||||
|
||||
|
@ -120,28 +128,38 @@ class EquivalentValues extends React.Component<Props, State> {
|
|||
const isFetching =
|
||||
!balance || balance.isPending || !tokenBalances || Object.keys(rates).length === 0;
|
||||
const pairRates = this.generateValues(equivalentValues.label, equivalentValues.value);
|
||||
const fiatSymbols = {
|
||||
const fiatSymbols: FiatSymbols = {
|
||||
USD: '$',
|
||||
EUR: '€',
|
||||
GBP: '£',
|
||||
CHF: ' '
|
||||
};
|
||||
const coinAndTokenSymbols = {
|
||||
const coinAndTokenSymbols: any = {
|
||||
BTC: btcIco,
|
||||
ETH: ethIco,
|
||||
REP: repIco
|
||||
};
|
||||
interface ValueProps {
|
||||
className: string;
|
||||
rate: string;
|
||||
value: BN | null;
|
||||
symbol?: string;
|
||||
icon?: string;
|
||||
key?: number | string;
|
||||
}
|
||||
|
||||
const Value = ({ className = '', rate, value, symbol = '', icon = '' }) => (
|
||||
<div className={`EquivalentValues-values-currency ${className}`}>
|
||||
<img src={icon} />
|
||||
{!!symbol && <span className="EquivalentValues-values-currency-fiat-symbol">{symbol}</span>}
|
||||
<span className="EquivalentValues-values-currency-label">{rate}</span>{' '}
|
||||
const Value = (props: ValueProps) => (
|
||||
<div className={`EquivalentValues-values-currency ${props.className}`}>
|
||||
<img src={props.icon} />
|
||||
{!!props.symbol && (
|
||||
<span className="EquivalentValues-values-currency-fiat-symbol">{props.symbol}</span>
|
||||
)}
|
||||
<span className="EquivalentValues-values-currency-label">{props.rate}</span>{' '}
|
||||
<span className="EquivalentValues-values-currency-value">
|
||||
<UnitDisplay
|
||||
unit={'ether'}
|
||||
value={value}
|
||||
displayShortBalance={rateSymbols.isFiat(rate) ? 2 : 3}
|
||||
value={props.value}
|
||||
displayShortBalance={rateSymbols.isFiat(props.rate) ? 2 : 3}
|
||||
checkOffline={true}
|
||||
/>
|
||||
</span>
|
||||
|
@ -157,7 +175,7 @@ class EquivalentValues extends React.Component<Props, State> {
|
|||
// TODO: Update type
|
||||
value={equivalentValues as any}
|
||||
options={options as any}
|
||||
onChange={this.selectOption}
|
||||
onChange={this.selectOption as any}
|
||||
clearable={false}
|
||||
searchable={false}
|
||||
/>
|
||||
|
@ -224,7 +242,7 @@ class EquivalentValues extends React.Component<Props, State> {
|
|||
const allRates = Object.values(balance).map(
|
||||
value => !!rates[value.symbol] && rates[value.symbol]
|
||||
);
|
||||
const allEquivalentValues = allRates.map((rateType, i) => {
|
||||
const allEquivalentValues = allRates.map((rateType: any, i) => {
|
||||
return {
|
||||
symbol: Object.keys(rates)[i],
|
||||
equivalentValues: [
|
||||
|
@ -260,9 +278,9 @@ class EquivalentValues extends React.Component<Props, State> {
|
|||
// return equivalent value (unit * rate * balance)
|
||||
private handleValues(unit: string, balance: Balance['wei']) {
|
||||
const { rates } = this.props;
|
||||
const ratesObj = { ...rates[unit] };
|
||||
const ratesObj: Rates = { ...rates[unit] };
|
||||
return Object.keys(ratesObj).map(key => {
|
||||
const value = (balance as Wei).muln(ratesObj[key]);
|
||||
const value = balance!.muln(ratesObj[key]);
|
||||
return { rate: key, value };
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,10 +2,14 @@ import React from 'react';
|
|||
import CoinbaseLogo from 'assets/images/logo-coinbase.svg';
|
||||
import { NewTabLink } from 'components/ui';
|
||||
|
||||
export const Coinbase: React.SFC = () => (
|
||||
interface Props {
|
||||
address: string;
|
||||
}
|
||||
|
||||
export const Coinbase: React.SFC<Props> = ({ address }) => (
|
||||
<NewTabLink
|
||||
className="Promos-promo Promos--coinbase"
|
||||
href="https://buy.coinbase.com?code=60c05061-3a76-57be-b1cd-a7afa97bcb8c&address=0xA7DeFf12461661212734dB35AdE9aE7d987D648c&crypto_currency=ETH¤cy=USD"
|
||||
href={`https://buy.coinbase.com?code=60c05061-3a76-57be-b1cd-a7afa97bcb8c&address=${address}&crypto_currency=ETH¤cy=USD`}
|
||||
>
|
||||
<div className="Promos-promo-inner">
|
||||
<div className="Promos-promo-text">
|
||||
|
@ -13,7 +17,7 @@ export const Coinbase: React.SFC = () => (
|
|||
<h5 key="2">Buy ETH with USD</h5>
|
||||
</div>
|
||||
<div className="Promos-promo-images">
|
||||
<img src={CoinbaseLogo} />
|
||||
<img src={CoinbaseLogo} alt="Coinbase logo" />
|
||||
</div>
|
||||
</div>
|
||||
</NewTabLink>
|
||||
|
|
|
@ -11,8 +11,8 @@ export const HardwareWallets: React.SFC = () => (
|
|||
<h6>Learn more about protecting your funds.</h6>
|
||||
</div>
|
||||
<div className="Promos-promo-images">
|
||||
<img src={ledgerLogo} />
|
||||
<img src={trezorLogo} />
|
||||
<img src={ledgerLogo} alt="Ledger Logo" />
|
||||
<img src={trezorLogo} alt="Trezor Logo" />
|
||||
</div>
|
||||
</div>
|
||||
</HelpLink>
|
||||
|
|
|
@ -13,7 +13,7 @@ export const Shapeshift: React.SFC = () => (
|
|||
</h5>
|
||||
</div>
|
||||
<div className="Promos-promo-images">
|
||||
<img src={ShapeshiftLogo} />
|
||||
<img src={ShapeshiftLogo} alt="Shapeshift Logo" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
|
|
@ -85,7 +85,6 @@
|
|||
height: 12px;
|
||||
border: 3px solid $gray-lightest;
|
||||
border-radius: 100%;
|
||||
outline: none;
|
||||
opacity: 0.6;
|
||||
|
||||
&.is-active {
|
||||
|
@ -96,7 +95,7 @@
|
|||
|
||||
// Per-promo customizations
|
||||
&--shapeshift {
|
||||
background-color: #263A52;
|
||||
background-color: #263a52;
|
||||
|
||||
.Promos-promo-images {
|
||||
max-width: 130px;
|
||||
|
|
|
@ -2,21 +2,29 @@ import React from 'react';
|
|||
import { TransitionGroup, CSSTransition } from 'react-transition-group';
|
||||
import { HardwareWallets, Coinbase, Shapeshift } from './PromoComponents';
|
||||
import './Promos.scss';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from '../../reducers';
|
||||
|
||||
const promos = [HardwareWallets, Coinbase, Shapeshift];
|
||||
|
||||
const CarouselAnimation = ({ children, ...props }) => (
|
||||
const CarouselAnimation = ({ children, ...props }: any) => (
|
||||
<CSSTransition {...props} timeout={300} classNames="carousel">
|
||||
{children}
|
||||
</CSSTransition>
|
||||
);
|
||||
|
||||
// Don't change Coinbase index
|
||||
const promos = [HardwareWallets, Coinbase, Shapeshift];
|
||||
|
||||
interface State {
|
||||
activePromo: number;
|
||||
}
|
||||
|
||||
export default class Promos extends React.PureComponent<{}, State> {
|
||||
interface StateProps {
|
||||
wallet: AppState['wallet']['inst'];
|
||||
}
|
||||
|
||||
class PromosClass extends React.PureComponent<StateProps, State> {
|
||||
public timer: any = null;
|
||||
public promos = [HardwareWallets, Coinbase, Shapeshift];
|
||||
|
||||
public state = {
|
||||
activePromo: parseInt(String(Math.random() * promos.length), 10)
|
||||
|
@ -30,13 +38,27 @@ export default class Promos extends React.PureComponent<{}, State> {
|
|||
clearInterval(this.timer);
|
||||
}
|
||||
|
||||
public getPromo() {
|
||||
const { activePromo } = this.state;
|
||||
const { wallet } = this.props;
|
||||
if (activePromo === 1) {
|
||||
if (wallet) {
|
||||
return <Coinbase address={wallet.getAddressString()} />;
|
||||
} else {
|
||||
return <Shapeshift />;
|
||||
}
|
||||
} else {
|
||||
return promos[activePromo];
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { activePromo } = this.state;
|
||||
|
||||
return (
|
||||
<div className="Promos">
|
||||
<TransitionGroup className="Promos-promo-wrapper">
|
||||
<CarouselAnimation key={Math.random()}>{promos[activePromo]}</CarouselAnimation>
|
||||
<CarouselAnimation key={Math.random()}>{this.getPromo()}</CarouselAnimation>
|
||||
</TransitionGroup>
|
||||
<div className="Promos-nav">
|
||||
{promos.map((_, index) => {
|
||||
|
@ -64,3 +86,11 @@ export default class Promos extends React.PureComponent<{}, State> {
|
|||
this.setState({ activePromo });
|
||||
};
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState): StateProps {
|
||||
return {
|
||||
wallet: state.wallet.inst
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, {})(PromosClass);
|
||||
|
|
|
@ -66,11 +66,11 @@ export default class AddCustomTokenForm extends React.PureComponent<Props, State
|
|||
{fields.map(field => {
|
||||
return (
|
||||
<label className="AddCustom-field form-group" key={field.name}>
|
||||
<span className="AddCustom-field-label">{field.label}</span>
|
||||
<div className="input-group-header">{field.label}</div>
|
||||
<Input
|
||||
className={`${
|
||||
errors[field.name] ? 'invalid' : field.value ? 'valid' : ''
|
||||
} AddCustom-field-input input-sm`}
|
||||
} input-group-input-small`}
|
||||
type="text"
|
||||
name={field.name}
|
||||
value={field.value}
|
||||
|
|
|
@ -15,19 +15,23 @@ interface Props {
|
|||
onRemoveCustomToken(symbol: string): any;
|
||||
}
|
||||
|
||||
interface TrackedTokens {
|
||||
[symbol: string]: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
trackedTokens: { [symbol: string]: boolean };
|
||||
showCustomTokenForm: boolean;
|
||||
}
|
||||
export default class TokenBalances extends React.PureComponent<Props, State> {
|
||||
public state = {
|
||||
public state: State = {
|
||||
trackedTokens: {},
|
||||
showCustomTokenForm: false
|
||||
};
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
if (nextProps.tokenBalances !== this.props.tokenBalances) {
|
||||
const trackedTokens = nextProps.tokenBalances.reduce((prev, t) => {
|
||||
const trackedTokens = nextProps.tokenBalances.reduce<TrackedTokens>((prev, t) => {
|
||||
prev[t.symbol] = !t.balance.isZero();
|
||||
return prev;
|
||||
}, {});
|
||||
|
|
|
@ -54,6 +54,7 @@ export default class TokenRow extends React.PureComponent<Props, State> {
|
|||
{!!custom && (
|
||||
<img
|
||||
src={removeIcon}
|
||||
alt="Remove"
|
||||
className="TokenRow-symbol-remove"
|
||||
title="Remove Token"
|
||||
onClick={this.onRemove}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@import "common/sass/variables";
|
||||
@import "common/sass/mixins";
|
||||
@import 'common/sass/variables';
|
||||
@import 'common/sass/mixins';
|
||||
|
||||
.BetaAgreement {
|
||||
@include cover-message;
|
||||
|
@ -20,7 +20,6 @@
|
|||
margin: 0 auto;
|
||||
border: none;
|
||||
padding: 0;
|
||||
outline: none;
|
||||
transition: $transition;
|
||||
|
||||
&.is-continue {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { connect } from 'react-redux';
|
|||
import { SerializedTransaction } from 'components/renderCbs';
|
||||
import { AppState } from 'reducers';
|
||||
import { getFrom, getUnit, isEtherTransaction } from 'selectors/transaction';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
|
||||
interface StateProps {
|
||||
from: AppState['transaction']['meta']['from'];
|
||||
|
@ -24,7 +25,9 @@ class AddressesClass extends Component<StateProps> {
|
|||
return (
|
||||
<SerializedTransaction
|
||||
withSerializedTransaction={(_, { to, data }) => {
|
||||
const toFormatted = isToken ? ERC20.transfer.decodeInput(data)._to : to;
|
||||
const toFormatted = toChecksumAddress(
|
||||
isToken ? ERC20.transfer.decodeInput(data)._to : to
|
||||
);
|
||||
return (
|
||||
<div className="tx-modal-address">
|
||||
<div className="tx-modal-address-from">
|
||||
|
|
|
@ -13,7 +13,7 @@ interface StateProps {
|
|||
}
|
||||
|
||||
interface OwnProps {
|
||||
withProps(props: CallBackProps);
|
||||
withProps(props: CallBackProps): null | React.ReactElement<any>;
|
||||
onChange(value: React.FormEvent<HTMLInputElement>): void;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,9 +41,9 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
|
|||
}
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps) {
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
if (nextProps.privateKey !== this.props.privateKey) {
|
||||
this.setState({ privateKey: nextProps.privateKey });
|
||||
this.setState({ privateKey: nextProps.privateKey || '' });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,13 +55,13 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
|
|||
|
||||
return (
|
||||
<Modal
|
||||
title={translate('Generate Keystore File')}
|
||||
title={translateRaw('Generate Keystore File')}
|
||||
isOpen={this.props.isOpen}
|
||||
handleClose={this.handleClose}
|
||||
>
|
||||
<form className="GenKeystore" onSubmit={this.handleSubmit}>
|
||||
<div className="input-group-wrapper GenKeystore-field">
|
||||
<label className="input-group input-group-inline-dropdown">
|
||||
<label className="input-group input-group-inline">
|
||||
<div className="input-group-header">Private Key</div>
|
||||
<TogglablePassword
|
||||
name="privateKey"
|
||||
|
@ -74,7 +74,7 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
|
|||
</label>
|
||||
</div>
|
||||
<div className="input-group-wrapper GenKeystore-field">
|
||||
<label className="input-group input-group-inline-dropdown">
|
||||
<label className="input-group input-group-inline">
|
||||
<div className="input-group-header">Password</div>
|
||||
<TogglablePassword
|
||||
name="password"
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
.CustomNodeModal {
|
||||
.flex-wrapper {
|
||||
margin: 0px -8px;
|
||||
> .input-group {
|
||||
margin: 0px 8px;
|
||||
> .input-group-input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
input[type='checkbox'] {
|
||||
margin-right: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import Modal, { IButton } from 'components/ui/Modal';
|
||||
import translate from 'translations';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { CustomNetworkConfig } from 'types/network';
|
||||
import { CustomNodeConfig } from 'types/node';
|
||||
import { TAddCustomNetwork, addCustomNetwork, AddCustomNodeAction } from 'actions/config';
|
||||
|
@ -13,19 +13,13 @@ import {
|
|||
} from 'selectors/config';
|
||||
import { CustomNode } from 'libs/nodes';
|
||||
import { Input } from 'components/ui';
|
||||
import Dropdown from 'components/ui/Dropdown';
|
||||
import './CustomNodeModal.scss';
|
||||
|
||||
const CUSTOM = 'custom';
|
||||
|
||||
interface InputProps {
|
||||
name: string;
|
||||
placeholder?: string;
|
||||
type?: string;
|
||||
autoComplete?: 'off';
|
||||
onFocus?(): void;
|
||||
onBlur?(): void;
|
||||
}
|
||||
const CUSTOM = { label: 'Custom', value: 'custom' };
|
||||
|
||||
interface OwnProps {
|
||||
isOpen: boolean;
|
||||
addCustomNode(payload: AddCustomNodeAction['payload']): void;
|
||||
handleClose(): void;
|
||||
}
|
||||
|
@ -55,7 +49,7 @@ interface State {
|
|||
type Props = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
class CustomNodeModal extends React.Component<Props, State> {
|
||||
public state: State = {
|
||||
public INITIAL_STATE = {
|
||||
name: '',
|
||||
url: '',
|
||||
network: Object.keys(this.props.staticNetworks)[0],
|
||||
|
@ -66,9 +60,17 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
username: '',
|
||||
password: ''
|
||||
};
|
||||
public state: State = this.INITIAL_STATE;
|
||||
|
||||
public componentDidUpdate(prevProps: Props) {
|
||||
// Reset state when modal opens
|
||||
if (!prevProps.isOpen && prevProps.isOpen !== this.props.isOpen) {
|
||||
this.setState(this.INITIAL_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { customNetworks, handleClose, staticNetworks } = this.props;
|
||||
const { customNetworks, handleClose, staticNetworks, isOpen } = this.props;
|
||||
const { network } = this.state;
|
||||
const isHttps = window.location.protocol.includes('https');
|
||||
const invalids = this.getInvalids();
|
||||
|
@ -88,16 +90,21 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
];
|
||||
|
||||
const conflictedNode = this.getConflictedNode();
|
||||
|
||||
const staticNetwrks = Object.keys(staticNetworks).map(net => {
|
||||
return { label: net, value: net };
|
||||
});
|
||||
const customNetwrks = Object.entries(customNetworks).map(([id, net]) => {
|
||||
return { label: net.name + ' (Custom)', value: id };
|
||||
});
|
||||
const options = [...staticNetwrks, ...customNetwrks, CUSTOM];
|
||||
return (
|
||||
<Modal
|
||||
title={translate('NODE_Title')}
|
||||
isOpen={true}
|
||||
title={translateRaw('NODE_Title')}
|
||||
isOpen={isOpen}
|
||||
buttons={buttons}
|
||||
handleClose={handleClose}
|
||||
maxWidth={580}
|
||||
>
|
||||
<div>
|
||||
{isHttps && <div className="alert alert-warning small">{translate('NODE_Warning')}</div>}
|
||||
|
||||
{conflictedNode && (
|
||||
|
@ -107,139 +114,128 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
</div>
|
||||
)}
|
||||
|
||||
<form>
|
||||
<div className="row">
|
||||
<div className="col-sm-7">
|
||||
<label>{translate('NODE_Name')}</label>
|
||||
{this.renderInput(
|
||||
{
|
||||
name: 'name',
|
||||
placeholder: 'My Node'
|
||||
},
|
||||
invalids
|
||||
)}
|
||||
</div>
|
||||
<div className="col-sm-5">
|
||||
<label>Network</label>
|
||||
<select
|
||||
className="form-control"
|
||||
name="network"
|
||||
<form className="CustomNodeModal">
|
||||
<div className="flex-wrapper">
|
||||
<label className="col-sm-9 input-group flex-grow-1">
|
||||
<div className="input-group-header">Node Name</div>
|
||||
<Input
|
||||
className={`input-group-input ${this.state.name && invalids.name ? 'invalid' : ''}`}
|
||||
type="text"
|
||||
placeholder="My Node"
|
||||
value={this.state.name}
|
||||
onChange={e => this.setState({ name: e.currentTarget.value })}
|
||||
/>
|
||||
</label>
|
||||
<label className="col-sm-3 input-group">
|
||||
<div className="input-group-header">Network</div>
|
||||
<Dropdown
|
||||
className="input-group-dropdown"
|
||||
value={network}
|
||||
onChange={this.handleChange}
|
||||
>
|
||||
{Object.keys(staticNetworks).map(net => (
|
||||
<option key={net} value={net}>
|
||||
{net}
|
||||
</option>
|
||||
))}
|
||||
{Object.entries(customNetworks).map(([id, net]) => (
|
||||
<option key={id} value={id}>
|
||||
{net.name} (Custom)
|
||||
</option>
|
||||
))}
|
||||
<option value={CUSTOM}>Custom...</option>
|
||||
</select>
|
||||
</div>
|
||||
options={options}
|
||||
clearable={false}
|
||||
onChange={(e: { label: string; value: string }) =>
|
||||
this.setState({ network: e.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{network === CUSTOM && (
|
||||
<div className="row">
|
||||
<div className="col-sm-6">
|
||||
<label className="is-required">Network Name</label>
|
||||
{this.renderInput(
|
||||
{
|
||||
name: 'customNetworkId',
|
||||
placeholder: 'My Custom Network'
|
||||
},
|
||||
invalids
|
||||
)}
|
||||
</div>
|
||||
<div className="col-sm-3">
|
||||
<label className="is-required">Currency</label>
|
||||
{this.renderInput(
|
||||
{
|
||||
name: 'customNetworkUnit',
|
||||
placeholder: 'ETH'
|
||||
},
|
||||
invalids
|
||||
)}
|
||||
</div>
|
||||
<div className="col-sm-3">
|
||||
<label>Chain ID</label>
|
||||
{this.renderInput(
|
||||
{
|
||||
name: 'customNetworkChainId',
|
||||
placeholder: 'e.g. 1'
|
||||
},
|
||||
invalids
|
||||
)}
|
||||
</div>
|
||||
{network === CUSTOM.value && (
|
||||
<div className="flex-wrapper">
|
||||
<label className="col-sm-6 input-group input-group-inline">
|
||||
<div className="input-group-header">Network Name</div>
|
||||
<Input
|
||||
className={`input-group-input ${
|
||||
this.state.customNetworkId && invalids.customNetworkId ? 'invalid' : ''
|
||||
}`}
|
||||
type="text"
|
||||
placeholder="My Custom Network"
|
||||
value={this.state.customNetworkId}
|
||||
onChange={e => this.setState({ customNetworkId: e.currentTarget.value })}
|
||||
/>
|
||||
</label>
|
||||
<label className="col-sm-3 input-group input-group-inline">
|
||||
<div className="input-group-header">Currency</div>
|
||||
<Input
|
||||
className={`input-group-input ${
|
||||
this.state.customNetworkUnit && invalids.customNetworkUnit ? 'invalid' : ''
|
||||
}`}
|
||||
type="text"
|
||||
placeholder="ETH"
|
||||
value={this.state.customNetworkUnit}
|
||||
onChange={e => this.setState({ customNetworkUnit: e.currentTarget.value })}
|
||||
/>
|
||||
</label>
|
||||
<label className="col-sm-3 input-group input-group-inline">
|
||||
<div className="input-group-header">Chain ID</div>
|
||||
<Input
|
||||
className={`input-group-input ${
|
||||
this.state.customNetworkChainId && invalids.customNetworkChainId
|
||||
? 'invalid'
|
||||
: ''
|
||||
}`}
|
||||
type="text"
|
||||
placeholder="1"
|
||||
value={this.state.customNetworkChainId}
|
||||
onChange={e => this.setState({ customNetworkChainId: e.currentTarget.value })}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<label>URL</label>
|
||||
{this.renderInput(
|
||||
{
|
||||
name: 'url',
|
||||
placeholder: 'e.g. https://127.0.0.1:8545/',
|
||||
autoComplete: 'off'
|
||||
},
|
||||
invalids
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<label className="input-group input-group-inline">
|
||||
<div className="input-group-header">URL</div>
|
||||
<Input
|
||||
className={`input-group-input ${this.state.url && invalids.url ? 'invalid' : ''}`}
|
||||
type="text"
|
||||
placeholder="https://127.0.0.1:8545/"
|
||||
value={this.state.url}
|
||||
onChange={e => this.setState({ url: e.currentTarget.value })}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="hasAuth"
|
||||
checked={this.state.hasAuth}
|
||||
onChange={this.handleCheckbox}
|
||||
/>{' '}
|
||||
onChange={() => this.setState({ hasAuth: !this.state.hasAuth })}
|
||||
/>
|
||||
<span>HTTP Basic Authentication</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.state.hasAuth && (
|
||||
<div className="row">
|
||||
<div className="col-sm-6">
|
||||
<label className="is-required">Username</label>
|
||||
{this.renderInput({ name: 'username' }, invalids)}
|
||||
</div>
|
||||
<div className="col-sm-6">
|
||||
<label className="is-required">Password</label>
|
||||
{this.renderInput(
|
||||
{
|
||||
name: 'password',
|
||||
type: 'password'
|
||||
},
|
||||
invalids
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-wrapper ">
|
||||
<label className="col-sm-6 input-group input-group-inline">
|
||||
<div className="input-group-header">Username</div>
|
||||
<Input
|
||||
className={`input-group-input ${
|
||||
this.state.username && invalids.username ? 'invalid' : ''
|
||||
}`}
|
||||
type="text"
|
||||
value={this.state.username}
|
||||
onChange={e => this.setState({ username: e.currentTarget.value })}
|
||||
/>
|
||||
</label>
|
||||
<label className="col-sm-6 input-group input-group-inline">
|
||||
<div className="input-group-header">Password</div>
|
||||
<Input
|
||||
className={`input-group-input ${
|
||||
this.state.password && invalids.password ? 'invalid' : ''
|
||||
}`}
|
||||
type="password"
|
||||
value={this.state.password}
|
||||
onChange={e => this.setState({ password: e.currentTarget.value })}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
private renderInput(input: InputProps, invalids: { [key: string]: boolean }) {
|
||||
return (
|
||||
<Input
|
||||
className={`${this.state[input.name] && invalids[input.name] ? 'invalid' : ''}`}
|
||||
value={this.state[input.name]}
|
||||
onChange={this.handleChange}
|
||||
autoComplete="off"
|
||||
{...input}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private getInvalids(): { [key: string]: boolean } {
|
||||
const {
|
||||
url,
|
||||
|
@ -278,7 +274,7 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
// If they have a custom network, make sure info is provided
|
||||
if (network === CUSTOM) {
|
||||
if (network === CUSTOM.value) {
|
||||
if (!customNetworkId) {
|
||||
invalids.customNetworkId = true;
|
||||
}
|
||||
|
@ -315,7 +311,7 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
const { network, url, name, username, password } = this.state;
|
||||
|
||||
const networkId =
|
||||
network === CUSTOM
|
||||
network === CUSTOM.value
|
||||
? this.makeCustomNetworkId(this.makeCustomNetworkConfigFromState())
|
||||
: network;
|
||||
|
||||
|
@ -348,20 +344,10 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
return customNodes[config.id];
|
||||
}
|
||||
|
||||
private handleChange = (ev: React.FormEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||
const { name, value } = ev.currentTarget;
|
||||
this.setState({ [name as any]: value });
|
||||
};
|
||||
|
||||
private handleCheckbox = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
const { name } = ev.currentTarget;
|
||||
this.setState({ [name as any]: !this.state[name as keyof State] });
|
||||
};
|
||||
|
||||
private saveAndAdd = () => {
|
||||
const node = this.makeCustomNodeConfigFromState();
|
||||
|
||||
if (this.state.network === CUSTOM) {
|
||||
if (this.state.network === CUSTOM.value) {
|
||||
const network = this.makeCustomNetworkConfigFromState();
|
||||
|
||||
this.props.addCustomNetwork({ config: network, id: node.network });
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
margin-top: -.1rem;
|
||||
width: 1.3rem;
|
||||
height: 1.3rem;
|
||||
background-image: url('~assets/images/swap.svg');
|
||||
background-image: url('~assets/images/logo-shapeshift-no-text.svg');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
|
|
|
@ -192,12 +192,11 @@ class Header extends Component<Props, State> {
|
|||
|
||||
<Navigation color={!network.isCustom && network.color} />
|
||||
|
||||
{isAddingCustomNode && (
|
||||
<CustomNodeModal
|
||||
isOpen={isAddingCustomNode}
|
||||
addCustomNode={this.addCustomNode}
|
||||
handleClose={this.closeCustomNodeModal}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ interface State {
|
|||
}
|
||||
|
||||
class LogOutPromptClass extends React.Component<Props, State> {
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
nextLocation: null,
|
||||
|
|
|
@ -100,8 +100,8 @@ export default class PaperWallet extends React.Component<Props, {}> {
|
|||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<img src={sidebarImg} style={styles.sidebar} />
|
||||
<img src={ethLogo} style={styles.ethLogo} />
|
||||
<img src={sidebarImg} style={styles.sidebar} alt="MyCrypto Logo" />
|
||||
<img src={ethLogo} style={styles.ethLogo} alt="ETH Logo" />
|
||||
|
||||
<div style={styles.block}>
|
||||
<div style={styles.box}>
|
||||
|
@ -111,7 +111,7 @@ export default class PaperWallet extends React.Component<Props, {}> {
|
|||
</div>
|
||||
|
||||
<div style={styles.block}>
|
||||
<img src={notesBg} style={styles.box} />
|
||||
<img src={notesBg} style={styles.box} aria-hidden={true} />
|
||||
<p style={styles.blockText}>AMOUNT / NOTES</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import Slider from 'rc-slider';
|
||||
import Slider, { createSliderWithTooltip } from 'rc-slider';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import FeeSummary from './FeeSummary';
|
||||
import './SimpleGas.scss';
|
||||
|
@ -16,12 +16,13 @@ import { getEstimates, getIsEstimating } from 'selectors/gas';
|
|||
import { Wei, fromWei } from 'libs/units';
|
||||
import { gasPriceDefaults } from 'config';
|
||||
import { InlineSpinner } from 'components/ui/InlineSpinner';
|
||||
const SliderWithTooltip = Slider.createSliderWithTooltip(Slider);
|
||||
import { TInputGasPrice } from 'actions/transaction';
|
||||
const SliderWithTooltip = createSliderWithTooltip(Slider);
|
||||
|
||||
interface OwnProps {
|
||||
gasPrice: AppState['transaction']['fields']['gasPrice'];
|
||||
inputGasPrice(rawGas: string);
|
||||
setGasPrice(rawGas: string);
|
||||
setGasPrice: TInputGasPrice;
|
||||
inputGasPrice(rawGas: string): void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
|
@ -39,14 +40,22 @@ interface ActionProps {
|
|||
|
||||
type Props = OwnProps & StateProps & ActionProps;
|
||||
|
||||
interface State {
|
||||
hasSetRecommendedGasPrice: boolean;
|
||||
}
|
||||
|
||||
class SimpleGas extends React.Component<Props> {
|
||||
public state: State = {
|
||||
hasSetRecommendedGasPrice: false
|
||||
};
|
||||
|
||||
public componentDidMount() {
|
||||
this.fixGasPrice();
|
||||
this.props.fetchGasEstimates();
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
if (!this.props.gasEstimates && nextProps.gasEstimates) {
|
||||
if (!this.state.hasSetRecommendedGasPrice && nextProps.gasEstimates) {
|
||||
this.setState({ hasSetRecommendedGasPrice: true });
|
||||
this.props.setGasPrice(nextProps.gasEstimates.fast.toString());
|
||||
}
|
||||
}
|
||||
|
@ -120,21 +129,6 @@ class SimpleGas extends React.Component<Props> {
|
|||
this.props.inputGasPrice(gasGwei.toString());
|
||||
};
|
||||
|
||||
private fixGasPrice() {
|
||||
const { gasPrice, gasEstimates } = this.props;
|
||||
if (!gasEstimates) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the gas price is above or below our minimum, bring it in line
|
||||
const gasPriceGwei = this.getGasPriceGwei(gasPrice.value);
|
||||
if (gasPriceGwei < gasEstimates.safeLow) {
|
||||
this.props.setGasPrice(gasEstimates.safeLow.toString());
|
||||
} else if (gasPriceGwei > gasEstimates.fastest) {
|
||||
this.props.setGasPrice(gasEstimates.fastest.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private getGasPriceGwei(gasPriceValue: Wei) {
|
||||
return parseFloat(fromWei(gasPriceValue, 'gwei'));
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ interface Props {
|
|||
isValid?: boolean;
|
||||
isVisible?: boolean;
|
||||
validity?: 'valid' | 'invalid' | 'semivalid';
|
||||
readOnly?: boolean;
|
||||
|
||||
// Textarea-only props
|
||||
isTextareaWhenVisible?: boolean;
|
||||
|
@ -61,18 +62,16 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
|
|||
onChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
handleToggleVisibility
|
||||
handleToggleVisibility,
|
||||
readOnly
|
||||
} = this.props;
|
||||
const { isVisible } = this.state;
|
||||
const validClass = validity
|
||||
? `is-${validity}`
|
||||
: isValid === null || isValid === undefined ? '' : isValid ? 'is-valid' : 'is-invalid';
|
||||
|
||||
return (
|
||||
<div className={`TogglablePassword input-group input-group-inline-dropdown ${className}`}>
|
||||
<div className={`TogglablePassword input-group input-group-inline ${className}`}>
|
||||
{isTextareaWhenVisible && isVisible ? (
|
||||
<TextArea
|
||||
className={validClass}
|
||||
className={validity || !isValid ? 'invalid' : ''}
|
||||
value={value}
|
||||
name={name}
|
||||
disabled={disabled}
|
||||
|
@ -83,6 +82,7 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
|
|||
placeholder={placeholder}
|
||||
rows={this.props.rows || 3}
|
||||
aria-label={ariaLabel}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
|
@ -90,12 +90,13 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
|
|||
name={name}
|
||||
disabled={disabled}
|
||||
type={isVisible ? 'text' : 'password'}
|
||||
className={`${validClass}`}
|
||||
className={`${validity || !isValid ? 'invalid' : ''} border-rad-right-0`}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
aria-label={ariaLabel}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
|
|
|
@ -7,6 +7,7 @@ import { connect } from 'react-redux';
|
|||
import { AppState } from 'reducers';
|
||||
import { getUnit } from 'selectors/transaction';
|
||||
import { getNetworkUnit } from 'selectors/config';
|
||||
import { Option } from 'react-select';
|
||||
|
||||
interface DispatchProps {
|
||||
setUnitMeta: TSetUnitMeta;
|
||||
|
@ -41,7 +42,10 @@ class UnitDropdownClass extends Component<DispatchProps & StateProps> {
|
|||
/>
|
||||
);
|
||||
}
|
||||
private handleOnChange = unit => {
|
||||
private handleOnChange = (unit: Option<string>) => {
|
||||
if (!unit.value) {
|
||||
throw Error('No unit value found');
|
||||
}
|
||||
this.props.setUnitMeta(unit.value);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -40,7 +40,6 @@ $speed: 500ms;
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
&:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
@ -80,7 +79,6 @@ $speed: 500ms;
|
|||
}
|
||||
|
||||
&:active {
|
||||
outline: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
@ -106,7 +104,7 @@ $speed: 500ms;
|
|||
.DecryptContent {
|
||||
&-enter {
|
||||
opacity: 0;
|
||||
transition: opacity $speed * .25 ease $speed * .125;
|
||||
transition: opacity $speed * 0.25 ease $speed * 0.125;
|
||||
|
||||
&-active {
|
||||
opacity: 1;
|
||||
|
@ -119,7 +117,7 @@ $speed: 500ms;
|
|||
left: 0;
|
||||
width: 100%;
|
||||
opacity: 1;
|
||||
transition: opacity $speed * .25 ease;
|
||||
transition: opacity $speed * 0.25 ease;
|
||||
pointer-events: none;
|
||||
|
||||
&-active {
|
||||
|
|
|
@ -126,7 +126,7 @@ type InsecureWallets = { [key in InsecureWalletName]: InsecureWalletInfo };
|
|||
type MiscWallet = { [key in MiscWalletName]: MiscWalletInfo };
|
||||
type Wallets = SecureWallets & InsecureWallets & MiscWallet;
|
||||
|
||||
const WEB3_TYPE: string | false =
|
||||
const WEB3_TYPE: keyof typeof WEB3_TYPES | false =
|
||||
(window as any).web3 && (window as any).web3.currentProvider.constructor.name;
|
||||
|
||||
const SECURE_WALLETS = Object.values(SecureWalletName);
|
||||
|
|
|
@ -73,7 +73,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
this.getAddresses();
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps) {
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
const { publicKey, chainCode, seed, dPath } = this.props;
|
||||
if (
|
||||
nextProps.publicKey !== publicKey ||
|
||||
|
@ -245,7 +245,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
}
|
||||
};
|
||||
|
||||
private selectAddress(selectedAddress, selectedAddrIndex) {
|
||||
private selectAddress(selectedAddress: string, selectedAddrIndex: number) {
|
||||
this.setState({ selectedAddress, selectedAddrIndex });
|
||||
}
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
|
|||
showTip: false
|
||||
});
|
||||
|
||||
ledger.comm_u2f.create_async().then(comm => {
|
||||
ledger.comm_u2f.create_async().then((comm: any) => {
|
||||
new ledger.eth(comm)
|
||||
.getAddress_async(dPath, false, true)
|
||||
.then(res => {
|
||||
|
@ -144,7 +144,7 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
|
|||
isLoading: false
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err: any) => {
|
||||
if (err && err.metaData && err.metaData.code === 5) {
|
||||
this.showTip();
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
|
|||
isValid={isValidMnemonic}
|
||||
isTextareaWhenVisible={true}
|
||||
onChange={this.onMnemonicChange}
|
||||
onEnter={isValidMnemonic && this.onDWModalOpen}
|
||||
onEnter={isValidMnemonic ? this.onDWModalOpen : undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
|
@ -134,7 +134,7 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
|
|||
this.setState({ dPath });
|
||||
};
|
||||
|
||||
private handleUnlock = (address, index) => {
|
||||
private handleUnlock = (address: string, index: number) => {
|
||||
const { formattedPhrase, pass, dPath } = this.state;
|
||||
|
||||
this.props.onUnlock({
|
||||
|
|
|
@ -113,7 +113,7 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
|
|||
|
||||
(TrezorConnect as any).getXPubKey(
|
||||
dPath,
|
||||
res => {
|
||||
(res: any) => {
|
||||
if (res.success) {
|
||||
this.setState({
|
||||
dPath,
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
transition: transform 150ms ease, box-shadow 150ms ease;
|
||||
animation: wallet-button-enter 400ms ease 1;
|
||||
animation-fill-mode: backwards;
|
||||
outline: none;
|
||||
|
||||
@for $i from 0 to 5 {
|
||||
&:nth-child(#{$i}) {
|
||||
|
@ -60,7 +59,6 @@
|
|||
}
|
||||
|
||||
&.is-disabled {
|
||||
outline: none;
|
||||
cursor: not-allowed;
|
||||
@include show-tooltip-on-hover;
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ interface Icon {
|
|||
icon: string;
|
||||
tooltip: string;
|
||||
href?: string;
|
||||
arialabel: string;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
@ -50,18 +51,21 @@ export class WalletButton extends React.PureComponent<Props> {
|
|||
if (isReadOnly) {
|
||||
icons.push({
|
||||
icon: 'eye',
|
||||
tooltip: translateRaw('You cannot send using address only')
|
||||
tooltip: translateRaw('You cannot send using address only'),
|
||||
arialabel: 'Read Only'
|
||||
});
|
||||
} else {
|
||||
if (isSecure) {
|
||||
icons.push({
|
||||
icon: 'shield',
|
||||
tooltip: translateRaw('This wallet type is secure')
|
||||
tooltip: translateRaw('This wallet type is secure'),
|
||||
arialabel: 'Secure wallet type'
|
||||
});
|
||||
} else {
|
||||
icons.push({
|
||||
icon: 'exclamation-triangle',
|
||||
tooltip: translateRaw('This wallet type is insecure')
|
||||
tooltip: translateRaw('This wallet type is insecure'),
|
||||
arialabel: 'Insecure wallet type'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +73,8 @@ export class WalletButton extends React.PureComponent<Props> {
|
|||
icons.push({
|
||||
icon: 'question-circle',
|
||||
tooltip: translateRaw('NAV_Help'),
|
||||
href: helpLink
|
||||
href: helpLink,
|
||||
arialabel: 'More info'
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -86,22 +91,30 @@ export class WalletButton extends React.PureComponent<Props> {
|
|||
>
|
||||
<div className="WalletButton-inner">
|
||||
<div className="WalletButton-title">
|
||||
{icon && <img className="WalletButton-title-icon" src={icon} />}
|
||||
{icon && <img className="WalletButton-title-icon" src={icon} alt={name + ' logo'} />}
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
|
||||
{description && <div className="WalletButton-description">{description}</div>}
|
||||
{example && <div className="WalletButton-example">{example}</div>}
|
||||
{description && (
|
||||
<div className="WalletButton-description" aria-label="description">
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
{example && (
|
||||
<div className="WalletButton-example" aria-label="example" aria-hidden={true}>
|
||||
{example}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="WalletButton-icons">
|
||||
{icons.map(i => (
|
||||
<span className="WalletButton-icons-icon" key={i.icon} onClick={this.stopPropogation}>
|
||||
{i.href ? (
|
||||
<NewTabLink href={i.href} onClick={this.stopPropogation}>
|
||||
<NewTabLink href={i.href} onClick={this.stopPropogation} aria-label={i.arialabel}>
|
||||
<i className={`fa fa-${i.icon}`} />
|
||||
</NewTabLink>
|
||||
) : (
|
||||
<i className={`fa fa-${i.icon}`} />
|
||||
<i className={`fa fa-${i.icon}`} aria-label={i.arialabel} />
|
||||
)}
|
||||
{!isDisabled && <Tooltip size="sm">{i.tooltip}</Tooltip>}
|
||||
</span>
|
||||
|
|
|
@ -92,6 +92,7 @@ export default class ColorDropdown<T> extends PureComponent<Props<T>, {}> {
|
|||
className="ColorDropdown-item-remove"
|
||||
onClick={this.onRemove.bind(null, option.onRemove)}
|
||||
src={removeIcon}
|
||||
alt="remove"
|
||||
/>
|
||||
)}
|
||||
</a>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import Select, { ReactSelectProps } from 'react-select';
|
||||
import Select, { ReactSelectProps, Option } from 'react-select';
|
||||
|
||||
interface Props extends ReactSelectProps {
|
||||
className?: string;
|
||||
|
@ -13,11 +13,11 @@ export default class Dropdown extends React.Component<Props> {
|
|||
hasBlurred: false
|
||||
};
|
||||
|
||||
public handleChange = selectedOption => {
|
||||
public handleChange = (selectedOption: Option) => {
|
||||
this.setState({ selectedOption });
|
||||
};
|
||||
|
||||
public formatOptions = options => {
|
||||
public formatOptions = (options: Option[]) => {
|
||||
if (typeof options[0] === 'object') {
|
||||
return options;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ export default class Dropdown extends React.Component<Props> {
|
|||
// use ref to prevent <label /> from stealing focus when used inline with an input
|
||||
ref={el => {
|
||||
if (!!el && !!(el as any).control) {
|
||||
(el as any).control.addEventListener('click', e => {
|
||||
(el as any).control.addEventListener('click', (e: React.FormEvent<any>) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ export default class Dropdown extends React.Component<Props> {
|
|||
className={`${this.props.className} ${this.state.hasBlurred ? 'has-blurred' : ''}`}
|
||||
value={value}
|
||||
onChange={obj => {
|
||||
this.handleChange(obj);
|
||||
this.handleChange(obj as any);
|
||||
onChange();
|
||||
}}
|
||||
{...this.props}
|
||||
|
@ -56,7 +56,7 @@ export default class Dropdown extends React.Component<Props> {
|
|||
this.props.onBlur(e);
|
||||
}
|
||||
}}
|
||||
options={options}
|
||||
options={options as any}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ interface Props {
|
|||
const Help = ({ size = 'x1', link }: Props) => {
|
||||
return (
|
||||
<a href={link} className={`Help Help-${size}`} target="_blank" rel="noopener noreferrer">
|
||||
<img src={icon} />
|
||||
<img src={icon} alt="help" />
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -24,6 +24,7 @@ export default function Identicon(props: Props) {
|
|||
<React.Fragment>
|
||||
<img
|
||||
src={identiconDataUrl}
|
||||
alt="Unique Address Image"
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
|
|
|
@ -12,6 +12,22 @@
|
|||
> .TogglablePassword {
|
||||
width: 100%;
|
||||
}
|
||||
&-inline {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 1rem;
|
||||
flex-wrap: wrap;
|
||||
> .input-group-header {
|
||||
width: 100%;
|
||||
}
|
||||
> .input-group-input {
|
||||
flex-grow: 1;
|
||||
width: auto;
|
||||
}
|
||||
> .Select {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
&-header {
|
||||
display: flex;
|
||||
font-size: 1rem;
|
||||
|
@ -29,6 +45,9 @@
|
|||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
}
|
||||
&-dropdown {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
&-input {
|
||||
width: 100%;
|
||||
border: 1px solid #e5ecf3;
|
||||
|
@ -40,6 +59,17 @@
|
|||
box-shadow: inset 0 1px 0 0 rgba(63, 63, 68, 0.05);
|
||||
transition: border-color 120ms, box-shadow 120ms;
|
||||
margin-bottom: 1rem;
|
||||
&.border-rad-right-0 {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
&.border-rad-left-0 {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
&-small {
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
&::placeholder {
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
@ -60,23 +90,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.input-group-inline-dropdown {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 1rem;
|
||||
flex-wrap: wrap;
|
||||
> .input-group-header {
|
||||
width: 100%;
|
||||
}
|
||||
> .input-group-input {
|
||||
flex-grow: 1;
|
||||
width: auto;
|
||||
}
|
||||
> .Select {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.Swap-dropdown {
|
||||
.Select-input {
|
||||
left: 24px;
|
||||
|
|
|
@ -1,149 +0,0 @@
|
|||
import closeIcon from 'assets/images/close.svg';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
||||
import './Modal.scss';
|
||||
|
||||
export interface IButton {
|
||||
text: string | React.ReactElement<string>;
|
||||
type?: 'default' | 'primary' | 'success' | 'info' | 'warning' | 'danger' | 'link';
|
||||
disabled?: boolean;
|
||||
onClick?(): void;
|
||||
}
|
||||
interface Props {
|
||||
isOpen?: boolean;
|
||||
title?: string | React.ReactElement<any>;
|
||||
disableButtons?: boolean;
|
||||
children: any;
|
||||
buttons?: IButton[];
|
||||
maxWidth?: number;
|
||||
handleClose?(): void;
|
||||
}
|
||||
interface ModalStyle {
|
||||
width?: string;
|
||||
maxWidth?: string;
|
||||
}
|
||||
|
||||
const Fade = ({ children, ...props }) => (
|
||||
<CSSTransition {...props} timeout={300} classNames="animate-modal">
|
||||
{children}
|
||||
</CSSTransition>
|
||||
);
|
||||
|
||||
export default class Modal extends PureComponent<Props, {}> {
|
||||
private modalContent: HTMLElement | null = null;
|
||||
|
||||
public componentDidMount() {
|
||||
this.updateBodyClass();
|
||||
document.addEventListener('keydown', this.escapeListner);
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
this.updateBodyClass();
|
||||
}
|
||||
|
||||
public updateBodyClass() {
|
||||
document.body.classList.toggle('no-scroll', !!this.props.isOpen);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.escapeListner);
|
||||
document.body.classList.remove('no-scroll');
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isOpen, title, children, buttons, handleClose, maxWidth } = this.props;
|
||||
const hasButtons = buttons && buttons.length;
|
||||
const modalStyle: ModalStyle = {};
|
||||
|
||||
if (maxWidth) {
|
||||
modalStyle.width = '100%';
|
||||
modalStyle.maxWidth = `${maxWidth}px`;
|
||||
}
|
||||
|
||||
return (
|
||||
<TransitionGroup>
|
||||
{isOpen && (
|
||||
<Fade>
|
||||
<div>
|
||||
<div className="Modalshade" />
|
||||
<div className="Modal" style={modalStyle}>
|
||||
{title && (
|
||||
<div className="Modal-header flex-wrapper">
|
||||
<h2 className="Modal-header-title">{title}</h2>
|
||||
<div className="flex-spacer" />
|
||||
<button className="Modal-header-close" onClick={handleClose}>
|
||||
<img className="Modal-header-close-icon" src={closeIcon} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="Modal-content" ref={el => (this.modalContent = el)}>
|
||||
{isOpen && children}
|
||||
<div className="Modal-fade" />
|
||||
</div>
|
||||
{hasButtons && <div className="Modal-footer">{this.renderButtons()}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</Fade>
|
||||
)}
|
||||
</TransitionGroup>
|
||||
);
|
||||
}
|
||||
|
||||
public scrollContentToTop = () => {
|
||||
if (this.modalContent) {
|
||||
this.modalContent.scrollTop = 0;
|
||||
}
|
||||
};
|
||||
|
||||
private escapeListner = (ev: KeyboardEvent) => {
|
||||
if (!this.props.isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't trigger if they hit escape while on an input
|
||||
if (ev.target) {
|
||||
if (
|
||||
(ev.target as HTMLElement).tagName === 'INPUT' ||
|
||||
(ev.target as HTMLElement).tagName === 'SELECT' ||
|
||||
(ev.target as HTMLElement).tagName === 'TEXTAREA' ||
|
||||
(ev.target as HTMLElement).isContentEditable
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.key === 'Escape' || ev.keyCode === 27) {
|
||||
if (!this.props.handleClose) {
|
||||
return;
|
||||
}
|
||||
this.props.handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
private renderButtons = () => {
|
||||
const { disableButtons, buttons } = this.props;
|
||||
if (!buttons || !buttons.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return buttons.map((btn, idx) => {
|
||||
let btnClass = 'Modal-footer-btn btn';
|
||||
|
||||
if (btn.type) {
|
||||
btnClass += ` btn-${btn.type}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={btnClass}
|
||||
onClick={btn.onClick}
|
||||
key={idx}
|
||||
disabled={disableButtons || btn.disabled}
|
||||
>
|
||||
{btn.text}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
import React, { CSSProperties } from 'react';
|
||||
import closeIcon from 'assets/images/close.svg';
|
||||
import { IButton } from 'components/ui/Modal';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
children: any;
|
||||
modalStyle?: CSSProperties;
|
||||
hasButtons?: number;
|
||||
buttons?: IButton[];
|
||||
disableButtons?: any;
|
||||
handleClose(): void;
|
||||
}
|
||||
|
||||
export default class ModalBody extends React.Component<Props> {
|
||||
private modal: HTMLElement;
|
||||
private modalContent: HTMLElement;
|
||||
private focusedElementBeforeModal: HTMLElement;
|
||||
private firstTabStop: HTMLElement;
|
||||
private lastTabStop: HTMLElement;
|
||||
|
||||
public componentDidMount() {
|
||||
this.focusedElementBeforeModal = document.activeElement as HTMLElement;
|
||||
// Find all focusable children
|
||||
const focusableElementsString =
|
||||
'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]';
|
||||
const focusableElements = Array.prototype.slice.call(
|
||||
this.modal.querySelectorAll(focusableElementsString)
|
||||
);
|
||||
|
||||
// Convert NodeList to Array
|
||||
this.firstTabStop = focusableElements[0];
|
||||
this.lastTabStop = focusableElements[focusableElements.length - 1];
|
||||
|
||||
// Focus first child
|
||||
this.firstTabStop.focus();
|
||||
|
||||
this.modal.addEventListener('keydown', this.keyDownListener);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.keyDownListener);
|
||||
}
|
||||
|
||||
public scrollContentToTop = () => {
|
||||
this.modalContent.scrollTop = 0;
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { title, children, modalStyle, hasButtons, handleClose } = this.props;
|
||||
return (
|
||||
<div
|
||||
className="Modal"
|
||||
style={modalStyle}
|
||||
role="dialog"
|
||||
aria-labelledby="Modal-header-title"
|
||||
ref={div => {
|
||||
this.modal = div as HTMLElement;
|
||||
}}
|
||||
>
|
||||
{title && (
|
||||
<div className="Modal-header flex-wrapper">
|
||||
<h2 className="Modal-header-title">{title}</h2>
|
||||
<div className="flex-spacer" />
|
||||
<button className="Modal-header-close" aria-label="Close" onClick={handleClose}>
|
||||
<img className="Modal-header-close-icon" src={closeIcon} alt="Close" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="Modal-content" ref={div => (this.modalContent = div as HTMLElement)}>
|
||||
{children}
|
||||
<div className="Modal-fade" />
|
||||
</div>
|
||||
{hasButtons && <div className="Modal-footer">{this.renderButtons()}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderButtons = () => {
|
||||
const { disableButtons, buttons } = this.props;
|
||||
if (!buttons || !buttons.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return buttons.map((btn, idx: number) => {
|
||||
let btnClass = 'Modal-footer-btn btn';
|
||||
|
||||
if (btn.type) {
|
||||
btnClass += ` btn-${btn.type}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={btnClass}
|
||||
onClick={btn.onClick}
|
||||
key={idx}
|
||||
disabled={disableButtons || btn.disabled}
|
||||
>
|
||||
{btn.text}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
private keyDownListener = (e: KeyboardEvent) => {
|
||||
// Check for TAB key press
|
||||
if (e.keyCode === 9) {
|
||||
// SHIFT + TAB
|
||||
if (e.shiftKey) {
|
||||
if (document.activeElement === this.firstTabStop) {
|
||||
e.preventDefault();
|
||||
this.lastTabStop.focus();
|
||||
}
|
||||
|
||||
// TAB
|
||||
} else {
|
||||
if (document.activeElement === this.lastTabStop) {
|
||||
e.preventDefault();
|
||||
this.firstTabStop.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for ESC key press
|
||||
if (e.keyCode === 27) {
|
||||
this.focusedElementBeforeModal.focus();
|
||||
this.props.handleClose();
|
||||
}
|
||||
};
|
||||
}
|
|
@ -13,17 +13,6 @@ $m-footer-padding: 0.5rem 2rem 1rem 2rem;
|
|||
$m-close-size: 26px;
|
||||
$m-anim-speed: 400ms;
|
||||
|
||||
.Modalshade {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(#000, 0.54);
|
||||
z-index: $zindex-modal-background;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.Modal {
|
||||
position: fixed;
|
||||
top: $m-window-padding-h;
|
||||
|
@ -45,6 +34,17 @@ $m-anim-speed: 400ms;
|
|||
box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.2), 0px 16px 24px 2px rgba(0, 0, 0, 0.14),
|
||||
0px 6px 30px 5px rgba(0, 0, 0, 0.12);
|
||||
|
||||
&-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(#000, 0.54);
|
||||
z-index: $zindex-modal-background;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&-fade {
|
||||
background: linear-gradient(to bottom, #fff0, #fff);
|
||||
position: fixed;
|
|
@ -0,0 +1,70 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
||||
import ModalBody from './ModalBody';
|
||||
import './index.scss';
|
||||
|
||||
export interface IButton {
|
||||
text: string | React.ReactElement<string>;
|
||||
type?: 'default' | 'primary' | 'success' | 'info' | 'warning' | 'danger' | 'link';
|
||||
disabled?: boolean;
|
||||
onClick?(): void;
|
||||
}
|
||||
interface Props {
|
||||
isOpen?: boolean;
|
||||
title?: string;
|
||||
disableButtons?: boolean;
|
||||
children: any;
|
||||
buttons?: IButton[];
|
||||
maxWidth?: number;
|
||||
handleClose(): void;
|
||||
}
|
||||
interface ModalStyle {
|
||||
width?: string;
|
||||
maxWidth?: string;
|
||||
}
|
||||
|
||||
const Fade = ({ children, ...props }: any) => (
|
||||
<CSSTransition {...props} timeout={300} classNames="animate-modal">
|
||||
{children}
|
||||
</CSSTransition>
|
||||
);
|
||||
|
||||
export default class Modal extends PureComponent<Props, {}> {
|
||||
public modalBody: ModalBody;
|
||||
|
||||
public componentDidUpdate(prevProps: Props) {
|
||||
if (prevProps.isOpen !== this.props.isOpen) {
|
||||
document.body.classList.toggle('no-scroll', !!this.props.isOpen);
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
document.body.classList.remove('no-scroll');
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isOpen, title, children, buttons, handleClose, maxWidth } = this.props;
|
||||
const hasButtons = buttons && buttons.length;
|
||||
const modalStyle: ModalStyle = {};
|
||||
|
||||
if (maxWidth) {
|
||||
modalStyle.width = '100%';
|
||||
modalStyle.maxWidth = `${maxWidth}px`;
|
||||
}
|
||||
|
||||
const modalBodyProps = { title, children, modalStyle, hasButtons, buttons, handleClose };
|
||||
|
||||
return (
|
||||
<TransitionGroup>
|
||||
{isOpen && (
|
||||
<Fade>
|
||||
<div>
|
||||
<div className="Modal-overlay" onClick={handleClose} />
|
||||
<ModalBody {...modalBodyProps} ref={div => (this.modalBody = div as ModalBody)} />
|
||||
</div>
|
||||
</Fade>
|
||||
)}
|
||||
</TransitionGroup>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ const OfflineSymbol = ({ offline, size }: OfflineSymbolProps) => {
|
|||
break;
|
||||
}
|
||||
|
||||
return <img src={offline ? wifiOff : wifiOn} width={width} height={height} />;
|
||||
return <img src={offline ? wifiOff : wifiOn} alt="wifi status" width={width} height={height} />;
|
||||
};
|
||||
|
||||
export default OfflineSymbol;
|
||||
|
|
|
@ -64,8 +64,8 @@ export default class DropdownComponent<T> extends PureComponent<Props<T>, State>
|
|||
overflowY: 'auto'
|
||||
};
|
||||
const searchRegex = new RegExp(search, 'gi');
|
||||
const onSearchChange = e => {
|
||||
this.setState({ search: e.target.value });
|
||||
const onSearchChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
this.setState({ search: e.currentTarget.value });
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -35,6 +35,7 @@ export default class QRCode extends React.PureComponent<Props, State> {
|
|||
return (
|
||||
<img
|
||||
src={qr}
|
||||
alt="QR Code"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
|
|
|
@ -11,7 +11,7 @@ interface SpinnerProps {
|
|||
const Spinner = ({ size = 'x1', light = false }: SpinnerProps) => {
|
||||
const color = light ? 'Spinner-light' : 'Spinner-dark';
|
||||
return (
|
||||
<svg className={`Spinner Spinner-${size} ${color}`} viewBox="0 0 50 50">
|
||||
<svg className={`Spinner Spinner-${size} ${color}`} viewBox="0 0 50 50" aria-busy="true">
|
||||
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="5" />
|
||||
</svg>
|
||||
);
|
||||
|
|
|
@ -8,9 +8,6 @@
|
|||
padding: 0.4rem 1rem;
|
||||
border-radius: 2px;
|
||||
height: 2.5rem;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
&:active,
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
|
|
|
@ -18,22 +18,22 @@ interface Props<T> {
|
|||
const ValueComp: React.SFC = (props: any) => {
|
||||
return (
|
||||
<div className={`${props.className} swap-option-wrapper`}>
|
||||
<img src={props.value.img} className="swap-option-img" />
|
||||
<img src={props.value.img} className="swap-option-img" alt={props.value.label + ' logo'} />
|
||||
<span className="swap-option-label">{props.value.label}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OptionComp: React.SFC = (props: any) => {
|
||||
const handleMouseDown = event => {
|
||||
const handleMouseDown = (event: React.MouseEvent<any>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
props.onSelect(props.option, event);
|
||||
};
|
||||
const handleMouseEnter = event => {
|
||||
const handleMouseEnter = (event: React.MouseEvent<any>) => {
|
||||
props.onFocus(props.option, event);
|
||||
};
|
||||
const handleMouseMove = event => {
|
||||
const handleMouseMove = (event: React.MouseEvent<any>) => {
|
||||
if (props.isFocused) {
|
||||
return;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ const OptionComp: React.SFC = (props: any) => {
|
|||
onMouseEnter={handleMouseEnter}
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
<img src={props.option.img} className="swap-option-img" />
|
||||
<img src={props.option.img} className="swap-option-img" alt={props.option.label + ' logo'} />
|
||||
<span className="swap-option-label">{props.option.label}</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -6,7 +6,11 @@ import { HELP_ARTICLE } from 'config';
|
|||
import OnboardSlide from './OnboardSlide';
|
||||
import onboardIconTen from 'assets/images/onboarding/slide-10.svg';
|
||||
|
||||
const FinalSlide = ({ closeModal }) => {
|
||||
interface Props {
|
||||
closeModal(): void;
|
||||
}
|
||||
|
||||
const FinalSlide: React.SFC<Props> = ({ closeModal }) => {
|
||||
const header = translate('ONBOARD_final_title');
|
||||
const subheader = translate('ONBOARD_final_subtitle');
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ interface Props {
|
|||
class OnboardModal extends React.Component<Props, State> {
|
||||
private modal: Modal | null = null;
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpen: false
|
||||
|
@ -120,7 +120,11 @@ class OnboardModal extends React.Component<Props, State> {
|
|||
|
||||
return (
|
||||
<div className="OnboardModal">
|
||||
<Modal isOpen={isOpen} buttons={buttons} ref={el => (this.modal = el)}>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
buttons={buttons}
|
||||
handleClose={() => (slideNumber === NUMBER_OF_SLIDES ? this.closeModal : null)}
|
||||
>
|
||||
<div className="OnboardModal-stepper">
|
||||
<Stepper
|
||||
steps={steps}
|
||||
|
@ -171,7 +175,7 @@ class OnboardModal extends React.Component<Props, State> {
|
|||
localStorage.setItem(ONBOARD_LOCAL_STORAGE_KEY, String(prevSlideNum));
|
||||
this.props.decrementSlide();
|
||||
if (this.modal) {
|
||||
this.modal.scrollContentToTop();
|
||||
this.modal.modalBody.scrollContentToTop();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -180,7 +184,7 @@ class OnboardModal extends React.Component<Props, State> {
|
|||
localStorage.setItem(ONBOARD_LOCAL_STORAGE_KEY, String(nextSlideNum));
|
||||
this.props.incrementSlide();
|
||||
if (this.modal) {
|
||||
this.modal.scrollContentToTop();
|
||||
this.modal.modalBody.scrollContentToTop();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ interface Props {
|
|||
export class Notifications extends React.Component<Props, {}> {
|
||||
public render() {
|
||||
return (
|
||||
<TransitionGroup className="Notifications">
|
||||
<TransitionGroup className="Notifications" aria-live="polite">
|
||||
{this.props.notifications.map(n => {
|
||||
return (
|
||||
<CSSTransition classNames="NotificationAnimation" timeout={500} key={n.id}>
|
||||
|
|
|
@ -36,7 +36,7 @@ interface State {
|
|||
inputs: {
|
||||
[key: string]: { rawData: string; parsedData: string[] | string };
|
||||
};
|
||||
outputs;
|
||||
outputs: any;
|
||||
selectedFunction: null | ContractOption;
|
||||
}
|
||||
|
||||
|
@ -124,7 +124,7 @@ class InteractExplorerClass extends Component<Props, State> {
|
|||
</div>
|
||||
);
|
||||
})}
|
||||
{selectedFunction.contract.outputs.map((output, index) => {
|
||||
{selectedFunction.contract.outputs.map((output: any, index: number) => {
|
||||
const { type, name } = output;
|
||||
const parsedName = name === '' ? index : name;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ interface StateProps {
|
|||
}
|
||||
|
||||
interface OwnProps {
|
||||
accessContract(contractAbi: string, address: string): (ev) => void;
|
||||
accessContract(contractAbi: string, address: string): (ev: any) => void;
|
||||
resetState(): void;
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ const abiJsonPlaceholder = [
|
|||
class InteractForm extends Component<Props, State> {
|
||||
private abiJsonPlaceholder = JSON.stringify(abiJsonPlaceholder, null, 0);
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
address: '',
|
||||
|
@ -140,7 +140,9 @@ class InteractForm extends Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private handleInput = name => (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
private handleInput = (name: any) => (
|
||||
ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||
) => {
|
||||
this.props.resetState();
|
||||
this.setState({ [name]: ev.currentTarget.value });
|
||||
};
|
||||
|
|
|
@ -33,10 +33,12 @@ class NameInput extends Component<Props, State> {
|
|||
return (
|
||||
<form className="ENSInput" onSubmit={this.onSubmit}>
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group input-group-inline-dropdown ENSInput-name">
|
||||
<label className="input-group input-group-inline ENSInput-name">
|
||||
<Input
|
||||
value={domainToCheck}
|
||||
className={!domainToCheck ? '' : isValidDomain ? 'is-valid' : 'is-invalid'}
|
||||
className={`${
|
||||
!domainToCheck ? '' : isValidDomain ? '' : 'invalid'
|
||||
} border-rad-right-0`}
|
||||
type="text"
|
||||
placeholder="mycrypto"
|
||||
onChange={this.onChange}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { IOwnedDomainRequest } from 'libs/ens';
|
||||
import { NewTabLink, Address } from 'components/ui';
|
||||
const lookupLink = name => `https://etherscan.io/enslookup?q=${name}`;
|
||||
const lookupLink = (name: string) => `https://etherscan.io/enslookup?q=${name}`;
|
||||
|
||||
type ChildrenProps = any;
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class CountDown extends Component<Props, State> {
|
|||
|
||||
private startCountDown = () => {
|
||||
const time = moment(this.props.initialTime);
|
||||
let intervalId;
|
||||
let intervalId: number;
|
||||
|
||||
const setTimeDisplay = () => {
|
||||
const diff = moment.duration(time.diff(moment()));
|
||||
|
@ -44,7 +44,7 @@ class CountDown extends Component<Props, State> {
|
|||
this.setState({ timeDisplay });
|
||||
};
|
||||
|
||||
intervalId = setInterval(setTimeDisplay, 1000);
|
||||
intervalId = window.setInterval(setTimeDisplay, 1000);
|
||||
setTimeDisplay();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -53,7 +53,11 @@ const CryptoWarning: React.SFC<{}> = () => (
|
|||
className="CryptoWarning-browsers-browser"
|
||||
>
|
||||
<div>
|
||||
<img className="CryptoWarning-browsers-browser-icon" src={browser.icon} />
|
||||
<img
|
||||
className="CryptoWarning-browsers-browser-icon"
|
||||
src={browser.icon}
|
||||
alt={browser.name + ' logo'}
|
||||
/>
|
||||
<div className="CryptoWarning-browsers-browser-name">{browser.name}</div>
|
||||
</div>
|
||||
</NewTabLink>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import "common/sass/variables";
|
||||
@import 'common/sass/variables';
|
||||
|
||||
.GenPaper {
|
||||
&-title {
|
||||
|
@ -6,8 +6,17 @@
|
|||
}
|
||||
|
||||
&-private {
|
||||
max-width: 700px;
|
||||
max-width: 680px;
|
||||
margin: 0 auto $space * 3;
|
||||
margin-top: 12px;
|
||||
> .input-group-header {
|
||||
margin-bottom: 1rem;
|
||||
// This selector is an exception, it targets the span returned using `translate`.
|
||||
> span {
|
||||
font-size: 2rem;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-paper,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import PrintableWallet from 'components/PrintableWallet';
|
||||
import { IV3Wallet } from 'ethereumjs-wallet';
|
||||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { stripHexPrefix } from 'libs/values';
|
||||
import './PaperWallet.scss';
|
||||
import Template from '../Template';
|
||||
|
@ -17,18 +17,20 @@ const PaperWallet: React.SFC<Props> = props => (
|
|||
<Template>
|
||||
<div className="GenPaper">
|
||||
{/* Private Key */}
|
||||
<h1 className="GenPaper-title">{translate('GEN_Label_5')}</h1>
|
||||
<label className="input-group GenPaper-private">
|
||||
{/* translateRaw isn't used here because it wont properly render the ` characters as a string of code in markdown*/}
|
||||
<h1 className="input-group-header">{translate('GEN_Label_5')}</h1>
|
||||
<Input
|
||||
className="GenPaper-private"
|
||||
value={stripHexPrefix(props.privateKey)}
|
||||
aria-label={translate('x_PrivKey', true)}
|
||||
aria-label={translateRaw('x_PrivKey')}
|
||||
aria-describedby="x_PrivKeyDesc"
|
||||
type="text"
|
||||
readOnly={true}
|
||||
/>
|
||||
</label>
|
||||
|
||||
{/* Download Paper Wallet */}
|
||||
<h1 className="GenPaper-title">{translate('x_Print')}</h1>
|
||||
<h2 className="GenPaper-title">{translate('x_Print')}</h2>
|
||||
<div className="GenPaper-paper">
|
||||
<PrintableWallet address={props.keystore.address} privateKey={props.privateKey} />
|
||||
</div>
|
||||
|
|
|
@ -28,10 +28,10 @@ export default class MnemonicWord extends React.Component<Props, State> {
|
|||
|
||||
return (
|
||||
<div className="input-group-wrapper MnemonicWord">
|
||||
<label className="input-group input-group-inline-dropdown ENSInput-name">
|
||||
<label className="input-group input-group-inline ENSInput-name">
|
||||
<span className="input-group-addon input-group-addon--transparent">{index + 1}.</span>
|
||||
<Input
|
||||
className={classnames('MnemonicWord-word-input', word === value && 'valid')}
|
||||
className={`MnemonicWord-word-input ${!isReadOnly && 'border-rad-right-0'}`}
|
||||
value={readOnly ? word : value}
|
||||
onChange={this.handleChange}
|
||||
readOnly={readOnly}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
top: 36px;
|
||||
left: 30px;
|
||||
opacity: 0.3;
|
||||
outline: none;
|
||||
color: $text-color;
|
||||
|
||||
@media (max-width: $screen-sm) {
|
||||
|
|
|
@ -46,7 +46,7 @@ const WalletTypes: React.SFC<{}> = () => {
|
|||
|
||||
<div className="WalletTypes-types row">
|
||||
<div className="col-md-1" />
|
||||
{Object.keys(typeInfo).map(type => (
|
||||
{Object.keys(typeInfo).map((type: keyof typeof typeInfo) => (
|
||||
<div key={type} className="WalletType col-md-5">
|
||||
<h2 className="WalletType-title">{translate(typeInfo[type].name)}</h2>
|
||||
<ul className="WalletType-features">
|
||||
|
|
|
@ -44,7 +44,8 @@ interface ActionProps {
|
|||
|
||||
type Props = OwnProps & StateProps & ActionProps;
|
||||
|
||||
const isValidAmount = decimal => amount => validNumber(+amount) && validDecimal(amount, decimal);
|
||||
const isValidAmount = (decimal: number) => (amount: string) =>
|
||||
validNumber(+amount) && validDecimal(amount, decimal);
|
||||
|
||||
class RequestPayment extends React.Component<Props, {}> {
|
||||
public state = {
|
||||
|
@ -145,7 +146,7 @@ class RequestPayment extends React.Component<Props, {}> {
|
|||
private generateEIP681String(
|
||||
currentTo: string,
|
||||
tokenContractAddress: string,
|
||||
currentValue,
|
||||
currentValue: { raw: string; value: BN | null },
|
||||
gasLimit: { raw: string; value: BN | null },
|
||||
unit: string,
|
||||
decimal: number,
|
||||
|
@ -162,7 +163,11 @@ class RequestPayment extends React.Component<Props, {}> {
|
|||
return '';
|
||||
}
|
||||
|
||||
if (this.props.isNetworkUnit) {
|
||||
const currentValueIsEther = (
|
||||
_: AppState['transaction']['fields']['value'] | AppState['transaction']['meta']['tokenTo']
|
||||
): _ is AppState['transaction']['fields']['value'] => this.props.isNetworkUnit;
|
||||
|
||||
if (currentValueIsEther(currentValue)) {
|
||||
return buildEIP681EtherRequest(currentTo, chainId, currentValue);
|
||||
} else {
|
||||
return buildEIP681TokenRequest(
|
||||
|
|
|
@ -30,8 +30,8 @@ export interface ActionProps {
|
|||
|
||||
interface State {
|
||||
disabled: boolean;
|
||||
origin: SwapInput;
|
||||
destination: SwapInput;
|
||||
origin: SwapOpt;
|
||||
destination: SwapOpt;
|
||||
originKindOptions: any[];
|
||||
destinationKindOptions: any[];
|
||||
originErr: string;
|
||||
|
@ -49,7 +49,7 @@ interface SwapOpt extends SwapInput {
|
|||
}
|
||||
|
||||
export default class CurrencySwap extends PureComponent<Props, State> {
|
||||
public state = {
|
||||
public state: State = {
|
||||
disabled: true,
|
||||
origin: {
|
||||
label: 'BTC',
|
||||
|
@ -57,14 +57,14 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
status: 'available',
|
||||
image: 'https://shapeshift.io/images/coins/bitcoin.png',
|
||||
amount: NaN
|
||||
} as SwapOpt,
|
||||
},
|
||||
destination: {
|
||||
label: 'ETH',
|
||||
value: 'Ether',
|
||||
status: 'available',
|
||||
image: 'https://shapeshift.io/images/coins/ether.png',
|
||||
amount: NaN
|
||||
} as SwapOpt,
|
||||
},
|
||||
originKindOptions: [],
|
||||
destinationKindOptions: [],
|
||||
originErr: '',
|
||||
|
@ -151,7 +151,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
return merge(shapeshiftRates, bityRates);
|
||||
};
|
||||
|
||||
public getMinMax = (originKind: WhitelistedCoins, destinationKind) => {
|
||||
public getMinMax = (originKind: WhitelistedCoins, destinationKind: string) => {
|
||||
let min;
|
||||
let max;
|
||||
|
||||
|
@ -176,7 +176,11 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
return { min, max };
|
||||
};
|
||||
|
||||
public isMinMaxValid = (originAmount: number, originKind: WhitelistedCoins, destinationKind) => {
|
||||
public isMinMaxValid = (
|
||||
originAmount: number,
|
||||
originKind: WhitelistedCoins,
|
||||
destinationKind: string
|
||||
) => {
|
||||
const rate = this.getMinMax(originKind, destinationKind);
|
||||
const higherThanMin = originAmount >= rate.min;
|
||||
const lowerThanMax = originAmount <= rate.max;
|
||||
|
@ -201,7 +205,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
this.debouncedCreateErrString(origin, destination, showError);
|
||||
}
|
||||
|
||||
public setErrorMessages = (originErr, destinationErr) => {
|
||||
public setErrorMessages = (originErr: string, destinationErr: string) => {
|
||||
this.setState({
|
||||
originErr,
|
||||
destinationErr
|
||||
|
@ -269,15 +273,17 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
: this.updateDestinationAmount(origin, destination, amount);
|
||||
};
|
||||
|
||||
public onChangeOriginKind = newOption => {
|
||||
public onChangeOriginKind = (newOption: any) => {
|
||||
const { origin, destination, destinationKindOptions } = this.state;
|
||||
const { options, initSwap } = this.props;
|
||||
|
||||
const newOrigin = { ...origin, label: newOption.label, value: newOption.value, amount: '' };
|
||||
const newOrigin = { ...origin, label: newOption.label, value: newOption.value, amount: 0 };
|
||||
const newDest = {
|
||||
label: newOption.label === destination.label ? origin.label : destination.label,
|
||||
value: newOption.value === destination.value ? origin.value : destination.value,
|
||||
amount: ''
|
||||
amount: 0,
|
||||
status: '',
|
||||
image: ''
|
||||
};
|
||||
|
||||
this.setState({
|
||||
|
@ -292,16 +298,16 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
initSwap({ origin: newOrigin, destination: newDest });
|
||||
};
|
||||
|
||||
public onChangeDestinationKind = newOption => {
|
||||
public onChangeDestinationKind = (newOption: any) => {
|
||||
const { initSwap } = this.props;
|
||||
const { origin, destination } = this.state;
|
||||
|
||||
const newOrigin = {
|
||||
...origin,
|
||||
amount: ''
|
||||
amount: 0
|
||||
};
|
||||
|
||||
const newDest = { ...destination, label: newOption.label, value: newOption.value, amount: '' };
|
||||
const newDest = { ...destination, label: newOption.label, value: newOption.value, amount: 0 };
|
||||
this.setState({
|
||||
origin: newOrigin,
|
||||
destination: newDest
|
||||
|
@ -335,11 +341,11 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
<div className="flex-spacer" />
|
||||
<div className="input-group-wrapper">
|
||||
<div className="input-group-header">Deposit</div>
|
||||
<label className="input-group input-group-inline-dropdown">
|
||||
<label className="input-group input-group-inline">
|
||||
<Input
|
||||
id="origin-swap-input"
|
||||
className={`input-group-input ${
|
||||
String(origin.amount) !== '' &&
|
||||
!origin.amount &&
|
||||
this.isMinMaxValid(origin.amount, origin.label, destination.label)
|
||||
? ''
|
||||
: 'invalid'
|
||||
|
@ -359,12 +365,12 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
</div>
|
||||
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group input-group-inline-dropdown">
|
||||
<label className="input-group input-group-inline">
|
||||
<div className="input-group-header">Recieve</div>
|
||||
<Input
|
||||
id="destination-swap-input"
|
||||
className={`${
|
||||
String(destination.amount) !== '' &&
|
||||
!destination.amount &&
|
||||
this.isMinMaxValid(origin.amount, origin.label, destination.label)
|
||||
? ''
|
||||
: 'invalid'
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
@import "common/sass/mixins";
|
||||
|
||||
.SwapRates {
|
||||
margin-bottom: $space;
|
||||
|
||||
&-title {
|
||||
margin-top: 0;
|
||||
text-align: center;
|
||||
|
|
|
@ -24,6 +24,7 @@ import { getOffline } from 'selectors/config';
|
|||
import Rates from './Rates';
|
||||
import { AppState } from 'reducers';
|
||||
import './CurrentRates.scss';
|
||||
import { Optional } from 'utils/types';
|
||||
|
||||
interface StateProps {
|
||||
isOffline: boolean;
|
||||
|
@ -40,7 +41,7 @@ interface ActionProps {
|
|||
type Props = StateProps & ActionProps;
|
||||
|
||||
class CurrentRates extends PureComponent<Props> {
|
||||
private shapeShiftRateCache = null;
|
||||
private shapeShiftRateCache: any = null;
|
||||
|
||||
public componentDidMount() {
|
||||
if (!this.props.isOffline) {
|
||||
|
@ -79,7 +80,7 @@ class CurrentRates extends PureComponent<Props> {
|
|||
|
||||
public buildSSPairs = (shapeshiftRates: NormalizedShapeshiftRates, n: number = 4) => {
|
||||
const pairCollection = times(n, () => this.getRandomSSPairData(shapeshiftRates));
|
||||
const byId = pairCollection.reduce((acc, cur) => {
|
||||
const byId = pairCollection.reduce<{ [id: string]: NormalizedShapeshiftRate }>((acc, cur) => {
|
||||
acc[cur.id] = cur;
|
||||
return acc;
|
||||
}, {});
|
||||
|
@ -90,7 +91,7 @@ class CurrentRates extends PureComponent<Props> {
|
|||
};
|
||||
};
|
||||
|
||||
public isValidRates = rates => {
|
||||
public isValidRates = (rates: Optional<NormalizedShapeshiftRates>) => {
|
||||
return rates && rates.allIds && rates.allIds.length > 0;
|
||||
};
|
||||
|
||||
|
@ -118,7 +119,7 @@ class CurrentRates extends PureComponent<Props> {
|
|||
return fixedRates;
|
||||
};
|
||||
|
||||
public swapEl = (providerURL, providerLogo, children) => {
|
||||
public swapEl = (providerURL: string, providerLogo: string, children: any) => {
|
||||
return (
|
||||
<article className="SwapRates">
|
||||
<h3 className="SwapRates-title">{translate('SWAP_rates')}</h3>
|
||||
|
@ -131,7 +132,7 @@ class CurrentRates extends PureComponent<Props> {
|
|||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img src={providerLogo} width={120} height={49} />
|
||||
<img src={providerLogo} width={120} height={49} alt="Shapeshift Logo" />
|
||||
</a>
|
||||
</section>
|
||||
</article>
|
||||
|
|
|
@ -5,7 +5,8 @@ import {
|
|||
TStartPollShapeshiftOrderStatus,
|
||||
TStopOrderTimerSwap,
|
||||
TStopPollBityOrderStatus,
|
||||
TStopPollShapeshiftOrderStatus
|
||||
TStopPollShapeshiftOrderStatus,
|
||||
TStartOrderTimerSwap
|
||||
} from 'actions/swap';
|
||||
import { SwapInput } from 'reducers/swap/types';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
@ -28,6 +29,7 @@ interface ReduxStateProps {
|
|||
}
|
||||
|
||||
interface ReduxActionProps {
|
||||
startOrderTimerSwap: TStartOrderTimerSwap;
|
||||
restartSwap: TRestartSwap;
|
||||
startPollBityOrderStatus: TStartPollBityOrderStatus;
|
||||
stopPollBityOrderStatus: TStopPollBityOrderStatus;
|
||||
|
@ -45,6 +47,7 @@ export default class PartThree extends PureComponent<ReduxActionProps & ReduxSta
|
|||
} else {
|
||||
this.props.startPollBityOrderStatus();
|
||||
}
|
||||
this.props.startOrderTimerSwap();
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
|
|
|
@ -47,7 +47,7 @@ interface State {
|
|||
}
|
||||
|
||||
export default class Rates extends Component<Props, State> {
|
||||
public state = {
|
||||
public state: State = {
|
||||
pairs: {}
|
||||
};
|
||||
|
||||
|
@ -72,7 +72,7 @@ export default class Rates extends Component<Props, State> {
|
|||
public getPairs = () => {
|
||||
const { rates } = this.props;
|
||||
const { allIds } = rates;
|
||||
return allIds.reduce((acc, cur) => {
|
||||
return allIds.reduce<{ [id: string]: 1 }>((acc, cur) => {
|
||||
acc[cur] = 1;
|
||||
return acc;
|
||||
}, {});
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
@import 'common/sass/variables';
|
||||
|
||||
.ShapeshiftBanner {
|
||||
margin: $space 0;
|
||||
text-align: center;
|
||||
|
||||
&-banner {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
padding: $space-sm $space;
|
||||
background-color: #3a526d;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 3px 8px 0 rgba(0,0,0,0.1), inset 0 0 3px 0 rgba(0,0,0,0.1);
|
||||
p {
|
||||
display: inline-block;
|
||||
color: white;
|
||||
vertical-align: middle;
|
||||
margin-bottom: 0px;
|
||||
|
||||
b {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
img {
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
box-sizing: content-box;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import React from 'react';
|
||||
import './ShapeshiftBanner.scss';
|
||||
import shapeshiftSvg from 'assets/images/logo-shapeshift.svg';
|
||||
|
||||
const ShapeshiftBanner: React.SFC<{}> = () => (
|
||||
<div className="ShapeshiftBanner">
|
||||
<div className="ShapeshiftBanner-banner">
|
||||
<p>
|
||||
<b className="ShapeshiftBanner-banner-new">New Feature:</b>
|
||||
Exchange coins & tokens with
|
||||
</p>
|
||||
<img src={shapeshiftSvg} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ShapeshiftBanner;
|
|
@ -1,7 +1,7 @@
|
|||
import { RestartSwapAction } from 'actions/swap';
|
||||
import bityLogo from 'assets/images/logo-bity.svg';
|
||||
import shapeshiftLogo from 'assets/images/shapeshift-dark.svg';
|
||||
import { bityReferralURL } from 'config';
|
||||
import { shapeshiftReferralURL, bitboxReferralURL } from 'config';
|
||||
import React, { PureComponent } from 'react';
|
||||
import translate from 'translations';
|
||||
import './SwapInfoHeader.scss';
|
||||
|
@ -29,11 +29,11 @@ export default class SwapInfoHeaderTitle extends PureComponent<SwapInfoHeaderTit
|
|||
<div className="col-xs-3">
|
||||
<a
|
||||
className="SwapInfo-top-logo"
|
||||
href={bityReferralURL}
|
||||
href={provider === 'shapeshift' ? shapeshiftReferralURL : bitboxReferralURL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img className="SwapInfo-top-logo-img" src={logoToRender} />
|
||||
<img className="SwapInfo-top-logo-img" src={logoToRender} alt={provider + ' logo'} />
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -47,7 +47,6 @@ import PartThree from './components/PartThree';
|
|||
import SupportFooter from './components/SupportFooter';
|
||||
import ReceivingAddress from './components/ReceivingAddress';
|
||||
import SwapInfoHeader from './components/SwapInfoHeader';
|
||||
import ShapeshiftBanner from './components/ShapeshiftBanner';
|
||||
import TabSection from 'containers/TabSection';
|
||||
import { merge } from 'lodash';
|
||||
import { RouteNotFound } from 'components/RouteNotFound';
|
||||
|
@ -219,7 +218,6 @@ class Swap extends Component<ReduxActionProps & ReduxStateProps & RouteComponent
|
|||
render={() => (
|
||||
<React.Fragment>
|
||||
{step === 1 && <CurrentRates />}
|
||||
{step === 1 && <ShapeshiftBanner />}
|
||||
{(step === 2 || step === 3) && <SwapInfoHeader {...SwapInfoHeaderProps} />}
|
||||
<main className="Tab-content-pane">
|
||||
{step === 1 && <CurrencySwap {...CurrencySwapProps} />}
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import abi from 'ethereumjs-abi';
|
||||
import { toChecksumAddress, addHexPrefix } from 'ethereumjs-util';
|
||||
import BN from 'bn.js';
|
||||
import { FuncParams, FunctionOutputMappings, Output, Input } from './types';
|
||||
import {
|
||||
FuncParams,
|
||||
FunctionOutputMappings,
|
||||
Output,
|
||||
Input,
|
||||
ITypeMapping,
|
||||
ISuppliedArgs
|
||||
} from './types';
|
||||
|
||||
export default class AbiFunction {
|
||||
public constant: boolean;
|
||||
|
@ -53,7 +60,6 @@ export default class AbiFunction {
|
|||
|
||||
// Convert argdata to a hex buffer for ethereumjs-abi
|
||||
const argBuffer = new Buffer(argString, 'hex');
|
||||
|
||||
// Decode!
|
||||
const argArr = abi.rawDecode(this.outputTypes, argBuffer);
|
||||
|
||||
|
@ -80,13 +86,13 @@ export default class AbiFunction {
|
|||
}
|
||||
|
||||
private parsePostDecodedValue = (type: string, value: any) => {
|
||||
const valueMapping = {
|
||||
address: val => toChecksumAddress(val.toString(16))
|
||||
const valueMapping: ITypeMapping = {
|
||||
address: (val: any) => toChecksumAddress(val.toString(16))
|
||||
};
|
||||
|
||||
return valueMapping[type]
|
||||
? valueMapping[type](value)
|
||||
: BN.isBN(value) ? value.toString() : value;
|
||||
const mapppedType = valueMapping[type];
|
||||
|
||||
return mapppedType ? mapppedType(value) : BN.isBN(value) ? value.toString() : value;
|
||||
};
|
||||
|
||||
private parsePreEncodedValue = (_: string, value: any) =>
|
||||
|
@ -95,7 +101,7 @@ export default class AbiFunction {
|
|||
private makeFuncParams = () =>
|
||||
this.inputs.reduce((accumulator, currInput) => {
|
||||
const { name, type } = currInput;
|
||||
const inputHandler = inputToParse =>
|
||||
const inputHandler = (inputToParse: any) =>
|
||||
//TODO: introduce typechecking and typecasting mapping for inputs
|
||||
({ name, type, value: this.parsePreEncodedValue(type, inputToParse) });
|
||||
|
||||
|
@ -110,7 +116,7 @@ export default class AbiFunction {
|
|||
return addHexPrefix(`${this.methodSelector}${encodedArgs}`);
|
||||
};
|
||||
|
||||
private processSuppliedArgs = (suppliedArgs: object) =>
|
||||
private processSuppliedArgs = (suppliedArgs: ISuppliedArgs) =>
|
||||
this.inputNames.map(name => {
|
||||
const type = this.funcParams[name].type;
|
||||
//TODO: parse args based on type
|
||||
|
|
|
@ -6,6 +6,11 @@ const ABIFUNC_METHOD_NAMES = ['encodeInput', 'decodeInput', 'decodeOutput'];
|
|||
enum ABIMethodTypes {
|
||||
FUNC = 'function'
|
||||
}
|
||||
|
||||
export default interface Contract {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type TContract = typeof Contract;
|
||||
|
||||
export default class Contract {
|
||||
|
@ -22,14 +27,14 @@ export default class Contract {
|
|||
return isFunc ? { ...accu, [currContractMethodName]: currContractMethod } : accu;
|
||||
}, {});
|
||||
|
||||
public abi;
|
||||
public abi: any;
|
||||
|
||||
constructor(abi, outputMappings: ContractOutputMappings = {}) {
|
||||
constructor(abi: any, outputMappings: ContractOutputMappings = {}) {
|
||||
this.assignABIFuncs(abi, outputMappings);
|
||||
}
|
||||
|
||||
private assignABIFuncs = (abi, outputMappings: ContractOutputMappings) => {
|
||||
abi.forEach(currentABIMethod => {
|
||||
private assignABIFuncs = (abi: any, outputMappings: ContractOutputMappings) => {
|
||||
abi.forEach((currentABIMethod: any) => {
|
||||
const { name, type } = currentABIMethod;
|
||||
if (type === ABIMethodTypes.FUNC) {
|
||||
//only grab the functions we need
|
||||
|
|
|
@ -36,3 +36,11 @@ export interface FuncParams {
|
|||
processInput(value: any): any;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ITypeMapping {
|
||||
[type: string]: (value: any) => any;
|
||||
}
|
||||
|
||||
export interface ISuppliedArgs {
|
||||
[argumentName: string]: any;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { mnemonicToSeed, validateMnemonic } from 'bip39';
|
||||
import { createDecipheriv, createHash } from 'crypto';
|
||||
import { privateToAddress } from 'ethereumjs-util';
|
||||
import { fromMasterSeed } from 'hdkey';
|
||||
import HDkey from 'hdkey';
|
||||
import { stripHexPrefixAndLower } from 'libs/values';
|
||||
|
||||
// adapted from https://github.com/kvhnuke/etherwallet/blob/de536ffebb4f2d1af892a32697e89d1a0d906b01/app/scripts/myetherwallet.js#L230
|
||||
|
@ -37,7 +37,7 @@ export function decodeCryptojsSalt(input: string): any {
|
|||
export function evp_kdf(data: Buffer, salt: Buffer, opts: any) {
|
||||
// A single EVP iteration, returns `D_i`, where block equlas to `D_(i-1)`
|
||||
|
||||
function iter(block) {
|
||||
function iter(block: Buffer) {
|
||||
let hash = createHash(opts.digest || 'md5');
|
||||
hash.update(block);
|
||||
hash.update(data);
|
||||
|
@ -83,7 +83,7 @@ export function decryptMnemonicToPrivKey(
|
|||
}
|
||||
|
||||
const seed = mnemonicToSeed(phrase, pass);
|
||||
const derived = fromMasterSeed(seed).derive(path);
|
||||
const derived = HDkey.fromMasterSeed(seed).derive(path);
|
||||
const dPrivKey = derived.privateKey;
|
||||
const dAddress = privateToAddress(dPrivKey).toString('hex');
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import uts46 from 'idna-uts46';
|
||||
import ethUtil from 'ethereumjs-util';
|
||||
|
||||
export function normalise(name: string) {
|
||||
export function normalise(name: string): string {
|
||||
try {
|
||||
return uts46.toUnicode(name, { useStd3ASCII: true, transitional: false });
|
||||
} catch (e) {
|
||||
|
@ -65,7 +65,7 @@ export enum NameState {
|
|||
NotYetAvailable = '5'
|
||||
}
|
||||
|
||||
export const modeStrMap = name => [
|
||||
export const modeStrMap = (name: string) => [
|
||||
`${name} is available and the auction hasn’t started`,
|
||||
`${name} is available and the auction has been started`,
|
||||
`${name} is taken and currently owned by someone`,
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
GetCurrentBlockRequest
|
||||
} from './types';
|
||||
import { Token } from 'types/network';
|
||||
import { IHexStrWeb3Transaction, IHexStrTransaction } from 'libs/transaction';
|
||||
|
||||
export default class EtherscanRequests extends RPCRequests {
|
||||
public sendRawTx(signedTx: string): SendRawTxRequest {
|
||||
|
@ -21,7 +22,7 @@ export default class EtherscanRequests extends RPCRequests {
|
|||
};
|
||||
}
|
||||
|
||||
public estimateGas(transaction): EstimateGasRequest {
|
||||
public estimateGas(transaction: IHexStrWeb3Transaction): EstimateGasRequest {
|
||||
return {
|
||||
module: 'proxy',
|
||||
action: 'eth_estimateGas',
|
||||
|
@ -41,7 +42,7 @@ export default class EtherscanRequests extends RPCRequests {
|
|||
};
|
||||
}
|
||||
|
||||
public ethCall(transaction): CallRequest {
|
||||
public ethCall(transaction: Pick<IHexStrTransaction, 'to' | 'data'>): CallRequest {
|
||||
return {
|
||||
module: 'proxy',
|
||||
action: 'eth_call',
|
||||
|
|
|
@ -41,10 +41,10 @@ export default class RPCClient {
|
|||
}).then(r => r.json());
|
||||
};
|
||||
|
||||
private createHeaders = headerObject => {
|
||||
private createHeaders = (headerObject: HeadersInit) => {
|
||||
const headers = new Headers();
|
||||
Object.keys(headerObject).forEach(name => {
|
||||
headers.append(name, headerObject[name]);
|
||||
Object.entries(headerObject).forEach(([name, value]) => {
|
||||
headers.append(name, value);
|
||||
});
|
||||
return headers;
|
||||
};
|
||||
|
|
|
@ -2,7 +2,6 @@ import BN from 'bn.js';
|
|||
import { IHexStrTransaction } from 'libs/transaction';
|
||||
import { Wei, TokenValue } from 'libs/units';
|
||||
import { stripHexPrefix } from 'libs/values';
|
||||
import { hexToNumber } from 'utils/formatters';
|
||||
import { INode, TxObj, TransactionData, TransactionReceipt } from '../INode';
|
||||
import RPCClient from './client';
|
||||
import RPCRequests from './requests';
|
||||
|
@ -18,6 +17,7 @@ import {
|
|||
isValidRawTxApi
|
||||
} from 'libs/validators';
|
||||
import { Token } from 'types/network';
|
||||
import { hexToNumber } from 'utils/formatters';
|
||||
|
||||
export default class RpcNode implements INode {
|
||||
public client: RPCClient;
|
||||
|
|
|
@ -13,14 +13,13 @@ import {
|
|||
import { hexEncodeData } from './utils';
|
||||
import { TxObj } from '../INode';
|
||||
import { Token } from 'types/network';
|
||||
import { IHexStrTransaction } from 'libs/transaction';
|
||||
|
||||
export default class RPCRequests {
|
||||
public getNetVersion() {
|
||||
return { method: 'net_version' };
|
||||
}
|
||||
|
||||
/* TODO: Fix `| any` on all of these */
|
||||
|
||||
public sendRawTx(signedTx: string): SendRawTxRequest | any {
|
||||
return {
|
||||
method: 'eth_sendRawTransaction',
|
||||
|
@ -28,7 +27,7 @@ export default class RPCRequests {
|
|||
};
|
||||
}
|
||||
|
||||
public estimateGas(transaction): EstimateGasRequest | any {
|
||||
public estimateGas(transaction: Partial<IHexStrTransaction>): EstimateGasRequest | any {
|
||||
return {
|
||||
method: 'eth_estimateGas',
|
||||
params: [transaction]
|
||||
|
|
|
@ -27,7 +27,7 @@ export interface GetAccountsRequest extends RPCRequestBase {
|
|||
method: 'eth_accounts';
|
||||
}
|
||||
|
||||
type TWeb3ProviderCallback = (error, result: JsonRpcResponse | JsonRpcResponse[]) => any;
|
||||
type TWeb3ProviderCallback = (error: string, result: JsonRpcResponse | JsonRpcResponse[]) => any;
|
||||
type TSendAsync = (request: RPCRequest | any, callback: TWeb3ProviderCallback) => void;
|
||||
|
||||
export interface IWeb3Provider {
|
||||
|
|
|
@ -10,9 +10,9 @@ export interface ITransaction {
|
|||
gasPrice: Wei;
|
||||
nonce: BN;
|
||||
chainId: number;
|
||||
v;
|
||||
r;
|
||||
s;
|
||||
v: Buffer;
|
||||
r: Buffer;
|
||||
s: Buffer;
|
||||
}
|
||||
|
||||
export interface IHexStrTransaction {
|
||||
|
|
|
@ -6,6 +6,7 @@ import { IFullWallet } from 'libs/wallet';
|
|||
import { translateRaw } from 'translations';
|
||||
import { ITransaction, IHexStrTransaction } from '../typings';
|
||||
import { hexEncodeQuantity, hexEncodeData } from 'libs/nodes/rpc/utils';
|
||||
import { TransactionFieldValues } from 'selectors/transaction/helpers';
|
||||
|
||||
// we dont include the signature paramaters because web3 transactions are unsigned
|
||||
const computeIndexingHash = (tx: Buffer) => bufferToHex(makeTransaction(tx).hash(false));
|
||||
|
@ -75,7 +76,13 @@ const validAddress = (t: ITransaction) => {
|
|||
};
|
||||
|
||||
const makeTransaction = (
|
||||
t: Partial<Tx> | Partial<ITransaction> | Partial<IHexStrTransaction> | Buffer | string
|
||||
t:
|
||||
| Partial<Tx>
|
||||
| Partial<ITransaction>
|
||||
| Partial<IHexStrTransaction>
|
||||
| Buffer
|
||||
| string
|
||||
| TransactionFieldValues
|
||||
) => new Tx(t);
|
||||
|
||||
//TODO: check that addresses are always checksummed
|
||||
|
|
|
@ -184,7 +184,7 @@ export const isValidNonce = (value: string): boolean => {
|
|||
return valid;
|
||||
};
|
||||
|
||||
function isValidResult(response: JsonRpcResponse, schemaFormat): boolean {
|
||||
function isValidResult(response: JsonRpcResponse, schemaFormat: typeof schema.RpcNode): boolean {
|
||||
return v.validate(response, schemaFormat).valid;
|
||||
}
|
||||
|
||||
|
@ -204,9 +204,25 @@ function formatErrors(response: JsonRpcResponse, apiType: string) {
|
|||
return `Invalid ${apiType} Error`;
|
||||
}
|
||||
|
||||
enum API_NAME {
|
||||
Get_Balance = 'Get Balance',
|
||||
Estimate_Gas = 'Estimate Gas',
|
||||
Call_Request = 'Call Request',
|
||||
Token_Balance = 'Token Balance',
|
||||
Transaction_Count = 'Transaction Count',
|
||||
Current_Block = 'Current Block',
|
||||
Raw_Tx = 'Raw Tx',
|
||||
Send_Transaction = 'Send Transaction',
|
||||
Sign_Message = 'Sign Message',
|
||||
Get_Accounts = 'Get Accounts',
|
||||
Net_Version = 'Net Version',
|
||||
Transaction_By_Hash = 'Transaction By Hash',
|
||||
Transaction_Receipt = 'Transaction Receipt'
|
||||
}
|
||||
|
||||
const isValidEthCall = (response: JsonRpcResponse, schemaType: typeof schema.RpcNode) => (
|
||||
apiName,
|
||||
cb?
|
||||
apiName: API_NAME,
|
||||
cb?: (res: JsonRpcResponse) => any
|
||||
) => {
|
||||
if (!isValidResult(response, schemaType)) {
|
||||
if (cb) {
|
||||
|
@ -218,45 +234,44 @@ const isValidEthCall = (response: JsonRpcResponse, schemaType: typeof schema.Rpc
|
|||
};
|
||||
|
||||
export const isValidGetBalance = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Get Balance');
|
||||
isValidEthCall(response, schema.RpcNode)(API_NAME.Get_Balance);
|
||||
|
||||
export const isValidEstimateGas = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Estimate Gas');
|
||||
isValidEthCall(response, schema.RpcNode)(API_NAME.Estimate_Gas);
|
||||
|
||||
export const isValidCallRequest = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Call Request');
|
||||
isValidEthCall(response, schema.RpcNode)(API_NAME.Call_Request);
|
||||
|
||||
export const isValidTokenBalance = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Token Balance', () => ({
|
||||
isValidEthCall(response, schema.RpcNode)(API_NAME.Token_Balance, () => ({
|
||||
result: 'Failed'
|
||||
}));
|
||||
|
||||
export const isValidTransactionCount = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Transaction Count');
|
||||
isValidEthCall(response, schema.RpcNode)(API_NAME.Transaction_Count);
|
||||
|
||||
export const isValidTransactionByHash = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Transaction By Hash');
|
||||
isValidEthCall(response, schema.RpcNode)(API_NAME.Transaction_By_Hash);
|
||||
|
||||
export const isValidTransactionReceipt = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Transaction Receipt');
|
||||
isValidEthCall(response, schema.RpcNode)(API_NAME.Transaction_Receipt);
|
||||
|
||||
export const isValidCurrentBlock = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Current Block');
|
||||
isValidEthCall(response, schema.RpcNode)(API_NAME.Current_Block);
|
||||
|
||||
export const isValidRawTxApi = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Raw Tx');
|
||||
isValidEthCall(response, schema.RpcNode)(API_NAME.Raw_Tx);
|
||||
|
||||
export const isValidSendTransaction = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Send Transaction');
|
||||
isValidEthCall(response, schema.RpcNode)(API_NAME.Send_Transaction);
|
||||
|
||||
export const isValidSignMessage = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Sign Message');
|
||||
isValidEthCall(response, schema.RpcNode)(API_NAME.Sign_Message);
|
||||
|
||||
export const isValidGetAccounts = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Get Accounts');
|
||||
isValidEthCall(response, schema.RpcNode)(API_NAME.Get_Accounts);
|
||||
|
||||
export const isValidGetNetVersion = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Net Version');
|
||||
|
||||
isValidEthCall(response, schema.RpcNode)(API_NAME.Net_Version);
|
||||
export const isValidTxHash = (hash: string) =>
|
||||
hash.substring(0, 2) === '0x' && hash.length === 66 && isValidHex(hash);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Wei, toTokenBase } from 'libs/units';
|
||||
import { addHexPrefix } from 'ethereumjs-util';
|
||||
import BN from 'bn.js';
|
||||
import { AppState } from 'reducers';
|
||||
|
||||
export function stripHexPrefix(value: string) {
|
||||
return value.replace('0x', '');
|
||||
|
@ -26,16 +26,16 @@ export function sanitizeHex(hex: string) {
|
|||
export const buildEIP681EtherRequest = (
|
||||
recipientAddr: string,
|
||||
chainId: number,
|
||||
etherValue: { raw: string; value: Wei | '' }
|
||||
etherValue: AppState['transaction']['fields']['value']
|
||||
) => `ethereum:${recipientAddr}${chainId !== 1 ? `@${chainId}` : ''}?value=${etherValue.raw}e18`;
|
||||
|
||||
export const buildEIP681TokenRequest = (
|
||||
recipientAddr: string,
|
||||
contractAddr: string,
|
||||
chainId: number,
|
||||
tokenValue: { raw: string; value: Wei | '' },
|
||||
tokenValue: AppState['transaction']['meta']['tokenTo'],
|
||||
decimal: number,
|
||||
gasLimit: { raw: string; value: BN | null }
|
||||
gasLimit: AppState['transaction']['fields']['gasLimit']
|
||||
) =>
|
||||
`ethereum:${contractAddr}${
|
||||
chainId !== 1 ? `@${chainId}` : ''
|
||||
|
|
|
@ -7,11 +7,11 @@ import { IFullWallet } from '../IWallet';
|
|||
import { translateRaw } from 'translations';
|
||||
|
||||
export class LedgerWallet extends DeterministicWallet implements IFullWallet {
|
||||
private ethApp: any;
|
||||
private ethApp: ledger.eth;
|
||||
|
||||
constructor(address: string, dPath: string, index: number) {
|
||||
super(address, dPath, index);
|
||||
ledger.comm_u2f.create_async().then(comm => {
|
||||
ledger.comm_u2f.create_async().then((comm: any) => {
|
||||
this.ethApp = new ledger.eth(comm);
|
||||
});
|
||||
}
|
||||
|
@ -50,9 +50,12 @@ export class LedgerWallet extends DeterministicWallet implements IFullWallet {
|
|||
const msgHex = Buffer.from(msg).toString('hex');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.ethApp.signPersonalMessage_async(this.getPath(), msgHex, async (signed, error) => {
|
||||
this.ethApp.signPersonalMessage_async(
|
||||
this.getPath(),
|
||||
msgHex,
|
||||
async (signed: any, error: any) => {
|
||||
if (error) {
|
||||
return reject(this.ethApp.getError(error));
|
||||
return reject((this.ethApp as any).getError(error));
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -61,7 +64,8 @@ export class LedgerWallet extends DeterministicWallet implements IFullWallet {
|
|||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ export class TrezorWallet extends DeterministicWallet implements IFullWallet {
|
|||
cleanedTx.data,
|
||||
chainId,
|
||||
// Callback
|
||||
result => {
|
||||
(result: any) => {
|
||||
if (!result.success) {
|
||||
return reject(Error(result.error));
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ export class TrezorWallet extends DeterministicWallet implements IFullWallet {
|
|||
return new Promise((resolve, reject) => {
|
||||
(TrezorConnect as any).ethereumGetAddress(
|
||||
dPath + '/' + index,
|
||||
res => {
|
||||
(res: any) => {
|
||||
if (res.error) {
|
||||
reject(res.error);
|
||||
} else {
|
||||
|
|
|
@ -15,9 +15,9 @@ const INITIAL_STATE: State = {
|
|||
gasPriceStatus: null
|
||||
};
|
||||
|
||||
const getPostFix = (str: string) => {
|
||||
const getPostFix = (str: string): keyof typeof RequestStatus => {
|
||||
const arr = str.split('_');
|
||||
return arr[arr.length - 1];
|
||||
return arr[arr.length - 1] as any;
|
||||
};
|
||||
|
||||
const nextState = (field: keyof State) => (state: State, action: Action): State => ({
|
||||
|
|
|
@ -22,14 +22,14 @@ export const resetHOF = (
|
|||
|
||||
if (includeFields) {
|
||||
(includeFields as any[]).forEach(fieldName => {
|
||||
stateCopy[fieldName] = returnState[fieldName];
|
||||
(stateCopy as any)[fieldName] = (returnState as any)[fieldName];
|
||||
});
|
||||
return returnCb ? returnCb(state, returnState) : { ...stateCopy };
|
||||
}
|
||||
|
||||
if (excludeFields) {
|
||||
(excludeFields as any[]).forEach(fieldName => {
|
||||
returnState[fieldName] = state[fieldName];
|
||||
(returnState as any)[fieldName] = (state as any)[fieldName];
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ export function* pruneCustomNetworks(): SagaIterator {
|
|||
|
||||
//construct lookup table of networks
|
||||
|
||||
const linkedNetworks = Object.values(customNodes).reduce(
|
||||
const linkedNetworks: { [key: string]: boolean } = Object.values(customNodes).reduce(
|
||||
(networkMap, currentNode) => ({ ...networkMap, [currentNode.network]: true }),
|
||||
{}
|
||||
);
|
||||
|
|
|
@ -6,6 +6,7 @@ import { changeNodeForce, TypeKeys, web3SetNode } from 'actions/config';
|
|||
import { getNodeId, getStaticAltNodeIdToWeb3, getNetworkNameByChainId } from 'selectors/config';
|
||||
import { setupWeb3Node, Web3Service } from 'libs/nodes/web3';
|
||||
import { Web3NodeConfig } from 'types/node';
|
||||
import { SetWalletAction } from 'actions/wallet';
|
||||
|
||||
export function* initWeb3Node(): SagaIterator {
|
||||
const { networkId, lib } = yield call(setupWeb3Node);
|
||||
|
@ -24,7 +25,7 @@ export function* initWeb3Node(): SagaIterator {
|
|||
}
|
||||
|
||||
// unset web3 as the selected node if a non-web3 wallet has been selected
|
||||
export function* unsetWeb3NodeOnWalletEvent(action): SagaIterator {
|
||||
export function* unsetWeb3NodeOnWalletEvent(action: SetWalletAction): SagaIterator {
|
||||
const node = yield select(getNodeId);
|
||||
const newWallet = action.payload;
|
||||
const isWeb3Wallet = newWallet instanceof Web3Wallet;
|
||||
|
@ -52,6 +53,5 @@ export function* unsetWeb3Node(): SagaIterator {
|
|||
|
||||
export const web3 = [
|
||||
takeEvery(TypeKeys.CONFIG_NODE_WEB3_UNSET, unsetWeb3Node),
|
||||
takeEvery(WalletTypeKeys.WALLET_SET, unsetWeb3NodeOnWalletEvent),
|
||||
takeEvery(WalletTypeKeys.WALLET_RESET, unsetWeb3NodeOnWalletEvent)
|
||||
takeEvery(WalletTypeKeys.WALLET_SET, unsetWeb3NodeOnWalletEvent)
|
||||
];
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue