Merge pull request #392 from trezor/feature/handle-no-backup

Feature/handle no backup
pull/399/head
Vladimir Volek 5 years ago committed by GitHub
commit 7e6639119d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -59,6 +59,17 @@ export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (di
});
};
export const onReceiveConfirmation = (confirmation: any): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
await TrezorConnect.uiResponse({
type: UI.RECEIVE_CONFIRMATION,
payload: confirmation,
});
dispatch({
type: MODAL.CLOSE,
});
};
export const onRememberDevice = (device: TrezorDevice): Action => ({
type: CONNECT.REMEMBER,
device,
@ -173,6 +184,7 @@ export const onQrScan = (parsedUri: parsedURI, networkType: string): ThunkAction
export default {
onPinSubmit,
onPassphraseSubmit,
onReceiveConfirmation,
onRememberDevice,
onForgetDevice,
onForgetSingleDevice,

@ -94,6 +94,10 @@ export const showAddress = (path: Array<number>): AsyncAction => async (dispatch
type: RECEIVE.HIDE_ADDRESS,
});
// special case: device no-backup permissions not granted
// $FlowIssue: remove this after trezor-connect@7.0.0 release
if (response.payload.code === 403) return;
dispatch({
type: NOTIFICATION.ADD,
payload: {

@ -347,6 +347,17 @@ export const gotoFirmwareUpdate = (): ThunkAction => (dispatch: Dispatch, getSta
dispatch(goto(`/device/${devUrl}/firmware-update`));
};
/*
* Go to NoBackup page
*/
export const gotoBackup = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const { selectedDevice } = getState().wallet;
if (!selectedDevice || !selectedDevice.features) return;
const devUrl: string = `${selectedDevice.features.device_id}${selectedDevice.instance ? `:${selectedDevice.instance}` : ''}`;
dispatch(goto(`/device/${devUrl}/backup`));
};
/*
* Try to redirect to initial url
*/

@ -7,3 +7,4 @@ export const CONTEXT_DEVICE: 'modal_ctx_device' = 'modal_ctx_device';
export const CONTEXT_EXTERNAL_WALLET: 'modal_ctx_external-wallet' = 'modal_ctx_external-wallet';
export const OPEN_SCAN_QR: 'modal__open_scan_qr' = 'modal__open_scan_qr';
export const CONTEXT_SCAN_QR: 'modal__ctx_scan_qr' = 'modal__ctx_scan_qr';
export const CONTEXT_CONFIRMATION: 'modal__ctx_confirmation' = 'modal__ctx_confirmation';

@ -0,0 +1,82 @@
/* @flow */
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import icons from 'config/icons';
import colors from 'config/colors';
import { H2 } from 'components/Heading';
import P from 'components/Paragraph';
import Icon from 'components/Icon';
import Button from 'components/Button';
import Link from 'components/Link';
import type { Props as BaseProps } from '../../Container';
type Props = {
onReceiveConfirmation: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onReceiveConfirmation'>;
}
const Wrapper = styled.div`
max-width: 370px;
padding: 30px 48px;
`;
const StyledLink = styled(Link)`
position: absolute;
right: 15px;
top: 15px;
`;
const BackupButton = styled(Button)`
width: 100%;
margin-bottom: 10px;
`;
const ProceedButton = styled(Button)`
background: transparent;
border-color: ${colors.WARNING_PRIMARY};
color: ${colors.WARNING_PRIMARY};
&:focus,
&:hover,
&:active {
color: ${colors.WHITE};
background: ${colors.WARNING_PRIMARY};
box-shadow: none;
}
`;
const StyledP = styled(P)`
padding-bottom: 20px;
`;
const Row = styled.div`
display: flex;
flex-direction: column;
`;
const Confirmation = (props: Props) => (
<Wrapper>
<StyledLink onClick={() => props.onReceiveConfirmation(false)}>
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
</StyledLink>
<H2>Your Trezor is not backed up</H2>
<Icon size={48} color={colors.WARNING_PRIMARY} icon={icons.WARNING} />
<StyledP isSmaller>If your device is ever lost or damaged, your funds will be lost. Backup your device first, to protect your coins against such events.</StyledP>
<Row>
<Link href="https://wallet.trezor.io/?backup">
<BackupButton onClick={() => props.onReceiveConfirmation(false)}>Create a backup in 3 minutes</BackupButton>
</Link>
<ProceedButton isWhite onClick={() => props.onReceiveConfirmation(true)}>Show address, I will take the risk</ProceedButton>
</Row>
</Wrapper>
);
Confirmation.propTypes = {
onReceiveConfirmation: PropTypes.func.isRequired,
};
export default Confirmation;

@ -31,11 +31,23 @@ const StyledLink = styled(Link)`
const Wrapper = styled.div`
max-width: 370px;
padding: 30px 48px;
padding: 30px 0px;
`;
const Content = styled.div`
padding: 0px 48px;
`;
const StyledP = styled(P)`
padding: 20px 0px;
padding-bottom: 20px;
`;
const Divider = styled.div`
width: 100%;
height: 1px;
background: ${colors.DIVIDER};
margin: 20px 0px;
`;
const Row = styled.div`
@ -47,6 +59,24 @@ const Row = styled.div`
}
`;
const BackupButton = styled(Button)`
width: 100%;
`;
const WarnButton = styled(Button)`
background: transparent;
border-color: ${colors.WARNING_PRIMARY};
color: ${colors.WARNING_PRIMARY};
&:focus,
&:hover,
&:active {
color: ${colors.WHITE};
background: ${colors.WARNING_PRIMARY};
box-shadow: none;
}
`;
class ConfirmUnverifiedAddress extends PureComponent<Props> {
componentDidMount(): void {
this.keyboardHandler = this.keyboardHandler.bind(this);
@ -86,26 +116,48 @@ class ConfirmUnverifiedAddress extends PureComponent<Props> {
let claim: string;
if (!device.connected) {
deviceStatus = `${device.label} is not connected`;
deviceStatus = `Device ${device.label} is not connected`;
claim = 'Please connect your device';
} else {
// corner-case where device is connected but it is unavailable because it was created with different "passphrase_protection" settings
const enable: string = device.features && device.features.passphrase_protection ? 'enable' : 'disable';
deviceStatus = `${device.label} is unavailable`;
deviceStatus = `Device ${device.label} is unavailable`;
claim = `Please ${enable} passphrase settings`;
}
const needsBackup = device.features && device.features.needs_backup;
return (
<Wrapper>
<StyledLink onClick={onCancel}>
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
</StyledLink>
<H2>{ deviceStatus }</H2>
<StyledP isSmaller>To prevent phishing attacks, you should verify the address on your Trezor first. { claim } to continue with the verification process.</StyledP>
<Row>
<Button onClick={() => (!account ? this.verifyAddress() : 'false')}>Try again</Button>
<Button isWhite onClick={() => this.showUnverifiedAddress()}>Show unverified address</Button>
</Row>
<Content>
<StyledLink onClick={onCancel}>
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
</StyledLink>
<H2>{ deviceStatus }</H2>
<StyledP isSmaller>To prevent phishing attacks, you should verify the address on your Trezor first. { claim } to continue with the verification process.</StyledP>
</Content>
<Content>
<Row>
<Button onClick={() => (!account ? this.verifyAddress() : 'false')}>Try again</Button>
<WarnButton isWhite onClick={() => this.showUnverifiedAddress()}>Show unverified address</WarnButton>
</Row>
</Content>
{needsBackup && <Divider />}
{needsBackup && (
<>
<Content>
<H2>Device {device.label} is not backed up</H2>
<StyledP isSmaller>If your device is ever lost or damaged, your funds will be lost. Backup your device first, to protect your coins against such events.</StyledP>
</Content>
<Content>
<Row>
<Link href="https://wallet.trezor.io/?backup">
<BackupButton>Create a backup in 3 minutes</BackupButton>
</Link>
</Row>
</Content>
</>
)}
</Wrapper>
);
}

@ -19,6 +19,7 @@ import PassphraseType from 'components/modals/passphrase/Type';
import ConfirmSignTx from 'components/modals/confirm/SignTx';
import ConfirmAction from 'components/modals/confirm/Action';
import ConfirmUnverifiedAddress from 'components/modals/confirm/UnverifiedAddress';
import ConfirmNoBackup from 'components/modals/confirm/NoBackup';
import ForgetDevice from 'components/modals/device/Forget';
import RememberDevice from 'components/modals/device/Remember';
import DuplicateDevice from 'components/modals/device/Duplicate';
@ -189,6 +190,19 @@ const getQrModal = (props: Props) => {
);
};
const getConfirmationModal = (props: Props) => {
const { modal, modalActions } = props;
if (modal.context !== MODAL.CONTEXT_CONFIRMATION) return null;
switch (modal.windowType) {
case 'no-backup':
return (<ConfirmNoBackup onReceiveConfirmation={modalActions.onReceiveConfirmation} />);
default:
return null;
}
};
// modal container component
const Modal = (props: Props) => {
const { modal } = props;
@ -205,6 +219,9 @@ const Modal = (props: Props) => {
case MODAL.CONTEXT_SCAN_QR:
component = getQrModal(props);
break;
case MODAL.CONTEXT_CONFIRMATION:
component = getConfirmationModal(props);
break;
default:
break;
}

@ -0,0 +1,25 @@
/* @flow */
import * as React from 'react';
import Notification from 'components/Notification';
import type { Props } from '../../index';
export default (props: Props) => {
const { selectedDevice } = props.wallet;
const needsBackup = selectedDevice && selectedDevice.features && selectedDevice.features.needs_backup;
if (!needsBackup) return null;
return (
<Notification
key="no-backup"
type="warning"
title="Your Trezor is not backed up!"
message="If your device is ever lost or damaged, your funds will be lost. Backup your device first, to protect your coins against such events."
actions={
[{
label: 'Create a backup',
callback: props.routerActions.gotoBackup,
}]
}
/>
);
};

@ -12,6 +12,7 @@ import * as RouterActions from 'actions/RouterActions';
import OnlineStatus from './components/OnlineStatus';
import UpdateBridge from './components/UpdateBridge';
import UpdateFirmware from './components/UpdateFirmware';
import NoBackup from './components/NoBackup';
export type StateProps = {
connect: $ElementType<State, 'connect'>;
@ -33,6 +34,7 @@ const Notifications = (props: Props) => (
<OnlineStatus {...props} />
<UpdateBridge {...props} />
<UpdateFirmware {...props} />
<NoBackup {...props} />
</React.Fragment>
);

@ -20,7 +20,10 @@ export type State = {
windowType?: string;
} | {
context: typeof MODAL.CONTEXT_SCAN_QR,
}
} | {
context: typeof MODAL.CONTEXT_CONFIRMATION,
windowType: string;
};
const initialState: State = {
context: MODAL.CONTEXT_NONE,
@ -98,6 +101,12 @@ export default function modal(state: State = initialState, action: Action): Stat
context: MODAL.CONTEXT_SCAN_QR,
};
case UI.REQUEST_CONFIRMATION:
return {
context: MODAL.CONTEXT_CONFIRMATION,
windowType: action.payload.view,
};
default:
return state;
}

@ -62,6 +62,11 @@ export const routes: Array<Route> = [
pattern: '/device/:device/firmware-update',
fields: ['device', 'firmware-update'],
},
{
name: 'wallet-backup',
pattern: '/device/:device/backup',
fields: ['device', 'backup'],
},
{
name: 'wallet-device-settings',
pattern: '/device/:device/settings',

@ -0,0 +1,62 @@
/* @flow */
import React from 'react';
import styled from 'styled-components';
import { H1 } from 'components/Heading';
import P from 'components/Paragraph';
import Link from 'components/Link';
import Button from 'components/Button';
import Icon from 'components/Icon';
import { FONT_SIZE } from 'config/variables';
import colors from 'config/colors';
import icons from 'config/icons';
const Wrapper = styled.section`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 90px 35px 40px 35px;
`;
const StyledNavLink = styled(Link)`
color: ${colors.TEXT_SECONDARY};
padding-top: 20px;
font-size: ${FONT_SIZE.BASE};
`;
const StyledH1 = styled(H1)`
text-align: center;
`;
const StyledP = styled(P)`
max-width: 550px;
padding-bottom: 15px;
`;
const Message = styled.div`
text-align: center;
padding: 0 0 15px 0;
`;
const FirmwareUpdate = () => (
<Wrapper>
<Icon
size={128}
color={colors.WARNING_PRIMARY}
icon={icons.WARNING}
/>
<StyledH1>Your Trezor is not backed up!</StyledH1>
<Message>
<StyledP>If your device is ever lost or damaged, your funds will be lost. Backup your device first, to protect your coins against such events.</StyledP>
<P>Please use Bitcoin wallet interface to create a backup.</P>
</Message>
<Link href="https://wallet.trezor.io?backup=1">
<Button>Take me to the Bitcoin wallet</Button>
</Link>
<StyledNavLink to="/">Ill do that later.</StyledNavLink>
</Wrapper>
);
export default FirmwareUpdate;

@ -27,6 +27,7 @@ import WalletDeviceSettings from 'views/Wallet/views/DeviceSettings';
import WalletSettings from 'views/Wallet/views/WalletSettings';
import WalletBootloader from 'views/Wallet/views/Bootloader';
import WalletFirmwareUpdate from 'views/Wallet/views/FirmwareUpdate';
import WalletNoBackup from 'views/Wallet/views/NoBackup';
import WalletInitialize from 'views/Wallet/views/Initialize';
import WalletSeedless from 'views/Wallet/views/Seedless';
import WalletAcquire from 'views/Wallet/views/Acquire';
@ -54,6 +55,7 @@ const App = () => (
<Route exact path={getPattern('wallet-initialize')} component={WalletInitialize} />
<Route exact path={getPattern('wallet-seedless')} component={WalletSeedless} />
<Route exact path={getPattern('wallet-firmware-update')} component={WalletFirmwareUpdate} />
<Route exact path={getPattern('wallet-backup')} component={WalletNoBackup} />
<Route exact path={getPattern('wallet-device-settings')} component={WalletDeviceSettings} />
<Route exact path={getPattern('wallet-account-summary')} component={AccountSummary} />
<Route path={getPattern('wallet-account-send')} component={AccountSend} />

Loading…
Cancel
Save