1
0
mirror of https://github.com/trezor/trezor-wallet synced 2025-01-27 00:11:23 +00:00

Merge branch 'master' into fix-send-form-css

This commit is contained in:
Vaclav Mlejnsky 2018-09-21 13:32:37 +02:00 committed by GitHub
commit 2f988beb05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 485 additions and 161 deletions

View File

@ -4,6 +4,9 @@
"plugin:flowtype/recommended", "plugin:flowtype/recommended",
"plugin:jest/recommended" "plugin:jest/recommended"
], ],
"globals": {
"COMMITHASH": true
},
"env": { "env": {
"browser": true, "browser": true,
"jest": true "jest": true

View File

@ -1,11 +1,19 @@
module.exports = { module.exports = {
rootDir: './src', rootDir: './src',
automock: false,
coverageDirectory: 'coverage/',
collectCoverage: true, collectCoverage: true,
testURL: 'http://localhost', testURL: 'http://localhost',
modulePathIgnorePatterns: [ modulePathIgnorePatterns: [
'node_modules', 'node_modules',
'utils/windowUtils.js',
'utils/promiseUtils.js',
'utils/networkUtils.js',
], ],
collectCoverageFrom: [ collectCoverageFrom: [
'utils/**.js', 'utils/**.js',
], ],
setupFiles: [
'./support/setupJest.js',
],
}; };

View File

@ -31,8 +31,11 @@
"ethereumjs-tx": "^1.3.3", "ethereumjs-tx": "^1.3.3",
"ethereumjs-units": "^0.2.0", "ethereumjs-units": "^0.2.0",
"ethereumjs-util": "^5.1.4", "ethereumjs-util": "^5.1.4",
"git-revision-webpack-plugin": "^3.0.3",
"flow-webpack-plugin": "^1.2.0",
"hdkey": "^0.8.0", "hdkey": "^0.8.0",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"jest-fetch-mock": "^1.6.5",
"npm-run-all": "^4.1.3", "npm-run-all": "^4.1.3",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"raf": "^3.4.0", "raf": "^3.4.0",
@ -47,8 +50,7 @@
"react-router-redux": "next", "react-router-redux": "next",
"react-scale-text": "^1.2.2", "react-scale-text": "^1.2.2",
"react-select": "2.0.0", "react-select": "2.0.0",
"react-sticky-el": "^1.0.20", "react-transition-group": "^2.4.0",
"react-transition-group": "^2.2.1",
"redbox-react": "^1.6.0", "redbox-react": "^1.6.0",
"redux": "4.0.0", "redux": "4.0.0",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",

View File

@ -13,6 +13,7 @@ import { initialState } from 'reducers/SendFormReducer';
import { findToken } from 'reducers/TokensReducer'; import { findToken } from 'reducers/TokensReducer';
import { findDevice, getPendingAmount, getPendingNonce } from 'reducers/utils'; import { findDevice, getPendingAmount, getPendingNonce } from 'reducers/utils';
import * as stateUtils from 'reducers/utils'; import * as stateUtils from 'reducers/utils';
import { validateAddress } from 'utils/ethUtils';
import type { import type {
Dispatch, Dispatch,
@ -345,32 +346,19 @@ export const validation = (props: Props): void => {
if (state.untouched) return; if (state.untouched) return;
// valid address // valid address
if (state.touched.address) { if (state.touched.address) {
/* if (state.address.length < 1) { const addressError = validateAddress(state.address);
errors.address = 'Address is not set'; if (addressError) {
} else if (!EthereumjsUtil.isValidAddress(state.address)) { errors.address = addressError;
errors.address = 'Address is not valid'; }
} else {
// address warning or info are set in addressValidation ThunkAction
// do not override this
if (state.warnings.address) {
warnings.address = state.warnings.address;
} else if (state.infos.address) {
infos.address = state.infos.address;
}
} */
/* eslint (no-lonely-if) */ // address warning or info may be set in addressValidation ThunkAction
if (state.address.length < 1) { // do not override them
errors.address = 'Address is not set'; if (state.warnings.address) {
} else if (!EthereumjsUtil.isValidAddress(state.address)) {
errors.address = 'Address is not valid';
} else if (state.warnings.address) {
// address warning or info are set in addressValidation ThunkAction
// do not override this
warnings.address = state.warnings.address; warnings.address = state.warnings.address;
if (state.infos.address) { }
infos.address = state.infos.address;
} if (state.infos.address) {
infos.address = state.infos.address;
} }
} }

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'styled-components'; import styled, { css } from 'styled-components';
import colors from 'config/colors'; import colors from 'config/colors';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import icons from 'config/icons'; import icons from 'config/icons';
@ -34,8 +34,10 @@ const IconWrapper = styled.div`
&:hover, &:hover,
&:focus { &:focus {
border: 1px solid ${colors.TEXT_PRIMARY}; ${props => !props.checked && css`
background: ${props => (props.checked ? colors.TEXT_PRIMARY : colors.WHITE)}; border: 1px solid ${colors.GREEN_PRIMARY};
`}
background: ${props => (props.checked ? colors.GREEN_PRIMARY : colors.WHITE)};
} }
`; `;
@ -74,7 +76,12 @@ class Checkbox extends PureComponent {
<IconWrapper checked={checked}> <IconWrapper checked={checked}>
{checked && ( {checked && (
<Tick> <Tick>
<Icon size={26} color={checked ? colors.WHITE : colors.GREEN_PRIMARY} icon={icons.SUCCESS} /> <Icon
hoverColor={colors.WHITE}
size={26}
color={checked ? colors.WHITE : colors.GREEN_PRIMARY}
icon={icons.SUCCESS}
/>
</Tick> </Tick>
) )
} }

View File

@ -29,7 +29,7 @@ const Copy = styled.div`
const Footer = ({ toggle }) => ( const Footer = ({ toggle }) => (
<Wrapper> <Wrapper>
<Copy>&copy; {getYear(new Date())}</Copy> <Copy title={COMMITHASH}>&copy; {getYear(new Date())}</Copy>
<StyledLink href="http://satoshilabs.com" target="_blank" rel="noreferrer noopener" isGreen>SatoshiLabs</StyledLink> <StyledLink href="http://satoshilabs.com" target="_blank" rel="noreferrer noopener" isGreen>SatoshiLabs</StyledLink>
<StyledLink href="/assets/tos.pdf" target="_blank" rel="noreferrer noopener" isGreen>Terms</StyledLink> <StyledLink href="/assets/tos.pdf" target="_blank" rel="noreferrer noopener" isGreen>Terms</StyledLink>
<StyledLink onClick={toggle} isGreen>Show Log</StyledLink> <StyledLink onClick={toggle} isGreen>Show Log</StyledLink>

View File

@ -26,10 +26,7 @@ const P = ({ children, className, isSmaller = false }) => (
P.propTypes = { P.propTypes = {
className: PropTypes.string, className: PropTypes.string,
isSmaller: PropTypes.bool, isSmaller: PropTypes.bool,
children: PropTypes.oneOfType([ children: PropTypes.node,
PropTypes.array,
PropTypes.string,
]),
}; };
export default P; export default P;

View File

@ -3,15 +3,10 @@ import RcTooltip from 'rc-tooltip';
import colors from 'config/colors'; import colors from 'config/colors';
import styled from 'styled-components'; import styled from 'styled-components';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FONT_SIZE } from 'config/variables';
const TooltipContent = styled.div`
width: ${props => (props.isAside ? '260px' : '320px')};
font-size: ${FONT_SIZE.SMALLEST};
`;
const Wrapper = styled.div` const Wrapper = styled.div`
.rc-tooltip { .rc-tooltip {
max-width: ${props => `${props.maxWidth}px` || 'auto'};
position: absolute; position: absolute;
z-index: 1070; z-index: 1070;
display: block; display: block;
@ -177,9 +172,11 @@ class Tooltip extends Component {
placement, placement,
content, content,
children, children,
maxWidth,
} = this.props; } = this.props;
return ( return (
<Wrapper <Wrapper
maxWidth={maxWidth}
className={className} className={className}
innerRef={(node) => { this.tooltipContainerRef = node; }} innerRef={(node) => { this.tooltipContainerRef = node; }}
> >
@ -187,7 +184,7 @@ class Tooltip extends Component {
getTooltipContainer={() => this.tooltipContainerRef} getTooltipContainer={() => this.tooltipContainerRef}
arrowContent={<div className="rc-tooltip-arrow-inner" />} arrowContent={<div className="rc-tooltip-arrow-inner" />}
placement={placement} placement={placement}
overlay={<TooltipContent>{content}</TooltipContent>} overlay={content}
> >
{children} {children}
</RcTooltip> </RcTooltip>
@ -203,6 +200,7 @@ Tooltip.propTypes = {
PropTypes.element, PropTypes.element,
PropTypes.string, PropTypes.string,
]), ]),
maxWidth: PropTypes.number,
content: PropTypes.oneOfType([ content: PropTypes.oneOfType([
PropTypes.element, PropTypes.element,
PropTypes.string, PropTypes.string,

View File

@ -113,6 +113,7 @@ class Input extends Component {
)} )}
<StyledInput <StyledInput
hasIcon={!!this.props.state} hasIcon={!!this.props.state}
innerRef={this.props.innerRef}
hasAddon={!!this.props.sideAddons} hasAddon={!!this.props.sideAddons}
type={this.props.type} type={this.props.type}
placeholder={this.props.placeholder} placeholder={this.props.placeholder}
@ -142,13 +143,14 @@ class Input extends Component {
Input.propTypes = { Input.propTypes = {
className: PropTypes.string, className: PropTypes.string,
innerRef: PropTypes.func,
placeholder: PropTypes.string, placeholder: PropTypes.string,
type: PropTypes.string, type: PropTypes.string,
autoComplete: PropTypes.string, autoComplete: PropTypes.string,
autoCorrect: PropTypes.string, autoCorrect: PropTypes.string,
autoCapitalize: PropTypes.string, autoCapitalize: PropTypes.string,
spellCheck: PropTypes.string, spellCheck: PropTypes.string,
value: PropTypes.string.isRequired, value: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
state: PropTypes.string, state: PropTypes.string,
bottomText: PropTypes.string, bottomText: PropTypes.string,

View File

@ -5,6 +5,7 @@ import P from 'components/Paragraph';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import icons from 'config/icons'; import icons from 'config/icons';
import { H3 } from 'components/Heading'; import { H3 } from 'components/Heading';
import { LINE_HEIGHT } from 'config/variables';
const Wrapper = styled.div` const Wrapper = styled.div`
width: 390px; width: 390px;
@ -21,6 +22,12 @@ const Content = styled.div`
padding: 24px 48px; padding: 24px 48px;
`; `;
const StyledP = styled(P)`
word-wrap: break-word;
padding: 5px 0;
line-height: ${LINE_HEIGHT.SMALL}
`;
const Label = styled.div` const Label = styled.div`
padding-top: 5px; padding-top: 5px;
font-size: 10px; font-size: 10px;
@ -49,7 +56,7 @@ const ConfirmSignTx = (props) => {
<Label>Send</Label> <Label>Send</Label>
<P>{`${amount} ${currency}` }</P> <P>{`${amount} ${currency}` }</P>
<Label>To</Label> <Label>To</Label>
<P>{ address }</P> <StyledP>{ address }</StyledP>
<Label>Fee</Label> <Label>Fee</Label>
<P>{ selectedFeeLevel.label }</P> <P>{ selectedFeeLevel.label }</P>
</Content> </Content>

View File

@ -2,6 +2,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import raf from 'raf'; import raf from 'raf';
import colors from 'config/colors'; import colors from 'config/colors';
import { H2 } from 'components/Heading';
import P from 'components/Paragraph'; import P from 'components/Paragraph';
import { FONT_SIZE } from 'config/variables'; import { FONT_SIZE } from 'config/variables';
import Link from 'components/Link'; import Link from 'components/Link';
@ -23,7 +24,10 @@ const Label = styled.div`
padding-bottom: 5px; padding-bottom: 5px;
`; `;
const PassphraseError = styled.div``; const PassphraseError = styled.div`
margin-top: 8px;
color: ${colors.ERROR_PRIMARY};
`;
const Row = styled.div` const Row = styled.div`
position: relative; position: relative;
@ -137,22 +141,25 @@ export default class PinModal extends Component<Props, State> {
} = this.state; } = this.state;
// } = this.props.modal; // } = this.props.modal;
let passphraseInputValue: string = passphrase; const passphraseInputValue: string = passphrase;
let passphraseRevisionInputValue: string = passphraseRevision; const passphraseRevisionInputValue: string = passphraseRevision;
if (!visible && !passphraseFocused) { // if (!visible && !passphraseFocused) {
passphraseInputValue = passphrase.replace(/./g, '•'); // passphraseInputValue = passphrase.replace(/./g, '•');
} // }
if (!visible && !passphraseRevisionFocused) { // if (!visible && !passphraseRevisionFocused) {
passphraseRevisionInputValue = passphraseRevision.replace(/./g, '•'); // passphraseRevisionInputValue = passphraseRevision.replace(/./g, '•');
} // }
if (this.passphraseInput) { if (this.passphraseInput) {
this.passphraseInput.value = passphraseInputValue; // this.passphraseInput.value = passphraseInputValue;
this.passphraseInput.setAttribute('type', visible || (!visible && !passphraseFocused) ? 'text' : 'password'); // this.passphraseInput.setAttribute('type', visible || (!visible && !passphraseFocused) ? 'text' : 'password');
this.passphraseInput.setAttribute('type', visible ? 'text' : 'password');
} }
if (this.passphraseRevisionInput) { if (this.passphraseRevisionInput) {
this.passphraseRevisionInput.value = passphraseRevisionInputValue; // this.passphraseRevisionInput.value = passphraseRevisionInputValue;
this.passphraseRevisionInput.setAttribute('type', visible || (!visible && !passphraseRevisionFocused) ? 'text' : 'password'); // this.passphraseRevisionInput.setAttribute('type', visible || (!visible && !passphraseRevisionFocused) ? 'text' : 'password');
this.passphraseRevisionInput.setAttribute('type', visible ? 'text' : 'password');
} }
} }
@ -174,6 +181,14 @@ export default class PinModal extends Component<Props, State> {
match: previousState.singleInput || previousState.passphraseRevision === value, match: previousState.singleInput || previousState.passphraseRevision === value,
passphrase: value, passphrase: value,
})); }));
if (this.state.visible && this.passphraseRevisionInput) {
this.setState({
match: true,
passphraseRevision: value,
});
this.passphraseRevisionInput.value = value;
}
} else { } else {
this.setState(previousState => ({ this.setState(previousState => ({
match: previousState.passphrase === value, match: previousState.passphrase === value,
@ -211,12 +226,29 @@ export default class PinModal extends Component<Props, State> {
this.setState({ this.setState({
visible: true, visible: true,
}); });
if (this.passphraseRevisionInput) {
this.passphraseRevisionInput.disabled = true;
this.passphraseRevisionInput.value = this.state.passphrase;
this.setState(previousState => ({
passphraseRevision: previousState.passphrase,
match: true,
}));
}
} }
onPassphraseHide = (): void => { onPassphraseHide = (): void => {
this.setState({ this.setState({
visible: false, visible: false,
}); });
if (this.passphraseRevisionInput) {
const emptyPassphraseRevisionValue = '';
this.passphraseRevisionInput.value = emptyPassphraseRevisionValue;
this.setState(previousState => ({
passphraseRevision: emptyPassphraseRevisionValue,
match: emptyPassphraseRevisionValue === previousState.passphrase,
}));
this.passphraseRevisionInput.disabled = false;
}
} }
submit = (empty: boolean = false): void => { submit = (empty: boolean = false): void => {
@ -232,7 +264,7 @@ export default class PinModal extends Component<Props, State> {
//this.passphraseRevisionInput.style.display = 'none'; //this.passphraseRevisionInput.style.display = 'none';
//this.passphraseRevisionInput.setAttribute('readonly', 'readonly'); //this.passphraseRevisionInput.setAttribute('readonly', 'readonly');
const p = passphrase; // const p = passphrase;
this.setState({ this.setState({
passphrase: '', passphrase: '',
@ -271,11 +303,10 @@ export default class PinModal extends Component<Props, State> {
//let passphraseRevisionInputType: string = visible || passphraseRevisionFocused ? "text" : "password"; //let passphraseRevisionInputType: string = visible || passphraseRevisionFocused ? "text" : "password";
const showPassphraseCheckboxFn: Function = visible ? this.onPassphraseHide : this.onPassphraseShow; const showPassphraseCheckboxFn: Function = visible ? this.onPassphraseHide : this.onPassphraseShow;
console.log('passphraseInputType', passphraseInputType);
return ( return (
<Wrapper> <Wrapper>
{/* ?<H2>Enter { deviceLabel } passphrase</H2> */} <H2>Enter { deviceLabel } passphrase</H2>
{/* <P isSmaller>Note that passphrase is case-sensitive.</P> */} <P isSmaller>Note that passphrase is case-sensitive.</P>
<Row> <Row>
<Label>Passphrase</Label> <Label>Passphrase</Label>
<Input <Input
@ -289,7 +320,7 @@ export default class PinModal extends Component<Props, State> {
data-lpignore="true" data-lpignore="true"
onFocus={() => this.onPassphraseFocus('passphrase')} onFocus={() => this.onPassphraseFocus('passphrase')}
onBlur={() => this.onPassphraseBlur('passphrase')} onBlur={() => this.onPassphraseBlur('passphrase')}
tabIndex="1" tabIndex="0"
/> />
</Row> </Row>
{!singleInput && ( {!singleInput && (
@ -306,7 +337,6 @@ export default class PinModal extends Component<Props, State> {
data-lpignore="true" data-lpignore="true"
onFocus={() => this.onPassphraseFocus('revision')} onFocus={() => this.onPassphraseFocus('revision')}
onBlur={() => this.onPassphraseBlur('revision')} onBlur={() => this.onPassphraseBlur('revision')}
tabIndex="2"
/> />
{!match && passphraseRevisionTouched && <PassphraseError>Passphrases do not match</PassphraseError> } {!match && passphraseRevisionTouched && <PassphraseError>Passphrases do not match</PassphraseError> }
</Row> </Row>
@ -315,7 +345,7 @@ export default class PinModal extends Component<Props, State> {
<Checkbox onClick={showPassphraseCheckboxFn} checked={visible}>Show passphrase</Checkbox> <Checkbox onClick={showPassphraseCheckboxFn} checked={visible}>Show passphrase</Checkbox>
</Row> </Row>
<Row> <Row>
<Button type="button" tabIndex="4" disabled={!match} onClick={event => this.submit()}>Enter</Button> <Button type="button" disabled={!match} onClick={() => this.submit()}>Enter</Button>
</Row> </Row>
<Footer> <Footer>
<P isSmaller>If you want to access your default account</P> <P isSmaller>If you want to access your default account</P>

View File

@ -41,5 +41,6 @@ export const TRANSITION = {
}; };
export const LINE_HEIGHT = { export const LINE_HEIGHT = {
SMALL: '1.4',
BASE: '1.8', BASE: '1.8',
}; };

View File

@ -57,6 +57,7 @@ export default function modal(state: State = initialState, action: Action): Stat
if (state.opened && action.device.path === state.device.path && action.device.status === 'occupied') { if (state.opened && action.device.path === state.device.path && action.device.status === 'occupied') {
return initialState; return initialState;
} }
return state; return state;
case DEVICE.DISCONNECT: case DEVICE.DISCONNECT:

View File

@ -63,6 +63,44 @@ const baseStyles = () => injectGlobal`
url('./fonts/roboto/roboto-mono-v4-greek_cyrillic-ext_greek-ext_latin_cyrillic_vietnamese_latin-ext-regular.svg#RobotoMono') format('svg'); /* Legacy iOS */ url('./fonts/roboto/roboto-mono-v4-greek_cyrillic-ext_greek-ext_latin_cyrillic_vietnamese_latin-ext-regular.svg#RobotoMono') format('svg'); /* Legacy iOS */
} }
.slide-left-enter {
transform: translate(100%);
pointer-events: none;
}
.slide-left-enter.slide-left-enter-active {
transform: translate(0%);
transition: transform 300ms ease-in-out;
}
.slide-left-exit {
transform: translate(-100%);
}
.slide-left-exit.slide-left-exit-active {
transform: translate(0%);
transition: transform 300ms ease-in-out;
}
.slide-right-enter {
transform: translate(-100%);
pointer-events: none;
}
.slide-right-enter.slide-right-enter-active {
transform: translate(0%);
transition: transform 300ms ease-in-out;
}
.slide-right-exit {
transform: translate(-100%);
}
.slide-right-exit.slide-right-exit-active {
transform: translate(-200%);
transition: transform 300ms ease-in-out;
}
`; `;
export default baseStyles; export default baseStyles;

1
src/support/setupJest.js Normal file
View File

@ -0,0 +1 @@
global.fetch = require('jest-fetch-mock');

View File

@ -55,3 +55,13 @@ exports[`device utils get version 4`] = `"1"`;
exports[`device utils get version 5`] = `"1"`; exports[`device utils get version 5`] = `"1"`;
exports[`device utils get version 6`] = `"T"`; exports[`device utils get version 6`] = `"T"`;
exports[`device utils isDisabled 1`] = `false`;
exports[`device utils isDisabled 2`] = `true`;
exports[`device utils isWebUSB 1`] = `true`;
exports[`device utils isWebUSB 2`] = `false`;
exports[`device utils isWebUSB 3`] = `true`;

View File

@ -1,5 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`eth utils calcGasPrice 1`] = `"89090990901"`;
exports[`eth utils decimalToHex 1`] = `"0"`; exports[`eth utils decimalToHex 1`] = `"0"`;
exports[`eth utils decimalToHex 2`] = `"1"`; exports[`eth utils decimalToHex 2`] = `"1"`;
@ -8,4 +10,36 @@ exports[`eth utils decimalToHex 3`] = `"2"`;
exports[`eth utils decimalToHex 4`] = `"64"`; exports[`eth utils decimalToHex 4`] = `"64"`;
exports[`eth utils decimalToHex 5`] = `"3e7"`; exports[`eth utils decimalToHex 5`] = `"2540be3ff"`;
exports[`eth utils hexToDecimal 1`] = `"9999999999"`;
exports[`eth utils hexToDecimal 2`] = `"100"`;
exports[`eth utils hexToDecimal 3`] = `"2"`;
exports[`eth utils hexToDecimal 4`] = `"1"`;
exports[`eth utils hexToDecimal 5`] = `"0"`;
exports[`eth utils hexToDecimal 6`] = `"null"`;
exports[`eth utils padLeftEven 1`] = `"02540be3ff"`;
exports[`eth utils sanitizeHex 1`] = `"0x02540be3ff"`;
exports[`eth utils sanitizeHex 2`] = `"0x01"`;
exports[`eth utils sanitizeHex 3`] = `"0x02"`;
exports[`eth utils sanitizeHex 4`] = `"0x0100"`;
exports[`eth utils sanitizeHex 5`] = `null`;
exports[`eth utils sanitizeHex 6`] = `""`;
exports[`eth utils strip 1`] = `""`;
exports[`eth utils strip 2`] = `"02540be3ff"`;
exports[`eth utils strip 3`] = `"02540be3ff"`;

View File

@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`format utils btckb2satoshib 1`] = `0`;
exports[`format utils btckb2satoshib 2`] = `100000`;
exports[`format utils btckb2satoshib 3`] = `200000`;
exports[`format utils btckb2satoshib 4`] = `10000000`;
exports[`format utils btckb2satoshib 5`] = `99900000`;
exports[`format utils formatAmount 1`] = `"0 btc"`;
exports[`format utils formatAmount 2`] = `"10 mBTC"`;
exports[`format utils formatAmount 3`] = `"0.000005 mBTC"`;
exports[`format utils formatAmount 4`] = `"1e-8 eth"`;
exports[`format utils formatAmount 5`] = `"0.00099999 tau"`;
exports[`format utils formatTime 1`] = `"No time estimate"`;
exports[`format utils formatTime 2`] = `"1 minutes"`;
exports[`format utils formatTime 3`] = `"2 minutes"`;
exports[`format utils formatTime 4`] = `"1 hour 40 minutes"`;
exports[`format utils formatTime 5`] = `"16 hours 39 minutes"`;
exports[`format utils formatTime 6`] = `"45 minutes"`;
exports[`format utils hexToString 1`] = `"test"`;
exports[`format utils hexToString 2`] = `"0001"`;
exports[`format utils hexToString 3`] = `"test99999"`;
exports[`format utils stringToHex 1`] = `"0074006500730074"`;
exports[`format utils stringToHex 2`] = `"0030003000300031"`;
exports[`format utils stringToHex 3`] = `"007400650073007400390039003900390039"`;

View File

@ -38,6 +38,29 @@ describe('device utils', () => {
}); });
}); });
it('isWebUSB', () => {
const data = [
{ transport: { version: ['webusb'] } },
{ transport: { version: ['aaaaaa'] } },
{ transport: { version: ['webusb', 'test'] } },
];
data.forEach((item) => {
expect(dUtils.isWebUSB(item.transport)).toMatchSnapshot();
});
});
it('isDisabled', () => {
const data = [
{ selectedDevice: { features: null }, devices: [1, 2, 3], transport: { version: ['webusb', 'test'] } },
{ selectedDevice: { features: null }, devices: [], transport: { version: ['test'] } },
];
data.forEach((item) => {
expect(dUtils.isDisabled(item.selectedDevice, item.devices, item.transport)).toMatchSnapshot();
});
});
it('get version', () => { it('get version', () => {
const deviceMock = [ const deviceMock = [
{ }, { },

View File

@ -1,11 +1,53 @@
import BigNumber from 'bignumber.js';
import * as ethUtils from '../ethUtils'; import * as ethUtils from '../ethUtils';
describe('eth utils', () => { describe('eth utils', () => {
it('decimalToHex', () => { it('decimalToHex', () => {
const input = [0, 1, 2, 100, 999]; const input = [0, 1, 2, 100, 9999999999];
input.forEach((entry) => { input.forEach((entry) => {
expect(ethUtils.decimalToHex(entry)).toMatchSnapshot(); expect(ethUtils.decimalToHex(entry)).toMatchSnapshot();
}); });
}); });
it('hexToDecimal', () => {
const input = ['2540be3ff', '64', '2', '1', '0', ''];
input.forEach((entry) => {
expect(ethUtils.hexToDecimal(entry)).toMatchSnapshot();
});
});
it('padLeftEven', () => {
const input = ['2540be3ff'];
input.forEach((entry) => {
expect(ethUtils.padLeftEven(entry)).toMatchSnapshot();
});
});
it('sanitizeHex', () => {
const input = ['0x2540be3ff', '1', '2', '100', 999, ''];
input.forEach((entry) => {
expect(ethUtils.sanitizeHex(entry)).toMatchSnapshot();
});
});
it('strip', () => {
const input = ['0x', '0x2540be3ff', '2540be3ff'];
input.forEach((entry) => {
expect(ethUtils.strip(entry)).toMatchSnapshot();
});
});
it('calcGasPrice', () => {
const input = [{ price: new BigNumber(9898998989), limit: '9' }];
input.forEach((entry) => {
expect(ethUtils.calcGasPrice(entry.price, entry.limit)).toMatchSnapshot();
});
});
}); });

View File

@ -0,0 +1,49 @@
import * as formatUtils from '../formatUtils';
describe('format utils', () => {
it('formatAmount', () => {
const input = [
{ amount: 0, coinInfo: { isBitcoin: true, currencyUnits: 'mbtc', shortcut: 'btc' } },
{ amount: 1000000, coinInfo: { isBitcoin: true, currencyUnits: 'mbtc', shortcut: 'btc' } },
{ amount: 0.5, coinInfo: { isBitcoin: true, currencyUnits: 'mbtc', shortcut: 'btc' } },
{ amount: 1, coinInfo: { isBitcoin: false, shortcut: 'eth' } },
{ amount: 99999, coinInfo: { isBitcoin: false, shortcut: 'tau' } },
];
input.forEach((entry) => {
expect(formatUtils.formatAmount(entry.amount, entry.coinInfo, entry.coinInfo.currencyUnits)).toMatchSnapshot();
});
});
it('formatTime', () => {
const input = [0, 1, 2, 100, 999, 45];
input.forEach((entry) => {
expect(formatUtils.formatTime(entry)).toMatchSnapshot();
});
});
it('btckb2satoshib', () => {
const input = [0, 1, 2, 100, 999];
input.forEach((entry) => {
expect(formatUtils.btckb2satoshib(entry)).toMatchSnapshot();
});
});
it('stringToHex', () => {
const input = ['test', '0001', 'test99999'];
input.forEach((entry) => {
expect(formatUtils.stringToHex(entry)).toMatchSnapshot();
});
});
it('hexToString', () => {
const input = ['0074006500730074', '0030003000300031', '007400650073007400390039003900390039'];
input.forEach((entry) => {
expect(formatUtils.hexToString(entry)).toMatchSnapshot();
});
});
});

View File

@ -1,6 +1,7 @@
/* @flow */ /* @flow */
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import EthereumjsUtil from 'ethereumjs-util';
export const decimalToHex = (dec: number): string => new BigNumber(dec).toString(16); export const decimalToHex = (dec: number): string => new BigNumber(dec).toString(16);
@ -29,3 +30,15 @@ export const strip = (str: string): string => {
}; };
export const calcGasPrice = (price: BigNumber, limit: string): string => price.times(limit).toString(); export const calcGasPrice = (price: BigNumber, limit: string): string => price.times(limit).toString();
export const validateAddress = (address: string): ?string => {
const hasUpperCase = new RegExp('^(.*[A-Z].*)$');
if (address.length < 1) {
return 'Address is not set';
} else if (!EthereumjsUtil.isValidAddress(address)) {
return 'Address is not valid';
} else if (address.match(hasUpperCase) && !EthereumjsUtil.isValidChecksumAddress(address)) {
return 'Address is not a valid checksum';
}
return null;
}

View File

@ -1,11 +1,9 @@
/* @flow */ /* @flow */
const currencyUnits: string = 'mbtc2';
// TODO: chagne currency units // TODO: chagne currency units
const currencyUnitsConstant: string = 'mbtc2';
export const formatAmount = (n: number, coinInfo: any): string => { export const formatAmount = (n: number, coinInfo: any, currencyUnits: string = currencyUnitsConstant): string => {
const amount = (n / 1e8); const amount = (n / 1e8);
if (coinInfo.isBitcoin && currencyUnits === 'mbtc' && amount <= 0.1 && n !== 0) { if (coinInfo.isBitcoin && currencyUnits === 'mbtc' && amount <= 0.1 && n !== 0) {
const s = (n / 1e5).toString(); const s = (n / 1e5).toString();

View File

@ -1,6 +1,5 @@
/* @flow */ /* @flow */
import 'whatwg-fetch'; import 'whatwg-fetch';
export const httpRequest = async (url: string, type: string = 'text'): any => { export const httpRequest = async (url: string, type: string = 'text'): any => {
@ -15,16 +14,6 @@ export const httpRequest = async (url: string, type: string = 'text'): any => {
await response.text(); await response.text();
} }
throw new Error(`${url} ${response.statusText}`); throw new Error(`${url} ${response.statusText}`);
// return fetch(url, { credentials: 'same-origin' }).then((response) => {
// if (response.status === 200) {
// return response.text().then(result => (json ? JSON.parse(result) : result));
// } else {
// throw new Error(response.statusText);
// }
// })
}; };
export const JSONRequest = async (url: string): Promise<JSON> => { export const JSONRequest = async (url: string): Promise<JSON> => {

View File

@ -86,7 +86,7 @@ class ConnectDevice extends Component<Props> {
</React.Fragment> </React.Fragment>
)} )}
</ConnectTrezorWrapper> </ConnectTrezorWrapper>
{this.props.showWebUsb && ( {this.props.showWebUsb && !this.props.showDisconnect && (
<React.Fragment> <React.Fragment>
<P>and</P> <P>and</P>
<Button isWebUsb> <Button isWebUsb>

View File

@ -20,7 +20,6 @@ import type { Props } from '../common';
import Row from '../Row'; import Row from '../Row';
import RowCoin from '../RowCoin'; import RowCoin from '../RowCoin';
const Wrapper = styled.div``; const Wrapper = styled.div``;
const Text = styled.span` const Text = styled.span`
@ -28,6 +27,10 @@ const Text = styled.span`
color: ${colors.TEXT_SECONDARY}; color: ${colors.TEXT_SECONDARY};
`; `;
const TooltipContent = styled.div`
font-size: ${FONT_SIZE.SMALLEST};
`;
const RowAccountWrapper = styled.div` const RowAccountWrapper = styled.div`
width: 100%; width: 100%;
display: flex; display: flex;
@ -120,7 +123,7 @@ const AccountMenu = (props: Props): ?React$Element<string> => {
const deviceAccounts: Accounts = findDeviceAccounts(accounts, selected, location.state.network); const deviceAccounts: Accounts = findDeviceAccounts(accounts, selected, location.state.network);
let selectedAccounts = deviceAccounts.map((account, i) => { const selectedAccounts = deviceAccounts.map((account, i) => {
// const url: string = `${baseUrl}/network/${location.state.network}/account/${i}`; // const url: string = `${baseUrl}/network/${location.state.network}/account/${i}`;
const url: string = location.pathname.replace(/account+\/([0-9]*)/, `account/${i}`); const url: string = location.pathname.replace(/account+\/([0-9]*)/, `account/${i}`);
@ -158,26 +161,6 @@ const AccountMenu = (props: Props): ?React$Element<string> => {
); );
}); });
if (selectedAccounts.length < 1) {
if (selected.connected) {
const url: string = location.pathname.replace(/account+\/([0-9]*)/, 'account/0');
selectedAccounts = (
<NavLink
to={url}
>
<Row column>
<RowAccountWrapper
isSelected
borderTop
>
Account #1
</RowAccountWrapper>
</Row>
</NavLink>
);
}
}
let discoveryStatus = null; let discoveryStatus = null;
const discovery = props.discovery.find(d => d.deviceState === selected.state && d.network === location.state.network); const discovery = props.discovery.find(d => d.deviceState === selected.state && d.network === location.state.network);
@ -204,7 +187,8 @@ const AccountMenu = (props: Props): ?React$Element<string> => {
} else { } else {
discoveryStatus = ( discoveryStatus = (
<Tooltip <Tooltip
content={<React.Fragment>To add a new account, last account must have some transactions.</React.Fragment>} maxWidth={300}
content={<TooltipContent>To add a new account, last account must have some transactions.</TooltipContent>}
placement="bottom" placement="bottom"
> >
<Row> <Row>

View File

@ -20,6 +20,8 @@ const RowCoinWrapper = styled.div`
display: block; display: block;
font-size: ${FONT_SIZE.BASE}; font-size: ${FONT_SIZE.BASE};
color: ${colors.TEXT_PRIMARY}; color: ${colors.TEXT_PRIMARY};
transition: background-color 0.3s, color 0.3s;
&:hover { &:hover {
background-color: ${colors.GRAY_LIGHT}; background-color: ${colors.GRAY_LIGHT};
} }

View File

@ -19,6 +19,7 @@ const AsideWrapper = styled.aside`
position: relative; position: relative;
top: 0; top: 0;
width: 320px; width: 320px;
min-width: 320px;
overflow: hidden; overflow: hidden;
background: ${colors.MAIN}; background: ${colors.MAIN};
border-right: 1px solid ${colors.DIVIDER}; border-right: 1px solid ${colors.DIVIDER};

View File

@ -24,15 +24,15 @@ const TransitionContentWrapper = styled.div`
`; `;
const Footer = styled.div` const Footer = styled.div`
position: fixed; position: relative;
width: 320px;
bottom: 0; bottom: 0;
background: ${colors.MAIN}; background: ${colors.MAIN};
border-right: 1px solid ${colors.DIVIDER}; border-right: 1px solid ${colors.DIVIDER};
`; `;
const Body = styled.div` const Body = styled.div`
overflow: auto; width: 320px;
background: ${colors.LANDING};
`; `;
const Help = styled.div` const Help = styled.div`
@ -58,6 +58,25 @@ const A = styled.a`
} }
`; `;
const TransitionMenu = (props: TransitionMenuProps): React$Element<TransitionGroup> => (
<TransitionGroupWrapper component="div" className="transition-container">
<CSSTransition
key={props.animationType}
onExit={() => { window.dispatchEvent(new Event('resize')); }}
onExited={() => window.dispatchEvent(new Event('resize'))}
in
out
classNames={props.animationType}
appear={false}
timeout={300}
>
<TransitionContentWrapper>
{ props.children }
</TransitionContentWrapper>
</CSSTransition>
</TransitionGroupWrapper>
);
class LeftNavigation extends Component { class LeftNavigation extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -96,39 +115,6 @@ class LeftNavigation extends Component {
} }
} }
// TODO: refactor to transition component for reuse of transitions
getMenuTransition(children) {
return (
<TransitionGroupWrapper component="div" className="transition-container">
<CSSTransition
key={this.state.animationType}
onEnter={() => {
console.warn('ON ENTER');
}}
onEntering={() => {
console.warn('ON ENTERING (ACTIVE)');
}}
onExit={() => {
console.warn('ON EXIT');
window.dispatchEvent(new Event('resize'));
}}
onExiting={() => {
console.warn('ON EXITING (ACTIVE)');
}}
onExited={() => window.dispatchEvent(new Event('resize'))}
classNames={this.state.animationType}
appear={false}
timeout={30000}
in
out
>
<TransitionContentWrapper>
{children}
</TransitionContentWrapper>
</CSSTransition>
</TransitionGroupWrapper>);
}
shouldRenderAccounts() { shouldRenderAccounts() {
const { selectedDevice } = this.props.wallet; const { selectedDevice } = this.props.wallet;
return selectedDevice return selectedDevice
@ -148,6 +134,22 @@ class LeftNavigation extends Component {
} }
render() { render() {
const { props } = this;
let menu;
if (this.shouldRenderAccounts()) {
menu = (
<TransitionMenu animationType="slide-left">
<AccountMenu {...props} />
</TransitionMenu>
);
} else if (this.shouldRenderCoins()) {
menu = (
<TransitionMenu animationType="slide-right">
<CoinMenu {...props} />
</TransitionMenu>
);
}
return ( return (
<StickyContainer <StickyContainer
location={this.props.location.pathname} location={this.props.location.pathname}
@ -163,8 +165,7 @@ class LeftNavigation extends Component {
/> />
<Body> <Body>
{this.state.shouldRenderDeviceSelection && <DeviceMenu {...this.props} />} {this.state.shouldRenderDeviceSelection && <DeviceMenu {...this.props} />}
{this.shouldRenderAccounts() && this.getMenuTransition(<AccountMenu {...this.props} />)} {menu}
{this.shouldRenderCoins() && this.getMenuTransition(<CoinMenu {...this.props} />)}
</Body> </Body>
<Footer className="sticky-bottom"> <Footer className="sticky-bottom">
<Help> <Help>

View File

@ -1,12 +1,15 @@
import webpack from 'webpack'; import webpack from 'webpack';
import GitRevisionPlugin from 'git-revision-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin';
import FlowWebpackPlugin from 'flow-webpack-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import { import {
SRC, BUILD, PORT, PUBLIC, SRC, BUILD, PORT, PUBLIC,
} from './constants'; } from './constants';
const gitRevisionPlugin = new GitRevisionPlugin();
module.exports = { module.exports = {
watch: true, watch: true,
@ -46,6 +49,7 @@ module.exports = {
{ {
loader: 'stylelint-custom-processor-loader', loader: 'stylelint-custom-processor-loader',
options: { options: {
emitWarning: true,
configPath: '.stylelintrc', configPath: '.stylelintrc',
}, },
}, },
@ -89,6 +93,10 @@ module.exports = {
hints: false, hints: false,
}, },
plugins: [ plugins: [
new webpack.DefinePlugin({
COMMITHASH: JSON.stringify(gitRevisionPlugin.commithash()),
}),
new FlowWebpackPlugin(),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
chunks: ['index'], chunks: ['index'],
template: `${SRC}index.html`, template: `${SRC}index.html`,

View File

@ -1,5 +1,6 @@
import webpack from 'webpack'; import webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import GitRevisionPlugin from 'git-revision-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin';
import CopyWebpackPlugin from 'copy-webpack-plugin'; import CopyWebpackPlugin from 'copy-webpack-plugin';
import MiniCssExtractPlugin from '../../trezor-connect/node_modules/mini-css-extract-plugin'; import MiniCssExtractPlugin from '../../trezor-connect/node_modules/mini-css-extract-plugin';
@ -15,6 +16,8 @@ import {
PORT, PORT,
} from './constants'; } from './constants';
const gitRevisionPlugin = new GitRevisionPlugin();
module.exports = { module.exports = {
watch: true, watch: true,
mode: 'development', mode: 'development',
@ -115,7 +118,6 @@ module.exports = {
filename: '[name].css', filename: '[name].css',
chunkFilename: '[id].css', chunkFilename: '[id].css',
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
chunks: ['index'], chunks: ['index'],
template: `${SRC}index.html`, template: `${SRC}index.html`,
@ -164,6 +166,7 @@ module.exports = {
new webpack.DefinePlugin({ new webpack.DefinePlugin({
LOCAL: JSON.stringify(`http://localhost:${PORT}/`), LOCAL: JSON.stringify(`http://localhost:${PORT}/`),
COMMITHASH: JSON.stringify(gitRevisionPlugin.commithash()),
}), }),
// ignore node lib from trezor-link // ignore node lib from trezor-link

View File

@ -1,9 +1,13 @@
import webpack from 'webpack'; import webpack from 'webpack';
import GitRevisionPlugin from 'git-revision-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin';
import CopyWebpackPlugin from 'copy-webpack-plugin'; import CopyWebpackPlugin from 'copy-webpack-plugin';
import FlowWebpackPlugin from 'flow-webpack-plugin';
import { SRC, BUILD, PUBLIC } from './constants'; import { SRC, BUILD, PUBLIC } from './constants';
const gitRevisionPlugin = new GitRevisionPlugin();
module.exports = { module.exports = {
mode: 'production', mode: 'production',
entry: { entry: {
@ -60,6 +64,10 @@ module.exports = {
hints: false, hints: false,
}, },
plugins: [ plugins: [
new webpack.DefinePlugin({
COMMITHASH: JSON.stringify(gitRevisionPlugin.commithash()),
}),
new FlowWebpackPlugin(),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
chunks: ['index'], chunks: ['index'],
template: `${SRC}index.html`, template: `${SRC}index.html`,

View File

@ -255,6 +255,10 @@
version "0.7.0" version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
"@types/jest@^23.0.0":
version "23.3.2"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.2.tgz#07b90f6adf75d42c34230c026a2529e56c249dbb"
"@webassemblyjs/ast@1.5.13": "@webassemblyjs/ast@1.5.13":
version "1.5.13" version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.5.13.tgz#81155a570bd5803a30ec31436bc2c9c0ede38f25" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.5.13.tgz#81155a570bd5803a30ec31436bc2c9c0ede38f25"
@ -3293,6 +3297,10 @@ dom-helpers@^3.2.0:
version "3.2.1" version "3.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a"
dom-helpers@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
dom-serializer@0: dom-serializer@0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
@ -4306,6 +4314,10 @@ flow-parser@^0.*:
version "0.72.0" version "0.72.0"
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.72.0.tgz#6c8041e76ac7d0be1a71ce29c00cd1435fb6013c" resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.72.0.tgz#6c8041e76ac7d0be1a71ce29c00cd1435fb6013c"
flow-webpack-plugin@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/flow-webpack-plugin/-/flow-webpack-plugin-1.2.0.tgz#1958821d16135028e391cad5ee2f3a4fa78197ec"
flush-write-stream@^1.0.0: flush-write-stream@^1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417"
@ -4519,6 +4531,10 @@ gh-got@^6.0.0:
got "^7.0.0" got "^7.0.0"
is-plain-obj "^1.1.0" is-plain-obj "^1.1.0"
git-revision-webpack-plugin@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/git-revision-webpack-plugin/-/git-revision-webpack-plugin-3.0.3.tgz#f909949d7851d1039ed530518f73f5d46594e66f"
github-username@^4.0.0: github-username@^4.0.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/github-username/-/github-username-4.1.0.tgz#cbe280041883206da4212ae9e4b5f169c30bf417" resolved "https://registry.yarnpkg.com/github-username/-/github-username-4.1.0.tgz#cbe280041883206da4212ae9e4b5f169c30bf417"
@ -5589,7 +5605,7 @@ isobject@^3.0.0, isobject@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
isomorphic-fetch@^2.1.1: isomorphic-fetch@^2.1.1, isomorphic-fetch@^2.2.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
dependencies: dependencies:
@ -5781,6 +5797,14 @@ jest-environment-node@^23.4.0:
jest-mock "^23.2.0" jest-mock "^23.2.0"
jest-util "^23.4.0" jest-util "^23.4.0"
jest-fetch-mock@^1.6.5:
version "1.6.5"
resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-1.6.5.tgz#178fa1a937ef6f61fb8e8483b6d4602b17e0d96d"
dependencies:
"@types/jest" "^23.0.0"
isomorphic-fetch "^2.2.1"
promise-polyfill "^7.1.1"
jest-get-type@^22.1.0: jest-get-type@^22.1.0:
version "22.4.3" version "22.4.3"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4"
@ -8030,6 +8054,10 @@ promise-inflight@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
promise-polyfill@^7.1.1:
version "7.1.2"
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-7.1.2.tgz#ab05301d8c28536301622d69227632269a70ca3b"
promise@^7.1.1: promise@^7.1.1:
version "7.3.1" version "7.3.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
@ -8051,13 +8079,6 @@ prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, pr
loose-envify "^1.3.1" loose-envify "^1.3.1"
object-assign "^4.1.1" object-assign "^4.1.1"
prop-types@>=15.5.10, prop-types@^15.6.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
dependencies:
loose-envify "^1.3.1"
object-assign "^4.1.1"
prop-types@^15.6.1: prop-types@^15.6.1:
version "15.6.1" version "15.6.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
@ -8066,6 +8087,13 @@ prop-types@^15.6.1:
loose-envify "^1.3.1" loose-envify "^1.3.1"
object-assign "^4.1.1" object-assign "^4.1.1"
prop-types@^15.6.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
dependencies:
loose-envify "^1.3.1"
object-assign "^4.1.1"
proxy-addr@~2.0.3: proxy-addr@~2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
@ -8398,12 +8426,6 @@ react-select@2.0.0:
react-input-autosize "^2.2.1" react-input-autosize "^2.2.1"
react-transition-group "^2.2.1" react-transition-group "^2.2.1"
react-sticky-el@^1.0.20:
version "1.0.20"
resolved "https://registry.yarnpkg.com/react-sticky-el/-/react-sticky-el-1.0.20.tgz#b3c5e7128218633f440dc67aec239d1cd078342d"
dependencies:
prop-types ">=15.5.10"
react-transition-group@^2.2.1: react-transition-group@^2.2.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10"
@ -8415,6 +8437,15 @@ react-transition-group@^2.2.1:
prop-types "^15.5.8" prop-types "^15.5.8"
warning "^3.0.0" warning "^3.0.0"
react-transition-group@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.4.0.tgz#1d9391fabfd82e016f26fabd1eec329dbd922b5a"
dependencies:
dom-helpers "^3.3.1"
loose-envify "^1.3.1"
prop-types "^15.6.2"
react-lifecycles-compat "^3.0.4"
"react@^15.4.2 || ^16.0.0": "react@^15.4.2 || ^16.0.0":
version "16.2.0" version "16.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"