Disable more wallets conditionally + explain why (#924)
* Add more wallet disables, move all into selector. * Add reasons for disabled wallets. * Disable read only in lite send. * Fix view address showing insecure icon.
This commit is contained in:
parent
df52521c17
commit
2309d05c06
|
@ -32,8 +32,9 @@ import {
|
|||
InsecureWalletWarning
|
||||
} from './components';
|
||||
import { AppState } from 'reducers';
|
||||
import DISABLES from './disables';
|
||||
import { showNotification, TShowNotification } from 'actions/notifications';
|
||||
import { getDisabledWallets } from 'selectors/wallet';
|
||||
import { DisabledWallets } from './disables';
|
||||
|
||||
import LedgerIcon from 'assets/images/wallets/ledger.svg';
|
||||
import MetamaskIcon from 'assets/images/wallets/metamask.svg';
|
||||
|
@ -48,12 +49,10 @@ import {
|
|||
isWeb3NodeAvailable,
|
||||
knowledgeBaseURL
|
||||
} from 'config';
|
||||
import { unSupportedWalletFormatsOnNetwork } from 'utils/network';
|
||||
import { getNetworkConfig } from '../../selectors/config';
|
||||
|
||||
interface OwnProps {
|
||||
hidden?: boolean;
|
||||
disabledWallets?: WalletName[];
|
||||
disabledWallets?: DisabledWallets;
|
||||
showGenerateLink?: boolean;
|
||||
}
|
||||
|
||||
|
@ -69,8 +68,7 @@ interface DispatchProps {
|
|||
}
|
||||
|
||||
interface StateProps {
|
||||
computedDisabledWallets: WalletName[];
|
||||
offline: boolean;
|
||||
computedDisabledWallets: DisabledWallets;
|
||||
isWalletPending: AppState['wallet']['isWalletPending'];
|
||||
isPasswordPending: AppState['wallet']['isPasswordPending'];
|
||||
}
|
||||
|
@ -282,6 +280,9 @@ export class WalletDecrypt extends Component<Props, State> {
|
|||
};
|
||||
|
||||
public buildWalletOptions() {
|
||||
const { computedDisabledWallets } = this.props;
|
||||
const { reasons } = computedDisabledWallets;
|
||||
|
||||
return (
|
||||
<div className="WalletDecrypt-wallets">
|
||||
<h2 className="WalletDecrypt-wallets-title">{translate('decrypt_Access')}</h2>
|
||||
|
@ -299,6 +300,7 @@ export class WalletDecrypt extends Component<Props, State> {
|
|||
walletType={walletType}
|
||||
isSecure={true}
|
||||
isDisabled={this.isWalletDisabled(walletType)}
|
||||
disableReason={reasons[walletType]}
|
||||
onClick={this.handleWalletChoice}
|
||||
/>
|
||||
);
|
||||
|
@ -316,6 +318,7 @@ export class WalletDecrypt extends Component<Props, State> {
|
|||
walletType={walletType}
|
||||
isSecure={false}
|
||||
isDisabled={this.isWalletDisabled(walletType)}
|
||||
disableReason={reasons[walletType]}
|
||||
onClick={this.handleWalletChoice}
|
||||
/>
|
||||
);
|
||||
|
@ -332,6 +335,7 @@ export class WalletDecrypt extends Component<Props, State> {
|
|||
walletType={walletType}
|
||||
isReadOnly={true}
|
||||
isDisabled={this.isWalletDisabled(walletType)}
|
||||
disableReason={reasons[walletType]}
|
||||
onClick={this.handleWalletChoice}
|
||||
/>
|
||||
);
|
||||
|
@ -426,24 +430,26 @@ export class WalletDecrypt extends Component<Props, State> {
|
|||
};
|
||||
|
||||
private isWalletDisabled = (walletKey: WalletName) => {
|
||||
if (this.props.offline && DISABLES.ONLINE_ONLY.includes(walletKey)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.props.computedDisabledWallets.indexOf(walletKey) !== -1;
|
||||
return this.props.computedDisabledWallets.wallets.indexOf(walletKey) !== -1;
|
||||
};
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState, ownProps: Props) {
|
||||
const { disabledWallets } = ownProps;
|
||||
const network = getNetworkConfig(state);
|
||||
const networkDisabledFormats = unSupportedWalletFormatsOnNetwork(network);
|
||||
const computedDisabledWallets = disabledWallets
|
||||
? disabledWallets.concat(networkDisabledFormats)
|
||||
: networkDisabledFormats;
|
||||
let computedDisabledWallets = getDisabledWallets(state);
|
||||
|
||||
if (disabledWallets) {
|
||||
computedDisabledWallets = {
|
||||
wallets: [...computedDisabledWallets.wallets, ...disabledWallets.wallets],
|
||||
reasons: {
|
||||
...computedDisabledWallets.reasons,
|
||||
...disabledWallets.reasons
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
computedDisabledWallets,
|
||||
offline: state.config.offline,
|
||||
isWalletPending: state.wallet.isWalletPending,
|
||||
isPasswordPending: state.wallet.isPasswordPending
|
||||
};
|
||||
|
|
|
@ -12,16 +12,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes wallet-button-enter-disabled {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
30%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
.WalletButton {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
|
@ -70,10 +60,17 @@
|
|||
}
|
||||
|
||||
&.is-disabled {
|
||||
opacity: 0.3 !important;
|
||||
outline: none;
|
||||
cursor: not-allowed;
|
||||
animation-name: wallet-button-enter, wallet-button-enter-disabled;
|
||||
@include show-tooltip-on-hover;
|
||||
|
||||
.WalletButton-inner {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
&-inner {
|
||||
transition: opacity 200ms ease;
|
||||
}
|
||||
|
||||
&-title {
|
||||
|
|
|
@ -16,6 +16,7 @@ interface OwnProps {
|
|||
isSecure?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
isDisabled?: boolean;
|
||||
disableReason?: string;
|
||||
onClick(walletType: string): void;
|
||||
}
|
||||
|
||||
|
@ -23,6 +24,12 @@ interface StateProps {
|
|||
isFormatDisabled?: boolean;
|
||||
}
|
||||
|
||||
interface Icon {
|
||||
icon: string;
|
||||
tooltip: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
export class WalletButton extends React.PureComponent<Props> {
|
||||
|
@ -35,9 +42,37 @@ export class WalletButton extends React.PureComponent<Props> {
|
|||
helpLink,
|
||||
isSecure,
|
||||
isReadOnly,
|
||||
isDisabled
|
||||
isDisabled,
|
||||
disableReason
|
||||
} = this.props;
|
||||
|
||||
const icons: Icon[] = [];
|
||||
if (isReadOnly) {
|
||||
icons.push({
|
||||
icon: 'eye',
|
||||
tooltip: translateRaw('You cannot send using address only')
|
||||
});
|
||||
} else {
|
||||
if (isSecure) {
|
||||
icons.push({
|
||||
icon: 'shield',
|
||||
tooltip: translateRaw('This wallet type is secure')
|
||||
});
|
||||
} else {
|
||||
icons.push({
|
||||
icon: 'exclamation-triangle',
|
||||
tooltip: translateRaw('This wallet type is insecure')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (helpLink) {
|
||||
icons.push({
|
||||
icon: 'question-circle',
|
||||
tooltip: translateRaw('NAV_Help'),
|
||||
href: helpLink
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames({
|
||||
|
@ -49,42 +84,32 @@ export class WalletButton extends React.PureComponent<Props> {
|
|||
tabIndex={isDisabled ? -1 : 0}
|
||||
aria-disabled={isDisabled}
|
||||
>
|
||||
<div className="WalletButton-title">
|
||||
{icon && <img className="WalletButton-title-icon" src={icon} />}
|
||||
<span>{name}</span>
|
||||
<div className="WalletButton-inner">
|
||||
<div className="WalletButton-title">
|
||||
{icon && <img className="WalletButton-title-icon" src={icon} />}
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
|
||||
{description && <div className="WalletButton-description">{description}</div>}
|
||||
{example && <div className="WalletButton-example">{example}</div>}
|
||||
|
||||
<div className="WalletButton-icons">
|
||||
{icons.map(i => (
|
||||
<span className="WalletButton-icons-icon" key={i.icon} onClick={this.stopPropogation}>
|
||||
{i.href ? (
|
||||
<NewTabLink href={i.href} onClick={this.stopPropogation}>
|
||||
<i className={`fa fa-${i.icon}`} />
|
||||
</NewTabLink>
|
||||
) : (
|
||||
<i className={`fa fa-${i.icon}`} />
|
||||
)}
|
||||
{!isDisabled && <Tooltip size="sm">{i.tooltip}</Tooltip>}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{description && <div className="WalletButton-description">{description}</div>}
|
||||
{example && <div className="WalletButton-example">{example}</div>}
|
||||
|
||||
<div className="WalletButton-icons">
|
||||
{isSecure ? (
|
||||
<span className="WalletButton-icons-icon" onClick={this.stopPropogation}>
|
||||
<i className="fa fa-shield" />
|
||||
<Tooltip>{translateRaw('This wallet type is secure')}</Tooltip>
|
||||
</span>
|
||||
) : (
|
||||
<span className="WalletButton-icons-icon" onClick={this.stopPropogation}>
|
||||
<i className="fa fa-exclamation-triangle" />
|
||||
<Tooltip>{translateRaw('This wallet type is insecure')}</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
{isReadOnly && (
|
||||
<span className="WalletButton-icons-icon" onClick={this.stopPropogation}>
|
||||
<i className="fa fa-eye" />
|
||||
<Tooltip>{translateRaw('You cannot send using address only')}</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
|
||||
{helpLink && (
|
||||
<span className="WalletButton-icons-icon">
|
||||
<NewTabLink href={helpLink} onClick={this.stopPropogation}>
|
||||
<i className="fa fa-question-circle" />
|
||||
</NewTabLink>
|
||||
<Tooltip>{translateRaw('NAV_Help')}</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{isDisabled && disableReason && <Tooltip>{disableReason}</Tooltip>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,31 @@
|
|||
import { MiscWalletName, SecureWalletName, WalletName } from 'config';
|
||||
|
||||
export interface DisabledWallets {
|
||||
wallets: WalletName[];
|
||||
reasons: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
enum WalletMode {
|
||||
READ_ONLY = 'READ_ONLY',
|
||||
UNABLE_TO_SIGN = 'UNABLE_TO_SIGN',
|
||||
ONLINE_ONLY = 'ONLINE_ONLY'
|
||||
UNABLE_TO_SIGN = 'UNABLE_TO_SIGN'
|
||||
}
|
||||
|
||||
const walletModes: { [key in WalletMode]: WalletName[] } = {
|
||||
[WalletMode.READ_ONLY]: [MiscWalletName.VIEW_ONLY],
|
||||
[WalletMode.UNABLE_TO_SIGN]: [SecureWalletName.TREZOR, MiscWalletName.VIEW_ONLY],
|
||||
[WalletMode.ONLINE_ONLY]: [SecureWalletName.WEB3, SecureWalletName.TREZOR]
|
||||
// Duplicating reasons is kind of tedious, but saves having to run through a
|
||||
// bunch of loops to format it differently
|
||||
export const DISABLE_WALLETS: { [key in WalletMode]: DisabledWallets } = {
|
||||
[WalletMode.READ_ONLY]: {
|
||||
wallets: [MiscWalletName.VIEW_ONLY],
|
||||
reasons: {
|
||||
[MiscWalletName.VIEW_ONLY]: 'Read only is not allowed'
|
||||
}
|
||||
},
|
||||
[WalletMode.UNABLE_TO_SIGN]: {
|
||||
wallets: [SecureWalletName.TREZOR, MiscWalletName.VIEW_ONLY],
|
||||
reasons: {
|
||||
[SecureWalletName.TREZOR]: 'This wallet can’t sign messages',
|
||||
[MiscWalletName.VIEW_ONLY]: 'This wallet can’t sign messages'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default walletModes;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import WalletDecrypt from './WalletDecrypt';
|
||||
export default WalletDecrypt;
|
||||
export { default as DISABLE_WALLETS } from './disables';
|
||||
export * from './disables';
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
@import 'common/sass/variables';
|
||||
@import 'common/sass/mixins';
|
||||
|
||||
$tooltip-bg: rgba(#222, 0.95);
|
||||
|
||||
.Tooltip {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 220px;
|
||||
color: #FFF;
|
||||
font-size: $font-size-xs;
|
||||
font-size: $font-size-small;
|
||||
font-family: $font-family-sans-serif;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
|
@ -20,9 +22,9 @@
|
|||
|
||||
> span {
|
||||
display: inline-block;
|
||||
background: rgba(#000, 0.9);
|
||||
border-radius: 2px;
|
||||
padding: 4px 8px;
|
||||
background: $tooltip-bg;
|
||||
border-radius: 3px;
|
||||
padding: 6px 10px;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
|
@ -30,7 +32,36 @@
|
|||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 100%);
|
||||
@include triangle(8px, rgba(#000, 0.9), down);
|
||||
@include triangle(10px, $tooltip-bg, down);
|
||||
}
|
||||
}
|
||||
|
||||
// Sizing, medium is default
|
||||
&.is-size-sm {
|
||||
width: 200px;
|
||||
font-size: $font-size-xs;
|
||||
|
||||
> span {
|
||||
padding: 4px 8px;
|
||||
border-radius: 2px;
|
||||
|
||||
&:after {
|
||||
@include triangle(8px, $tooltip-bg, down);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-size-lg {
|
||||
width: 240px;
|
||||
font-size: $font-size-base;
|
||||
|
||||
> span {
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
|
||||
&:after {
|
||||
@include triangle(12px, $tooltip-bg, down);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import './Tooltip.scss';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactElement<string> | string;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
const Tooltip: React.SFC<Props> = ({ children }) => (
|
||||
<div className="Tooltip">
|
||||
const Tooltip: React.SFC<Props> = ({ size, children }) => (
|
||||
<div
|
||||
className={classnames({
|
||||
Tooltip: true,
|
||||
[`is-size-${size}`]: !!size
|
||||
})}
|
||||
>
|
||||
<span className="Tooltip-text">{children}</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -2,15 +2,14 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import translate, { TranslateType } from 'translations';
|
||||
import WalletDecrypt from 'components/WalletDecrypt';
|
||||
import WalletDecrypt, { DisabledWallets } from 'components/WalletDecrypt';
|
||||
import { IWallet } from 'libs/wallet/IWallet';
|
||||
import './UnlockHeader.scss';
|
||||
import { WalletName } from 'config';
|
||||
|
||||
interface Props {
|
||||
title: TranslateType;
|
||||
wallet: IWallet;
|
||||
disabledWallets?: WalletName[];
|
||||
disabledWallets?: DisabledWallets;
|
||||
showGenerateLink?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { Component } from 'react';
|
||||
import WalletDecrypt from 'components/WalletDecrypt';
|
||||
import WalletDecrypt, { DISABLE_WALLETS } from 'components/WalletDecrypt';
|
||||
import { OnlyUnlocked } from 'components/renderCbs';
|
||||
import { Fields } from './Fields';
|
||||
import { isUnlocked as isUnlockedSelector } from 'selectors/wallet';
|
||||
|
@ -41,7 +41,11 @@ class LiteSendClass extends Component<Props> {
|
|||
</div>
|
||||
);
|
||||
} else {
|
||||
renderMe = isUnlocked ? <OnlyUnlocked whenUnlocked={<Fields />} /> : <WalletDecrypt />;
|
||||
renderMe = isUnlocked ? (
|
||||
<OnlyUnlocked whenUnlocked={<Fields />} />
|
||||
) : (
|
||||
<WalletDecrypt disabledWallets={DISABLE_WALLETS.READ_ONLY} />
|
||||
);
|
||||
}
|
||||
|
||||
return <React.Fragment>{renderMe}</React.Fragment>;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { TokenValue, Wei } from 'libs/units';
|
||||
import { Token } from 'config';
|
||||
import { Token, SecureWalletName, WalletName } from 'config';
|
||||
import { AppState } from 'reducers';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import { getNetworkConfig, getOffline } from 'selectors/config';
|
||||
import { IWallet, Web3Wallet, LedgerWallet, TrezorWallet, WalletConfig } from 'libs/wallet';
|
||||
import { isEtherTransaction, getUnit } from './transaction';
|
||||
import { unSupportedWalletFormatsOnNetwork } from 'utils/network';
|
||||
import { DisabledWallets } from 'components/WalletDecrypt';
|
||||
|
||||
export function getWalletInst(state: AppState): IWallet | null | undefined {
|
||||
return state.wallet.inst;
|
||||
|
@ -139,3 +141,56 @@ export function getShownTokenBalances(
|
|||
|
||||
return tokenBalances.filter(t => walletTokens.includes(t.symbol));
|
||||
}
|
||||
|
||||
// TODO: Convert to reselect selector (Issue #884)
|
||||
export function getDisabledWallets(state: AppState): DisabledWallets {
|
||||
const network = getNetworkConfig(state);
|
||||
const isOffline = getOffline(state);
|
||||
const disabledWallets: DisabledWallets = {
|
||||
wallets: [],
|
||||
reasons: {}
|
||||
};
|
||||
|
||||
const addReason = (wallets: WalletName[], reason: string) => {
|
||||
if (!wallets.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
disabledWallets.wallets = disabledWallets.wallets.concat(wallets);
|
||||
wallets.forEach(wallet => {
|
||||
disabledWallets.reasons[wallet] = reason;
|
||||
});
|
||||
};
|
||||
|
||||
// Some wallets don't support some networks
|
||||
addReason(
|
||||
unSupportedWalletFormatsOnNetwork(network),
|
||||
`${network.name} does not support this wallet`
|
||||
);
|
||||
|
||||
// Some wallets are unavailable offline
|
||||
if (isOffline) {
|
||||
addReason(
|
||||
[SecureWalletName.WEB3, SecureWalletName.TREZOR],
|
||||
'This wallet cannot be accessed offline'
|
||||
);
|
||||
}
|
||||
|
||||
// Some wallets are disabled on certain platforms
|
||||
if (process.env.BUILD_DOWNLOADABLE) {
|
||||
addReason(
|
||||
[SecureWalletName.LEDGER_NANO_S],
|
||||
'This wallet is only supported at MyEtherWallet.com'
|
||||
);
|
||||
}
|
||||
if (process.env.BUILD_ELECTRON) {
|
||||
addReason([SecureWalletName.WEB3], 'This wallet is not supported in the MyEtherWallet app');
|
||||
}
|
||||
|
||||
// Dedupe and sort for consistency
|
||||
disabledWallets.wallets = disabledWallets.wallets
|
||||
.filter((name, idx) => disabledWallets.wallets.indexOf(name) === idx)
|
||||
.sort();
|
||||
|
||||
return disabledWallets;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue