mirror of
https://github.com/status-im/MyCrypto.git
synced 2025-02-04 07:14:03 +00:00
Show Recent Addresses on View Only (#1514)
* Add recent addresses to reducer, show them on view only decrypt. * Update test
This commit is contained in:
parent
f63bd92edf
commit
7798b9cef1
@ -43,7 +43,7 @@ export class KeystoreDecrypt extends PureComponent {
|
||||
const unlockDisabled = !file || (passReq && !password);
|
||||
|
||||
return (
|
||||
<form id="selectedUploadKey" onSubmit={this.unlock}>
|
||||
<form onSubmit={this.unlock}>
|
||||
<div className="form-group">
|
||||
<input
|
||||
className="hidden"
|
||||
|
26
common/components/WalletDecrypt/components/ViewOnly.scss
Normal file
26
common/components/WalletDecrypt/components/ViewOnly.scss
Normal file
@ -0,0 +1,26 @@
|
||||
@import 'common/sass/variables';
|
||||
@import 'common/sass/mixins';
|
||||
|
||||
.ViewOnly {
|
||||
&-recent {
|
||||
text-align: left;
|
||||
|
||||
&-separator {
|
||||
display: block;
|
||||
margin: $space-sm 0;
|
||||
text-align: center;
|
||||
color: $gray-light;
|
||||
}
|
||||
|
||||
.Select {
|
||||
&-option {
|
||||
@include ellipsis;
|
||||
}
|
||||
|
||||
.Identicon {
|
||||
display: inline-block;
|
||||
margin-right: $space-sm;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,63 +1,101 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import translate from 'translations';
|
||||
import { donationAddressMap } from 'config';
|
||||
import { connect } from 'react-redux';
|
||||
import Select, { Option } from 'react-select';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { isValidETHAddress } from 'libs/validators';
|
||||
import { AddressOnlyWallet } from 'libs/wallet';
|
||||
import { TextArea } from 'components/ui';
|
||||
import { getRecentAddresses } from 'selectors/wallet';
|
||||
import { AppState } from 'reducers';
|
||||
import { Input, Identicon } from 'components/ui';
|
||||
import './ViewOnly.scss';
|
||||
|
||||
interface Props {
|
||||
interface OwnProps {
|
||||
onUnlock(param: any): void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
recentAddresses: AppState['wallet']['recentAddresses'];
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
interface State {
|
||||
address: string;
|
||||
}
|
||||
|
||||
export class ViewOnlyDecrypt extends PureComponent<Props, State> {
|
||||
class ViewOnlyDecryptClass extends PureComponent<Props, State> {
|
||||
public state = {
|
||||
address: ''
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { recentAddresses } = this.props;
|
||||
const { address } = this.state;
|
||||
const isValid = isValidETHAddress(address);
|
||||
|
||||
const recentOptions = (recentAddresses.map(addr => ({
|
||||
label: (
|
||||
<React.Fragment>
|
||||
<Identicon address={addr} />
|
||||
{addr}
|
||||
</React.Fragment>
|
||||
),
|
||||
value: addr
|
||||
// I hate this assertion, but React elements definitely work as labels
|
||||
})) as any) as Option[];
|
||||
|
||||
return (
|
||||
<div id="selectedUploadKey">
|
||||
<div className="ViewOnly">
|
||||
<form className="form-group" onSubmit={this.openWallet}>
|
||||
<TextArea
|
||||
className={isValid ? 'is-valid' : 'is-invalid'}
|
||||
{!!recentOptions.length && (
|
||||
<div className="ViewOnly-recent">
|
||||
<Select
|
||||
value={address}
|
||||
onChange={this.handleSelectAddress}
|
||||
options={recentOptions}
|
||||
placeholder={translateRaw('VIEW_ONLY_RECENT')}
|
||||
/>
|
||||
<em className="ViewOnly-recent-separator">{translate('OR')}</em>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Input
|
||||
className={`ViewOnly-input ${isValid ? 'is-valid' : 'is-invalid'}`}
|
||||
value={address}
|
||||
onChange={this.changeAddress}
|
||||
onKeyDown={this.handleEnterKey}
|
||||
placeholder={donationAddressMap.ETH}
|
||||
rows={3}
|
||||
placeholder={translateRaw('VIEW_ONLY_ENTER')}
|
||||
/>
|
||||
|
||||
<button className="btn btn-primary btn-block" disabled={!isValid}>
|
||||
{translate('NAV_VIEWWALLET')}
|
||||
<button className="ViewOnly-submit btn btn-primary btn-block" disabled={!isValid}>
|
||||
{translate('VIEW_ADDR')}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private changeAddress = (ev: React.FormEvent<HTMLTextAreaElement>) => {
|
||||
private changeAddress = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
this.setState({ address: ev.currentTarget.value });
|
||||
};
|
||||
|
||||
private handleEnterKey = (ev: React.KeyboardEvent<HTMLElement>) => {
|
||||
if (ev.keyCode === 13) {
|
||||
this.openWallet(ev);
|
||||
}
|
||||
private handleSelectAddress = (option: Option) => {
|
||||
const address = option && option.value ? option.value.toString() : '';
|
||||
this.setState({ address }, () => this.openWallet());
|
||||
};
|
||||
|
||||
private openWallet = (ev: React.FormEvent<HTMLElement>) => {
|
||||
private openWallet = (ev?: React.FormEvent<HTMLElement>) => {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
const { address } = this.state;
|
||||
ev.preventDefault();
|
||||
if (isValidETHAddress(address)) {
|
||||
const wallet = new AddressOnlyWallet(address);
|
||||
this.props.onUnlock(wallet);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const ViewOnlyDecrypt = connect((state: AppState): StateProps => ({
|
||||
recentAddresses: getRecentAddresses(state)
|
||||
}))(ViewOnlyDecryptClass);
|
||||
|
@ -78,7 +78,7 @@
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
&::placeholder {
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
color: $input-color-placeholder;
|
||||
}
|
||||
&:not([disabled]):not([readonly]) {
|
||||
&.invalid.has-blurred.has-value {
|
||||
|
@ -28,6 +28,7 @@ export interface State {
|
||||
isPasswordPending: boolean;
|
||||
tokensError: string | null;
|
||||
hasSavedWalletTokens: boolean;
|
||||
recentAddresses: string[];
|
||||
}
|
||||
|
||||
export const INITIAL_STATE: State = {
|
||||
@ -39,16 +40,32 @@ export const INITIAL_STATE: State = {
|
||||
isPasswordPending: false,
|
||||
isTokensLoading: false,
|
||||
tokensError: null,
|
||||
hasSavedWalletTokens: true
|
||||
hasSavedWalletTokens: true,
|
||||
recentAddresses: []
|
||||
};
|
||||
|
||||
export const RECENT_ADDRESS_LIMIT = 10;
|
||||
|
||||
function addRecentAddress(addresses: string[], newWallet: IWallet | null) {
|
||||
if (!newWallet) {
|
||||
return addresses;
|
||||
}
|
||||
// Push new address onto the front
|
||||
const newAddresses = [newWallet.getAddressString(), ...addresses];
|
||||
// Dedupe addresses, limit length
|
||||
return newAddresses
|
||||
.filter((addr, idx) => newAddresses.indexOf(addr) === idx)
|
||||
.splice(0, RECENT_ADDRESS_LIMIT);
|
||||
}
|
||||
|
||||
function setWallet(state: State, action: SetWalletAction): State {
|
||||
return {
|
||||
...state,
|
||||
inst: action.payload,
|
||||
config: INITIAL_STATE.config,
|
||||
balance: INITIAL_STATE.balance,
|
||||
tokens: INITIAL_STATE.tokens
|
||||
tokens: INITIAL_STATE.tokens,
|
||||
recentAddresses: addRecentAddress(state.recentAddresses, action.payload)
|
||||
};
|
||||
}
|
||||
|
||||
@ -145,12 +162,19 @@ function setWalletConfig(state: State, action: SetWalletConfigAction): State {
|
||||
};
|
||||
}
|
||||
|
||||
function resetWallet(state: State): State {
|
||||
return {
|
||||
...INITIAL_STATE,
|
||||
recentAddresses: state.recentAddresses
|
||||
};
|
||||
}
|
||||
|
||||
export function wallet(state: State = INITIAL_STATE, action: WalletAction): State {
|
||||
switch (action.type) {
|
||||
case TypeKeys.WALLET_SET:
|
||||
return setWallet(state, action);
|
||||
case TypeKeys.WALLET_RESET:
|
||||
return INITIAL_STATE;
|
||||
return resetWallet(state);
|
||||
case TypeKeys.WALLET_SET_BALANCE_PENDING:
|
||||
return setBalancePending(state);
|
||||
case TypeKeys.WALLET_SET_BALANCE_FULFILLED:
|
||||
|
@ -24,6 +24,7 @@
|
||||
}
|
||||
&-placeholder {
|
||||
line-height: $line-height-base;
|
||||
color: $input-color-placeholder;
|
||||
}
|
||||
&-input {
|
||||
position: absolute;
|
||||
|
@ -3,7 +3,7 @@ $input-bg-disabled: $gray-lightest;
|
||||
$input-color: #333333;
|
||||
$input-border: $gray-lighter;
|
||||
$input-border-focus: rgba($brand-primary, 0.6);
|
||||
$input-color-placeholder: darken($gray-lighter, 10%);
|
||||
$input-color-placeholder: rgba(0, 0, 0, 0.3);
|
||||
$input-padding-x: 1rem;
|
||||
$input-padding-y: 0.75rem;
|
||||
$input-padding: $input-padding-y $input-padding-x;
|
||||
|
@ -201,3 +201,7 @@ export function getDisabledWallets(state: AppState): DisabledWallets {
|
||||
|
||||
return disabledWallets;
|
||||
}
|
||||
|
||||
export function getRecentAddresses(state: AppState) {
|
||||
return state.wallet.recentAddresses;
|
||||
}
|
||||
|
@ -8,7 +8,8 @@ import {
|
||||
INITIAL_STATE as initialTransactionsState,
|
||||
State as TransactionsState
|
||||
} from 'reducers/transactions';
|
||||
import { State as SwapState, INITIAL_STATE as swapInitialState } from 'reducers/swap';
|
||||
import { State as SwapState, INITIAL_STATE as initialSwapState } from 'reducers/swap';
|
||||
import { State as WalletState, INITIAL_STATE as initialWalletState } from 'reducers/wallet';
|
||||
import { applyMiddleware, createStore, Store } from 'redux';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||
import { createLogger } from 'redux-logger';
|
||||
@ -38,17 +39,19 @@ const configureStore = () => {
|
||||
middleware = applyMiddleware(sagaMiddleware, routerMiddleware(history as any));
|
||||
}
|
||||
|
||||
// ONLY LOAD SWAP STATE FROM LOCAL STORAGE IF STEP WAS 3
|
||||
const localSwapState = loadStatePropertyOrEmptyObject<SwapState>('swap');
|
||||
const swapState =
|
||||
localSwapState && localSwapState.step === 3
|
||||
? {
|
||||
...swapInitialState,
|
||||
...initialSwapState,
|
||||
...localSwapState
|
||||
}
|
||||
: { ...swapInitialState };
|
||||
: { ...initialSwapState };
|
||||
|
||||
const savedTransactionState = loadStatePropertyOrEmptyObject<TransactionState>('transaction');
|
||||
const savedTransactionsState = loadStatePropertyOrEmptyObject<TransactionsState>('transactions');
|
||||
const savedWalletState = loadStatePropertyOrEmptyObject<WalletState>('wallet');
|
||||
|
||||
const persistedInitialState: Partial<AppState> = {
|
||||
transaction: {
|
||||
@ -64,13 +67,15 @@ const configureStore = () => {
|
||||
: transactionInitialState.fields.gasPrice
|
||||
}
|
||||
},
|
||||
|
||||
// ONLY LOAD SWAP STATE FROM LOCAL STORAGE IF STEP WAS 3
|
||||
swap: swapState,
|
||||
transactions: {
|
||||
...initialTransactionsState,
|
||||
...savedTransactionsState
|
||||
},
|
||||
wallet: {
|
||||
...initialWalletState,
|
||||
...savedWalletState
|
||||
},
|
||||
...rehydrateConfigAndCustomTokenState()
|
||||
};
|
||||
|
||||
@ -109,6 +114,9 @@ const configureStore = () => {
|
||||
transactions: {
|
||||
recent: state.transactions.recent
|
||||
},
|
||||
wallet: {
|
||||
recentAddresses: state.wallet.recentAddresses
|
||||
},
|
||||
...getConfigAndCustomTokensStateToSubscribe(state)
|
||||
});
|
||||
}, 50)
|
||||
|
@ -418,6 +418,8 @@
|
||||
"MNEMONIC_FINAL_STEP_3": "Select your wallet type",
|
||||
"MNEMONIC_FINAL_STEP_4": "Enter your phrase",
|
||||
"MNEMONIC_FINAL_STEP_5": "Provide file & password",
|
||||
"VIEW_ONLY_RECENT": "Select a recent address",
|
||||
"VIEW_ONLY_ENTER": "Enter an address (e.g. 0x4bbeEB066eD09...)",
|
||||
"GO_TO_ACCOUNT": "Go to Account",
|
||||
"INSECURE_WALLET_TYPE_TITLE": "This is not a recommended way to access your wallet",
|
||||
"INSECURE_WALLET_TYPE_DESC": "Entering your $wallet_type on a website is dangerous. If our website is compromised, or you accidentally visit a phishing website, you could lose your funds. Before you continue, consider:",
|
||||
|
@ -5,13 +5,14 @@ import * as walletActions from 'actions/wallet';
|
||||
configuredStore.getState();
|
||||
|
||||
describe('wallet reducer', () => {
|
||||
it('should handle WALLET_SET', () => {
|
||||
describe('WALLET_SET', () => {
|
||||
const address = '0x123';
|
||||
const doSomething = new Promise<string>(resolve => {
|
||||
setTimeout(() => resolve('Success'), 10);
|
||||
});
|
||||
|
||||
const walletInstance = {
|
||||
getAddressString: () => doSomething,
|
||||
getAddressString: () => address,
|
||||
signRawTransaction: () => doSomething,
|
||||
signMessage: () => doSomething
|
||||
};
|
||||
@ -19,7 +20,8 @@ describe('wallet reducer', () => {
|
||||
//@ts-ignore
|
||||
expect(wallet(undefined, walletActions.setWallet(walletInstance))).toEqual({
|
||||
...INITIAL_STATE,
|
||||
inst: walletInstance
|
||||
inst: walletInstance,
|
||||
recentAddresses: [address]
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user