Implement initial functionality
This commit is contained in:
parent
97bfbba51f
commit
6b2a8eedbc
|
@ -13,6 +13,12 @@ interface OwnProps {
|
|||
isSelfAddress?: boolean;
|
||||
isCheckSummed?: boolean;
|
||||
showLabelMatch?: boolean;
|
||||
showIdenticon?: boolean;
|
||||
showInputLabel?: boolean;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
dropdownThreshold?: number;
|
||||
onChangeOverride?: (ev: React.FormEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
|
@ -26,26 +32,38 @@ const AddressField: React.SFC<Props> = ({
|
|||
isSelfAddress,
|
||||
isCheckSummed,
|
||||
showLabelMatch,
|
||||
toChecksumAddress
|
||||
toChecksumAddress,
|
||||
showIdenticon,
|
||||
placeholder = donationAddressMap.ETH,
|
||||
showInputLabel = true,
|
||||
onChangeOverride,
|
||||
value,
|
||||
dropdownThreshold
|
||||
}) => (
|
||||
<AddressFieldFactory
|
||||
isSelfAddress={isSelfAddress}
|
||||
showLabelMatch={showLabelMatch}
|
||||
showIdenticon={showIdenticon}
|
||||
onChangeOverride={onChangeOverride}
|
||||
value={value}
|
||||
dropdownThreshold={dropdownThreshold}
|
||||
withProps={({ currentTo, isValid, isLabelEntry, onChange, onFocus, onBlur, readOnly }) => (
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">
|
||||
{translate(isSelfAddress ? 'X_ADDRESS' : 'SEND_ADDR')}
|
||||
</div>
|
||||
{showInputLabel && (
|
||||
<div className="input-group-header">
|
||||
{translate(isSelfAddress ? 'X_ADDRESS' : 'SEND_ADDR')}
|
||||
</div>
|
||||
)}
|
||||
<Input
|
||||
className={`input-group-input ${!isValid && !isLabelEntry ? 'invalid' : ''}`}
|
||||
isValid={isValid}
|
||||
type="text"
|
||||
value={isCheckSummed ? toChecksumAddress(currentTo.raw) : currentTo.raw}
|
||||
placeholder={donationAddressMap.ETH}
|
||||
value={value ? value : isCheckSummed ? toChecksumAddress(currentTo.raw) : currentTo.raw}
|
||||
placeholder={placeholder}
|
||||
readOnly={!!(isReadOnly || readOnly)}
|
||||
spellCheck={false}
|
||||
onChange={onChange}
|
||||
onChange={onChangeOverride || onChange}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
|
|
|
@ -9,6 +9,9 @@ import { Address, Identicon } from 'components/ui';
|
|||
import './AddressFieldDropdown.scss';
|
||||
|
||||
interface StateProps {
|
||||
value?: string;
|
||||
dropdownThreshold?: number;
|
||||
onChangeOverride?: (ev: React.FormEvent<HTMLInputElement>) => void;
|
||||
labelAddresses: ReturnType<typeof addressBookSelectors.getLabelAddresses>;
|
||||
currentTo: ReturnType<typeof transactionSelectors.getToRaw>;
|
||||
}
|
||||
|
@ -37,14 +40,15 @@ class AddressFieldDropdown extends React.Component<Props> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const { currentTo } = this.props;
|
||||
const noMatchContent = currentTo.startsWith('0x') ? null : (
|
||||
const { value, currentTo, dropdownThreshold = 3 } = this.props;
|
||||
const stringInQuestion = value != null ? value : currentTo;
|
||||
const noMatchContent = stringInQuestion.startsWith('0x') ? null : (
|
||||
<li className="AddressFieldDropdown-dropdown-item AddressFieldDropdown-dropdown-item-no-match">
|
||||
<i className="fa fa-warning" /> {translate('NO_LABEL_FOUND_CONTAINING')} "{currentTo}".
|
||||
<i className="fa fa-warning" /> {translate('NO_LABEL_FOUND_CONTAINING')} "{stringInQuestion}".
|
||||
</li>
|
||||
);
|
||||
|
||||
return this.props.currentTo.length > 1 ? (
|
||||
return stringInQuestion.length >= dropdownThreshold ? (
|
||||
<ul className="AddressFieldDropdown" role="listbox">
|
||||
{this.getFilteredLabels().length > 0 ? this.renderDropdownItems() : noMatchContent}
|
||||
</ul>
|
||||
|
@ -53,6 +57,7 @@ class AddressFieldDropdown extends React.Component<Props> {
|
|||
|
||||
private renderDropdownItems = () =>
|
||||
this.getFilteredLabels().map((filteredLabel, index: number) => {
|
||||
const { onChangeOverride, setCurrentTo } = this.props;
|
||||
const { activeIndex } = this.state;
|
||||
const { address, label } = filteredLabel;
|
||||
const isActive = activeIndex === index;
|
||||
|
@ -64,7 +69,11 @@ class AddressFieldDropdown extends React.Component<Props> {
|
|||
<li
|
||||
key={address}
|
||||
className={className}
|
||||
onClick={() => this.props.setCurrentTo(address)}
|
||||
onClick={() =>
|
||||
onChangeOverride
|
||||
? onChangeOverride({ currentTarget: { value: address } })
|
||||
: setCurrentTo(address)
|
||||
}
|
||||
role="option"
|
||||
title={`${translateRaw('SEND_TO')}${label}`}
|
||||
>
|
||||
|
@ -79,11 +88,15 @@ class AddressFieldDropdown extends React.Component<Props> {
|
|||
);
|
||||
});
|
||||
|
||||
private getFilteredLabels = () =>
|
||||
Object.keys(this.props.labelAddresses)
|
||||
.filter(label => label.toLowerCase().includes(this.props.currentTo.toLowerCase()))
|
||||
private getFilteredLabels = () => {
|
||||
const { value, currentTo } = this.props;
|
||||
const includedString = value != null ? value : currentTo.toLowerCase();
|
||||
|
||||
return Object.keys(this.props.labelAddresses)
|
||||
.filter(label => label.toLowerCase().includes(includedString))
|
||||
.map(label => ({ address: this.props.labelAddresses[label], label }))
|
||||
.slice(0, 5);
|
||||
};
|
||||
|
||||
private getIsVisible = () =>
|
||||
this.props.currentTo.length > 1 && this.getFilteredLabels().length > 0;
|
||||
|
|
|
@ -15,6 +15,10 @@ interface OwnProps {
|
|||
to: string | null;
|
||||
isSelfAddress?: boolean;
|
||||
showLabelMatch?: boolean;
|
||||
showIdenticon?: boolean;
|
||||
value?: string;
|
||||
dropdownThreshold?: number;
|
||||
onChangeOverride?: (ev: React.FormEvent<HTMLInputElement>) => void;
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
|
@ -56,16 +60,30 @@ class AddressFieldFactoryClass extends React.Component<Props> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
isSelfAddress,
|
||||
showLabelMatch,
|
||||
withProps,
|
||||
showIdenticon,
|
||||
onChangeOverride,
|
||||
value,
|
||||
dropdownThreshold
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="AddressField">
|
||||
<AddressInputFactory
|
||||
isSelfAddress={this.props.isSelfAddress}
|
||||
showLabelMatch={this.props.showLabelMatch}
|
||||
isSelfAddress={isSelfAddress}
|
||||
showLabelMatch={showLabelMatch}
|
||||
withProps={withProps}
|
||||
showIdenticon={showIdenticon}
|
||||
onChangeOverride={onChangeOverride}
|
||||
value={value}
|
||||
dropdownThreshold={dropdownThreshold}
|
||||
isFocused={this.state.isFocused}
|
||||
onChange={this.setAddress}
|
||||
onFocus={this.focus}
|
||||
onBlur={this.setBlurTimeout}
|
||||
withProps={this.props.withProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -76,8 +94,10 @@ class AddressFieldFactoryClass extends React.Component<Props> {
|
|||
private blur = () => this.setState({ isFocused: false });
|
||||
|
||||
private setAddress = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
const { onChangeOverride, setCurrentTo } = this.props;
|
||||
const { value } = ev.currentTarget;
|
||||
this.props.setCurrentTo(value);
|
||||
|
||||
onChangeOverride ? onChangeOverride(ev) : setCurrentTo(value);
|
||||
};
|
||||
|
||||
private setBlurTimeout = () => (this.goingToBlur = window.setTimeout(this.blur, 150));
|
||||
|
@ -90,13 +110,21 @@ const AddressFieldFactory = connect(null, { setCurrentTo: transactionActions.set
|
|||
interface DefaultAddressFieldProps {
|
||||
isSelfAddress?: boolean;
|
||||
showLabelMatch?: boolean;
|
||||
showIdenticon?: boolean;
|
||||
value?: string;
|
||||
dropdownThreshold?: number;
|
||||
onChangeOverride?: (ev: React.FormEvent<HTMLInputElement>) => void;
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
const DefaultAddressField: React.SFC<DefaultAddressFieldProps> = ({
|
||||
isSelfAddress,
|
||||
showLabelMatch,
|
||||
withProps
|
||||
showIdenticon,
|
||||
value,
|
||||
withProps,
|
||||
onChangeOverride,
|
||||
dropdownThreshold
|
||||
}) => (
|
||||
<Query
|
||||
params={['to']}
|
||||
|
@ -106,6 +134,10 @@ const DefaultAddressField: React.SFC<DefaultAddressFieldProps> = ({
|
|||
isSelfAddress={isSelfAddress}
|
||||
showLabelMatch={showLabelMatch}
|
||||
withProps={withProps}
|
||||
showIdenticon={showIdenticon}
|
||||
onChangeOverride={onChangeOverride}
|
||||
value={value}
|
||||
dropdownThreshold={dropdownThreshold}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -27,7 +27,12 @@ interface StateProps {
|
|||
interface OwnProps {
|
||||
isSelfAddress?: boolean;
|
||||
showLabelMatch?: boolean;
|
||||
showIdenticon?: boolean;
|
||||
isFocused?: boolean;
|
||||
className?: string;
|
||||
value?: string;
|
||||
dropdownThreshold?: number;
|
||||
onChangeOverride?: (ev: React.FormEvent<HTMLInputElement>) => void;
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
onFocus(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
onBlur(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
|
@ -58,6 +63,7 @@ type Props = OwnProps & StateProps;
|
|||
class AddressInputFactoryClass extends Component<Props> {
|
||||
public render() {
|
||||
const {
|
||||
className,
|
||||
label,
|
||||
currentTo,
|
||||
onChange,
|
||||
|
@ -69,16 +75,28 @@ class AddressInputFactoryClass extends Component<Props> {
|
|||
showLabelMatch,
|
||||
isSelfAddress,
|
||||
isResolving,
|
||||
isFocused
|
||||
isFocused,
|
||||
showIdenticon = true,
|
||||
onChangeOverride,
|
||||
value,
|
||||
dropdownThreshold
|
||||
} = this.props;
|
||||
const { value } = currentTo;
|
||||
const addr = addHexPrefix(value ? value.toString('hex') : '0');
|
||||
const inputClassName = `AddressInput-input ${label ? 'AddressInput-input-with-label' : ''}`;
|
||||
const sendingTo = `${translateRaw('SENDING_TO')} ${label}`;
|
||||
const isENSAddress = currentTo.raw.includes('.eth');
|
||||
|
||||
/**
|
||||
* @desc Initially set the address to the passed value.
|
||||
* If there wasn't a value passed, use the value from the redux store.
|
||||
*/
|
||||
let addr = value;
|
||||
|
||||
if (addr == null) {
|
||||
addr = addHexPrefix(currentTo.value ? currentTo.value.toString('hex') : '0');
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="AddressInput form-group">
|
||||
<div className={`AddressInput form-group`}>
|
||||
<div className={inputClassName}>
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
|
@ -95,7 +113,14 @@ class AddressInputFactoryClass extends Component<Props> {
|
|||
}
|
||||
/>
|
||||
<ENSStatus ensAddress={currentTo.raw} isLoading={isResolving} rawAddress={addr} />
|
||||
{isFocused && !isENSAddress && <AddressFieldDropdown />}
|
||||
{isFocused &&
|
||||
!isENSAddress && (
|
||||
<AddressFieldDropdown
|
||||
onChangeOverride={onChangeOverride}
|
||||
value={value}
|
||||
dropdownThreshold={dropdownThreshold}
|
||||
/>
|
||||
)}
|
||||
{showLabelMatch &&
|
||||
label && (
|
||||
<div title={sendingTo} className="AddressInput-input-label">
|
||||
|
@ -103,9 +128,11 @@ class AddressInputFactoryClass extends Component<Props> {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="AddressInput-identicon">
|
||||
<Identicon address={addr} />
|
||||
</div>
|
||||
{showIdenticon && (
|
||||
<div className="AddressInput-identicon">
|
||||
<Identicon address={addr} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
@import 'common/sass/mixins';
|
||||
|
||||
.ViewOnly {
|
||||
& .AddressInput {
|
||||
margin-top: 15px;
|
||||
|
||||
& .input-group-input {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-recent {
|
||||
text-align: left;
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import { AppState } from 'features/reducers';
|
|||
import { getIsValidAddressFn } from 'features/config';
|
||||
import { walletSelectors } from 'features/wallet';
|
||||
import { Input, Identicon } from 'components/ui';
|
||||
import { AddressField } from 'components';
|
||||
import './ViewOnly.scss';
|
||||
|
||||
interface OwnProps {
|
||||
|
@ -23,17 +24,20 @@ type Props = OwnProps & StateProps;
|
|||
|
||||
interface State {
|
||||
address: string;
|
||||
addressFromBook: string;
|
||||
}
|
||||
|
||||
class ViewOnlyDecryptClass extends PureComponent<Props, State> {
|
||||
public state = {
|
||||
address: ''
|
||||
address: '',
|
||||
addressFromBook: ''
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { recentAddresses, isValidAddress } = this.props;
|
||||
const { address } = this.state;
|
||||
const { address, addressFromBook } = this.state;
|
||||
const isValid = isValidAddress(address);
|
||||
const or = <em className="ViewOnly-recent-separator">{translate('OR')}</em>;
|
||||
|
||||
const recentOptions = (recentAddresses.map(addr => ({
|
||||
label: (
|
||||
|
@ -48,7 +52,7 @@ class ViewOnlyDecryptClass extends PureComponent<Props, State> {
|
|||
|
||||
return (
|
||||
<div className="ViewOnly">
|
||||
<form className="form-group" onSubmit={this.openWallet}>
|
||||
<form className="form-group" onSubmit={this.openWalletWithAddress}>
|
||||
{!!recentOptions.length && (
|
||||
<div className="ViewOnly-recent">
|
||||
<Select
|
||||
|
@ -57,10 +61,20 @@ class ViewOnlyDecryptClass extends PureComponent<Props, State> {
|
|||
options={recentOptions}
|
||||
placeholder={translateRaw('VIEW_ONLY_RECENT')}
|
||||
/>
|
||||
<em className="ViewOnly-recent-separator">{translate('OR')}</em>
|
||||
{or}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="ViewOnly-book">
|
||||
<AddressField
|
||||
value={addressFromBook}
|
||||
showInputLabel={false}
|
||||
showIdenticon={false}
|
||||
placeholder={translateRaw('SELECT_FROM_ADDRESS_BOOK')}
|
||||
onChangeOverride={this.handleSelectAddressFromBook}
|
||||
dropdownThreshold={0}
|
||||
/>
|
||||
{or}
|
||||
</div>
|
||||
<Input
|
||||
isValid={isValid}
|
||||
className="ViewOnly-input"
|
||||
|
@ -68,7 +82,6 @@ class ViewOnlyDecryptClass extends PureComponent<Props, State> {
|
|||
onChange={this.changeAddress}
|
||||
placeholder={translateRaw('VIEW_ONLY_ENTER')}
|
||||
/>
|
||||
|
||||
<button className="ViewOnly-submit btn btn-primary btn-block" disabled={!isValid}>
|
||||
{translate('VIEW_ADDR')}
|
||||
</button>
|
||||
|
@ -83,16 +96,33 @@ class ViewOnlyDecryptClass extends PureComponent<Props, State> {
|
|||
|
||||
private handleSelectAddress = (option: Option) => {
|
||||
const address = option && option.value ? option.value.toString() : '';
|
||||
this.setState({ address }, () => this.openWallet());
|
||||
this.setState({ address }, this.openWalletWithAddress);
|
||||
};
|
||||
|
||||
private openWallet = (ev?: React.FormEvent<HTMLElement>) => {
|
||||
private handleSelectAddressFromBook = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
const { currentTarget: { value: addressFromBook } } = ev;
|
||||
this.setState({ addressFromBook }, this.openWalletWithAddressBook);
|
||||
};
|
||||
|
||||
private openWalletWithAddress = (ev?: React.FormEvent<HTMLElement>) => {
|
||||
const { address } = this.state;
|
||||
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
this.openWallet(address);
|
||||
};
|
||||
|
||||
private openWalletWithAddressBook = () => {
|
||||
const { addressFromBook } = this.state;
|
||||
|
||||
this.openWallet(addressFromBook);
|
||||
};
|
||||
|
||||
private openWallet = (address: string) => {
|
||||
const { isValidAddress } = this.props;
|
||||
const { address } = this.state;
|
||||
|
||||
if (isValidAddress(address)) {
|
||||
const wallet = new AddressOnlyWallet(address);
|
||||
this.props.onUnlock(wallet);
|
||||
|
|
|
@ -665,6 +665,7 @@
|
|||
"WHAT_IS_PAYMENT_ID": "what's a payment ID?",
|
||||
"ANNOUNCEMENT_MESSAGE": "MyCrypto.com no longer allows the use of private keys, mnemonics, or keystore files in the browser. To continue using them, please download the [MyCrypto Desktop App](https://download.mycrypto.com).",
|
||||
"U2F_NOT_SUPPORTED": "The U2F standard that hardware wallets use does not seem to be supported by your browser. Please try again using Google Chrome.",
|
||||
"SIMILAR_TRANSACTION_ERROR": "This transaction is very similar to a recent transaction. Please wait a few moments and try again, or click 'Advanced' and manually set the nonce to a new value."
|
||||
"SIMILAR_TRANSACTION_ERROR": "This transaction is very similar to a recent transaction. Please wait a few moments and try again, or click 'Advanced' and manually set the nonce to a new value.",
|
||||
"SELECT_FROM_ADDRESS_BOOK": "Select from your address book"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue