Update dropdowns, inputs, and textareas (#1169)
* Align footer to bottom * Fix request payment offset padding * Update request payment padding * Add new Input and Dropdown components * Fix offset margins in equiv vals * Update all send tx inputs & dropdowns * Update generate wallet dropdowns * Update inputs & dropdowns for contracts tab * Add inputs & dropdowns for all but swap tab * amend * Fix imports * inputs are invalid when not disabled or readonly * Fix offset refresh button * Add togglable password back to wallet generation * Update swap inputs, textareas, and dropdowns * Update any outstanding inputs * Make UnitDropDown searchable * unitdropdown searchanble if options > 10 * Fix css issues * Reset before setting currentTo
This commit is contained in:
parent
95072d350a
commit
9ef1920fe0
|
@ -8,7 +8,7 @@ export interface Pairs {
|
|||
}
|
||||
|
||||
export interface SwapInput {
|
||||
id: string;
|
||||
label: string;
|
||||
amount: number | string;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import { AddressFieldFactory } from './AddressFieldFactory';
|
||||
import { donationAddressMap } from 'config';
|
||||
import translate from 'translations';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
isReadOnly?: boolean;
|
||||
|
@ -9,16 +11,20 @@ interface Props {
|
|||
export const AddressField: React.SFC<Props> = ({ isReadOnly }) => (
|
||||
<AddressFieldFactory
|
||||
withProps={({ currentTo, isValid, onChange, readOnly }) => (
|
||||
<React.Fragment>
|
||||
<input
|
||||
className={`form-control ${isValid ? 'is-valid' : 'is-invalid'}`}
|
||||
type="text"
|
||||
value={currentTo.raw}
|
||||
placeholder={donationAddressMap.ETH}
|
||||
readOnly={!!(isReadOnly || readOnly)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</React.Fragment>
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('SEND_addr')}</div>
|
||||
<Input
|
||||
className={`input-group-input ${isValid ? '' : 'invalid'}`}
|
||||
type="text"
|
||||
value={currentTo.raw}
|
||||
placeholder={donationAddressMap.ETH}
|
||||
readOnly={!!(isReadOnly || readOnly)}
|
||||
spellCheck={false}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Identicon, Spinner } from 'components/ui';
|
||||
import translate from 'translations';
|
||||
import { Query } from 'components/renderCbs';
|
||||
import { ICurrentTo, getCurrentTo, isValidCurrentTo } from 'selectors/transaction';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -49,7 +48,6 @@ class AddressInputFactoryClass extends Component<Props> {
|
|||
return (
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-11">
|
||||
<label>{translate('SEND_addr')}:</label>
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
withQuery={({ readOnly }) =>
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { AmountFieldFactory } from './AmountFieldFactory';
|
||||
import { UnitDropDown } from 'components';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
hasUnitDropdown?: boolean;
|
||||
|
@ -16,12 +17,12 @@ export const AmountField: React.SFC<Props> = ({
|
|||
}) => (
|
||||
<AmountFieldFactory
|
||||
withProps={({ currentValue: { raw }, isValid, onChange, readOnly }) => (
|
||||
<React.Fragment>
|
||||
<label>{translate('SEND_amount')}</label>
|
||||
<div className="input-group">
|
||||
<input
|
||||
className={`form-control ${
|
||||
isAmountValid(raw, customValidator, isValid) ? 'is-valid' : 'is-invalid'
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group input-group-inline-dropdown">
|
||||
<div className="input-group-header">{translate('SEND_amount')}</div>
|
||||
<Input
|
||||
className={`input-group-input ${
|
||||
isAmountValid(raw, customValidator, isValid) ? '' : 'invalid'
|
||||
}`}
|
||||
type="number"
|
||||
placeholder={translateRaw('SEND_amount_short')}
|
||||
|
@ -30,8 +31,8 @@ export const AmountField: React.SFC<Props> = ({
|
|||
onChange={onChange}
|
||||
/>
|
||||
{hasUnitDropdown && <UnitDropDown showAllTokens={showAllTokens} />}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
&-fiat-symbol {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
margin-right: 0.5rem;
|
||||
text-align: center;
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { HELP_ARTICLE } from 'config';
|
||||
import { isPositiveIntegerOrZero, isValidETHAddress } from 'libs/validators';
|
||||
import translate from 'translations';
|
||||
import { HelpLink } from 'components/ui';
|
||||
import { HelpLink, Input } from 'components/ui';
|
||||
import './AddCustomTokenForm.scss';
|
||||
import { Token } from 'types/network';
|
||||
|
||||
|
@ -42,7 +41,6 @@ export default class AddCustomTokenForm extends React.PureComponent<Props, State
|
|||
|
||||
public render() {
|
||||
const { address, symbol, decimal } = this.state;
|
||||
const inputClasses = 'AddCustom-field-input form-control input-sm';
|
||||
const errors = this.getErrors();
|
||||
|
||||
const fields = [
|
||||
|
@ -69,11 +67,10 @@ export default class AddCustomTokenForm extends React.PureComponent<Props, State
|
|||
return (
|
||||
<label className="AddCustom-field form-group" key={field.name}>
|
||||
<span className="AddCustom-field-label">{field.label}</span>
|
||||
<input
|
||||
className={classnames(
|
||||
inputClasses,
|
||||
errors[field.name] ? 'is-invalid' : field.value ? 'is-valid' : ''
|
||||
)}
|
||||
<Input
|
||||
className={`${
|
||||
errors[field.name] ? 'invalid' : field.value ? 'valid' : ''
|
||||
} AddCustom-field-input input-sm`}
|
||||
type="text"
|
||||
name={field.name}
|
||||
value={field.value}
|
||||
|
|
|
@ -2,21 +2,24 @@ import { DataFieldFactory } from './DataFieldFactory';
|
|||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
import { donationAddressMap } from 'config';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
export const DataField: React.SFC<{}> = () => (
|
||||
<DataFieldFactory
|
||||
withProps={({ data: { raw }, dataExists, onChange, readOnly }) => (
|
||||
<>
|
||||
<label>{translate('OFFLINE_Step2_Label_6')}</label>
|
||||
<input
|
||||
className={`form-control ${dataExists ? 'is-valid' : 'is-invalid'}`}
|
||||
type="text"
|
||||
placeholder={donationAddressMap.ETH}
|
||||
value={raw}
|
||||
readOnly={!!readOnly}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</>
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('OFFLINE_Step2_Label_6')}</div>
|
||||
<Input
|
||||
className={dataExists ? 'is-valid' : 'is-invalid'}
|
||||
type="text"
|
||||
placeholder={donationAddressMap.ETH}
|
||||
value={raw}
|
||||
readOnly={!!readOnly}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -4,6 +4,7 @@ import translate from 'translations';
|
|||
import { gasLimitValidator } from 'libs/validators';
|
||||
import { InlineSpinner } from 'components/ui/InlineSpinner';
|
||||
import './GasLimitField.scss';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
customLabel?: string;
|
||||
|
@ -13,22 +14,24 @@ interface Props {
|
|||
export const GasLimitField: React.SFC<Props> = ({ customLabel, disabled }) => (
|
||||
<GasLimitFieldFactory
|
||||
withProps={({ gasLimit: { raw }, onChange, readOnly, gasEstimationPending }) => (
|
||||
<React.Fragment>
|
||||
<div className="gaslimit-label-wrapper flex-wrapper">
|
||||
{customLabel ? <label>{customLabel} </label> : <label>{translate('TRANS_gas')} </label>}
|
||||
<div className="flex-spacer" />
|
||||
<InlineSpinner active={gasEstimationPending} text="Calculating" />
|
||||
</div>
|
||||
<input
|
||||
className={`form-control ${gasLimitValidator(raw) ? 'is-valid' : 'is-invalid'}`}
|
||||
type="number"
|
||||
placeholder="e.g. 21000"
|
||||
readOnly={!!readOnly}
|
||||
value={raw}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</React.Fragment>
|
||||
<div className="input-group-wrapper AdvancedGas-gas-price">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">
|
||||
{customLabel ? customLabel : translate('TRANS_gas')}
|
||||
<div className="flex-spacer" />
|
||||
<InlineSpinner active={gasEstimationPending} text="Calculating" />
|
||||
</div>
|
||||
<Input
|
||||
className={gasLimitValidator(raw) ? 'is-valid' : 'is-invalid'}
|
||||
type="number"
|
||||
placeholder="e.g. 21000"
|
||||
readOnly={!!readOnly}
|
||||
value={raw}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -60,27 +60,31 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
|
|||
handleClose={this.handleClose}
|
||||
>
|
||||
<form className="GenKeystore" onSubmit={this.handleSubmit}>
|
||||
<label className="GenKeystore-field">
|
||||
<h4 className="GenKeystore-field-label">Private Key</h4>
|
||||
<TogglablePassword
|
||||
name="privateKey"
|
||||
value={privateKey}
|
||||
disabled={!!privateKey}
|
||||
onChange={this.handleInput}
|
||||
placeholder="f1d0e0789c6d40f39..."
|
||||
isValid={isPrivateKeyValid}
|
||||
/>
|
||||
</label>
|
||||
<label className="GenKeystore-field">
|
||||
<h4 className="GenKeystore-field-label">Password</h4>
|
||||
<TogglablePassword
|
||||
name="password"
|
||||
value={password}
|
||||
onChange={this.handleInput}
|
||||
placeholder={translateRaw('Minimum 9 characters')}
|
||||
isValid={isPasswordValid}
|
||||
/>
|
||||
</label>
|
||||
<div className="input-group-wrapper GenKeystore-field">
|
||||
<label className="input-group input-group-inline-dropdown">
|
||||
<div className="input-group-header">Private Key</div>
|
||||
<TogglablePassword
|
||||
name="privateKey"
|
||||
value={privateKey}
|
||||
disabled={!!privateKey}
|
||||
onChange={this.handleInput}
|
||||
placeholder="f1d0e0789c6d40f39..."
|
||||
isValid={isPrivateKeyValid}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="input-group-wrapper GenKeystore-field">
|
||||
<label className="input-group input-group-inline-dropdown">
|
||||
<div className="input-group-header">Password</div>
|
||||
<TogglablePassword
|
||||
name="password"
|
||||
value={password}
|
||||
onChange={this.handleInput}
|
||||
placeholder={translateRaw('Minimum 9 characters')}
|
||||
isValid={isPasswordValid}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{!keystoreFile ? (
|
||||
<button
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import Modal, { IButton } from 'components/ui/Modal';
|
||||
import translate from 'translations';
|
||||
import { CustomNetworkConfig } from 'types/network';
|
||||
|
@ -13,10 +12,11 @@ import {
|
|||
getStaticNetworkConfigs
|
||||
} from 'selectors/config';
|
||||
import { CustomNode } from 'libs/nodes';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
const CUSTOM = 'custom';
|
||||
|
||||
interface Input {
|
||||
interface InputProps {
|
||||
name: string;
|
||||
placeholder?: string;
|
||||
type?: string;
|
||||
|
@ -228,13 +228,10 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private renderInput(input: Input, invalids: { [key: string]: boolean }) {
|
||||
private renderInput(input: InputProps, invalids: { [key: string]: boolean }) {
|
||||
return (
|
||||
<input
|
||||
className={classnames({
|
||||
'form-control': true,
|
||||
'is-invalid': this.state[input.name] && invalids[input.name]
|
||||
})}
|
||||
<Input
|
||||
className={`${this.state[input.name] && invalids[input.name] ? 'invalid' : ''}`}
|
||||
value={this.state[input.name]}
|
||||
onChange={this.handleChange}
|
||||
autoComplete="off"
|
||||
|
|
|
@ -81,8 +81,7 @@ $small-size: 900px;
|
|||
color: white;
|
||||
padding: 0;
|
||||
background: #274e7e;
|
||||
background-image: url('~assets/images/header-bg.jpg'),
|
||||
linear-gradient(130deg, #37709e, #274e7e);
|
||||
background-image: url('~assets/images/header-bg.jpg'), linear-gradient(130deg, #37709e, #274e7e);
|
||||
background-size: cover;
|
||||
background-position: left;
|
||||
|
||||
|
@ -111,8 +110,7 @@ $small-size: 900px;
|
|||
width: auto;
|
||||
padding: 8px;
|
||||
padding-right: 0;
|
||||
filter: drop-shadow(0 1px 0 rgba(#000, 0.12))
|
||||
drop-shadow(1px 1px 0 rgba(#000, 0.12));
|
||||
filter: drop-shadow(0 1px 0 rgba(#000, 0.12)) drop-shadow(1px 1px 0 rgba(#000, 0.12));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
addCustomNetwork
|
||||
} from 'actions/config';
|
||||
import logo from 'assets/images/logo-mycrypto.svg';
|
||||
import { Dropdown, ColorDropdown } from 'components/ui';
|
||||
import { OldDropDown, ColorDropdown } from 'components/ui';
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
@ -100,7 +100,7 @@ class Header extends Component<Props, State> {
|
|||
} = this.props;
|
||||
const { isAddingCustomNode } = this.state;
|
||||
const selectedLanguage = languageSelection;
|
||||
const LanguageDropDown = Dropdown as new () => Dropdown<typeof selectedLanguage>;
|
||||
const LanguageDropDown = OldDropDown as new () => OldDropDown<typeof selectedLanguage>;
|
||||
const options = nodeOptions.map(n => {
|
||||
if (n.isCustom) {
|
||||
const { label, isCustom, id, ...rest } = n;
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
&-refresh {
|
||||
@include reset-button;
|
||||
height: $input-height-base;
|
||||
opacity: 0.3;
|
||||
transition: opacity 300ms;
|
||||
|
||||
|
@ -31,16 +30,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
&-spinner {
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
&-spinner,
|
||||
&-refresh {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%) translateZ(0);
|
||||
margin: 0 1rem;
|
||||
bottom: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import translate from 'translations';
|
||||
import { NonceFieldFactory } from 'components/NonceFieldFactory';
|
||||
import Help from 'components/ui/Help';
|
||||
import { Spinner } from 'components/ui';
|
||||
import { Spinner, Input } from 'components/ui';
|
||||
import { connect } from 'react-redux';
|
||||
import { getNonceRequested, TGetNonceRequested } from 'actions/transaction';
|
||||
import { nonceRequestPending } from 'selectors/transaction';
|
||||
|
@ -32,19 +32,17 @@ class NonceField extends React.Component<Props> {
|
|||
<NonceFieldFactory
|
||||
withProps={({ nonce: { raw, value }, onChange, readOnly, shouldDisplay }) => {
|
||||
return alwaysDisplay || shouldDisplay ? (
|
||||
<React.Fragment>
|
||||
<div className="Nonce-label flex-wrapper">
|
||||
<label className="Nonce-label-text">{translate('OFFLINE_Step2_Label_5')}</label>
|
||||
<Help
|
||||
size="x1"
|
||||
link="https://support.mycrypto.com/transactions/what-is-nonce.html"
|
||||
/>
|
||||
</div>
|
||||
<div className="Nonce-field">
|
||||
<input
|
||||
className={`Nonce-field-input form-control ${
|
||||
!!value ? 'is-valid' : 'is-invalid'
|
||||
}`}
|
||||
<div className="input-group-wrapper Nonce-label">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">
|
||||
{translate('OFFLINE_Step2_Label_5')}
|
||||
<Help
|
||||
size="x1"
|
||||
link="https://support.mycrypto.com/transactions/what-is-nonce.html"
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
className={`Nonce-field-input ${!!value ? 'is-valid' : 'is-invalid'}`}
|
||||
type="number"
|
||||
placeholder="e.g. 7"
|
||||
value={raw}
|
||||
|
@ -63,8 +61,8 @@ class NonceField extends React.Component<Props> {
|
|||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</label>
|
||||
</div>
|
||||
) : null;
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -9,6 +9,7 @@ import React, { Component } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { ConfirmationModal } from 'components/ConfirmationModal';
|
||||
import { TextArea } from 'components/ui';
|
||||
|
||||
export interface CallbackProps {
|
||||
onClick(): void;
|
||||
|
@ -42,12 +43,7 @@ class SendButtonFactoryClass extends Component<Props> {
|
|||
? 'Transaction Parameters'
|
||||
: translate('SEND_raw')}
|
||||
</label>
|
||||
<textarea
|
||||
className="form-control"
|
||||
value={getStringifiedTx(serializedTransaction)}
|
||||
rows={4}
|
||||
readOnly={true}
|
||||
/>
|
||||
<TextArea value={getStringifiedTx(serializedTransaction)} rows={4} readOnly={true} />
|
||||
</div>
|
||||
{!onlyTransactionParameters && (
|
||||
<div className="col-sm-6">
|
||||
|
@ -56,12 +52,7 @@ class SendButtonFactoryClass extends Component<Props> {
|
|||
? 'Serialized Transaction Parameters'
|
||||
: translate('SEND_signed')}
|
||||
</label>
|
||||
<textarea
|
||||
className="form-control"
|
||||
value={addHexPrefix(serializedTransaction)}
|
||||
rows={4}
|
||||
readOnly={true}
|
||||
/>
|
||||
<TextArea value={addHexPrefix(serializedTransaction)} rows={4} readOnly={true} />
|
||||
</div>
|
||||
)}
|
||||
<OfflineBroadcast />
|
||||
|
|
|
@ -27,7 +27,7 @@ class SendEverythingClass extends Component<Props> {
|
|||
!readOnly ? (
|
||||
<span className="help-block">
|
||||
<a onClick={this.onSendEverything}>
|
||||
<span className="strong">{translate('SEND_TransferTotal')}</span>
|
||||
<span className="">{translate('SEND_TransferTotal')}</span>
|
||||
</a>
|
||||
</span>
|
||||
) : null
|
||||
|
|
|
@ -111,11 +111,9 @@ class TXMetaDataPanel extends React.Component<Props, State> {
|
|||
!disableToggle && (
|
||||
<div className="help-block">
|
||||
<a className="Gas-toggle" onClick={this.toggleAdvanced}>
|
||||
<strong>
|
||||
{showAdvanced
|
||||
? `- ${translateRaw('Back to simple')}`
|
||||
: `+ ${translateRaw('Advanced Settings')}`}
|
||||
</strong>
|
||||
{showAdvanced
|
||||
? `- ${translateRaw('Back to simple')}`
|
||||
: `+ ${translateRaw('Advanced Settings')}`}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import FeeSummary from './FeeSummary';
|
||||
import './AdvancedGas.scss';
|
||||
|
@ -11,6 +10,7 @@ import { connect } from 'react-redux';
|
|||
import { getAutoGasLimitEnabled } from 'selectors/config';
|
||||
import { isValidGasPrice } from 'selectors/transaction';
|
||||
import { sanitizeNumericalInput } from 'libs/values';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
export interface AdvancedOptions {
|
||||
gasPriceField?: boolean;
|
||||
|
@ -71,17 +71,19 @@ class AdvancedGas extends React.Component<Props, State> {
|
|||
|
||||
<div className="AdvancedGas-flex-wrapper flex-wrapper">
|
||||
{gasPriceField && (
|
||||
<div className="AdvancedGas-gas-price">
|
||||
<label>{translate('OFFLINE_Step2_Label_3')} (gwei)</label>
|
||||
<input
|
||||
className={classnames('form-control', {
|
||||
'is-invalid': !!gasPrice.raw && !validGasPrice
|
||||
})}
|
||||
type="number"
|
||||
placeholder="40"
|
||||
value={gasPrice.raw}
|
||||
onChange={this.handleGasPriceChange}
|
||||
/>
|
||||
<div className="input-group-wrapper AdvancedGas-gas-price">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">
|
||||
{translate('OFFLINE_Step2_Label_3')} (gwei)
|
||||
</div>
|
||||
<Input
|
||||
className={!!gasPrice.raw && !validGasPrice ? 'is-invalid' : ''}
|
||||
type="number"
|
||||
placeholder="40"
|
||||
value={gasPrice.raw}
|
||||
onChange={this.handleGasPriceChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
// yourself, otherwise all visibiility changes are managed in internal state.
|
||||
import React from 'react';
|
||||
import './TogglablePassword.scss';
|
||||
import { Input, TextArea } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
// Shared props
|
||||
className?: string;
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
name?: string;
|
||||
|
@ -46,6 +48,7 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
|
|||
|
||||
public render() {
|
||||
const {
|
||||
className,
|
||||
value,
|
||||
placeholder,
|
||||
name,
|
||||
|
@ -66,10 +69,10 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
|
|||
: isValid === null || isValid === undefined ? '' : isValid ? 'is-valid' : 'is-invalid';
|
||||
|
||||
return (
|
||||
<div className="TogglablePassword input-group">
|
||||
<div className={`TogglablePassword input-group input-group-inline-dropdown ${className}`}>
|
||||
{isTextareaWhenVisible && isVisible ? (
|
||||
<textarea
|
||||
className={`form-control ${validClass}`}
|
||||
<TextArea
|
||||
className={validClass}
|
||||
value={value}
|
||||
name={name}
|
||||
disabled={disabled}
|
||||
|
@ -82,12 +85,12 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
|
|||
aria-label={ariaLabel}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
<Input
|
||||
value={value}
|
||||
name={name}
|
||||
disabled={disabled}
|
||||
type={isVisible ? 'text' : 'password'}
|
||||
className={`form-control ${validClass}`}
|
||||
className={`${validClass}`}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
onFocus={onFocus}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
import { Identicon, UnitDisplay, NewTabLink, Address } from 'components/ui';
|
||||
import { Identicon, UnitDisplay, NewTabLink, TextArea, Address } from 'components/ui';
|
||||
import { TransactionData, TransactionReceipt } from 'libs/nodes';
|
||||
import { NetworkConfig } from 'types/network';
|
||||
import './TransactionDataTable.scss';
|
||||
|
@ -152,9 +152,7 @@ const TransactionDataTable: React.SFC<Props> = ({ data, receipt, network }) => {
|
|||
},
|
||||
{
|
||||
label: translate('TRANS_data'),
|
||||
data: hasInputData ? (
|
||||
<textarea className="form-control" value={data.input} disabled={true} />
|
||||
) : null
|
||||
data: hasInputData ? <TextArea value={data.input} disabled={true} /> : null
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import { setUnitMeta, TSetUnitMeta } from 'actions/transaction';
|
||||
import Dropdown from 'components/ui/Dropdown';
|
||||
import { withConditional } from 'components/hocs';
|
||||
import { TokenBalance, MergedToken, getShownTokenBalances, getTokens } from 'selectors/wallet';
|
||||
import { Query } from 'components/renderCbs';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -22,34 +21,29 @@ interface StateProps {
|
|||
network: NetworkConfig;
|
||||
}
|
||||
|
||||
const StringDropdown = Dropdown as new () => Dropdown<string>;
|
||||
const ConditionalStringDropDown = withConditional(StringDropdown);
|
||||
|
||||
class UnitDropdownClass extends Component<DispatchProps & StateProps> {
|
||||
public render() {
|
||||
const { tokens, allTokens, showAllTokens, unit, network } = this.props;
|
||||
const focusedTokens = showAllTokens ? allTokens : tokens;
|
||||
const options = [network.unit, ...getTokenSymbols(focusedTokens)];
|
||||
return (
|
||||
<div className="input-group-btn">
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
withQuery={({ readOnly }) => (
|
||||
<ConditionalStringDropDown
|
||||
options={[network.unit, ...getTokenSymbols(focusedTokens)]}
|
||||
value={unit === 'ether' ? network.unit : unit}
|
||||
condition={!readOnly}
|
||||
conditionalProps={{
|
||||
onChange: this.handleOnChange
|
||||
}}
|
||||
ariaLabel={'dropdown'}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
withQuery={({ readOnly }) => (
|
||||
<Dropdown
|
||||
options={options}
|
||||
value={unit === 'ether' ? network.unit : unit}
|
||||
onChange={this.handleOnChange}
|
||||
clearable={false}
|
||||
searchable={options.length > 10}
|
||||
disabled={!!readOnly}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
private handleOnChange = (unit: string) => {
|
||||
this.props.setUnitMeta(unit);
|
||||
private handleOnChange = unit => {
|
||||
this.props.setUnitMeta(unit.value);
|
||||
};
|
||||
}
|
||||
const getTokenSymbols = (tokens: (TokenBalance | MergedToken)[]) => tokens.map(t => t.symbol);
|
||||
|
|
|
@ -13,7 +13,7 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import { getTokens, MergedToken } from 'selectors/wallet';
|
||||
import { UnitDisplay } from 'components/ui';
|
||||
import { UnitDisplay, Input } from 'components/ui';
|
||||
import './DeterministicWalletsModal.scss';
|
||||
import { StaticNetworkConfig } from 'types/network';
|
||||
import Select from 'react-select';
|
||||
|
@ -97,7 +97,6 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
walletType
|
||||
} = this.props;
|
||||
const { selectedAddress, customPath, page } = this.state;
|
||||
const validPathClass = isValidPath(customPath) ? 'is-valid' : 'is-invalid';
|
||||
|
||||
const buttons: IButton[] = [
|
||||
{
|
||||
|
@ -137,8 +136,8 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
/>
|
||||
{/* TODO/Hack - Custom Paths are temporarily disabled. `false` is used for smallest diff */}
|
||||
{false && (
|
||||
<input
|
||||
className={`form-control ${validPathClass}`}
|
||||
<Input
|
||||
className={isValidPath(customPath) ? '' : 'invalid'}
|
||||
value={customPath}
|
||||
placeholder="m/44'/60'/0'/0"
|
||||
onChange={this.handleChangeCustomPath}
|
||||
|
|
|
@ -3,6 +3,7 @@ import React, { PureComponent } from 'react';
|
|||
import translate, { translateRaw } from 'translations';
|
||||
import Spinner from 'components/ui/Spinner';
|
||||
import { TShowNotification } from 'actions/notifications';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
export interface KeystoreValue {
|
||||
file: string;
|
||||
|
@ -36,7 +37,7 @@ export class KeystoreDecrypt extends PureComponent {
|
|||
};
|
||||
|
||||
public render() {
|
||||
const { isWalletPending, isPasswordPending, value: { file, password } } = this.props;
|
||||
const { isWalletPending, value: { file, password } } = this.props;
|
||||
const passReq = isPassRequired(file);
|
||||
const unlockDisabled = !file || (passReq && !password);
|
||||
|
||||
|
@ -44,7 +45,7 @@ export class KeystoreDecrypt extends PureComponent {
|
|||
<form id="selectedUploadKey" onSubmit={this.unlock}>
|
||||
<div className="form-group">
|
||||
<input
|
||||
className={'hidden'}
|
||||
className="hidden"
|
||||
type="file"
|
||||
id="fselector"
|
||||
onChange={this.handleFileSelection}
|
||||
|
@ -55,17 +56,16 @@ export class KeystoreDecrypt extends PureComponent {
|
|||
</a>
|
||||
</label>
|
||||
{isWalletPending ? <Spinner /> : ''}
|
||||
<div className={file.length && isPasswordPending ? '' : 'hidden'}>
|
||||
<p>{translate('ADD_Label_3')}</p>
|
||||
<input
|
||||
className={`form-control ${password.length > 0 ? 'is-valid' : 'is-invalid'}`}
|
||||
value={password}
|
||||
onChange={this.onPasswordChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={translateRaw('x_Password')}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
className={`${password.length > 0 ? 'is-valid' : 'is-invalid'} ${
|
||||
file.length && isWalletPending ? 'hidden' : ''
|
||||
}`}
|
||||
value={password}
|
||||
onChange={this.onPasswordChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={translateRaw('x_Password')}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button className="btn btn-primary btn-block" disabled={unlockDisabled}>
|
||||
|
|
|
@ -8,6 +8,7 @@ import { AppState } from 'reducers';
|
|||
import { connect } from 'react-redux';
|
||||
import { getSingleDPath, getPaths } from 'selectors/config/wallet';
|
||||
import { TogglablePassword } from 'components';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface OwnProps {
|
||||
onUnlock(param: any): void;
|
||||
|
@ -63,8 +64,7 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
|
|||
</div>
|
||||
<div className="form-group">
|
||||
<p>Password (optional):</p>
|
||||
<input
|
||||
className="form-control"
|
||||
<Input
|
||||
value={pass}
|
||||
onChange={this.onPasswordChange}
|
||||
placeholder={translateRaw('x_Password')}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { stripHexPrefix } from 'libs/values';
|
|||
import React, { PureComponent } from 'react';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { TogglablePassword } from 'components';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
export interface PrivateKeyValue {
|
||||
key: string;
|
||||
|
@ -53,29 +54,32 @@ export class PrivateKeyDecrypt extends PureComponent<Props> {
|
|||
|
||||
return (
|
||||
<form id="selectedTypeKey" onSubmit={this.unlock}>
|
||||
<div className="form-group">
|
||||
<TogglablePassword
|
||||
value={key}
|
||||
rows={4}
|
||||
placeholder={translateRaw('x_PrivKey2')}
|
||||
isValid={isValidPkey}
|
||||
isTextareaWhenVisible={true}
|
||||
onChange={this.onPkeyChange}
|
||||
onEnter={this.props.onUnlock}
|
||||
/>
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group">
|
||||
<TogglablePassword
|
||||
value={key}
|
||||
rows={4}
|
||||
placeholder={translateRaw('x_PrivKey2')}
|
||||
isValid={isValidPkey}
|
||||
onChange={this.onPkeyChange}
|
||||
onEnter={this.props.onUnlock}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{isValidPkey &&
|
||||
isPassRequired && (
|
||||
<div className="form-group">
|
||||
<p>{translate('ADD_Label_3')}</p>
|
||||
<input
|
||||
className={`form-control ${password.length > 0 ? 'is-valid' : 'is-invalid'}`}
|
||||
value={password}
|
||||
onChange={this.onPasswordChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={translateRaw('x_Password')}
|
||||
type="password"
|
||||
/>
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('ADD_Label_3')}</div>
|
||||
<Input
|
||||
className={`form-control ${password.length > 0 ? 'is-valid' : 'is-invalid'}`}
|
||||
value={password}
|
||||
onChange={this.onPasswordChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={translateRaw('x_Password')}
|
||||
type="password"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<button className="btn btn-block btn-primary" disabled={unlockDisabled}>
|
||||
|
|
|
@ -3,6 +3,7 @@ import translate from 'translations';
|
|||
import { donationAddressMap } from 'config';
|
||||
import { isValidETHAddress } from 'libs/validators';
|
||||
import { AddressOnlyWallet } from 'libs/wallet';
|
||||
import { TextArea } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
onUnlock(param: any): void;
|
||||
|
@ -24,10 +25,8 @@ export class ViewOnlyDecrypt extends PureComponent<Props, State> {
|
|||
return (
|
||||
<div id="selectedUploadKey">
|
||||
<form className="form-group" onSubmit={this.openWallet}>
|
||||
<textarea
|
||||
className={`form-control
|
||||
${isValid ? 'is-valid' : 'is-invalid'}
|
||||
`}
|
||||
<TextArea
|
||||
className={isValid ? 'is-valid' : 'is-invalid'}
|
||||
value={address}
|
||||
onChange={this.changeAddress}
|
||||
onKeyDown={this.handleEnterKey}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { withConditional } from 'components/hocs';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
const Input: React.SFC<React.InputHTMLAttributes<any>> = props => <input {...props} />;
|
||||
export const ConditionalInput = withConditional(Input);
|
||||
const inpt: React.SFC<React.InputHTMLAttributes<any>> = props => <Input {...props} />;
|
||||
export const ConditionalInput = withConditional(inpt);
|
||||
|
|
|
@ -1,123 +1,63 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import DropdownShell from './DropdownShell';
|
||||
import React from 'react';
|
||||
import Select, { ReactSelectProps } from 'react-select';
|
||||
|
||||
interface Props<T> {
|
||||
value: T | undefined;
|
||||
options: T[];
|
||||
ariaLabel: string;
|
||||
label?: string;
|
||||
extra?: any;
|
||||
size?: string;
|
||||
color?: string;
|
||||
menuAlign?: string;
|
||||
formatTitle?(option: T): any;
|
||||
onChange?(value: T): void;
|
||||
interface Props extends ReactSelectProps {
|
||||
className?: string;
|
||||
options: any;
|
||||
onChange: any;
|
||||
}
|
||||
|
||||
interface State {
|
||||
search: string;
|
||||
}
|
||||
|
||||
export default class DropdownComponent<T> extends PureComponent<Props<T>, State> {
|
||||
export default class Dropdown extends React.Component<Props> {
|
||||
public state = {
|
||||
search: ''
|
||||
selectedOption: { value: '', label: '' },
|
||||
hasBlurred: false
|
||||
};
|
||||
|
||||
private dropdownShell: DropdownShell | null;
|
||||
public handleChange = selectedOption => {
|
||||
this.setState({ selectedOption });
|
||||
};
|
||||
|
||||
public formatOptions = options => {
|
||||
if (typeof options[0] === 'object') {
|
||||
return options;
|
||||
}
|
||||
const formatted = options.map(opt => {
|
||||
return { value: opt, label: opt };
|
||||
});
|
||||
return formatted;
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { ariaLabel, color, size } = this.props;
|
||||
const { onChange } = this.props;
|
||||
const { selectedOption } = this.state;
|
||||
const value = selectedOption && selectedOption.value;
|
||||
const options = this.formatOptions(this.props.options);
|
||||
|
||||
return (
|
||||
<DropdownShell
|
||||
renderLabel={this.renderLabel}
|
||||
renderOptions={this.renderOptions}
|
||||
size={size}
|
||||
color={color}
|
||||
ariaLabel={ariaLabel}
|
||||
ref={el => (this.dropdownShell = el)}
|
||||
<Select
|
||||
// use ref to prevent <label /> from stealing focus when used inline with an input
|
||||
ref={el => {
|
||||
if (!!el && !!(el as any).control) {
|
||||
(el as any).control.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
}}
|
||||
className={`${this.props.className} ${this.state.hasBlurred ? 'has-blurred' : ''}`}
|
||||
value={value}
|
||||
onChange={obj => {
|
||||
this.handleChange(obj);
|
||||
onChange();
|
||||
}}
|
||||
{...this.props}
|
||||
onBlur={e => {
|
||||
this.setState({ hasBlurred: true });
|
||||
if (this.props && this.props.onBlur) {
|
||||
this.props.onBlur(e);
|
||||
}
|
||||
}}
|
||||
options={options}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderLabel = () => {
|
||||
const { value } = this.props;
|
||||
const labelStr = this.props.label ? `${this.props.label}:` : '';
|
||||
return (
|
||||
<span>
|
||||
{labelStr} {this.formatTitle(value)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
private renderOptions = () => {
|
||||
const { options, value, menuAlign, extra } = this.props;
|
||||
const { search } = this.state;
|
||||
const searchable = options.length > 20;
|
||||
const menuClass = classnames({
|
||||
'dropdown-menu': true,
|
||||
[`dropdown-menu-${menuAlign || ''}`]: !!menuAlign
|
||||
});
|
||||
const searchableStyle = {
|
||||
maxHeight: '300px',
|
||||
overflowY: 'auto'
|
||||
};
|
||||
const searchRegex = new RegExp(search, 'gi');
|
||||
const onSearchChange = e => {
|
||||
this.setState({ search: e.target.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<ul className={menuClass} style={searchable ? searchableStyle : {}}>
|
||||
{searchable && (
|
||||
<input
|
||||
className="form-control"
|
||||
placeholder={'Search'}
|
||||
onChange={onSearchChange}
|
||||
value={search}
|
||||
/>
|
||||
)}
|
||||
|
||||
{options
|
||||
.filter(option => {
|
||||
if (searchable && search.length) {
|
||||
return option.toString().match(searchRegex);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((option, i) => {
|
||||
return (
|
||||
<li key={i}>
|
||||
<a
|
||||
className={option === value ? 'active' : ''}
|
||||
onClick={this.onChange.bind(null, option)}
|
||||
>
|
||||
{this.props.formatTitle ? this.formatTitle(option) : option}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{extra && <li key={'separator'} role="separator" className="divider" />}
|
||||
{extra}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
private formatTitle = (option: any) => {
|
||||
if (this.props.formatTitle) {
|
||||
return this.props.formatTitle(option);
|
||||
} else {
|
||||
return option;
|
||||
}
|
||||
};
|
||||
|
||||
private onChange = (value: any) => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
if (this.dropdownShell) {
|
||||
this.dropdownShell.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
.Help {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
padding: 4px;
|
||||
box-sizing: content-box;
|
||||
box-sizing: border-box;
|
||||
line-height: inherit;
|
||||
vertical-align: top;
|
||||
transition: opacity 0.3s;
|
||||
|
@ -14,17 +13,17 @@
|
|||
}
|
||||
|
||||
&-x1 {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
&-x2 {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
&-x3 {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
@import 'common/sass/variables';
|
||||
|
||||
.example-form {
|
||||
max-width: 744px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> .TogglablePassword {
|
||||
width: 100%;
|
||||
}
|
||||
&-header {
|
||||
display: flex;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 400;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
> *:first-child {
|
||||
margin-right: 8px;
|
||||
}
|
||||
> .flex-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
> .small {
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
}
|
||||
&-input {
|
||||
width: 100%;
|
||||
border: 1px solid #e5ecf3;
|
||||
border-radius: 2px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
box-shadow: inset 0 1px 0 0 rgba(63, 63, 68, 0.05);
|
||||
transition: border-color 120ms, box-shadow 120ms;
|
||||
margin-bottom: 1rem;
|
||||
&::placeholder {
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
&:not([disabled]):not([readonly]) {
|
||||
&.invalid.has-blurred.has-value {
|
||||
border-color: $brand-danger;
|
||||
box-shadow: inset 0px 0px 0px 1px $brand-danger;
|
||||
}
|
||||
&:focus {
|
||||
border-color: #4295bc;
|
||||
box-shadow: inset 0px 0px 0px 1px #4295bc;
|
||||
&.valid {
|
||||
border-color: #8dd17b;
|
||||
box-shadow: inset 0px 0px 0px 1px #8dd17b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-group-inline-dropdown {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 1rem;
|
||||
flex-wrap: wrap;
|
||||
> .input-group-header {
|
||||
width: 100%;
|
||||
}
|
||||
> .input-group-input {
|
||||
flex-grow: 1;
|
||||
width: auto;
|
||||
}
|
||||
> .Select {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.Swap-dropdown {
|
||||
.Select-input {
|
||||
left: 24px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import React, { HTMLProps } from 'react';
|
||||
import './Input.scss';
|
||||
|
||||
interface State {
|
||||
hasBlurred: boolean;
|
||||
}
|
||||
|
||||
class Input extends React.Component<HTMLProps<HTMLInputElement>, State> {
|
||||
public state: State = {
|
||||
hasBlurred: false
|
||||
};
|
||||
public render() {
|
||||
return (
|
||||
<input
|
||||
{...this.props}
|
||||
onBlur={e => {
|
||||
this.setState({ hasBlurred: true });
|
||||
if (this.props && this.props.onBlur) {
|
||||
this.props.onBlur(e);
|
||||
}
|
||||
}}
|
||||
className={`input-group-input ${this.props.className} ${
|
||||
this.state.hasBlurred ? 'has-blurred' : ''
|
||||
} ${!!this.props.value && this.props.value.toString().length > 0 ? 'has-value' : ''}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Input;
|
|
@ -0,0 +1,123 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import DropdownShell from './DropdownShell';
|
||||
|
||||
interface Props<T> {
|
||||
value: T | undefined;
|
||||
options: T[];
|
||||
ariaLabel: string;
|
||||
label?: string;
|
||||
extra?: any;
|
||||
size?: string;
|
||||
color?: string;
|
||||
menuAlign?: string;
|
||||
formatTitle?(option: T): any;
|
||||
onChange?(value: T): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
search: string;
|
||||
}
|
||||
|
||||
export default class DropdownComponent<T> extends PureComponent<Props<T>, State> {
|
||||
public state = {
|
||||
search: ''
|
||||
};
|
||||
|
||||
private dropdownShell: DropdownShell | null;
|
||||
|
||||
public render() {
|
||||
const { ariaLabel, color, size } = this.props;
|
||||
|
||||
return (
|
||||
<DropdownShell
|
||||
renderLabel={this.renderLabel}
|
||||
renderOptions={this.renderOptions}
|
||||
size={size}
|
||||
color={color}
|
||||
ariaLabel={ariaLabel}
|
||||
ref={el => (this.dropdownShell = el)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderLabel = () => {
|
||||
const { value } = this.props;
|
||||
const labelStr = this.props.label ? `${this.props.label}:` : '';
|
||||
return (
|
||||
<span>
|
||||
{labelStr} {this.formatTitle(value)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
private renderOptions = () => {
|
||||
const { options, value, menuAlign, extra } = this.props;
|
||||
const { search } = this.state;
|
||||
const searchable = options.length > 20;
|
||||
const menuClass = classnames({
|
||||
'dropdown-menu': true,
|
||||
[`dropdown-menu-${menuAlign || ''}`]: !!menuAlign
|
||||
});
|
||||
const searchableStyle = {
|
||||
maxHeight: '300px',
|
||||
overflowY: 'auto'
|
||||
};
|
||||
const searchRegex = new RegExp(search, 'gi');
|
||||
const onSearchChange = e => {
|
||||
this.setState({ search: e.target.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<ul className={menuClass} style={searchable ? searchableStyle : {}}>
|
||||
{searchable && (
|
||||
<input
|
||||
className="form-control"
|
||||
placeholder={'Search'}
|
||||
onChange={onSearchChange}
|
||||
value={search}
|
||||
/>
|
||||
)}
|
||||
|
||||
{options
|
||||
.filter(option => {
|
||||
if (searchable && search.length) {
|
||||
return option.toString().match(searchRegex);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((option, i) => {
|
||||
return (
|
||||
<li key={i}>
|
||||
<a
|
||||
className={option === value ? 'active' : ''}
|
||||
onClick={this.onChange.bind(null, option)}
|
||||
>
|
||||
{this.props.formatTitle ? this.formatTitle(option) : option}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{extra && <li key={'separator'} role="separator" className="divider" />}
|
||||
{extra}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
private formatTitle = (option: any) => {
|
||||
if (this.props.formatTitle) {
|
||||
return this.props.formatTitle(option);
|
||||
} else {
|
||||
return option;
|
||||
}
|
||||
};
|
||||
|
||||
private onChange = (value: any) => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
if (this.dropdownShell) {
|
||||
this.dropdownShell.close();
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
.SimpleButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> .Spinner {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import Spinner from './Spinner';
|
||||
import './SimpleButton.scss';
|
||||
|
||||
const DEFAULT_BUTTON_TYPE = 'primary';
|
||||
const DEFAULT_BUTTON_SIZE = 'lg';
|
||||
|
@ -29,8 +30,8 @@ export default class SimpleButton extends Component<Props, {}> {
|
|||
<div>
|
||||
<button onClick={onClick} disabled={loading || disabled} className={this.computedClass()}>
|
||||
{loading ? (
|
||||
<div>
|
||||
<Spinner /> {loadingText || text}
|
||||
<div className="SimpleButton">
|
||||
<Spinner light={true} /> {loadingText || text}
|
||||
</div>
|
||||
) : (
|
||||
<div>{text}</div>
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import Dropdown from './Dropdown';
|
||||
|
||||
interface Props {
|
||||
value: string | undefined;
|
||||
options: string[];
|
||||
ariaLabel?: string;
|
||||
onChange(value: string): void;
|
||||
}
|
||||
|
||||
export default class SimpleDropdown extends PureComponent<Props, void> {
|
||||
public render() {
|
||||
const { options, value, onChange, ariaLabel } = this.props;
|
||||
|
||||
const StringDropdown = Dropdown as new () => Dropdown<string>;
|
||||
|
||||
return (
|
||||
<StringDropdown
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ariaLabel={ariaLabel || 'dropdown'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -112,3 +112,16 @@
|
|||
padding-right: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.swap-option {
|
||||
&-wrapper {
|
||||
font-size: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
&-img {
|
||||
width: 1rem;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import './SwapDropdown.scss';
|
||||
import classnames from 'classnames';
|
||||
import { DropDown } from 'components/ui';
|
||||
|
||||
export interface SingleCoin {
|
||||
id: string;
|
||||
|
@ -10,91 +10,70 @@ export interface SingleCoin {
|
|||
}
|
||||
|
||||
interface Props<T> {
|
||||
ariaLabel: string;
|
||||
options: SingleCoin[];
|
||||
value: string;
|
||||
onChange(value: T): void;
|
||||
}
|
||||
|
||||
class SwapDropdown<T> extends PureComponent<Props<T>, {}> {
|
||||
public state = {
|
||||
open: false
|
||||
const ValueComp: React.SFC = (props: any) => {
|
||||
return (
|
||||
<div className={`${props.className} swap-option-wrapper`}>
|
||||
<img src={props.value.img} className="swap-option-img" />
|
||||
<span className="swap-option-label">{props.value.label}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OptionComp: React.SFC = (props: any) => {
|
||||
const handleMouseDown = event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
props.onSelect(props.option, event);
|
||||
};
|
||||
|
||||
private dropdown: HTMLElement | null;
|
||||
|
||||
public componentDidMount() {
|
||||
document.addEventListener('click', this.clickHandler);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
document.removeEventListener('click', this.clickHandler);
|
||||
}
|
||||
|
||||
public handleClickOutside() {
|
||||
this.toggleDropdown();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { open } = this.state;
|
||||
const { options, value } = this.props;
|
||||
const dropdownGrid = classnames(open && 'open', 'SwapDropdown-grid');
|
||||
|
||||
const mappedCoins = options.sort((a, b) => (a.id > b.id ? 1 : -1)).map((coin: SingleCoin) => {
|
||||
const cn = classnames(coin.status !== 'available' && 'inactive', 'SwapDropdown-item');
|
||||
return (
|
||||
<li className={cn} key={coin.id}>
|
||||
<a onClick={coin.status === 'available' ? this.onChange.bind(null, coin.id) : null}>
|
||||
<img src={coin.image} height="20" width="20" />
|
||||
{/* <div className="SwapDropdown-desc"> */}
|
||||
<strong>{coin.id}</strong>
|
||||
<br />
|
||||
<small>{coin.name}</small>
|
||||
{/* </div> */}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div className="SwapDropdown" ref={el => (this.dropdown = el)}>
|
||||
<button onClick={this.toggleDropdown}>
|
||||
{value}
|
||||
<i className="caret" />
|
||||
</button>
|
||||
<ul className={dropdownGrid}>{mappedCoins}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
private toggleDropdown = () => {
|
||||
this.setState({
|
||||
open: !this.state.open
|
||||
});
|
||||
const handleMouseEnter = event => {
|
||||
props.onFocus(props.option, event);
|
||||
};
|
||||
|
||||
private onChange = (value: any) => {
|
||||
this.props.onChange(value);
|
||||
if (this.state.open) {
|
||||
this.setState({
|
||||
open: false
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private clickHandler = (ev: Event) => {
|
||||
if (!this.state.open || !this.dropdown) {
|
||||
const handleMouseMove = event => {
|
||||
if (props.isFocused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.dropdown !== ev.target &&
|
||||
ev.target instanceof HTMLElement &&
|
||||
!this.dropdown.contains(ev.target)
|
||||
) {
|
||||
this.setState({
|
||||
open: false
|
||||
});
|
||||
}
|
||||
props.onFocus(props.option, event);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={`${props.className} swap-option-wrapper`}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
<img src={props.option.img} className="swap-option-img" />
|
||||
<span className="swap-option-label">{props.option.label}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class SwapDropdown<T> extends PureComponent<Props<T>> {
|
||||
public render() {
|
||||
const { options, value, onChange } = this.props;
|
||||
const mappedOptions = options.map(opt => {
|
||||
return { label: opt.id, value: opt.name, img: opt.image, status: opt.status };
|
||||
});
|
||||
return (
|
||||
<DropDown
|
||||
className="Swap-dropdown"
|
||||
options={mappedOptions}
|
||||
optionComponent={(props: any) => {
|
||||
return <OptionComp {...props} />;
|
||||
}}
|
||||
value={value}
|
||||
clearable={false}
|
||||
onChange={onChange}
|
||||
valueComponent={(props: any) => {
|
||||
return <ValueComp {...props} />;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SwapDropdown;
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import React, { HTMLProps } from 'react';
|
||||
import './Input.scss';
|
||||
|
||||
interface State {
|
||||
hasBlurred: boolean;
|
||||
}
|
||||
|
||||
class TextArea extends React.Component<HTMLProps<HTMLTextAreaElement>, State> {
|
||||
public state: State = {
|
||||
hasBlurred: false
|
||||
};
|
||||
public render() {
|
||||
return (
|
||||
<textarea
|
||||
{...this.props}
|
||||
onBlur={e => {
|
||||
this.setState({ hasBlurred: true });
|
||||
if (this.props && this.props.onBlur) {
|
||||
this.props.onBlur(e);
|
||||
}
|
||||
}}
|
||||
className={`input-group-input ${this.props.className} ${
|
||||
this.state.hasBlurred ? 'has-blurred' : ''
|
||||
}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TextArea;
|
|
@ -1,5 +1,6 @@
|
|||
export { default as ColorDropdown } from './ColorDropdown';
|
||||
export { default as Dropdown } from './Dropdown';
|
||||
export { default as OldDropDown } from './OldDropdown';
|
||||
export { default as DropDown } from './Dropdown';
|
||||
export { default as DropdownShell } from './DropdownShell';
|
||||
export { default as Identicon } from './Identicon';
|
||||
export { default as Modal } from './Modal';
|
||||
|
@ -12,6 +13,8 @@ export { default as SwapDropdown } from './SwapDropdown';
|
|||
export { default as Tooltip } from './Tooltip';
|
||||
export { default as TitleBar } from './TitleBar';
|
||||
export { default as HelpLink } from './HelpLink';
|
||||
export { default as Input } from './Input';
|
||||
export { default as TextArea } from './TextArea';
|
||||
export { default as Address } from './Address';
|
||||
export * from './ConditionalInput';
|
||||
export * from './Expandable';
|
||||
|
|
|
@ -9,9 +9,8 @@ import {
|
|||
TSignTransactionFailed
|
||||
} from 'actions/transaction';
|
||||
import { computeIndexingHash } from 'libs/transaction';
|
||||
import { QRCode } from 'components/ui';
|
||||
import { QRCode, TextArea } from 'components/ui';
|
||||
import EthTx from 'ethereumjs-tx';
|
||||
import classnames from 'classnames';
|
||||
import { SendButton } from 'components/SendButton';
|
||||
import { toBuffer, bufferToHex } from 'ethereumjs-util';
|
||||
import { getSerializedTransaction } from 'selectors/transaction';
|
||||
|
@ -40,11 +39,6 @@ class BroadcastTx extends Component<Props> {
|
|||
public render() {
|
||||
const { userInput } = this.state;
|
||||
const { stateTransaction } = this.props;
|
||||
const inputClasses = classnames({
|
||||
'form-control': true,
|
||||
'is-valid': !!stateTransaction,
|
||||
'is-invalid': !stateTransaction
|
||||
});
|
||||
const currentPath = this.props.match.url;
|
||||
return (
|
||||
<TabSection isUnavailableOffline={true}>
|
||||
|
@ -59,13 +53,19 @@ class BroadcastTx extends Component<Props> {
|
|||
<p className="BroadcastTx-help">
|
||||
Paste a signed transaction and press the "SEND TRANSACTION" button.
|
||||
</p>
|
||||
<label>{translateRaw('SEND_signed')}</label>
|
||||
<textarea
|
||||
className={inputClasses}
|
||||
rows={7}
|
||||
value={userInput}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
|
||||
<div className="input-group-wrapper InteractForm-interface">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translateRaw('SEND_signed')}</div>
|
||||
<TextArea
|
||||
className={stateTransaction ? '' : 'invalid'}
|
||||
rows={7}
|
||||
value={userInput}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<SendButton onlyTransactionParameters={true} />
|
||||
|
||||
<div className="BroadcastTx-qr">
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import translate from 'translations';
|
||||
import { isValidTxHash, isValidETHAddress } from 'libs/validators';
|
||||
import './TxHashInput.scss';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
hash?: string;
|
||||
|
@ -26,14 +27,14 @@ export default class TxHashInput extends React.Component<Props, State> {
|
|||
|
||||
public render() {
|
||||
const { hash } = this.state;
|
||||
const validClass = hash ? (isValidTxHash(hash) ? 'is-valid' : 'is-invalid') : '';
|
||||
const validClass = hash ? (isValidTxHash(hash) ? '' : 'invalid') : '';
|
||||
|
||||
return (
|
||||
<form className="TxHashInput" onSubmit={this.handleSubmit}>
|
||||
<input
|
||||
<Input
|
||||
value={hash}
|
||||
placeholder="0x16e521..."
|
||||
className={`TxHashInput-field form-control ${validClass}`}
|
||||
className={`TxHashInput-field ${validClass}`}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
|
||||
|
|
|
@ -2,13 +2,10 @@
|
|||
&-field {
|
||||
margin-top: 0;
|
||||
|
||||
&-label {
|
||||
float: left;
|
||||
}
|
||||
|
||||
&-reset {
|
||||
float: right;
|
||||
|
||||
display: block;
|
||||
margin: auto;
|
||||
margin-bottom: 0.25rem;
|
||||
.fa {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import { FullWalletOnly } from 'components/renderCbs';
|
|||
import { NonceField, TXMetaDataPanel } from 'components';
|
||||
import './Deploy.scss';
|
||||
import { ConfirmationModal } from 'components/ConfirmationModal';
|
||||
import { TextArea } from 'components/ui';
|
||||
|
||||
interface DispatchProps {
|
||||
setToField: TSetToField;
|
||||
|
@ -23,27 +24,30 @@ class DeployClass extends Component<DispatchProps> {
|
|||
public render() {
|
||||
const makeContent = () => (
|
||||
<main className="Deploy Tab-content-pane" role="main">
|
||||
<div className="Deploy-field form-group">
|
||||
<h3 className="Deploy-field-label">{translate('CONTRACT_ByteCode')}</h3>
|
||||
<button className="Deploy-field-reset btn btn-default btn-sm" onClick={this.changeWallet}>
|
||||
<i className="fa fa-refresh" />
|
||||
{translate('Change Wallet')}
|
||||
</button>
|
||||
<DataFieldFactory
|
||||
withProps={({ data: { raw, value }, onChange, readOnly }) => (
|
||||
<textarea
|
||||
name="byteCode"
|
||||
placeholder="0x8f87a973e..."
|
||||
rows={6}
|
||||
onChange={onChange}
|
||||
disabled={readOnly}
|
||||
className={classnames('Deploy-field-input', 'form-control', {
|
||||
'is-valid': value && value.length > 0
|
||||
})}
|
||||
value={raw}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<button className="Deploy-field-reset btn btn-default btn-sm" onClick={this.changeWallet}>
|
||||
<i className="fa fa-refresh" />
|
||||
{translate('Change Wallet')}
|
||||
</button>
|
||||
|
||||
<div className="input-group-wrapper Deploy-field">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('CONTRACT_ByteCode')}</div>
|
||||
<DataFieldFactory
|
||||
withProps={({ data: { raw, value }, onChange, readOnly }) => (
|
||||
<TextArea
|
||||
name="byteCode"
|
||||
placeholder="0x8f87a973e..."
|
||||
rows={6}
|
||||
onChange={onChange}
|
||||
disabled={readOnly}
|
||||
className={classnames('Deploy-field-input', 'form-control', {
|
||||
'is-valid': value && value.length > 0
|
||||
})}
|
||||
value={raw}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="row form-group">
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
import { AmountFieldFactory } from 'components/AmountFieldFactory';
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
export const AmountField: React.SFC<{}> = () => (
|
||||
<label className="InteractExplorer-field form-group">
|
||||
<h4 className="InteractExplorer-field-label">Value</h4>
|
||||
<AmountFieldFactory
|
||||
withProps={({ currentValue: { raw }, isValid, onChange, readOnly }) => (
|
||||
<input
|
||||
name="value"
|
||||
value={raw}
|
||||
onChange={onChange}
|
||||
readOnly={readOnly}
|
||||
className={classnames('InteractExplorer-field-input', 'form-control', {
|
||||
'is-invalid': !(isValid || raw === '')
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
export const AmountField: React.SFC = () => (
|
||||
<div className="input-group-wrapper InteractExplorer-field">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">Value</div>
|
||||
<AmountFieldFactory
|
||||
withProps={({ currentValue: { raw }, isValid, onChange, readOnly }) => (
|
||||
<Input
|
||||
name="value"
|
||||
value={raw}
|
||||
onChange={onChange}
|
||||
readOnly={readOnly}
|
||||
className={classnames('InteractExplorer-field-input', 'form-control', {
|
||||
'is-invalid': !(isValid || raw === '')
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -10,9 +10,10 @@ import { connect } from 'react-redux';
|
|||
import { Fields } from './components';
|
||||
import { setDataField, TSetDataField } from 'actions/transaction';
|
||||
import { Data } from 'libs/units';
|
||||
import Select from 'react-select';
|
||||
import { Web3Node } from 'libs/nodes';
|
||||
import RpcNode from 'libs/nodes/rpc';
|
||||
import { Input } from 'components/ui';
|
||||
import Dropdown from 'components/ui/Dropdown';
|
||||
|
||||
interface StateProps {
|
||||
nodeLib: RpcNode | Web3Node;
|
||||
|
@ -83,22 +84,25 @@ class InteractExplorerClass extends Component<Props, State> {
|
|||
|
||||
return (
|
||||
<div className="InteractExplorer">
|
||||
<h3 className="InteractExplorer-title">
|
||||
{translate('CONTRACT_Interact_Title')}
|
||||
<span className="InteractExplorer-title-address">{to.raw}</span>
|
||||
</h3>
|
||||
|
||||
<Select
|
||||
name="exploreContract"
|
||||
value={selectedFunction as any}
|
||||
placeholder="Please select a function..."
|
||||
onChange={this.handleFunctionSelect}
|
||||
options={contractFunctionsOptions}
|
||||
clearable={false}
|
||||
searchable={false}
|
||||
labelKey="name"
|
||||
valueKey="contract"
|
||||
/>
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">
|
||||
{translate('CONTRACT_Interact_Title')}
|
||||
<div className="flex-spacer" />
|
||||
<span className="small">{to.raw}</span>
|
||||
</div>
|
||||
<Dropdown
|
||||
name="exploreContract"
|
||||
value={selectedFunction as any}
|
||||
placeholder="Please select a function..."
|
||||
onChange={this.handleFunctionSelect}
|
||||
options={contractFunctionsOptions}
|
||||
clearable={false}
|
||||
labelKey="name"
|
||||
valueKey="contract"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{selectedFunction && (
|
||||
<div key={selectedFunction.name} className="InteractExplorer-func">
|
||||
|
@ -107,18 +111,17 @@ class InteractExplorerClass extends Component<Props, State> {
|
|||
const { type, name } = input;
|
||||
|
||||
return (
|
||||
<label key={name} className="InteractExplorer-func-in form-group">
|
||||
<h4 className="InteractExplorer-func-in-label">
|
||||
{name}
|
||||
<span className="InteractExplorer-func-in-label-type">{type}</span>
|
||||
</h4>
|
||||
<input
|
||||
className="InteractExplorer-func-in-input form-control"
|
||||
name={name}
|
||||
value={(inputs[name] && inputs[name].rawData) || ''}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
</label>
|
||||
<div key={name} className="input-group-wrapper InteractExplorer-func-in">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{name + ' ' + type}</div>
|
||||
<Input
|
||||
className="InteractExplorer-func-in-input"
|
||||
name={name}
|
||||
value={(inputs[name] && inputs[name].rawData) || ''}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{selectedFunction.contract.outputs.map((output, index) => {
|
||||
|
@ -126,17 +129,16 @@ class InteractExplorerClass extends Component<Props, State> {
|
|||
const parsedName = name === '' ? index : name;
|
||||
|
||||
return (
|
||||
<label key={parsedName} className="InteractExplorer-func-out form-group">
|
||||
<h4 className="InteractExplorer-func-out-label">
|
||||
↳ {name}
|
||||
<span className="InteractExplorer-func-out-label-type">{type}</span>
|
||||
</h4>
|
||||
<input
|
||||
className="InteractExplorer-func-out-input form-control"
|
||||
value={outputs[parsedName] || ''}
|
||||
disabled={true}
|
||||
/>
|
||||
</label>
|
||||
<div key={parsedName} className="input-group-wrapper InteractExplorer-func-out">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header"> ↳ {name + ' ' + type}</div>
|
||||
<Input
|
||||
className="InteractExplorer-func-out-input "
|
||||
value={outputs[parsedName] || ''}
|
||||
disabled={true}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
|
|
|
@ -5,9 +5,10 @@ import { connect } from 'react-redux';
|
|||
import { AppState } from 'reducers';
|
||||
import { isValidETHAddress, isValidAbiJson } from 'libs/validators';
|
||||
import classnames from 'classnames';
|
||||
import Select from 'react-select';
|
||||
import { NetworkContract } from 'types/network';
|
||||
import { donationAddressMap } from 'config';
|
||||
import { Input, TextArea } from 'components/ui';
|
||||
import Dropdown from 'components/ui/Dropdown';
|
||||
|
||||
interface ContractOption {
|
||||
name: string;
|
||||
|
@ -83,45 +84,44 @@ class InteractForm extends Component<Props, State> {
|
|||
return (
|
||||
<div className="InteractForm">
|
||||
<div className="InteractForm-address row">
|
||||
<label className="InteractForm-address-field form-group col-sm-6">
|
||||
<h4>{translate('CONTRACT_Title')}</h4>
|
||||
<input
|
||||
placeholder={`ensdomain.eth or ${donationAddressMap.ETH}`}
|
||||
name="contract_address"
|
||||
autoComplete="off"
|
||||
value={address}
|
||||
className={classnames('InteractForm-address-field-input', 'form-control', {
|
||||
'is-invalid': !validEthAddress
|
||||
})}
|
||||
onChange={this.handleInput('address')}
|
||||
/>
|
||||
</label>
|
||||
<div className="input-group-wrapper InteractForm-address-field col-sm-6">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('CONTRACT_Title')}</div>
|
||||
<Input
|
||||
placeholder={`ensdomain.eth or ${donationAddressMap.ETH}`}
|
||||
name="contract_address"
|
||||
autoComplete="off"
|
||||
value={address}
|
||||
className={classnames('InteractForm-address-field-input', {
|
||||
invalid: !validEthAddress
|
||||
})}
|
||||
onChange={this.handleInput('address')}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="InteractForm-address-contract form-group col-sm-6">
|
||||
<h4>{translate('CONTRACT_Title_2')}</h4>
|
||||
<Select
|
||||
name="interactContract"
|
||||
className={`${!contract ? 'is-invalid' : ''}`}
|
||||
value={contract as any}
|
||||
placeholder={this.state.contractPlaceholder}
|
||||
onChange={this.handleSelectContract}
|
||||
options={contractOptions}
|
||||
clearable={false}
|
||||
searchable={false}
|
||||
labelKey="name"
|
||||
/>
|
||||
</label>
|
||||
<div className="input-group-wrapper InteractForm-address-field col-sm-6">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('CONTRACT_Title_2')}</div>
|
||||
<Dropdown
|
||||
className={`${!contract ? 'invalid' : ''}`}
|
||||
value={contract as any}
|
||||
placeholder={this.state.contractPlaceholder}
|
||||
onChange={this.handleSelectContract}
|
||||
options={contractOptions}
|
||||
clearable={false}
|
||||
labelKey="name"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="InteractForm-interface">
|
||||
<label className="InteractForm-interface-field form-group">
|
||||
<h4 className="InteractForm-interface-field-label">{translate('CONTRACT_Json')}</h4>
|
||||
<textarea
|
||||
<div className="input-group-wrapper InteractForm-interface">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('CONTRACT_Json')}</div>
|
||||
<TextArea
|
||||
placeholder={this.abiJsonPlaceholder}
|
||||
name="abiJson"
|
||||
className={classnames('InteractForm-interface-field-input', 'form-control', {
|
||||
'is-invalid': !validAbiJson
|
||||
})}
|
||||
className={`InteractForm-interface-field-input ${validAbiJson ? '' : 'invalid'}`}
|
||||
onChange={this.handleInput('abiJson')}
|
||||
value={abiJson}
|
||||
rows={6}
|
||||
|
|
|
@ -5,10 +5,6 @@
|
|||
padding: $space-sm 0;
|
||||
margin: 0 auto;
|
||||
|
||||
&-name {
|
||||
margin-bottom: $space-md;
|
||||
}
|
||||
|
||||
&-button {
|
||||
.Spinner {
|
||||
margin-left: $space-md;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { resolveDomainRequested, TResolveDomainRequested } from 'actions/ens';
|
||||
import { isValidENSName } from 'libs/validators';
|
||||
import './NameInput.scss';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface State {
|
||||
domainToCheck: string;
|
||||
|
@ -32,21 +32,20 @@ class NameInput extends Component<Props, State> {
|
|||
|
||||
return (
|
||||
<form className="ENSInput" onSubmit={this.onSubmit}>
|
||||
<div className="ENSInput-name input-group">
|
||||
<input
|
||||
value={domainToCheck}
|
||||
className={classnames(
|
||||
'form-control',
|
||||
!domainToCheck ? '' : isValidDomain ? 'is-valid' : 'is-invalid'
|
||||
)}
|
||||
type="text"
|
||||
placeholder="mycrypto"
|
||||
onChange={this.onChange}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<span className="input-group-addon">.eth</span>
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group input-group-inline-dropdown ENSInput-name">
|
||||
<Input
|
||||
value={domainToCheck}
|
||||
className={!domainToCheck ? '' : isValidDomain ? 'is-valid' : 'is-invalid'}
|
||||
type="text"
|
||||
placeholder="mycrypto"
|
||||
onChange={this.onChange}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<span className="input-group-addon">.eth</span>
|
||||
</label>
|
||||
</div>
|
||||
{domainToCheck &&
|
||||
!isValidDomain &&
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
@import "common/sass/variables";
|
||||
@import 'common/sass/variables';
|
||||
|
||||
$pw-max-width: 40rem;
|
||||
|
||||
.EnterPw {
|
||||
&-title {
|
||||
margin: $space auto $space * 2.5;
|
||||
margin: $space auto;
|
||||
}
|
||||
|
||||
&-password {
|
||||
|
@ -12,18 +12,16 @@ $pw-max-width: 40rem;
|
|||
position: relative;
|
||||
max-width: $pw-max-width;
|
||||
width: 100%;
|
||||
margin: 0 auto $space;
|
||||
margin: 0 auto;
|
||||
|
||||
&-label {
|
||||
margin-bottom: $space;
|
||||
}
|
||||
|
||||
&-feedback {
|
||||
position: absolute;
|
||||
bottom: -$space;
|
||||
top: 100%;
|
||||
text-align: left;
|
||||
font-size: $font-size-small;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@ import React, { Component } from 'react';
|
|||
import zxcvbn, { ZXCVBNResult } from 'zxcvbn';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { MINIMUM_PASSWORD_LENGTH } from 'config';
|
||||
import { TogglablePassword } from 'components';
|
||||
import { Spinner } from 'components/ui';
|
||||
import Template from '../Template';
|
||||
import './EnterPassword.scss';
|
||||
import { TogglablePassword } from 'components';
|
||||
|
||||
interface Props {
|
||||
isGenerating: boolean;
|
||||
|
@ -41,36 +41,36 @@ export default class EnterPassword extends Component<Props, State> {
|
|||
Generate a {translate('x_Keystore2')}
|
||||
</h1>
|
||||
|
||||
<label className="EnterPw-password">
|
||||
<h4 className="EnterPw-password-label">{translate('GEN_Label_1')}</h4>
|
||||
<TogglablePassword
|
||||
value={password}
|
||||
placeholder={`Password must be uncommon and ${MINIMUM_PASSWORD_LENGTH}+ characters long`}
|
||||
validity={passwordValidity}
|
||||
ariaLabel={translateRaw('GEN_Aria_1')}
|
||||
toggleAriaLabel={translateRaw('GEN_Aria_2')}
|
||||
onChange={this.onPasswordChange}
|
||||
onBlur={this.showFeedback}
|
||||
/>
|
||||
{!isPasswordValid &&
|
||||
feedback && (
|
||||
<p className={`EnterPw-password-feedback help-block is-${passwordValidity}`}>
|
||||
{feedback}
|
||||
</p>
|
||||
)}
|
||||
</label>
|
||||
<div className="input-group-wrapper EnterPw-password">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('GEN_Label_1')}</div>
|
||||
<TogglablePassword
|
||||
className={!isPasswordValid && password.length > 0 ? 'invalid' : ''}
|
||||
value={password}
|
||||
placeholder={`Password must be uncommon and ${MINIMUM_PASSWORD_LENGTH}+ characters long`}
|
||||
onChange={this.onPasswordChange}
|
||||
onBlur={this.showFeedback}
|
||||
/>
|
||||
{!isPasswordValid &&
|
||||
feedback && (
|
||||
<p className={`EnterPw-password-feedback help-block is-${passwordValidity}`}>
|
||||
{feedback}
|
||||
</p>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="EnterPw-password">
|
||||
<h4 className="EnterPw-password-label">Confirm password</h4>
|
||||
<TogglablePassword
|
||||
value={confirmedPassword}
|
||||
placeholder={translateRaw('GEN_Placeholder_1')}
|
||||
ariaLabel="Confirm Password"
|
||||
toggleAriaLabel="toggle confirm password visibility"
|
||||
isValid={isConfirmValid}
|
||||
onChange={this.onConfirmChange}
|
||||
/>
|
||||
</label>
|
||||
<div className="input-group-wrapper EnterPw-password">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">Confirm password</div>
|
||||
<TogglablePassword
|
||||
className={!isConfirmValid && password.length > 0 ? 'invalid' : ''}
|
||||
value={confirmedPassword}
|
||||
placeholder={translateRaw('GEN_Placeholder_1')}
|
||||
onChange={this.onConfirmChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button disabled={!canSubmit} className="EnterPw-submit btn btn-primary btn-lg btn-block">
|
||||
{isGenerating ? <Spinner light={true} /> : translate('NAV_GenerateWallet')}
|
||||
|
|
|
@ -5,6 +5,7 @@ import translate from 'translations';
|
|||
import { stripHexPrefix } from 'libs/values';
|
||||
import './PaperWallet.scss';
|
||||
import Template from '../Template';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
keystore: IV3Wallet;
|
||||
|
@ -17,8 +18,8 @@ const PaperWallet: React.SFC<Props> = props => (
|
|||
<div className="GenPaper">
|
||||
{/* Private Key */}
|
||||
<h1 className="GenPaper-title">{translate('GEN_Label_5')}</h1>
|
||||
<input
|
||||
className="GenPaper-private form-control"
|
||||
<Input
|
||||
className="GenPaper-private"
|
||||
value={stripHexPrefix(props.privateKey)}
|
||||
aria-label={translate('x_PrivKey', true)}
|
||||
aria-describedby="x_PrivKeyDesc"
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import classnames from 'classnames';
|
||||
import { translateRaw } from 'translations';
|
||||
import './Word.scss';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
index: number;
|
||||
|
@ -26,15 +27,11 @@ export default class MnemonicWord extends React.Component<Props, State> {
|
|||
const readOnly = isReadOnly || isShowingWord;
|
||||
|
||||
return (
|
||||
<div className="MnemonicWord">
|
||||
<span className="MnemonicWord-number">{index + 1}.</span>
|
||||
<div className="MnemonicWord-word input-group">
|
||||
<input
|
||||
className={classnames(
|
||||
'MnemonicWord-word-input',
|
||||
'form-control',
|
||||
word === value && 'is-valid'
|
||||
)}
|
||||
<div className="input-group-wrapper MnemonicWord">
|
||||
<label className="input-group input-group-inline-dropdown ENSInput-name">
|
||||
<span className="input-group-addon input-group-addon--transparent">{index + 1}.</span>
|
||||
<Input
|
||||
className={classnames('MnemonicWord-word-input', word === value && 'valid')}
|
||||
value={readOnly ? word : value}
|
||||
onChange={this.handleChange}
|
||||
readOnly={readOnly}
|
||||
|
@ -55,7 +52,7 @@ export default class MnemonicWord extends React.Component<Props, State> {
|
|||
/>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
@import "common/sass/variables";
|
||||
@import 'common/sass/variables';
|
||||
|
||||
.RequestPayment {
|
||||
&-title {
|
||||
display: block;
|
||||
margin: 0.5rem 1rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
&-qr {
|
||||
position: relative;
|
||||
background: #FFF;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-codeContainer {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
&-codeBox {
|
||||
min-height: 180px;
|
||||
overflow-x: hidden;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
|||
import { AppState } from 'reducers';
|
||||
import translate from 'translations';
|
||||
import { IWallet } from 'libs/wallet';
|
||||
import { QRCode } from 'components/ui';
|
||||
import { QRCode, TextArea } from 'components/ui';
|
||||
import { getUnit, getDecimal } from 'selectors/transaction/meta';
|
||||
import {
|
||||
getCurrentTo,
|
||||
|
@ -51,10 +51,10 @@ class RequestPayment extends React.Component<Props, {}> {
|
|||
};
|
||||
|
||||
public componentDidMount() {
|
||||
this.props.reset();
|
||||
if (this.props.wallet) {
|
||||
this.setWalletAsyncState(this.props.wallet);
|
||||
}
|
||||
this.props.reset();
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
|
@ -121,18 +121,14 @@ class RequestPayment extends React.Component<Props, {}> {
|
|||
|
||||
{!!eip681String.length && (
|
||||
<div className="row form-group">
|
||||
<label className="RequestPayment-title">{translate('Payment QR & Code')}</label>
|
||||
<div className="col-xs-6">
|
||||
<label>{translate('Payment QR & Code')}</label>
|
||||
<div className="RequestPayment-qr well well-lg">
|
||||
<QRCode data={eip681String} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-xs-6 RequestPayment-codeContainer">
|
||||
<textarea
|
||||
className="RequestPayment-codeBox form-control"
|
||||
value={eip681String}
|
||||
disabled={true}
|
||||
/>
|
||||
<TextArea className="RequestPayment-codeBox" value={eip681String} disabled={true} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { toChecksumAddress } from 'ethereumjs-util';
|
|||
import translate, { translateRaw } from 'translations';
|
||||
import { IWallet } from 'libs/wallet';
|
||||
import { print } from 'components/PrintableWallet';
|
||||
import { Identicon, QRCode } from 'components/ui';
|
||||
import { Identicon, QRCode, Input } from 'components/ui';
|
||||
import { GenerateKeystoreModal, TogglablePassword } from 'components';
|
||||
import './WalletInfo.scss';
|
||||
|
||||
|
@ -44,8 +44,12 @@ export default class WalletInfo extends React.PureComponent<Props, State> {
|
|||
<div className="Tab-content-pane">
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-11">
|
||||
<label>{translate('x_Address')}</label>
|
||||
<input className="form-control" disabled={true} value={address} />
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('x_Address')}</div>
|
||||
<Input readOnly={true} value={address} />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-xs-1" style={{ padding: 0 }}>
|
||||
<Identicon address={address} />
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
}
|
||||
|
||||
&-reset {
|
||||
float: right;
|
||||
display: block;
|
||||
margin: auto;
|
||||
margin-bottom: 0.25rem;
|
||||
|
||||
.fa {
|
||||
margin-right: 8px;
|
||||
|
@ -19,9 +21,9 @@
|
|||
}
|
||||
|
||||
&-help {
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
font-style: italic;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
&-inputBox {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import classnames from 'classnames';
|
||||
import WalletDecrypt, { DISABLE_WALLETS } from 'components/WalletDecrypt';
|
||||
import translate from 'translations';
|
||||
import { showNotification, TShowNotification } from 'actions/notifications';
|
||||
|
@ -11,6 +10,7 @@ import { AppState } from 'reducers';
|
|||
import SignButton from './SignButton';
|
||||
import { isWalletFullyUnlocked } from 'selectors/wallet';
|
||||
import './index.scss';
|
||||
import { TextArea } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
wallet: IFullWallet;
|
||||
|
@ -42,17 +42,11 @@ export class SignMessage extends Component<Props, State> {
|
|||
public render() {
|
||||
const { wallet, unlocked } = this.props;
|
||||
const { message, signedMessage } = this.state;
|
||||
const messageBoxClass = classnames([
|
||||
'SignMessage-inputBox',
|
||||
'form-control',
|
||||
message ? 'is-valid' : 'is-invalid'
|
||||
]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{unlocked ? (
|
||||
<div className="Tab-content-pane">
|
||||
<h3 className="SignMessage-label">{translate('MSG_message')}</h3>
|
||||
<button
|
||||
className="SignMessage-reset btn btn-default btn-sm"
|
||||
onClick={this.changeWallet}
|
||||
|
@ -60,13 +54,17 @@ export class SignMessage extends Component<Props, State> {
|
|||
<i className="fa fa-refresh" />
|
||||
{translate('Change Wallet')}
|
||||
</button>
|
||||
<div className="form-group">
|
||||
<textarea
|
||||
className={messageBoxClass}
|
||||
placeholder={messagePlaceholder}
|
||||
value={message}
|
||||
onChange={this.handleMessageChange}
|
||||
/>
|
||||
|
||||
<div className="input-group-wrapper Deploy-field">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('MSG_message')}</div>
|
||||
<TextArea
|
||||
className={`SignMessage-inputBox ${message ? 'is-valid' : 'is-invalid'}`}
|
||||
placeholder={messagePlaceholder}
|
||||
value={message}
|
||||
onChange={this.handleMessageChange}
|
||||
/>
|
||||
</label>
|
||||
<div className="SignMessage-help">{translate('MSG_info2')}</div>
|
||||
</div>
|
||||
|
||||
|
@ -78,16 +76,16 @@ export class SignMessage extends Component<Props, State> {
|
|||
/>
|
||||
|
||||
{!!signedMessage && (
|
||||
<div>
|
||||
<h4>{translate('MSG_signature')}</h4>
|
||||
<div className="form-group">
|
||||
<textarea
|
||||
className="SignMessage-inputBox form-control"
|
||||
<div className="input-group-wrapper SignMessage-inputBox">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('MSG_signature')}</div>
|
||||
<TextArea
|
||||
className="SignMessage-inputBox"
|
||||
value={JSON.stringify(signedMessage, null, 2)}
|
||||
disabled={true}
|
||||
onChange={this.handleMessageChange}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import classnames from 'classnames';
|
||||
import translate from 'translations';
|
||||
import { showNotification, TShowNotification } from 'actions/notifications';
|
||||
import { verifySignedMessage, ISignedMessage } from 'libs/signing';
|
||||
import './index.scss';
|
||||
import { TextArea } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
showNotification: TShowNotification;
|
||||
|
@ -34,24 +34,20 @@ export class VerifyMessage extends Component<Props, State> {
|
|||
public render() {
|
||||
const { verifiedAddress, verifiedMessage, signature } = this.state;
|
||||
|
||||
const signatureBoxClass = classnames([
|
||||
'VerifyMessage-inputBox',
|
||||
'form-control',
|
||||
signature ? 'is-valid' : 'is-invalid'
|
||||
]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="Tab-content-pane">
|
||||
<h4>{translate('MSG_signature')}</h4>
|
||||
<div className="form-group">
|
||||
<textarea
|
||||
className={signatureBoxClass}
|
||||
placeholder={signaturePlaceholder}
|
||||
value={signature}
|
||||
onChange={this.handleSignatureChange}
|
||||
onPaste={this.handleSignaturePaste}
|
||||
/>
|
||||
<div className="input-group-wrapper ">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('MSG_signature')}</div>
|
||||
<TextArea
|
||||
className={`VerifyMessage-inputBox ${signature ? 'is-valid' : 'is-invalid'}`}
|
||||
placeholder={signaturePlaceholder}
|
||||
value={signature}
|
||||
onChange={this.handleSignatureChange}
|
||||
onPaste={this.handleSignaturePaste}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
|
|
@ -23,13 +23,24 @@
|
|||
text-align: left;
|
||||
}
|
||||
&-inner-wrap {
|
||||
display: block;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 0 -8px;
|
||||
margin-bottom: 2rem;
|
||||
> .input-group-wrapper {
|
||||
margin: 0 8px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
@media (min-width: $screen-xs-min) {
|
||||
@media (max-width: $screen-sm) {
|
||||
&-inner-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: block;
|
||||
margin: 0;
|
||||
> .input-group-wrapper {
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-dropdown {
|
||||
|
@ -45,13 +56,13 @@
|
|||
}
|
||||
|
||||
&-divider {
|
||||
height: $input-height-base;
|
||||
font-size: 2.1125rem;
|
||||
margin: 0rem 1rem;
|
||||
line-height: $input-height-base;
|
||||
margin: 0 $space * 2;
|
||||
font-weight: 100;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
&-submit {
|
||||
margin-top: $space * 2.5;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { generateKindMax, generateKindMin, WhitelistedCoins, bityConfig } from '
|
|||
import React, { PureComponent } from 'react';
|
||||
import translate from 'translations';
|
||||
import { combineAndUpper } from 'utils/formatters';
|
||||
import { SwapDropdown } from 'components/ui';
|
||||
import { SwapDropdown, Input } from 'components/ui';
|
||||
import Spinner from 'components/ui/Spinner';
|
||||
import { merge, reject, debounce } from 'lodash';
|
||||
import './CurrencySwap.scss';
|
||||
|
@ -41,21 +41,30 @@ interface State {
|
|||
|
||||
type Props = StateProps & ActionProps;
|
||||
|
||||
interface SwapOpt extends SwapInput {
|
||||
value: string;
|
||||
status: string;
|
||||
image: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export default class CurrencySwap extends PureComponent<Props, State> {
|
||||
public state = {
|
||||
disabled: true,
|
||||
origin: {
|
||||
id: 'BTC',
|
||||
label: 'BTC',
|
||||
value: 'Bitcoin',
|
||||
status: 'available',
|
||||
image: 'https://shapeshift.io/images/coins/bitcoin.png',
|
||||
amount: NaN
|
||||
} as SwapInput,
|
||||
} as SwapOpt,
|
||||
destination: {
|
||||
id: 'ETH',
|
||||
label: 'ETH',
|
||||
value: 'Ether',
|
||||
status: 'available',
|
||||
image: 'https://shapeshift.io/images/coins/ether.png',
|
||||
amount: NaN
|
||||
} as SwapInput,
|
||||
} as SwapOpt,
|
||||
originKindOptions: [],
|
||||
destinationKindOptions: [],
|
||||
originErr: '',
|
||||
|
@ -78,9 +87,11 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
}
|
||||
return errString;
|
||||
};
|
||||
const originErr = showError ? createErrString(origin.id, origin.amount, destination.id) : '';
|
||||
const originErr = showError
|
||||
? createErrString(origin.label, origin.amount, destination.label)
|
||||
: '';
|
||||
const destinationErr = showError
|
||||
? createErrString(destination.id, destination.amount, origin.id)
|
||||
? createErrString(destination.label, destination.amount, origin.label)
|
||||
: '';
|
||||
this.setErrorMessages(originErr, destinationErr);
|
||||
}, 1000);
|
||||
|
@ -99,7 +110,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
if (options.allIds && options.byId) {
|
||||
const originKindOptions: any[] = Object.values(options.byId);
|
||||
const destinationKindOptions: any[] = Object.values(
|
||||
reject<any>(options.byId, o => o.id === origin.id)
|
||||
reject<any>(options.byId, o => o.id === origin.label)
|
||||
);
|
||||
|
||||
this.setState({
|
||||
|
@ -125,7 +136,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
if (options.allIds !== prevProps.options.allIds && options.byId) {
|
||||
const originKindOptions: any[] = Object.values(options.byId);
|
||||
const destinationKindOptions: any[] = Object.values(
|
||||
reject<any>(options.byId, o => o.id === origin.id)
|
||||
reject<any>(options.byId, o => o.id === origin.label)
|
||||
);
|
||||
|
||||
this.setState({
|
||||
|
@ -175,7 +186,11 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
public setDisabled(origin: SwapInput, destination: SwapInput) {
|
||||
this.clearErrMessages();
|
||||
const amountsValid = origin.amount && destination.amount;
|
||||
const minMaxValid = this.isMinMaxValid(origin.amount as number, origin.id, destination.id);
|
||||
const minMaxValid = this.isMinMaxValid(
|
||||
origin.amount as number,
|
||||
origin.label,
|
||||
destination.label
|
||||
);
|
||||
const disabled = !(amountsValid && minMaxValid);
|
||||
const showError = disabled && amountsValid;
|
||||
|
||||
|
@ -216,7 +231,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
|
||||
public updateOriginAmount = (origin: SwapInput, destination: SwapInput, amount: number) => {
|
||||
if (amount || amount === 0) {
|
||||
const pairName = combineAndUpper(origin.id, destination.id);
|
||||
const pairName = combineAndUpper(origin.label, destination.label);
|
||||
const rate = this.rateMixer().byId[pairName].rate;
|
||||
const destinationAmount = amount * rate;
|
||||
this.setState({
|
||||
|
@ -230,7 +245,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
|
||||
public updateDestinationAmount = (origin: SwapInput, destination: SwapInput, amount: number) => {
|
||||
if (amount || amount === 0) {
|
||||
const pairNameReversed = combineAndUpper(destination.id, origin.id);
|
||||
const pairNameReversed = combineAndUpper(destination.label, origin.label);
|
||||
const rate = this.rateMixer().byId[pairNameReversed].rate;
|
||||
const originAmount = amount * rate;
|
||||
this.setState({
|
||||
|
@ -254,28 +269,30 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
: this.updateDestinationAmount(origin, destination, amount);
|
||||
};
|
||||
|
||||
public onChangeOriginKind = (newOption: WhitelistedCoins) => {
|
||||
public onChangeOriginKind = newOption => {
|
||||
const { origin, destination, destinationKindOptions } = this.state;
|
||||
const { options, initSwap } = this.props;
|
||||
|
||||
const newOrigin = { ...origin, id: newOption, amount: '' };
|
||||
const newOrigin = { ...origin, label: newOption.label, value: newOption.value, amount: '' };
|
||||
const newDest = {
|
||||
id: newOption === destination.id ? origin.id : destination.id,
|
||||
label: newOption.label === destination.label ? origin.label : destination.label,
|
||||
value: newOption.value === destination.value ? origin.value : destination.value,
|
||||
amount: ''
|
||||
};
|
||||
|
||||
this.setState({
|
||||
origin: newOrigin,
|
||||
destination: newDest,
|
||||
destinationKindOptions: reject(
|
||||
[...destinationKindOptions, options.byId[origin.id]],
|
||||
o => o.id === newOption
|
||||
[...destinationKindOptions, options.byId[origin.label]],
|
||||
o => o.id === newOption.label
|
||||
)
|
||||
});
|
||||
|
||||
initSwap({ origin: newOrigin, destination: newDest });
|
||||
};
|
||||
|
||||
public onChangeDestinationKind = (newOption: WhitelistedCoins) => {
|
||||
public onChangeDestinationKind = newOption => {
|
||||
const { initSwap } = this.props;
|
||||
const { origin, destination } = this.state;
|
||||
|
||||
|
@ -284,7 +301,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
amount: ''
|
||||
};
|
||||
|
||||
const newDest = { ...destination, id: newOption, amount: '' };
|
||||
const newDest = { ...destination, label: newOption.label, value: newOption.value, amount: '' };
|
||||
this.setState({
|
||||
origin: newOrigin,
|
||||
destination: newDest
|
||||
|
@ -304,9 +321,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
destinationErr,
|
||||
timeout
|
||||
} = this.state;
|
||||
const OriginKindDropDown = SwapDropdown as new () => SwapDropdown<any>;
|
||||
const DestinationKindDropDown = SwapDropdown as new () => SwapDropdown<any>;
|
||||
const pairName = combineAndUpper(origin.id, destination.id);
|
||||
const pairName = combineAndUpper(origin.label, destination.label);
|
||||
const bityLoaded = bityRates.byId && bityRates.byId[pairName] ? true : false;
|
||||
const shapeshiftLoaded = shapeshiftRates.byId && shapeshiftRates.byId[pairName] ? true : false;
|
||||
// This ensures both are loaded
|
||||
|
@ -314,73 +329,75 @@ export default class CurrencySwap extends PureComponent<Props, State> {
|
|||
const timeoutLoaded = (bityLoaded && timeout) || (shapeshiftLoaded && timeout);
|
||||
return (
|
||||
<article className="CurrencySwap">
|
||||
<h1 className="CurrencySwap-title">{translate('SWAP_init_1')}</h1>
|
||||
{loaded || timeoutLoaded ? (
|
||||
<div className="CurrencySwap-inner-wrap">
|
||||
<div className="CurrencySwap-input-group">
|
||||
{originErr && <span className="CurrencySwap-error-message">{originErr}</span>}
|
||||
<input
|
||||
id="origin-swap-input"
|
||||
className={`CurrencySwap-input form-control ${
|
||||
String(origin.amount) !== '' &&
|
||||
this.isMinMaxValid(origin.amount as number, origin.id, destination.id)
|
||||
? 'is-valid'
|
||||
: 'is-invalid'
|
||||
}`}
|
||||
type="number"
|
||||
placeholder="Amount"
|
||||
value={isNaN(origin.amount as number) ? '' : origin.amount}
|
||||
onChange={this.onChangeAmount}
|
||||
/>
|
||||
<div className="CurrencySwap-dropdown">
|
||||
<OriginKindDropDown
|
||||
ariaLabel={`change origin kind. current origin kind ${origin.id}`}
|
||||
options={originKindOptions}
|
||||
value={origin.id}
|
||||
onChange={this.onChangeOriginKind}
|
||||
/>
|
||||
<React.Fragment>
|
||||
<div className="CurrencySwap-inner-wrap">
|
||||
<div className="flex-spacer" />
|
||||
<div className="input-group-wrapper">
|
||||
<div className="input-group-header">Deposit</div>
|
||||
<label className="input-group input-group-inline-dropdown">
|
||||
<Input
|
||||
id="origin-swap-input"
|
||||
className={`input-group-input ${
|
||||
String(origin.amount) !== '' &&
|
||||
this.isMinMaxValid(origin.amount, origin.label, destination.label)
|
||||
? ''
|
||||
: 'invalid'
|
||||
}`}
|
||||
type="number"
|
||||
placeholder="Amount"
|
||||
value={isNaN(origin.amount) ? '' : origin.amount}
|
||||
onChange={this.onChangeAmount}
|
||||
/>
|
||||
<SwapDropdown
|
||||
options={originKindOptions}
|
||||
value={origin.value}
|
||||
onChange={this.onChangeOriginKind}
|
||||
/>
|
||||
</label>
|
||||
{originErr && <span className="CurrencySwap-error-message">{originErr}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="CurrencySwap-divider">{translate('SWAP_init_2')}</h1>
|
||||
<div className="CurrencySwap-input-group">
|
||||
{destinationErr && (
|
||||
<span className="CurrencySwap-error-message">{destinationErr}</span>
|
||||
)}
|
||||
<input
|
||||
id="destination-swap-input"
|
||||
className={`CurrencySwap-input form-control ${
|
||||
String(destination.amount) !== '' &&
|
||||
this.isMinMaxValid(origin.amount as number, origin.id, destination.id)
|
||||
? 'is-valid'
|
||||
: 'is-invalid'
|
||||
}`}
|
||||
type="number"
|
||||
placeholder="Amount"
|
||||
value={isNaN(destination.amount as number) ? '' : destination.amount}
|
||||
onChange={this.onChangeAmount}
|
||||
/>
|
||||
<div className="CurrencySwap-dropdown">
|
||||
<DestinationKindDropDown
|
||||
ariaLabel={`change destination kind. current destination kind ${destination.id}`}
|
||||
options={destinationKindOptions}
|
||||
value={destination.id}
|
||||
onChange={this.onChangeDestinationKind}
|
||||
/>
|
||||
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group input-group-inline-dropdown">
|
||||
<div className="input-group-header">Recieve</div>
|
||||
<Input
|
||||
id="destination-swap-input"
|
||||
className={`${
|
||||
String(destination.amount) !== '' &&
|
||||
this.isMinMaxValid(origin.amount, origin.label, destination.label)
|
||||
? ''
|
||||
: 'invalid'
|
||||
}`}
|
||||
type="number"
|
||||
placeholder="Amount"
|
||||
value={isNaN(destination.amount) ? '' : destination.amount}
|
||||
onChange={this.onChangeAmount}
|
||||
/>
|
||||
<SwapDropdown
|
||||
options={destinationKindOptions}
|
||||
value={destination.value}
|
||||
onChange={this.onChangeDestinationKind}
|
||||
/>
|
||||
</label>
|
||||
{destinationErr && (
|
||||
<span className="CurrencySwap-error-message">{destinationErr}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-spacer" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="CurrencySwap-submit">
|
||||
<SimpleButton
|
||||
onClick={this.onClickStartSwap}
|
||||
text={translate('SWAP_init_CTA')}
|
||||
disabled={this.state.disabled}
|
||||
type="primary"
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<Spinner />
|
||||
)}
|
||||
|
||||
<div className="CurrencySwap-submit">
|
||||
<SimpleButton
|
||||
onClick={this.onClickStartSwap}
|
||||
text={translate('SWAP_init_CTA')}
|
||||
disabled={this.state.disabled}
|
||||
type="primary"
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { getUnit } from 'selectors/transaction';
|
|||
import { getCurrentBalance } from 'selectors/wallet';
|
||||
import Spinner from 'components/ui/Spinner';
|
||||
import { Wei, TokenValue } from 'libs/units';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface StateProps {
|
||||
unit: string;
|
||||
|
@ -36,7 +37,7 @@ class FieldsClass extends Component<Props> {
|
|||
<div className="col-xs-12">
|
||||
<AddressFieldFactory
|
||||
withProps={({ currentTo }) => (
|
||||
<input className="form-control" type="text" value={currentTo.raw} readOnly={true} />
|
||||
<Input type="text" value={currentTo.raw} readOnly={true} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
@ -61,8 +62,7 @@ class FieldsClass extends Component<Props> {
|
|||
</h5>
|
||||
)}
|
||||
{isValid && (
|
||||
<input
|
||||
className="form-control"
|
||||
<Input
|
||||
type="text"
|
||||
value={`${currentValue.raw} ${this.props.unit}`}
|
||||
readOnly={true}
|
||||
|
|
|
@ -69,8 +69,8 @@ export default class PartThree extends PureComponent<ReduxActionProps & ReduxSta
|
|||
} = this.props;
|
||||
|
||||
const SwapProgressProps = {
|
||||
originId: origin.id,
|
||||
destinationId: destination.id,
|
||||
originId: origin.label,
|
||||
destinationId: destination.label,
|
||||
provider,
|
||||
bityOrderStatus,
|
||||
shapeshiftOrderStatus,
|
||||
|
@ -98,7 +98,7 @@ export default class PartThree extends PureComponent<ReduxActionProps & ReduxSta
|
|||
<PaymentInfo {...PaymentInfoProps} />
|
||||
|
||||
<LiteSend />
|
||||
{OpenOrder && origin.id === 'BTC' && <BitcoinQR {...BitcoinQRProps} />}
|
||||
{OpenOrder && origin.label === 'BTC' && <BitcoinQR {...BitcoinQRProps} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
|
|||
import translate from 'translations';
|
||||
import { SwapInput } from 'reducers/swap/types';
|
||||
import './PaymentInfo.scss';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
export interface Props {
|
||||
origin: SwapInput;
|
||||
|
@ -17,11 +18,11 @@ export default class PaymentInfo extends PureComponent<Props, {}> {
|
|||
<span>{translate('SWAP_order_CTA')}</span>
|
||||
<strong>
|
||||
{' '}
|
||||
{origin.amount} {origin.id}
|
||||
{origin.amount} {origin.label}
|
||||
</strong>
|
||||
<span> {translate('SENDModal_Content_2')}</span>
|
||||
<input
|
||||
className="SwapPayment-address form-control"
|
||||
<Input
|
||||
className="SwapPayment-address"
|
||||
value={this.props.paymentAddress || undefined}
|
||||
disabled={true}
|
||||
/>
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
TStopLoadBityRatesSwap
|
||||
} from 'actions/swap';
|
||||
import { SwapInput } from 'reducers/swap/types';
|
||||
import classnames from 'classnames';
|
||||
import SimpleButton from 'components/ui/SimpleButton';
|
||||
import { donationAddressMap } from 'config';
|
||||
import { isValidBTCAddress, isValidETHAddress } from 'libs/validators';
|
||||
|
@ -14,6 +13,7 @@ import React, { PureComponent } from 'react';
|
|||
import translate from 'translations';
|
||||
import { combineAndUpper } from 'utils/formatters';
|
||||
import './ReceivingAddress.scss';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
export interface StateProps {
|
||||
origin: SwapInput;
|
||||
|
@ -46,7 +46,7 @@ export default class ReceivingAddress extends PureComponent<StateProps & ActionP
|
|||
if (provider === 'shapeshift') {
|
||||
this.props.shapeshiftOrderCreateRequestedSwap(
|
||||
destinationAddress,
|
||||
origin.id,
|
||||
origin.label,
|
||||
destinationId,
|
||||
destinationKind
|
||||
);
|
||||
|
@ -54,7 +54,7 @@ export default class ReceivingAddress extends PureComponent<StateProps & ActionP
|
|||
this.props.bityOrderCreateRequestedSwap(
|
||||
origin.amount as number,
|
||||
this.props.destinationAddress,
|
||||
combineAndUpper(origin.id, destinationId)
|
||||
combineAndUpper(origin.label, destinationId)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -69,13 +69,6 @@ export default class ReceivingAddress extends PureComponent<StateProps & ActionP
|
|||
validAddress = isValidETHAddress(destinationAddress);
|
||||
}
|
||||
|
||||
const inputClasses = classnames({
|
||||
'SwapAddress-address-input': true,
|
||||
'form-control': true,
|
||||
'is-valid': validAddress,
|
||||
'is-invalid': !validAddress
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="SwapAddress block">
|
||||
<section className="row">
|
||||
|
@ -85,8 +78,8 @@ export default class ReceivingAddress extends PureComponent<StateProps & ActionP
|
|||
{translate('SWAP_rec_add')} ({destinationId})
|
||||
</h4>
|
||||
|
||||
<input
|
||||
className={inputClasses}
|
||||
<Input
|
||||
className={`SwapAddress-address-input ${!validAddress ? 'invalid' : ''}`}
|
||||
type="text"
|
||||
value={destinationAddress}
|
||||
onChange={this.onChangeDestinationAddress}
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import './SupportFooter.scss';
|
||||
import { SwapInput } from 'actions/swap';
|
||||
import { NormalizedBityRates, NormalizedShapeshiftRates } from 'reducers/swap/types';
|
||||
import { TextArea } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
origin: SwapInput;
|
||||
|
@ -30,7 +31,7 @@ class SupportFooter extends React.PureComponent<Props, {}> {
|
|||
shapeshiftRates,
|
||||
bityRates
|
||||
} = this.props;
|
||||
const pair = origin && destination ? origin.id + destination.id : 'BTCETH';
|
||||
const pair = origin && destination ? origin.label + destination.label : 'BTCETH';
|
||||
const rates = provider === 'shapeshift' ? shapeshiftRates.byId : bityRates.byId;
|
||||
const emailTo =
|
||||
provider === 'shapeshift' ? 'support@mycrypto.com' : 'support@mycrypto.com,mew@bity.com';
|
||||
|
@ -45,26 +46,26 @@ Provider: ${serviceProvider}
|
|||
|
||||
REF ID#: ${reference || ''}
|
||||
|
||||
Amount to send: ${origin.amount || ''} ${origin.id}
|
||||
Amount to send: ${origin.amount || ''} ${origin.label}
|
||||
|
||||
Amount to receive: ${destination.amount || ''} ${destination.id}
|
||||
Amount to receive: ${destination.amount || ''} ${destination.label}
|
||||
|
||||
Payment Address: ${paymentAddress || ''}
|
||||
|
||||
Receiving Address: ${destinationAddress || ''}
|
||||
|
||||
Rate: ${rates[pair].rate} ${origin.id}/${destination.id}
|
||||
Rate: ${rates[pair].rate} ${origin.label}/${destination.label}
|
||||
`);
|
||||
fallbackBody = `To: ${emailTo}
|
||||
Subject: Issue regarding my Swap via MyCrypto
|
||||
Message:
|
||||
Provider: ${serviceProvider}
|
||||
REF ID#: ${reference || ''}
|
||||
Amount to send: ${origin.amount || ''} ${origin.id}
|
||||
Amount to receive: ${destination.amount || ''} ${destination.id}
|
||||
Amount to send: ${origin.amount || ''} ${origin.label}
|
||||
Amount to receive: ${destination.amount || ''} ${destination.label}
|
||||
Payment Address: ${paymentAddress || ''}
|
||||
Receiving Address: ${destinationAddress || ''}
|
||||
Rate: ${rates[pair].rate} ${origin.id}/${destination.id}`;
|
||||
Rate: ${rates[pair].rate} ${origin.label}/${destination.label}`;
|
||||
}
|
||||
return (
|
||||
<section className="SupportFooter">
|
||||
|
@ -81,7 +82,7 @@ Rate: ${rates[pair].rate} ${origin.id}/${destination.id}`;
|
|||
<small>Click here if link doesn't work</small>
|
||||
</p>
|
||||
{open ? (
|
||||
<textarea defaultValue={fallbackBody} className="form-control input-sm" rows={9} />
|
||||
<TextArea defaultValue={fallbackBody} className="form-control input-sm" rows={9} />
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -69,7 +69,9 @@ export default class SwapInfoHeader extends PureComponent<SwapInfoHeaderProps, {
|
|||
{/*Amount to send*/}
|
||||
{!this.isExpanded() && (
|
||||
<div className={this.computedClass()}>
|
||||
<h3 className="SwapInfo-details-block-value">{` ${origin.amount} ${origin.id}`}</h3>
|
||||
<h3 className="SwapInfo-details-block-value">{` ${origin.amount} ${
|
||||
origin.label
|
||||
}`}</h3>
|
||||
<p className="SwapInfo-details-block-label">{translate('SEND_amount')}</p>
|
||||
</div>
|
||||
)}
|
||||
|
@ -93,7 +95,7 @@ export default class SwapInfoHeader extends PureComponent<SwapInfoHeaderProps, {
|
|||
{/*Amount to Receive*/}
|
||||
<div className={this.computedClass()}>
|
||||
<h3 className="SwapInfo-details-block-value">
|
||||
{` ${toFixedIfLarger(destination.amount as number, 4)} ${destination.id}`}
|
||||
{` ${toFixedIfLarger(destination.amount as number, 4)} ${destination.label}`}
|
||||
</h3>
|
||||
<p className="SwapInfo-details-block-label">{translate('SWAP_rec_amt')}</p>
|
||||
</div>
|
||||
|
@ -102,7 +104,9 @@ export default class SwapInfoHeader extends PureComponent<SwapInfoHeaderProps, {
|
|||
<div className={this.computedClass()}>
|
||||
<h3 className="SwapInfo-details-block-value">
|
||||
{`${computedOriginDestinationRatio &&
|
||||
toFixedIfLarger(computedOriginDestinationRatio, 4)} ${destination.id}/${origin.id}`}
|
||||
toFixedIfLarger(computedOriginDestinationRatio, 4)} ${destination.label}/${
|
||||
origin.label
|
||||
}`}
|
||||
</h3>
|
||||
<p className="SwapInfo-details-block-label">{translate('SWAP_your_rate')}</p>
|
||||
</div>
|
||||
|
|
|
@ -139,7 +139,7 @@ class Swap extends Component<ReduxActionProps & ReduxStateProps & RouteComponent
|
|||
const ReceivingAddressProps = {
|
||||
isPostingOrder,
|
||||
origin,
|
||||
destinationId: destination.id,
|
||||
destinationId: destination.label,
|
||||
destinationKind: destination.amount as number,
|
||||
destinationAddressSwap,
|
||||
destinationAddress,
|
||||
|
|
|
@ -32,8 +32,8 @@ export interface State {
|
|||
|
||||
export const INITIAL_STATE: State = {
|
||||
step: 1,
|
||||
origin: { id: 'BTC', amount: NaN },
|
||||
destination: { id: 'ETH', amount: NaN },
|
||||
origin: { label: 'BTC', amount: NaN },
|
||||
destination: { label: 'ETH', amount: NaN },
|
||||
options: {
|
||||
byId: {},
|
||||
allIds: []
|
||||
|
|
|
@ -4,7 +4,7 @@ import { WhitelistedCoins } from 'config';
|
|||
export type ProviderName = ProviderName;
|
||||
|
||||
export interface SwapInput {
|
||||
id: WhitelistedCoins;
|
||||
label: WhitelistedCoins;
|
||||
amount: number | string;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import { isUnlocked, isEtherBalancePending } from 'selectors/wallet';
|
|||
type SwapState = AppState['swap'];
|
||||
|
||||
export function* configureLiteSendSaga(): SagaIterator {
|
||||
const { amount, id }: SwapState['origin'] = yield select(getOrigin);
|
||||
const { amount, label }: SwapState['origin'] = yield select(getOrigin);
|
||||
const paymentAddress: SwapState['paymentAddress'] = yield call(fetchPaymentAddress);
|
||||
|
||||
if (!paymentAddress) {
|
||||
|
@ -28,7 +28,7 @@ export function* configureLiteSendSaga(): SagaIterator {
|
|||
return yield put(showLiteSend(false));
|
||||
}
|
||||
|
||||
const supportedUnit: boolean = yield select(isSupportedUnit, id);
|
||||
const supportedUnit: boolean = yield select(isSupportedUnit, label);
|
||||
if (!supportedUnit) {
|
||||
return yield put(showLiteSend(false));
|
||||
}
|
||||
|
@ -42,8 +42,8 @@ export function* configureLiteSendSaga(): SagaIterator {
|
|||
}
|
||||
|
||||
//if it's a token, manually scan for that tokens balance and wait for it to resolve
|
||||
if (!isEtherUnit(id)) {
|
||||
yield put(setTokenBalancePending({ tokenSymbol: id }));
|
||||
if (!isEtherUnit(label)) {
|
||||
yield put(setTokenBalancePending({ tokenSymbol: label }));
|
||||
yield take([
|
||||
WalletTK.WALLET_SET_TOKEN_BALANCE_FULFILLED,
|
||||
WalletTK.WALLET_SET_TOKEN_BALANCE_REJECTED
|
||||
|
@ -55,7 +55,7 @@ export function* configureLiteSendSaga(): SagaIterator {
|
|||
}
|
||||
}
|
||||
|
||||
yield put(setUnitMeta(id));
|
||||
yield put(setUnitMeta(label));
|
||||
yield put(setCurrentValue(amount.toString()));
|
||||
yield put(setCurrentTo(paymentAddress));
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
&-nowrap {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.flex-spacer {
|
||||
flex-grow: 2;
|
||||
}
|
||||
}
|
||||
.flex-spacer {
|
||||
flex-grow: 2;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
label {
|
||||
margin-bottom: $space-xs;
|
||||
font-size: $font-size-bump-more;
|
||||
|
||||
&.is-required:after {
|
||||
content: '*';
|
||||
|
@ -49,7 +48,6 @@ select.form-control {
|
|||
|
||||
.form-group {
|
||||
display: block;
|
||||
margin-top: $form-group-margin-bottom * 2;
|
||||
margin-bottom: $form-group-margin-bottom;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
// Input groups overrides
|
||||
@import "common/sass/variables";
|
||||
@import 'common/sass/variables';
|
||||
|
||||
label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
> * {
|
||||
|
@ -15,7 +19,15 @@
|
|||
|
||||
// Align with inputs
|
||||
.input-group-addon {
|
||||
padding: 0 $padding-base-horizontal;
|
||||
width: auto;
|
||||
font-size: 1rem;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
&--transparent {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.input-group-btn {
|
||||
|
@ -23,7 +35,7 @@
|
|||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
font-size: $font-size-base;
|
||||
padding: .5rem $space-xl;
|
||||
padding: 0.5rem $space-xl;
|
||||
}
|
||||
|
||||
// Fix for nested dropdown button
|
||||
|
|
|
@ -1,92 +1,92 @@
|
|||
// This syntax is necessary to override css styles in react-select
|
||||
@import 'common/sass/variables';
|
||||
|
||||
.Select {
|
||||
border-radius: 2px;
|
||||
background-color: white;
|
||||
border: 1px solid #e5ecf3;
|
||||
border-radius: 2px;
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
.Select-control {
|
||||
height: $input-height-base;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
font-weight: 400;
|
||||
border-radius: 0px;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
border: 1px solid $input-border;
|
||||
transition: $transition;
|
||||
box-shadow: inset 0 1px 0 0 rgba(63, 63, 68, 0.05);
|
||||
transition: border-color 120ms, box-shadow 120ms;
|
||||
height: 3rem;
|
||||
&-control {
|
||||
min-width: 7.5rem;
|
||||
height: inherit;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
&:hover {
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
border: 1px solid $input-border;
|
||||
box-shadow: none;
|
||||
}
|
||||
.Select-value {
|
||||
padding-left: $input-padding-x;
|
||||
padding-right: $input-padding-x;
|
||||
.Select-value-label {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
&-placeholder {
|
||||
line-height: $line-height-base;
|
||||
}
|
||||
&-input {
|
||||
position: absolute;
|
||||
> input {
|
||||
line-height: $line-height-base;
|
||||
padding: 0;
|
||||
}
|
||||
.Select-arrow-zone {
|
||||
float: right;
|
||||
}
|
||||
&-placeholder,
|
||||
&-input {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding: 0rem 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: block;
|
||||
height: auto;
|
||||
}
|
||||
&-option {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
&-menu-outer {
|
||||
z-index: 3;
|
||||
width: calc(100% + 2px);
|
||||
left: -1px;
|
||||
border-radius: 2px;
|
||||
background-color: white;
|
||||
border: none;
|
||||
border-left: 2px solid #5694b8;
|
||||
border-right: 2px solid #5694b8;
|
||||
border-bottom: 2px solid #5694b8;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
border-color: inherit;
|
||||
}
|
||||
&.invalid.has-blurred {
|
||||
border-color: $brand-danger;
|
||||
box-shadow: inset 0px 0px 0px 1px $brand-danger;
|
||||
}
|
||||
&.is-focused {
|
||||
border-color: #4295bc;
|
||||
box-shadow: inset 0px 0px 0px 1px #4295bc;
|
||||
&.valid {
|
||||
border-color: #8dd17b;
|
||||
box-shadow: inset 0px 0px 0px 1px #8dd17b;
|
||||
}
|
||||
}
|
||||
&-value {
|
||||
line-height: $line-height-base !important;
|
||||
padding-left: 0 !important;
|
||||
padding: 0.75rem 1rem !important;
|
||||
height: inherit !important;
|
||||
&-label {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.Select-menu-outer {
|
||||
box-sizing: content-box;
|
||||
font-weight: 400;
|
||||
border: 1px solid $input-border;
|
||||
width: calc(100% - 1px);
|
||||
}
|
||||
.Select-placeholder {
|
||||
color: #d3d3d3;
|
||||
padding: 0px $input-padding-x;
|
||||
line-height: $input-height-base;
|
||||
}
|
||||
.Select-input {
|
||||
opacity: 0;
|
||||
}
|
||||
&.is-open {
|
||||
.Select-control {
|
||||
border: 1px solid $input-border;
|
||||
}
|
||||
}
|
||||
&.is-focused {
|
||||
&:not(.is-open):not(.is-invalid) {
|
||||
.Select-control {
|
||||
border-color: $brand-primary;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 1px rgba(14, 151, 192, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin react-select-control-state($color) {
|
||||
.Select-control {
|
||||
border-color: lighten($color, 20%);
|
||||
@include input-shadow($color);
|
||||
}
|
||||
.Select-menu-outer {
|
||||
border-color: lighten($color, 20%);
|
||||
@include input-shadow($color);
|
||||
}
|
||||
|
||||
&.is-focused {
|
||||
.Select-control {
|
||||
border-color: lighten($color, 5%);
|
||||
@include input-focus-shadow($color);
|
||||
}
|
||||
.Select-menu-outer {
|
||||
border-color: lighten($color, 5%);
|
||||
@include input-focus-shadow($color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-valid {
|
||||
@include react-select-control-state($brand-success);
|
||||
}
|
||||
|
||||
&.is-invalid {
|
||||
@include react-select-control-state($brand-danger);
|
||||
}
|
||||
|
||||
&.is-warning {
|
||||
@include react-select-control-state($brand-warning);
|
||||
&-multi-value-wrapper {
|
||||
display: block;
|
||||
position: relative;
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ $brand-primary: $ether-blue;
|
|||
$brand-success: #5dba5a;
|
||||
$brand-info: $ether-navy;
|
||||
$brand-warning: #ff9800;
|
||||
$brand-danger: #ea4b40;
|
||||
$brand-danger: #db5846;
|
||||
|
||||
$body-bg: #fbfbfb;
|
||||
$text-color: $gray-dark;
|
||||
|
|
|
@ -15,7 +15,7 @@ exports[`render snapshot 1`] = `
|
|||
destination={
|
||||
Object {
|
||||
"amount": NaN,
|
||||
"id": "ETH",
|
||||
"label": "ETH",
|
||||
}
|
||||
}
|
||||
destinationAddress=""
|
||||
|
@ -71,7 +71,7 @@ exports[`render snapshot 1`] = `
|
|||
origin={
|
||||
Object {
|
||||
"amount": NaN,
|
||||
"id": "BTC",
|
||||
"label": "BTC",
|
||||
}
|
||||
}
|
||||
outputTx={null}
|
||||
|
|
|
@ -119,8 +119,8 @@ describe('swap reducer', () => {
|
|||
...INITIAL_STATE,
|
||||
bityRates: normalizedBityRates,
|
||||
shapeshiftRates: normalizedShapeshiftRates,
|
||||
origin: { id: 'BTC', amount: 1 },
|
||||
destination: { id: 'ETH', amount: 3 }
|
||||
origin: { label: 'BTC', amount: 1 },
|
||||
destination: { label: 'ETH', amount: 3 }
|
||||
},
|
||||
swapActions.restartSwap()
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue