chore: settings copy and styles

This commit is contained in:
Andre Neves 2019-02-24 16:26:07 -05:00
parent 9ab1c07d0c
commit 62cb8e417a
6 changed files with 234 additions and 67 deletions

View File

@ -33,6 +33,10 @@ export class Clipboard extends PureComponent<Props, State> {
if (document.body) document.body.removeChild(el); if (document.body) document.body.removeChild(el);
this.setState({ copied: true }); this.setState({ copied: true });
setTimeout(() => {
this.setState(() => ({ copied: false }));
}, 1500);
}; };
render() { render() {
@ -42,7 +46,7 @@ export class Clipboard extends PureComponent<Props, State> {
return ( return (
<div data-testid='Clipboard'> <div data-testid='Clipboard'>
<Button <Button
label={copied ? 'Copied!' : 'Copy!'} label={copied ? 'Copied!' : 'Copy'}
className={className} className={className}
onClick={this.handleClick} onClick={this.handleClick}
disabled={copied} disabled={copied}

View File

@ -13,6 +13,8 @@ const brandThree = '#5d5d65';
export const DARK_COLORS = { export const DARK_COLORS = {
// General // General
text, text,
success,
error,
background: '#212124', background: '#212124',
divider: black, divider: black,
inactiveItem: brandThree, inactiveItem: brandThree,

View File

@ -16,6 +16,8 @@ const border = '#DDDDDD';
export const LIGHT_COLORS = { export const LIGHT_COLORS = {
// General // General
text, text,
success,
error,
background: offWhite, background: offWhite,
divider: '#AAAAAA', divider: '#AAAAAA',
inactiveItem: success, inactiveItem: success,

View File

@ -1,6 +1,6 @@
// @flow // @flow
import React, { Fragment, PureComponent } from 'react'; import React, { PureComponent } from 'react';
import styled, { withTheme, keyframes } from 'styled-components'; import styled, { withTheme, keyframes } from 'styled-components';
import { BigNumber } from 'bignumber.js'; import { BigNumber } from 'bignumber.js';
import { Transition, animated } from 'react-spring'; import { Transition, animated } from 'react-spring';
@ -27,7 +27,8 @@ import MenuIconDark from '../assets/images/menu_icon_dark.svg';
import MenuIconLight from '../assets/images/menu_icon_light.svg'; import MenuIconLight from '../assets/images/menu_icon_light.svg';
import ValidIcon from '../assets/images/green_check_dark.png'; import ValidIcon from '../assets/images/green_check_dark.png';
import InvalidIcon from '../assets/images/error_icon_dark.png'; import InvalidIcon from '../assets/images/error_icon_dark.png';
import LoadingIcon from '../assets/images/sync_icon_dark.png'; import LoadingIconDark from '../assets/images/sync_icon_dark.png';
import LoadingIconLight from '../assets/images/sync_icon_light.png';
import ArrowUpIconDark from '../assets/images/arrow_up_dark.png'; import ArrowUpIconDark from '../assets/images/arrow_up_dark.png';
import ArrowUpIconLight from '../assets/images/arrow_up_light.png'; import ArrowUpIconLight from '../assets/images/arrow_up_light.png';
@ -43,14 +44,6 @@ const rotate = keyframes`
} }
`; `;
const Loader = styled.img`
width: 25px;
height: 25px;
animation: 2s linear infinite;
animation-name: ${rotate};
margin-bottom: 10px;
`;
const FormWrapper = styled.div` const FormWrapper = styled.div`
width: 71%; width: 71%;
`; `;
@ -170,12 +163,12 @@ const ModalContent = styled(ColumnComponent)`
justify-content: center; justify-content: center;
p { p {
word-break: break-all; word-break: break-word;
} }
`; `;
const ConfirmItemWrapper = styled(RowComponent)` const ConfirmItemWrapper = styled(RowComponent)`
padding: 22.5px 33.5px; padding: 22.5px 40px;
width: 100%; width: 100%;
`; `;
@ -294,8 +287,9 @@ const HexadecimalText = styled(TextComponent)`
const SimpleTooltip = styled.div` const SimpleTooltip = styled.div`
background: ${props => props.theme.colors.walletAddressTooltipBg}; background: ${props => props.theme.colors.walletAddressTooltipBg};
position: absolute; position: absolute;
top: -30px; top: -24px;
right: 0px; left: 0;
right: 0;
padding: 6px 10px; padding: 6px 10px;
border-radius: ${props => props.theme.boxBorderRadius}; border-radius: ${props => props.theme.boxBorderRadius};
`; `;
@ -313,6 +307,74 @@ const SendButtonWrapper = styled.div`
display: flex; display: flex;
`; `;
const ErrorWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
padding: 20px 40px;
`;
const ErrorLabel = styled(TextComponent)`
font-weight: 700;
font-size: 20px;
margin-bottom: 16px;
`;
const ErrorMessage = styled(TextComponent)`
font-size: 14px;
font-weight: 700;
color: ${props => props.theme.colors.error};
text-align: center;
margin-bottom: 20px;
`;
const LoaderWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
`;
const Loader = styled.img`
width: 45px;
height: 45px;
animation: 2s linear infinite;
animation-name: ${rotate};
margin-bottom: 30px;
`;
const ZSuccessWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px 40px;
`;
const ZSuccessContentWrapper = styled.div`
padding: 0 0 40px 0;
`;
const ZSuccessLabel = styled(TextComponent)`
color: ${props => props.theme.colors.success};
font-weight: 700;
font-size: 30px;
`;
const ZSuccessMessage = styled(TextComponent)`
text-align: center;
margin-bottom: 40px;
margin-top: 5px;
`;
const ZSuccessTransactionId = styled(TextComponent)`
text-align: center;
word-break: break-all !important;
`;
type Props = { type Props = {
match: Match, match: Match,
theme: AppTheme, theme: AppTheme,
@ -368,10 +430,13 @@ class Component extends PureComponent<Props, State> {
} }
updateTooltipVisibility = ({ balance, amount }: { balance: number, amount: number }) => { updateTooltipVisibility = ({ balance, amount }: { balance: number, amount: number }) => {
const { from, to } = this.state; const { from, to, fee } = this.state;
const feeValue = fee || 0;
this.setState({ this.setState({
showBalanceTooltip: !from || !to ? false : new BigNumber(amount).gt(balance), showBalanceTooltip: (!from || !to)
? false
: new BigNumber(amount).plus(feeValue).gt(balance),
}); });
}; };
@ -514,36 +579,63 @@ class Component extends PureComponent<Props, State> {
/* eslint-enable react/no-unused-prop-types */ /* eslint-enable react/no-unused-prop-types */
}) => { }) => {
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
const { operationId, isSending, error } = this.props; const {
operationId, isSending, error, theme,
} = this.props;
const { from, to } = this.state; const { from, to } = this.state;
const loadingIcon = theme.mode === DARK
? LoadingIconDark
: LoadingIconLight;
if (isSending) { if (isSending) {
return ( return (
<Fragment> <LoaderWrapper>
<Loader src={LoadingIcon} /> <Loader src={loadingIcon} />
<TextComponent value='Processing transaction...' /> <TextComponent value='Processing transaction...' />
</Fragment> </LoaderWrapper>
); );
} }
if (operationId) { if (operationId) {
return ( return (
<ColumnComponent width='100%' id='send-success-wrapper'> <ZSuccessWrapper id='send-success-wrapper'>
<TextComponent value={`Transaction ID: ${operationId}`} align='center' /> <ZSuccessLabel value='Success!' />
<button <ZSuccessContentWrapper>
type='button' <ZSuccessMessage value='Your transaction was sent successfully.' />
<ZSuccessTransactionId value={`Transaction ID: ${operationId}`} />
</ZSuccessContentWrapper>
<FormButton
label='Done'
variant='primary'
onClick={() => { onClick={() => {
this.reset(); this.reset();
toggle(); toggle();
}} }}
> />
Send again! </ZSuccessWrapper>
</button>
</ColumnComponent>
); );
} }
if (error) return <TextComponent id='send-error-message' value={error} />; if (error) {
return (
<ErrorWrapper>
<ErrorLabel value='Error' />
<ErrorMessage
id='send-error-message'
value={error}
/>
<FormButton
label='Try Again'
variant='primary'
onClick={() => {
this.reset();
toggle();
}}
/>
</ErrorWrapper>
);
}
return ( return (
<> <>
@ -646,7 +738,7 @@ class Component extends PureComponent<Props, State> {
<AmountInput <AmountInput
renderRight={() => ( renderRight={() => (
<MaxAvailableAmount <MaxAvailableAmount
onClick={() => this.handleChange('amount')(balance)} onClick={() => this.handleChange('amount')(balance - (Number(fee) || 0))}
disabled={!from} disabled={!from}
> >
<MaxAvailableAmountImg src={arrowUpIcon} /> <MaxAvailableAmountImg src={arrowUpIcon} />
@ -774,23 +866,22 @@ class Component extends PureComponent<Props, State> {
</InfoContent> </InfoContent>
</InfoCard> </InfoCard>
<ConfirmDialogComponent <ConfirmDialogComponent
title='Please Confirm Transaction Details' title='Transaction Status'
onConfirm={this.handleSubmit} onConfirm={this.handleSubmit}
showButtons={!isSending && !error && !operationId} showButtons={!isSending && !error && !operationId}
onClose={this.reset} onClose={this.reset}
renderTrigger={toggle => ( renderTrigger={toggle => (
<SendButtonWrapper> <SendButtonWrapper>
{showBalanceTooltip ? ( {!showBalanceTooltip ? null : (
<SimpleTooltip> <SimpleTooltip>
<TooltipText value='You do not seem' /> <TooltipText value='Not enough funds!' />
<TooltipText value='to have enough funds' />
</SimpleTooltip> </SimpleTooltip>
) : null} )}
<FormButton <FormButton
onClick={() => this.showModal(toggle)} onClick={() => this.showModal(toggle)}
id='send-submit-button' id='send-submit-button'
label='Send' label='Send'
variant='secondary' variant='primary'
focused focused
isFluid isFluid
disabled={this.shouldDisableSendButton()} disabled={this.shouldDisableSendButton()}
@ -808,7 +899,11 @@ class Component extends PureComponent<Props, State> {
</ModalContent> </ModalContent>
)} )}
</ConfirmDialogComponent> </ConfirmDialogComponent>
<FormButton label='Clear Form' variant='secondary' /> <FormButton
label='Clear Form'
variant='secondary'
onClick={this.reset}
/>
</SendWrapper> </SendWrapper>
</RowComponent> </RowComponent>
); );

View File

@ -17,7 +17,6 @@ import { ConfirmDialogComponent } from '../components/confirm-dialog';
import { TextComponent } from '../components/text'; import { TextComponent } from '../components/text';
import { InputComponent } from '../components/input'; import { InputComponent } from '../components/input';
import { InputLabelComponent } from '../components/input-label'; import { InputLabelComponent } from '../components/input-label';
import { RowComponent } from '../components/row';
import { Clipboard } from '../components/clipboard'; import { Clipboard } from '../components/clipboard';
import { SelectComponent } from '../components/select'; import { SelectComponent } from '../components/select';
@ -33,23 +32,25 @@ const EXPORT_VIEW_KEYS_CONTENT = 'Viewing keys for shielded addresses allow for
const EXPORT_VIEW_KEYS_LEARN_MORE = 'https://z.cash/blog/viewing-keys-selective-disclosure'; const EXPORT_VIEW_KEYS_LEARN_MORE = 'https://z.cash/blog/viewing-keys-selective-disclosure';
const IMPORT_PRIV_KEYS_TITLE = 'Import Private Keys'; const IMPORT_PRIV_KEYS_TITLE = 'Import Private Keys';
const IMPORT_PRIV_KEYS_CONTENT = 'Importing private keys will add the spendable coins to this wallet.'; const IMPORT_PRIV_KEYS_CONTENT = 'Importing private keys will add the spendable coins to this wallet.';
const IMPORT_PRIV_KEYS_CONTENT_MODAL = 'Paste your private keys here, one per line. These spending keys will be imported into your wallet.';
const IMPORT_PRIV_KEYS_SUCCESS_CONTENT = 'Private keys imported in your wallet. Any spendable coins should now be available.';
const EXPORT_PRIV_KEYS_TITLE = 'Export Private Keys'; const EXPORT_PRIV_KEYS_TITLE = 'Export Private Keys';
const EXPORT_PRIV_KEYS_CONTENT = 'Beware: exporting your private keys will allow anyone controlling them to spend your coins. Only perform this action on a trusted machine.'; const EXPORT_PRIV_KEYS_CONTENT = 'Beware: exporting your private keys will allow anyone controlling them to spend your coins. Only perform this action on a trusted machine.';
const BACKUP_WALLET_TITLE = 'Backup Wallet'; const BACKUP_WALLET_TITLE = 'Backup Wallet';
const BACKUP_WALLET_CONTENT = 'It is recommended that you backup your wallet often.'; const BACKUP_WALLET_CONTENT = 'It is recommended that you backup your wallet often to avoid possible issues arising from data corruption.';
const Wrapper = styled.div` const Wrapper = styled.div`
margin-top: ${props => props.theme.layoutContentPaddingTop}; margin-top: ${props => props.theme.layoutContentPaddingTop};
`; `;
const ModalContent = styled.div` const ModalContent = styled.div`
padding: 20px 30px; padding: 20px 40px;
width: 100%; width: 100%;
max-height: 600px; max-height: 600px;
overflow-y: auto; overflow-y: auto;
p { p {
word-break: break-all; word-break: break-word;
} }
`; `;
@ -128,6 +129,59 @@ const SettingsActionWrapper = styled.div`
justify-content: space-between; justify-content: space-between;
`; `;
const StatusWrapper = styled.div`
padding: 20px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
`;
const StatusTextError = styled(TextComponent)`
font-weight: 700;
color: ${props => props.theme.colors.error};
`;
const StatusTextSuccess = styled(TextComponent)`
font-weight: 700;
color: ${props => props.theme.colors.success};
`;
const ViewKeyHeader = styled.div`
display: flex;
flex-direction: column;
padding: 30px 0 10px 0;
`;
const ViewKeyLabel = styled(TextComponent)`
font-weight: ${props => String(props.theme.fontWeight.bold)};
font-size: ${props => String(props.theme.fontSize.small)};
color: ${props => props.theme.colors.modalItemLabel};
margin-bottom: 3.5px;
`;
const ViewKeyAddress = styled(TextComponent)`
font-size: 12px;
padding: 10px 0;
line-height: 1.5;
word-break: break-all !important;
`;
const ViewKeyContentWrapper = styled.div`
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
> :first-child {
width: 100%;
}
`;
const ViewKeyInputComponent = styled(InputComponent)`
font-size: 12px;
`;
type Key = { type Key = {
zAddress: string, zAddress: string,
key: string, key: string,
@ -321,27 +375,28 @@ export class SettingsView extends PureComponent<Props, State> {
)} )}
onConfirm={this.exportViewKeys} onConfirm={this.exportViewKeys}
showButtons={!successExportViewKeys} showButtons={!successExportViewKeys}
width={450} width={550}
> >
{() => ( {() => (
<ModalContent> <ModalContent>
{successExportViewKeys ? ( {successExportViewKeys ? (
viewKeys.map(({ zAddress, key }) => ( viewKeys.map(({ zAddress, key }, index) => (
<> <>
<InputLabelComponent value={zAddress} /> <ViewKeyHeader>
<RowComponent alignItems='center'> <ViewKeyLabel value={`View Key for Address #${index + 1}`} />
<InputComponent <ViewKeyAddress value={`Address: ${zAddress}`} />
</ViewKeyHeader>
<ViewKeyContentWrapper>
<ViewKeyInputComponent
value={key} value={key}
onFocus={(event) => { onFocus={event => event.currentTarget.select()}
event.currentTarget.select();
}}
/> />
<ClipboardButton text={key} /> <ClipboardButton text={key} />
</RowComponent> </ViewKeyContentWrapper>
</> </>
)) ))
) : ( ) : (
<TextComponent value='Ut id vulputate arcu. Curabitur mattis aliquam magna sollicitudin vulputate. Morbi tempus bibendum porttitor. Quisque dictum ac ipsum a luctus. Donec et lacus ac erat consectetur molestie a id erat.' /> <TextComponent value={EXPORT_VIEW_KEYS_CONTENT} />
)} )}
</ModalContent> </ModalContent>
)} )}
@ -359,27 +414,29 @@ export class SettingsView extends PureComponent<Props, State> {
)} )}
onConfirm={this.exportPrivateKeys} onConfirm={this.exportPrivateKeys}
showButtons={!successExportPrivateKeys} showButtons={!successExportPrivateKeys}
width={450} width={550}
> >
{() => ( {() => (
<ModalContent> <ModalContent>
{successExportPrivateKeys ? ( {successExportPrivateKeys ? (
privateKeys.map(({ zAddress, key }) => ( privateKeys.map(({ zAddress, key }, index) => (
<> <>
<InputLabelComponent value={zAddress} /> <ViewKeyHeader>
<RowComponent alignItems='center'> <ViewKeyLabel value={`Private Key for Address #${index + 1}`} />
<InputComponent <ViewKeyAddress value={`Address: ${zAddress}`} />
</ViewKeyHeader>
<ViewKeyContentWrapper>
<ViewKeyInputComponent
value={key} value={key}
onFocus={(event) => { width='100%'
event.currentTarget.select(); onFocus={event => event.currentTarget.select()}
}}
/> />
<ClipboardButton text={key} /> <ClipboardButton text={key} />
</RowComponent> </ViewKeyContentWrapper>
</> </>
)) ))
) : ( ) : (
<TextComponent value='Ut id vulputate arcu. Curabitur mattis aliquam magna sollicitudin vulputate. Morbi tempus bibendum porttitor. Quisque dictum ac ipsum a luctus. Donec et lacus ac erat consectetur molestie a id erat.' /> <TextComponent value={EXPORT_PRIV_KEYS_CONTENT} />
)} )}
</ModalContent> </ModalContent>
)} )}
@ -400,17 +457,22 @@ export class SettingsView extends PureComponent<Props, State> {
> >
{() => ( {() => (
<ModalContent> <ModalContent>
<InputLabelComponent value='Please paste your private keys here, one per line. The keys will be imported into your zcashd node' /> <InputLabelComponent
marginTop='0'
value={IMPORT_PRIV_KEYS_CONTENT_MODAL}
/>
<InputComponent <InputComponent
value={importedPrivateKeys} value={importedPrivateKeys}
onChange={value => this.setState({ importedPrivateKeys: value })} onChange={value => this.setState({ importedPrivateKeys: value })}
inputType='textarea' inputType='textarea'
rows={10} rows={10}
/> />
{successImportPrivateKeys && ( <StatusWrapper>
<TextComponent value='Private keys imported in your node' align='center' /> {successImportPrivateKeys && (
)} <StatusTextSuccess value={IMPORT_PRIV_KEYS_SUCCESS_CONTENT} />
{error && <TextComponent value={error} align='center' />} )}
{error && <StatusTextError value={error} align='center' />}
</StatusWrapper>
</ModalContent> </ModalContent>
)} )}
</ConfirmDialogComponent> </ConfirmDialogComponent>

View File

@ -5,6 +5,8 @@ import { ThemeSet } from 'styled-theming';
type Colors = { type Colors = {
// General // General
text: ThemeSet, text: ThemeSet,
success: ThemeSet,
error: ThemeSet,
background: ThemeSet, background: ThemeSet,
divider: ThemeSet, divider: ThemeSet,
activeItem: ThemeSet, activeItem: ThemeSet,