mirror of
https://github.com/trezor/trezor-wallet
synced 2024-11-28 03:08:30 +00:00
Merge branch 'sign/verify'
This commit is contained in:
commit
2cdd32c503
4
.babelrc
4
.babelrc
@ -25,7 +25,7 @@
|
||||
],
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": ["jest"]
|
||||
"presets": ["jest"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -59,6 +59,7 @@
|
||||
"react-router-redux": "next",
|
||||
"react-scale-text": "^1.2.2",
|
||||
"react-select": "2.0.0",
|
||||
"react-textarea-autosize": "^7.0.4",
|
||||
"react-transition-group": "^2.4.0",
|
||||
"redbox-react": "^1.6.0",
|
||||
"redux": "4.0.0",
|
||||
|
165
src/actions/SignVerifyActions.js
Normal file
165
src/actions/SignVerifyActions.js
Normal file
@ -0,0 +1,165 @@
|
||||
/* @flow */
|
||||
import TrezorConnect from 'trezor-connect';
|
||||
import type {
|
||||
GetState, Dispatch, ThunkAction, AsyncAction,
|
||||
} from 'flowtype';
|
||||
import { validateAddress } from 'utils/ethUtils';
|
||||
import * as NOTIFICATION from 'actions/constants/notification';
|
||||
import * as SIGN_VERIFY from './constants/signVerify';
|
||||
|
||||
export type SignVerifyAction = {
|
||||
type: typeof SIGN_VERIFY.SIGN_SUCCESS,
|
||||
signSignature: string
|
||||
} | {
|
||||
type: typeof SIGN_VERIFY.CLEAR_SIGN,
|
||||
} | {
|
||||
type: typeof SIGN_VERIFY.CLEAR_VERIFY,
|
||||
} | {
|
||||
type: typeof SIGN_VERIFY.INPUT_CHANGE,
|
||||
inputName: string,
|
||||
value: string
|
||||
} | {
|
||||
type: typeof SIGN_VERIFY.TOUCH,
|
||||
inputName: string,
|
||||
} | {
|
||||
type: typeof SIGN_VERIFY.ERROR,
|
||||
inputName: string,
|
||||
message: ?string
|
||||
} | {
|
||||
type: typeof SIGN_VERIFY.ERROR,
|
||||
inputName: string,
|
||||
message: ?string
|
||||
}
|
||||
|
||||
const sign = (
|
||||
path: Array<number>,
|
||||
message: string,
|
||||
hex: boolean = false,
|
||||
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
const selected = getState().wallet.selectedDevice;
|
||||
if (!selected) return;
|
||||
|
||||
const response = await TrezorConnect.ethereumSignMessage({
|
||||
device: {
|
||||
path: selected.path,
|
||||
instance: selected.instance,
|
||||
state: selected.state,
|
||||
},
|
||||
path,
|
||||
hex,
|
||||
message,
|
||||
useEmptyPassphrase: selected.useEmptyPassphrase,
|
||||
});
|
||||
|
||||
if (response && response.success) {
|
||||
dispatch({
|
||||
type: SIGN_VERIFY.SIGN_SUCCESS,
|
||||
signSignature: response.payload.signature,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: NOTIFICATION.ADD,
|
||||
payload: {
|
||||
type: 'error',
|
||||
title: 'Sign error',
|
||||
message: response.payload.error,
|
||||
cancelable: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const verify = (
|
||||
address: string,
|
||||
message: string,
|
||||
signature: string,
|
||||
hex: boolean = false,
|
||||
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
const selected = getState().wallet.selectedDevice;
|
||||
if (!selected) return;
|
||||
const hasError = validateAddress(address);
|
||||
|
||||
if (hasError) {
|
||||
dispatch({
|
||||
type: SIGN_VERIFY.ERROR,
|
||||
inputName: 'verifyAddress',
|
||||
message: validateAddress(address),
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasError) {
|
||||
const response = await TrezorConnect.ethereumVerifyMessage({
|
||||
device: {
|
||||
path: selected.path,
|
||||
instance: selected.instance,
|
||||
state: selected.state,
|
||||
},
|
||||
address,
|
||||
message,
|
||||
signature,
|
||||
hex,
|
||||
useEmptyPassphrase: selected.useEmptyPassphrase,
|
||||
});
|
||||
|
||||
if (response && response.success) {
|
||||
dispatch({
|
||||
type: NOTIFICATION.ADD,
|
||||
payload: {
|
||||
type: 'success',
|
||||
title: 'Verify success',
|
||||
message: 'signature is valid',
|
||||
cancelable: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: NOTIFICATION.ADD,
|
||||
payload: {
|
||||
type: 'error',
|
||||
title: 'Verify error',
|
||||
message: response.payload.error,
|
||||
cancelable: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const inputChange = (inputName: string, value: string): ThunkAction => (dispatch: Dispatch): void => {
|
||||
dispatch({
|
||||
type: SIGN_VERIFY.INPUT_CHANGE,
|
||||
inputName,
|
||||
value,
|
||||
});
|
||||
dispatch({
|
||||
type: SIGN_VERIFY.TOUCH,
|
||||
inputName,
|
||||
});
|
||||
if (inputName === 'verifyAddress' && validateAddress(value) !== null) {
|
||||
dispatch({
|
||||
type: SIGN_VERIFY.ERROR,
|
||||
inputName,
|
||||
message: validateAddress(value),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const clearSign = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||
dispatch({
|
||||
type: SIGN_VERIFY.CLEAR_SIGN,
|
||||
});
|
||||
};
|
||||
|
||||
const clearVerify = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||
dispatch({
|
||||
type: SIGN_VERIFY.CLEAR_VERIFY,
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
sign,
|
||||
verify,
|
||||
clearSign,
|
||||
clearVerify,
|
||||
inputChange,
|
||||
};
|
7
src/actions/constants/signVerify.js
Normal file
7
src/actions/constants/signVerify.js
Normal file
@ -0,0 +1,7 @@
|
||||
/* @flow */
|
||||
export const SIGN_SUCCESS: 'sign__verify__sign__success' = 'sign__verify__sign__success';
|
||||
export const INPUT_CHANGE: 'sign__verify__input__change' = 'sign__verify__input__change';
|
||||
export const TOUCH: 'sign__verify__input__touch' = 'sign__verify__input__touch';
|
||||
export const CLEAR_SIGN: 'sign__verify__sign__clear' = 'sign__verify__sign__clear';
|
||||
export const CLEAR_VERIFY: 'sign__verify__verify__clear' = 'sign__verify__verify__clear';
|
||||
export const ERROR: 'sign__verify__error' = 'sign__verify__error';
|
@ -1,11 +1,13 @@
|
||||
import React from 'react';
|
||||
import Textarea from 'react-textarea-autosize';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from 'styled-components';
|
||||
import styled, { css } from 'styled-components';
|
||||
import colors from 'config/colors';
|
||||
import { FONT_SIZE, FONT_WEIGHT, FONT_FAMILY } from 'config/variables';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
@ -13,13 +15,12 @@ const Wrapper = styled.div`
|
||||
|
||||
const disabledColor = colors.TEXT_PRIMARY;
|
||||
|
||||
const StyledTextarea = styled.textarea`
|
||||
const StyledTextarea = styled(Textarea)`
|
||||
width: 100%;
|
||||
min-height: 85px;
|
||||
margin-bottom: 10px;
|
||||
padding: 6px 12px;
|
||||
padding: 10px 12px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid ${props => (props.borderColor ? props.borderColor : colors.DIVIDER)};
|
||||
border: 1px solid ${props => (props.colorBorder ? props.colorBorder : colors.DIVIDER)};
|
||||
border-radius: 2px;
|
||||
resize: none;
|
||||
outline: none;
|
||||
@ -27,7 +28,7 @@ const StyledTextarea = styled.textarea`
|
||||
color: ${colors.TEXT_PRIMARY};
|
||||
background: ${colors.WHITE};
|
||||
font-weight: ${FONT_WEIGHT.BASE};
|
||||
font-size: ${FONT_SIZE.BASE};
|
||||
font-size: ${FONT_SIZE.SMALL};
|
||||
white-space: pre-wrap; /* css-3 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
@ -81,10 +82,19 @@ const StyledTextarea = styled.textarea`
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
${props => props.trezorAction && css`
|
||||
z-index: 10001; /* bigger than modal container */
|
||||
border-color: ${colors.WHITE};
|
||||
border-width: 2px;
|
||||
transform: translate(-1px, -1px);
|
||||
background: ${colors.DIVIDER};
|
||||
pointer-events: none;
|
||||
`}
|
||||
`;
|
||||
|
||||
const TopLabel = styled.span`
|
||||
padding-bottom: 4px;
|
||||
padding-bottom: 8px;
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
`;
|
||||
|
||||
@ -105,7 +115,34 @@ const getColor = (inputState) => {
|
||||
return color;
|
||||
};
|
||||
|
||||
const Textarea = ({
|
||||
const TrezorAction = styled.div`
|
||||
display: ${props => (props.action ? 'flex' : 'none')};
|
||||
align-items: center;
|
||||
margin: 0px 10px;
|
||||
padding: 0 14px 0 5px;
|
||||
position: absolute;
|
||||
background: black;
|
||||
bottom: -25px;
|
||||
color: ${colors.WHITE};
|
||||
border-radius: 5px;
|
||||
line-height: 37px;
|
||||
z-index: 10002;
|
||||
transform: translate(-1px, -1px);
|
||||
`;
|
||||
|
||||
const ArrowUp = styled.div`
|
||||
position: absolute;
|
||||
top: -9px;
|
||||
left: 12px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 9px solid transparent;
|
||||
border-right: 9px solid transparent;
|
||||
border-bottom: 9px solid black;
|
||||
z-index: 10001;
|
||||
`;
|
||||
|
||||
const TextArea = ({
|
||||
className,
|
||||
placeholder = '',
|
||||
value,
|
||||
@ -113,28 +150,40 @@ const Textarea = ({
|
||||
onFocus,
|
||||
onBlur,
|
||||
isDisabled,
|
||||
name,
|
||||
onChange,
|
||||
topLabel,
|
||||
rows,
|
||||
maxRows,
|
||||
autoSelect,
|
||||
state = '',
|
||||
bottomText = '',
|
||||
trezorAction = null,
|
||||
}) => (
|
||||
<Wrapper
|
||||
className={className}
|
||||
>
|
||||
<Wrapper className={className}>
|
||||
{topLabel && (
|
||||
<TopLabel>{topLabel}</TopLabel>
|
||||
)}
|
||||
<StyledTextarea
|
||||
spellCheck="false"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
maxRows={maxRows}
|
||||
rows={rows}
|
||||
className={className}
|
||||
disabled={isDisabled}
|
||||
name={name}
|
||||
style={customStyle}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
value={value}
|
||||
onClick={autoSelect ? event => event.target.select() : null}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
borderColor={getColor(state)}
|
||||
/>
|
||||
<TrezorAction action={trezorAction}>
|
||||
<ArrowUp />{trezorAction}
|
||||
</TrezorAction>
|
||||
{bottomText && (
|
||||
<BottomText
|
||||
color={getColor(state)}
|
||||
@ -145,7 +194,7 @@ const Textarea = ({
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
Textarea.propTypes = {
|
||||
TextArea.propTypes = {
|
||||
className: PropTypes.string,
|
||||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
@ -153,10 +202,15 @@ Textarea.propTypes = {
|
||||
customStyle: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
maxRows: PropTypes.number,
|
||||
rows: PropTypes.number,
|
||||
name: PropTypes.string,
|
||||
isDisabled: PropTypes.bool,
|
||||
topLabel: PropTypes.node,
|
||||
state: PropTypes.string,
|
||||
autoSelect: PropTypes.bool,
|
||||
bottomText: PropTypes.string,
|
||||
trezorAction: PropTypes.node,
|
||||
};
|
||||
|
||||
export default Textarea;
|
||||
export default TextArea;
|
||||
|
@ -29,19 +29,19 @@ const InputIconWrapper = styled.div`
|
||||
`;
|
||||
|
||||
const TopLabel = styled.span`
|
||||
padding-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input`
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
height: ${props => (props.height ? `${props.height}px` : '40px')};
|
||||
padding: 5px ${props => (props.hasIcon ? '40px' : '12px')} 6px 12px;
|
||||
|
||||
line-height: 1.42857143;
|
||||
font-size: ${FONT_SIZE.SMALL};
|
||||
font-size: ${props => (props.isSmallText ? `${FONT_SIZE.SMALLER}` : `${FONT_SIZE.SMALL}`)};
|
||||
font-weight: ${FONT_WEIGHT.BASE};
|
||||
color: ${props => (props.color ? props.color : colors.TEXT_SECONDARY)};
|
||||
color: ${props => (props.color ? props.color : colors.TEXT)};
|
||||
|
||||
border-radius: 2px;
|
||||
|
||||
@ -55,6 +55,7 @@ const StyledInput = styled.input`
|
||||
|
||||
background-color: ${colors.WHITE};
|
||||
transition: ${TRANSITION.HOVER};
|
||||
|
||||
&:disabled {
|
||||
pointer-events: none;
|
||||
background: ${colors.GRAY_LIGHT};
|
||||
@ -111,7 +112,7 @@ const TrezorAction = styled.div`
|
||||
color: ${colors.WHITE};
|
||||
border-radius: 5px;
|
||||
line-height: 37px;
|
||||
z-index: 10001;
|
||||
z-index: 10002;
|
||||
transform: translate(-1px, -1px);
|
||||
`;
|
||||
|
||||
@ -171,6 +172,8 @@ class Input extends PureComponent {
|
||||
<Overlay isPartiallyHidden={this.props.isPartiallyHidden} />
|
||||
{this.props.icon}
|
||||
<StyledInput
|
||||
autoComplete="off"
|
||||
height={this.props.height}
|
||||
trezorAction={this.props.trezorAction}
|
||||
hasIcon={this.getIcon(this.props.state).length > 0}
|
||||
innerRef={this.props.innerRef}
|
||||
@ -178,10 +181,10 @@ class Input extends PureComponent {
|
||||
type={this.props.type}
|
||||
color={this.getColor(this.props.state)}
|
||||
placeholder={this.props.placeholder}
|
||||
autocomplete={this.props.autocomplete}
|
||||
autocorrect={this.props.autocorrect}
|
||||
autocapitalize={this.props.autocapitalize}
|
||||
autoCorrect={this.props.autocorrect}
|
||||
autoCapitalize={this.props.autocapitalize}
|
||||
spellCheck={this.props.spellCheck}
|
||||
isSmallText={this.props.isSmallText}
|
||||
value={this.props.value}
|
||||
readOnly={this.props.readOnly}
|
||||
onChange={this.props.onChange}
|
||||
@ -214,7 +217,7 @@ Input.propTypes = {
|
||||
innerRef: PropTypes.func,
|
||||
placeholder: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
autocomplete: PropTypes.string,
|
||||
height: PropTypes.number,
|
||||
autocorrect: PropTypes.string,
|
||||
autocapitalize: PropTypes.string,
|
||||
icon: PropTypes.node,
|
||||
@ -230,12 +233,14 @@ Input.propTypes = {
|
||||
sideAddons: PropTypes.arrayOf(PropTypes.node),
|
||||
isDisabled: PropTypes.bool,
|
||||
name: PropTypes.string,
|
||||
isSmallText: PropTypes.bool,
|
||||
isPartiallyHidden: PropTypes.bool,
|
||||
};
|
||||
|
||||
Input.defaultProps = {
|
||||
type: 'text',
|
||||
autoSelect: false,
|
||||
height: 40,
|
||||
};
|
||||
|
||||
export default Input;
|
||||
|
24
src/components/modals/confirm/Action/index.js
Normal file
24
src/components/modals/confirm/Action/index.js
Normal file
@ -0,0 +1,24 @@
|
||||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { H3 } from 'components/Heading';
|
||||
import ICONS from 'config/icons';
|
||||
import Icon from 'components/Icon';
|
||||
|
||||
const Wrapper = styled.div``;
|
||||
|
||||
const Header = styled.div`
|
||||
padding: 48px;
|
||||
`;
|
||||
|
||||
const ConfirmAction = () => (
|
||||
<Wrapper>
|
||||
<Header>
|
||||
<Icon icon={ICONS.T1} size={100} />
|
||||
<H3>Confirm action on your Trezor</H3>
|
||||
</Header>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export default ConfirmAction;
|
@ -7,7 +7,7 @@ import styled from 'styled-components';
|
||||
import colors from 'config/colors';
|
||||
import { FONT_SIZE } from 'config/variables';
|
||||
|
||||
import H3 from 'components/Heading';
|
||||
import { H3 } from 'components/Heading';
|
||||
import P from 'components/Paragraph';
|
||||
|
||||
import type { Props } from '../../Container';
|
||||
|
@ -17,6 +17,7 @@ import InvalidPin from 'components/modals/pin/Invalid';
|
||||
import Passphrase from 'components/modals/passphrase/Passphrase';
|
||||
import PassphraseType from 'components/modals/passphrase/Type';
|
||||
import ConfirmSignTx from 'components/modals/confirm/SignTx';
|
||||
import ConfirmAction from 'components/modals/confirm/Action';
|
||||
import ConfirmUnverifiedAddress from 'components/modals/confirm/UnverifiedAddress';
|
||||
import ForgetDevice from 'components/modals/device/Forget';
|
||||
import RememberDevice from 'components/modals/device/Remember';
|
||||
@ -90,6 +91,12 @@ const getDeviceContextModal = (props: Props) => {
|
||||
case 'ButtonRequest_SignTx':
|
||||
return <ConfirmSignTx device={modal.device} sendForm={props.sendForm} />;
|
||||
|
||||
case 'ButtonRequest_ProtectCall':
|
||||
return <ConfirmAction />;
|
||||
|
||||
case 'ButtonRequest_Other':
|
||||
return <ConfirmAction />;
|
||||
|
||||
case RECEIVE.REQUEST_UNVERIFIED:
|
||||
return (
|
||||
<ConfirmUnverifiedAddress
|
||||
|
@ -3,6 +3,7 @@ export default {
|
||||
BACKGROUND: '#EBEBEB',
|
||||
|
||||
TEXT: '#333333',
|
||||
WALLET_VIEW_TITLE: '#505050',
|
||||
|
||||
HEADER: '#1A1A1A',
|
||||
BODY: '#E3E3E3',
|
||||
|
@ -3,8 +3,8 @@ export const FONT_SIZE = {
|
||||
SMALLER: '12px',
|
||||
SMALL: '14px',
|
||||
BASE: '16px',
|
||||
WALLET_TITLE: '18px',
|
||||
TOP_MENU: '17px',
|
||||
WALLET_TITLE: '18px',
|
||||
BIG: '21px',
|
||||
BIGGER: '32px',
|
||||
BIGGEST: '36px',
|
||||
|
@ -25,6 +25,7 @@ import type { ModalAction } from 'actions/ModalActions';
|
||||
import type { NotificationAction } from 'actions/NotificationActions';
|
||||
import type { PendingTxAction } from 'actions/PendingTxActions';
|
||||
import type { ReceiveAction } from 'actions/ReceiveActions';
|
||||
import type { SignVerifyAction } from 'actions/SignVerifyActions';
|
||||
import type { SendFormAction } from 'actions/SendFormActions';
|
||||
import type { SummaryAction } from 'actions/SummaryActions';
|
||||
import type { TokenAction } from 'actions/TokenActions';
|
||||
@ -135,6 +136,7 @@ export type Action =
|
||||
| DiscoveryAction
|
||||
| StorageAction
|
||||
| LogAction
|
||||
| SignVerifyAction
|
||||
| ModalAction
|
||||
| NotificationAction
|
||||
| PendingTxAction
|
||||
|
@ -6,7 +6,6 @@ import * as RECEIVE from 'actions/constants/receive';
|
||||
import * as ACCOUNT from 'actions/constants/account';
|
||||
import type { Action } from 'flowtype';
|
||||
|
||||
|
||||
export type State = {
|
||||
addressVerified: boolean;
|
||||
addressUnverified: boolean;
|
||||
|
95
src/reducers/SignVerifyReducer.js
Normal file
95
src/reducers/SignVerifyReducer.js
Normal file
@ -0,0 +1,95 @@
|
||||
/* @flow */
|
||||
import type { Action } from 'flowtype';
|
||||
|
||||
import * as ACCOUNT from 'actions/constants/account';
|
||||
import * as ACTION from 'actions/constants/signVerify';
|
||||
|
||||
export type Error = {
|
||||
inputName: string,
|
||||
message: ?string,
|
||||
};
|
||||
|
||||
export type State = {
|
||||
signAddress: string,
|
||||
signMessage: string,
|
||||
signSignature: string,
|
||||
verifyAddress: string,
|
||||
verifyMessage: string,
|
||||
verifySignature: string,
|
||||
touched: Array<string>,
|
||||
errors: Array<Error>
|
||||
}
|
||||
|
||||
export const initialState: State = {
|
||||
signAddress: '',
|
||||
signMessage: '',
|
||||
signSignature: '',
|
||||
verifyAddress: '',
|
||||
verifyMessage: '',
|
||||
verifySignature: '',
|
||||
touched: [],
|
||||
errors: [],
|
||||
};
|
||||
|
||||
export default (state: State = initialState, action: Action): State => {
|
||||
switch (action.type) {
|
||||
case ACTION.SIGN_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
signSignature: action.signSignature,
|
||||
};
|
||||
|
||||
case ACTION.ERROR: {
|
||||
const { inputName } = action;
|
||||
if (!state.errors.some(e => e.inputName === inputName)) {
|
||||
const error = { inputName, message: action.message };
|
||||
return {
|
||||
...state,
|
||||
errors: [...state.errors, error],
|
||||
};
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
case ACTION.TOUCH: {
|
||||
const { inputName } = action;
|
||||
if (!state.touched.includes(inputName)) {
|
||||
return {
|
||||
...state,
|
||||
touched: [...state.touched, action.inputName],
|
||||
};
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
errors: state.errors.filter(error => error.inputName !== inputName),
|
||||
};
|
||||
}
|
||||
|
||||
case ACTION.INPUT_CHANGE: {
|
||||
const change = { [action.inputName]: action.value };
|
||||
return { ...state, ...change };
|
||||
}
|
||||
|
||||
case ACCOUNT.DISPOSE:
|
||||
return initialState;
|
||||
|
||||
case ACTION.CLEAR_SIGN:
|
||||
return {
|
||||
...state,
|
||||
signAddress: '',
|
||||
signMessage: '',
|
||||
signSignature: '',
|
||||
};
|
||||
|
||||
case ACTION.CLEAR_VERIFY:
|
||||
return {
|
||||
...state,
|
||||
verifyAddress: '',
|
||||
verifyMessage: '',
|
||||
verifySignature: '',
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
@ -20,6 +20,7 @@ import fiat from 'reducers/FiatRateReducer';
|
||||
import wallet from 'reducers/WalletReducer';
|
||||
import devices from 'reducers/DevicesReducer';
|
||||
import blockchain from 'reducers/BlockchainReducer';
|
||||
import signVerify from 'reducers/SignVerifyReducer';
|
||||
|
||||
const reducers = {
|
||||
router: routerReducer,
|
||||
@ -41,6 +42,7 @@ const reducers = {
|
||||
wallet,
|
||||
devices,
|
||||
blockchain,
|
||||
signVerify,
|
||||
};
|
||||
|
||||
export type Reducers = typeof reducers;
|
||||
|
@ -58,7 +58,7 @@ const Content = ({
|
||||
);
|
||||
|
||||
Content.propTypes = {
|
||||
children: PropTypes.element,
|
||||
children: PropTypes.oneOfType([PropTypes.element, PropTypes.array]),
|
||||
isLoading: PropTypes.bool,
|
||||
title: PropTypes.string,
|
||||
message: PropTypes.string,
|
||||
|
26
src/views/Wallet/components/Title/index.js
Normal file
26
src/views/Wallet/components/Title/index.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from 'styled-components';
|
||||
import colors from 'config/colors';
|
||||
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
font-size: ${FONT_SIZE.WALLET_TITLE};
|
||||
font-weight: ${FONT_WEIGHT.BASE};
|
||||
color: ${colors.WALLET_TITLE};
|
||||
padding-bottom: 35px;
|
||||
`;
|
||||
|
||||
const Title = ({
|
||||
children,
|
||||
}) => (
|
||||
<Wrapper>
|
||||
{children}
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
Title.propTypes = {
|
||||
children: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Title;
|
@ -31,6 +31,7 @@ const StyledNavLink = styled(NavLink)`
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
margin: 0px 4px;
|
||||
padding: 20px;
|
||||
white-space: nowrap;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
@ -64,7 +65,7 @@ class TopNavigationAccount extends React.PureComponent<Props> {
|
||||
<StyledNavLink exact to={`${basePath}`}>Summary</StyledNavLink>
|
||||
<StyledNavLink to={`${basePath}/receive`}>Receive</StyledNavLink>
|
||||
<StyledNavLink to={`${basePath}/send`}>Send</StyledNavLink>
|
||||
{/* <StyledNavLink to={`${basePath}/signverify`}>Sign & Verify</StyledNavLink> */}
|
||||
<StyledNavLink to={`${basePath}/signverify`}>Sign & Verify</StyledNavLink>
|
||||
<Indicator pathname={pathname} wrapper={() => this.wrapper} />
|
||||
</Wrapper>
|
||||
);
|
||||
|
@ -8,7 +8,7 @@ import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||
import type { State, Dispatch } from 'flowtype';
|
||||
import AccountSend from './index';
|
||||
|
||||
type OwnProps = { }
|
||||
type OwnProps = {}
|
||||
|
||||
export type StateProps = {
|
||||
selectedAccount: $ElementType<State, 'selectedAccount'>,
|
||||
|
@ -10,8 +10,8 @@ import Link from 'components/Link';
|
||||
import ICONS from 'config/icons';
|
||||
import { FONT_SIZE, FONT_WEIGHT, TRANSITION } from 'config/variables';
|
||||
import colors from 'config/colors';
|
||||
import Title from 'views/Wallet/components/Title';
|
||||
import P from 'components/Paragraph';
|
||||
import { H2 } from 'components/Heading';
|
||||
import Content from 'views/Wallet/components/Content';
|
||||
import type { Token } from 'flowtype';
|
||||
import AdvancedForm from './components/AdvancedForm';
|
||||
@ -34,7 +34,7 @@ const AmountInputLabel = styled.span`
|
||||
`;
|
||||
|
||||
const InputRow = styled.div`
|
||||
margin: 20px 0;
|
||||
padding: 0 0 15px 0;
|
||||
`;
|
||||
|
||||
const SetMaxAmountButton = styled(Button)`
|
||||
@ -250,155 +250,152 @@ const AccountSend = (props: Props) => {
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<React.Fragment>
|
||||
<H2>Send Ethereum or tokens</H2>
|
||||
<InputRow>
|
||||
<Input
|
||||
state={getAddressInputState(address, errors.address, warnings.address)}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
topLabel="Address"
|
||||
bottomText={errors.address || warnings.address || infos.address}
|
||||
value={address}
|
||||
onChange={event => onAddressChange(event.target.value)}
|
||||
/>
|
||||
</InputRow>
|
||||
|
||||
<InputRow>
|
||||
<Input
|
||||
state={getAmountInputState(errors.amount, warnings.amount)}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
topLabel={(
|
||||
<AmountInputLabelWrapper>
|
||||
<AmountInputLabel>Amount</AmountInputLabel>
|
||||
{(isCurrentCurrencyToken && selectedToken) && (
|
||||
<AmountInputLabel>You have: {selectedTokenBalance} {selectedToken.symbol}</AmountInputLabel>
|
||||
)}
|
||||
</AmountInputLabelWrapper>
|
||||
)}
|
||||
value={amount}
|
||||
onChange={event => onAmountChange(event.target.value)}
|
||||
bottomText={errors.amount || warnings.amount || infos.amount}
|
||||
sideAddons={[
|
||||
(
|
||||
<SetMaxAmountButton
|
||||
key="icon"
|
||||
onClick={() => onSetMax()}
|
||||
isActive={setMax}
|
||||
>
|
||||
{!setMax && (
|
||||
<Icon
|
||||
icon={ICONS.TOP}
|
||||
size={25}
|
||||
color={colors.TEXT_SECONDARY}
|
||||
/>
|
||||
)}
|
||||
{setMax && (
|
||||
<Icon
|
||||
icon={ICONS.CHECKED}
|
||||
size={25}
|
||||
color={colors.WHITE}
|
||||
/>
|
||||
)}
|
||||
Set max
|
||||
</SetMaxAmountButton>
|
||||
),
|
||||
(
|
||||
<CurrencySelect
|
||||
key="currency"
|
||||
isSearchable={false}
|
||||
isClearable={false}
|
||||
value={tokensSelectValue}
|
||||
isDisabled={tokensSelectData.length < 2}
|
||||
onChange={onCurrencyChange}
|
||||
options={tokensSelectData}
|
||||
/>
|
||||
),
|
||||
]}
|
||||
/>
|
||||
</InputRow>
|
||||
|
||||
<InputRow>
|
||||
<FeeLabelWrapper>
|
||||
<FeeLabel>Fee</FeeLabel>
|
||||
{gasPriceNeedsUpdate && (
|
||||
<UpdateFeeWrapper>
|
||||
<Icon
|
||||
icon={ICONS.WARNING}
|
||||
color={colors.WARNING_PRIMARY}
|
||||
size={20}
|
||||
/>
|
||||
Recommended fees updated. <StyledLink onClick={updateFeeLevels} isGreen>Click here to use them</StyledLink>
|
||||
</UpdateFeeWrapper>
|
||||
)}
|
||||
</FeeLabelWrapper>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
isClearable={false}
|
||||
value={selectedFeeLevel}
|
||||
onChange={onFeeLevelChange}
|
||||
options={feeLevels}
|
||||
formatOptionLabel={option => (
|
||||
<FeeOptionWrapper>
|
||||
<P>{option.value}</P>
|
||||
<P>{option.label}</P>
|
||||
</FeeOptionWrapper>
|
||||
)}
|
||||
/>
|
||||
</InputRow>
|
||||
|
||||
<ToggleAdvancedSettingsWrapper
|
||||
isAdvancedSettingsHidden={isAdvancedSettingsHidden}
|
||||
>
|
||||
<ToggleAdvancedSettingsButton
|
||||
isTransparent
|
||||
onClick={toggleAdvanced}
|
||||
>
|
||||
Advanced settings
|
||||
<AdvancedSettingsIcon
|
||||
icon={ICONS.ARROW_DOWN}
|
||||
color={colors.TEXT_SECONDARY}
|
||||
size={24}
|
||||
isActive={advanced}
|
||||
canAnimate
|
||||
/>
|
||||
</ToggleAdvancedSettingsButton>
|
||||
|
||||
{isAdvancedSettingsHidden && (
|
||||
<SendButton
|
||||
isDisabled={isSendButtonDisabled}
|
||||
isAdvancedSettingsHidden={isAdvancedSettingsHidden}
|
||||
onClick={() => onSend()}
|
||||
>
|
||||
{sendButtonText}
|
||||
</SendButton>
|
||||
<Title>Send Ethereum or tokens</Title>
|
||||
<InputRow>
|
||||
<Input
|
||||
state={getAddressInputState(address, errors.address, warnings.address)}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
topLabel="Address"
|
||||
bottomText={errors.address || warnings.address || infos.address}
|
||||
value={address}
|
||||
onChange={event => onAddressChange(event.target.value)}
|
||||
/>
|
||||
</InputRow>
|
||||
<InputRow>
|
||||
<Input
|
||||
state={getAmountInputState(errors.amount, warnings.amount)}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
topLabel={(
|
||||
<AmountInputLabelWrapper>
|
||||
<AmountInputLabel>Amount</AmountInputLabel>
|
||||
{(isCurrentCurrencyToken && selectedToken) && (
|
||||
<AmountInputLabel>You have: {selectedTokenBalance} {selectedToken.symbol}</AmountInputLabel>
|
||||
)}
|
||||
</AmountInputLabelWrapper>
|
||||
)}
|
||||
</ToggleAdvancedSettingsWrapper>
|
||||
value={amount}
|
||||
onChange={event => onAmountChange(event.target.value)}
|
||||
bottomText={errors.amount || warnings.amount || infos.amount}
|
||||
sideAddons={[
|
||||
(
|
||||
<SetMaxAmountButton
|
||||
key="icon"
|
||||
onClick={() => onSetMax()}
|
||||
isActive={setMax}
|
||||
>
|
||||
{!setMax && (
|
||||
<Icon
|
||||
icon={ICONS.TOP}
|
||||
size={25}
|
||||
color={colors.TEXT_SECONDARY}
|
||||
/>
|
||||
)}
|
||||
{setMax && (
|
||||
<Icon
|
||||
icon={ICONS.CHECKED}
|
||||
size={25}
|
||||
color={colors.WHITE}
|
||||
/>
|
||||
)}
|
||||
Set max
|
||||
</SetMaxAmountButton>
|
||||
),
|
||||
(
|
||||
<CurrencySelect
|
||||
key="currency"
|
||||
isSearchable={false}
|
||||
isClearable={false}
|
||||
value={tokensSelectValue}
|
||||
isDisabled={tokensSelectData.length < 2}
|
||||
onChange={onCurrencyChange}
|
||||
options={tokensSelectData}
|
||||
/>
|
||||
),
|
||||
]}
|
||||
/>
|
||||
</InputRow>
|
||||
|
||||
{advanced && (
|
||||
<AdvancedForm {...props}>
|
||||
<SendButton
|
||||
isDisabled={isSendButtonDisabled}
|
||||
onClick={() => onSend()}
|
||||
>
|
||||
{sendButtonText}
|
||||
</SendButton>
|
||||
</AdvancedForm>
|
||||
)}
|
||||
<InputRow>
|
||||
<FeeLabelWrapper>
|
||||
<FeeLabel>Fee</FeeLabel>
|
||||
{gasPriceNeedsUpdate && (
|
||||
<UpdateFeeWrapper>
|
||||
<Icon
|
||||
icon={ICONS.WARNING}
|
||||
color={colors.WARNING_PRIMARY}
|
||||
size={20}
|
||||
/>
|
||||
Recommended fees updated. <StyledLink onClick={updateFeeLevels} isGreen>Click here to use them</StyledLink>
|
||||
</UpdateFeeWrapper>
|
||||
)}
|
||||
</FeeLabelWrapper>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
isClearable={false}
|
||||
value={selectedFeeLevel}
|
||||
onChange={onFeeLevelChange}
|
||||
options={feeLevels}
|
||||
formatOptionLabel={option => (
|
||||
<FeeOptionWrapper>
|
||||
<P>{option.value}</P>
|
||||
<P>{option.label}</P>
|
||||
</FeeOptionWrapper>
|
||||
)}
|
||||
/>
|
||||
</InputRow>
|
||||
|
||||
{props.selectedAccount.pending.length > 0 && (
|
||||
<PendingTransactions
|
||||
pending={props.selectedAccount.pending}
|
||||
tokens={props.selectedAccount.tokens}
|
||||
network={network}
|
||||
<ToggleAdvancedSettingsWrapper
|
||||
isAdvancedSettingsHidden={isAdvancedSettingsHidden}
|
||||
>
|
||||
<ToggleAdvancedSettingsButton
|
||||
isTransparent
|
||||
onClick={toggleAdvanced}
|
||||
>
|
||||
Advanced settings
|
||||
<AdvancedSettingsIcon
|
||||
icon={ICONS.ARROW_DOWN}
|
||||
color={colors.TEXT_SECONDARY}
|
||||
size={24}
|
||||
isActive={advanced}
|
||||
canAnimate
|
||||
/>
|
||||
</ToggleAdvancedSettingsButton>
|
||||
|
||||
{isAdvancedSettingsHidden && (
|
||||
<SendButton
|
||||
isDisabled={isSendButtonDisabled}
|
||||
isAdvancedSettingsHidden={isAdvancedSettingsHidden}
|
||||
onClick={() => onSend()}
|
||||
>
|
||||
{sendButtonText}
|
||||
</SendButton>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</ToggleAdvancedSettingsWrapper>
|
||||
|
||||
{advanced && (
|
||||
<AdvancedForm {...props}>
|
||||
<SendButton
|
||||
isDisabled={isSendButtonDisabled}
|
||||
onClick={() => onSend()}
|
||||
>
|
||||
{sendButtonText}
|
||||
</SendButton>
|
||||
</AdvancedForm>
|
||||
)}
|
||||
|
||||
{props.selectedAccount.pending.length > 0 && (
|
||||
<PendingTransactions
|
||||
pending={props.selectedAccount.pending}
|
||||
tokens={props.selectedAccount.tokens}
|
||||
network={network}
|
||||
/>
|
||||
)}
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
40
src/views/Wallet/views/Account/SignVerify/Container.js
Normal file
40
src/views/Wallet/views/Account/SignVerify/Container.js
Normal file
@ -0,0 +1,40 @@
|
||||
/* @flow */
|
||||
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import SignVerifyActions from 'actions/SignVerifyActions';
|
||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||
import type { State, Dispatch } from 'flowtype';
|
||||
import Component from './index';
|
||||
|
||||
type OwnProps = {}
|
||||
|
||||
export type Error = {
|
||||
inputName: string,
|
||||
message: ?string,
|
||||
};
|
||||
|
||||
export type StateProps = {
|
||||
wallet: $ElementType<State, 'wallet'>,
|
||||
selectedAccount: $ElementType<State, 'selectedAccount'>,
|
||||
signVerify: $ElementType<State, 'signVerify'>,
|
||||
}
|
||||
|
||||
export type DispatchProps = {
|
||||
signVerifyActions: typeof SignVerifyActions,
|
||||
}
|
||||
|
||||
export type Props = StateProps & DispatchProps;
|
||||
|
||||
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
|
||||
wallet: state.wallet,
|
||||
selectedAccount: state.selectedAccount,
|
||||
signVerify: state.signVerify,
|
||||
});
|
||||
|
||||
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
||||
signVerifyActions: bindActionCreators(SignVerifyActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Component);
|
@ -1,21 +1,36 @@
|
||||
import React from 'react';
|
||||
/* @flow */
|
||||
import React, { Component } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import Input from 'components/inputs/Input';
|
||||
import Textarea from 'components/Textarea';
|
||||
import Title from 'views/Wallet/components/Title';
|
||||
import Button from 'components/Button';
|
||||
import Content from 'views/Wallet/components/Content';
|
||||
|
||||
import { H2 } from 'components/Heading';
|
||||
import colors from 'config/colors';
|
||||
|
||||
import type { Props } from './Container';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
margin-top: -5px;
|
||||
flex-direction: row;
|
||||
background: ${colors.WHITE};
|
||||
`;
|
||||
|
||||
const StyledH2 = styled(H2)`
|
||||
padding-bottom: 10px;
|
||||
const Row = styled.div`
|
||||
padding: 0 0 25px 0;
|
||||
`;
|
||||
|
||||
const RowButtons = styled(Row)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
margin-left: 10px;
|
||||
width: 110px;
|
||||
`;
|
||||
|
||||
const Column = styled.div`
|
||||
@ -30,34 +45,149 @@ const Verify = styled(Column)`
|
||||
padding-left: 20px;
|
||||
`;
|
||||
|
||||
const Label = styled.div`
|
||||
color: ${colors.LABEL};
|
||||
padding: 5px 0px;
|
||||
`;
|
||||
class SignVerify extends Component <Props> {
|
||||
getError(inputName: string) {
|
||||
if (!this.props.signVerify) return null;
|
||||
return this.props.signVerify.errors.find(e => e.inputName === inputName);
|
||||
}
|
||||
|
||||
const AccountSignVerify = () => (
|
||||
<Content>
|
||||
<Wrapper>
|
||||
<Sign>
|
||||
<StyledH2>Sign message</StyledH2>
|
||||
<Label>Message</Label>
|
||||
<Textarea rows="4" maxLength="255" />
|
||||
<Label>Address</Label>
|
||||
<Input type="text" />
|
||||
<Label>Signature</Label>
|
||||
<Textarea rows="4" maxLength="255" readOnly="readonly" />
|
||||
</Sign>
|
||||
<Verify>
|
||||
<StyledH2>Verify message</StyledH2>
|
||||
<Label>Message</Label>
|
||||
<Textarea rows="4" maxLength="255" />
|
||||
<Label>Address</Label>
|
||||
<Input type="text" />
|
||||
<Label>Signature</Label>
|
||||
<Textarea rows="4" maxLength="255" />
|
||||
</Verify>
|
||||
</Wrapper>
|
||||
</Content>
|
||||
);
|
||||
handleInputChange = (event: SyntheticInputEvent<Text>) => {
|
||||
this.props.signVerifyActions.inputChange(event.target.name, event.target.value);
|
||||
}
|
||||
|
||||
export default AccountSignVerify;
|
||||
render() {
|
||||
const device = this.props.wallet.selectedDevice;
|
||||
const {
|
||||
account, discovery, shouldRender, notification,
|
||||
} = this.props.selectedAccount;
|
||||
const { type, title, message } = notification;
|
||||
if (!device || !account || !discovery || !shouldRender) return <Content type={type} title={title} message={message} isLoading />;
|
||||
const {
|
||||
signVerifyActions,
|
||||
signVerify: {
|
||||
signMessage,
|
||||
signSignature,
|
||||
verifyAddress,
|
||||
verifyMessage,
|
||||
verifySignature,
|
||||
errors,
|
||||
},
|
||||
} = this.props;
|
||||
const verifyAddressError = this.getError('verifyAddress');
|
||||
return (
|
||||
<Content>
|
||||
<Title>Sign & Verify</Title>
|
||||
<Wrapper>
|
||||
<Sign>
|
||||
<Row>
|
||||
<Input
|
||||
topLabel="Address"
|
||||
name="signAddress"
|
||||
value={account.address}
|
||||
type="text"
|
||||
isSmallText
|
||||
autoSelect
|
||||
isDisabled
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<Textarea
|
||||
topLabel="Message"
|
||||
name="signMessage"
|
||||
value={signMessage}
|
||||
onChange={this.handleInputChange}
|
||||
rows={4}
|
||||
maxRows={4}
|
||||
maxLength="255"
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<Textarea
|
||||
topLabel="Signature"
|
||||
name="signSignature"
|
||||
value={signSignature}
|
||||
rows={4}
|
||||
autoSelect
|
||||
maxRows={4}
|
||||
maxLength="255"
|
||||
isDisabled
|
||||
/>
|
||||
</Row>
|
||||
<RowButtons>
|
||||
<Button
|
||||
onClick={this.props.signVerifyActions.clearSign}
|
||||
isWhite
|
||||
>Clear
|
||||
</Button>
|
||||
<StyledButton
|
||||
onClick={() => signVerifyActions.sign(account.addressPath, signMessage)}
|
||||
>Sign
|
||||
</StyledButton>
|
||||
</RowButtons>
|
||||
</Sign>
|
||||
<Verify>
|
||||
<Row>
|
||||
<Input
|
||||
topLabel="Address"
|
||||
autoSelect
|
||||
name="verifyAddress"
|
||||
value={verifyAddress}
|
||||
onChange={this.handleInputChange}
|
||||
type="text"
|
||||
state={verifyAddressError ? 'error' : null}
|
||||
bottomText={verifyAddressError ? verifyAddressError.message : null}
|
||||
isSmallText
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<Textarea
|
||||
topLabel="Message"
|
||||
name="verifyMessage"
|
||||
value={verifyMessage}
|
||||
onChange={this.handleInputChange}
|
||||
rows={4}
|
||||
maxRows={4}
|
||||
maxLength="255"
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<Textarea
|
||||
topLabel="Signature"
|
||||
autoSelect
|
||||
name="verifySignature"
|
||||
value={verifySignature}
|
||||
onChange={this.handleInputChange}
|
||||
rows={4}
|
||||
maxRows={4}
|
||||
maxLength="255"
|
||||
/>
|
||||
</Row>
|
||||
<RowButtons>
|
||||
<Button
|
||||
onClick={signVerifyActions.clearVerify}
|
||||
isWhite
|
||||
>Clear
|
||||
</Button>
|
||||
<StyledButton
|
||||
onClick={
|
||||
() => {
|
||||
if (errors.length <= 0) {
|
||||
signVerifyActions.verify(
|
||||
verifyAddress,
|
||||
verifyMessage,
|
||||
verifySignature,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
>Verify
|
||||
</StyledButton>
|
||||
</RowButtons>
|
||||
</Verify>
|
||||
</Wrapper>
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SignVerify;
|
@ -57,6 +57,7 @@ const StyledCoinLogo = styled(CoinLogo)`
|
||||
const StyledIcon = styled(Icon)`
|
||||
position: relative;
|
||||
top: -7px;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import WalletContainer from 'views/Wallet';
|
||||
import AccountSummary from 'views/Wallet/views/Account/Summary/Container';
|
||||
import AccountSend from 'views/Wallet/views/Account/Send/Container';
|
||||
import AccountReceive from 'views/Wallet/views/Account/Receive/Container';
|
||||
import AccountSignVerify from 'views/Wallet/views/Account/SignVerify';
|
||||
import AccountSignVerify from 'views/Wallet/views/Account/SignVerify/Container';
|
||||
|
||||
import WalletDashboard from 'views/Wallet/views/Dashboard';
|
||||
import WalletDeviceSettings from 'views/Wallet/views/DeviceSettings';
|
||||
|
@ -8500,9 +8500,15 @@ react-select@2.0.0:
|
||||
react-input-autosize "^2.2.1"
|
||||
react-transition-group "^2.2.1"
|
||||
|
||||
<<<<<<< HEAD
|
||||
react-textarea-autosize@^7.0.4:
|
||||
version "7.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.0.4.tgz#4e4be649b544a88713e7b5043f76950f35d3d503"
|
||||
=======
|
||||
react-textarea-autosize@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "http://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz#df91387f8a8f22020b77e3833c09829d706a09a5"
|
||||
>>>>>>> master
|
||||
dependencies:
|
||||
prop-types "^15.6.0"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user