[refactor] UI improvements for transaction confirmations and shielded support (#110)

[refactor] UI improvements for transaction confirmations and shielded support
This commit is contained in:
André Neves 2019-04-27 19:26:21 -04:00 committed by GitHub
commit c39de61b3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 231 additions and 89 deletions

23
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,23 @@
# Contributing
When contributing to this repository, please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before making a change.
Please note we have a code of conduct, please follow it in all your interactions with the project.
## Pull Request Process
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
build.
2. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. Increase the version numbers in any examples files and the README.md to the new version that this
Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
do not have permission to do that, you may request the second reviewer to merge it for you.
Learn more about this project's [development workflow](https://github.com/andrerfneves/zepio/blob/master/DEVELOPMENT_WORKFLOW.md).
## Code of Conduct
For information on this project's Code of Conduct details, please see [this file](https://github.com/andrerfneves/zepio/blob/master/CODE_OF_CONDUCT.md).

View File

@ -6,6 +6,8 @@ Zepio is a Sapling-enabled shielded-address first Zcash wallet, featuring cross-
### [Latest Documentation at https://zepiowallet.com](https://zepiowallet.com)
> CURRENTLY ONLY A PRE-RELEASE AVAILABLE
## Stack Information

View File

@ -44,7 +44,6 @@ describe('<TransactionItem />', () => {
amount={0.8652}
date={new Date().toString()}
zecPrice={2.94}
fees={0.0001}
/>
</ThemeProvider>,
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M232.9 371.6c0 12.7 10.4 23.1 23.1 23.1s23.1-10.4 23.1-23.1c0-12.7-10.4-23.1-23.1-23.1s-23.1 10.3-23.1 23.1zm0-323.6v92.4h46.2V96.1c78.3 11.3 138.7 78.3 138.7 159.9 0 89.4-72.3 161.8-161.8 161.8S94.2 345.4 94.2 256c0-38.8 13.6-74.4 36.5-102.2L256 279.1l32.6-32.6L131.4 89.4v.5C80.8 127.7 48 187.8 48 256c0 114.9 92.9 208 208 208 114.9 0 208-93.1 208-208S370.9 48 256 48h-23.1zm161.8 208c0-12.7-10.4-23.1-23.1-23.1-12.7 0-23.1 10.4-23.1 23.1s10.4 23.1 23.1 23.1c12.7 0 23.1-10.4 23.1-23.1zm-277.4 0c0 12.7 10.4 23.1 23.1 23.1s23.1-10.4 23.1-23.1-10.4-23.1-23.1-23.1-23.1 10.4-23.1 23.1z" fill="#222"/></svg>

After

Width:  |  Height:  |  Size: 677 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M232.9 371.6c0 12.7 10.4 23.1 23.1 23.1s23.1-10.4 23.1-23.1c0-12.7-10.4-23.1-23.1-23.1s-23.1 10.3-23.1 23.1zm0-323.6v92.4h46.2V96.1c78.3 11.3 138.7 78.3 138.7 159.9 0 89.4-72.3 161.8-161.8 161.8S94.2 345.4 94.2 256c0-38.8 13.6-74.4 36.5-102.2L256 279.1l32.6-32.6L131.4 89.4v.5C80.8 127.7 48 187.8 48 256c0 114.9 92.9 208 208 208 114.9 0 208-93.1 208-208S370.9 48 256 48h-23.1zm161.8 208c0-12.7-10.4-23.1-23.1-23.1-12.7 0-23.1 10.4-23.1 23.1s10.4 23.1 23.1 23.1c12.7 0 23.1-10.4 23.1-23.1zm-277.4 0c0 12.7 10.4 23.1 23.1 23.1s23.1-10.4 23.1-23.1-10.4-23.1-23.1-23.1-23.1 10.4-23.1 23.1z" fill="#ccc"/></svg>

After

Width:  |  Height:  |  Size: 677 B

View File

@ -14,6 +14,7 @@ const Layout = styled.div`
padding-left: ${props => props.theme.layoutPaddingLeft};
padding-right: ${props => props.theme.layoutPaddingRight};
overflow: auto;
padding-bottom: 20px;
::-webkit-scrollbar {
display: none;

View File

@ -17,7 +17,6 @@ const TransactionsWrapper = styled.div`
padding: 0;
margin: 0;
box-sizing: border-box;
margin-bottom: 20px;
`;
const Day = styled(TextComponent)`
@ -40,7 +39,7 @@ export const TransactionDailyComponent = ({ transactionsDate, transactions, zecP
<TransactionsWrapper>
{transactions.map(
({
date, type, address, amount, transactionId, fees, confirmed, confirmations,
date, type, address, amount, transactionId, confirmed, confirmations,
}) => (
<Fragment key={`${address}-${type}-${amount}-${date}`}>
<TransactionItemComponent
@ -52,7 +51,6 @@ export const TransactionDailyComponent = ({ transactionsDate, transactions, zecP
address={address || 'N/A'}
amount={amount}
zecPrice={zecPrice}
fees={fees}
/>
</Fragment>
),

View File

@ -58,6 +58,10 @@ const CloseIconImg = styled.img`
margin-top: 12px;
margin-right: 12px;
cursor: pointer;
&:hover {
filter: brightness(150%);
}
`;
const InfoRow = styled(RowComponent)`
@ -126,9 +130,10 @@ type Props = {
date: string,
transactionId: string,
address: string,
fees: number | string,
handleClose: () => void,
theme: AppTheme,
confirmed: boolean,
confirmations: number,
};
const Component = ({
@ -138,15 +143,21 @@ const Component = ({
date,
transactionId,
address,
fees,
handleClose,
theme,
confirmed,
confirmations,
}: Props) => {
const isReceived = type === 'receive';
const receivedIcon = theme.mode === DARK ? ReceivedIconDark : ReceivedIconLight;
const sentIcon = theme.mode === DARK ? SentIconDark : SentIconLight;
const coinName = getCoinName();
let confirmationValue = 'Unconfirmed';
if (confirmations >= 3) confirmationValue = confirmations;
if (confirmed) confirmationValue = confirmations;
return (
<Wrapper>
<CloseIconWrapper>
@ -186,20 +197,11 @@ const Component = ({
</ColumnComponent>
<ColumnComponent>
<TextComponent
value='FEES'
value='Confirmations'
isBold
color={theme.colors.transactionDetailsLabel({ theme })}
/>
<TextComponent
value={
fees === 'N/A'
? 'N/A'
: formatNumber({
value: new BigNumber(fees).toFormat(4),
append: `${coinName} `,
})
}
/>
<TextComponent value={String(confirmationValue)} />
</ColumnComponent>
</InfoRow>
<Divider />

View File

@ -10,6 +10,8 @@ import SentIconDark from '../assets/images/transaction_sent_icon_dark.svg';
import ReceivedIconDark from '../assets/images/transaction_received_icon_dark.svg';
import SentIconLight from '../assets/images/transaction_sent_icon_light.svg';
import ReceivedIconLight from '../assets/images/transaction_received_icon_light.svg';
import UnconfirmedLight from '../assets/images/unconfirmed_light.svg';
import UnconfirmedDark from '../assets/images/unconfirmed_dark.svg';
import { RowComponent } from './row';
import { ColumnComponent } from './column';
@ -70,27 +72,44 @@ const TransactionColumn = styled(ColumnComponent)`
min-width: 60px;
`;
const UnconfirmedStatusWrapper = styled.div`
right: 30px;
position: absolute;
`;
const UnconfirmedStatus = styled.img`
width: 20px;
height: 20px;
opacity: 0.6;
${String(Wrapper)}:hover & {
opacity: 1;
}
`;
const RelativeRowComponent = styled(RowComponent)`
position: relative;
`;
export type Transaction = {
confirmed: boolean, // eslint-disable-line
confirmations: number, // eslint-disable-line
confirmed: boolean,
confirmations: number,
type: 'send' | 'receive',
date: string,
address: string,
amount: number,
fees: number | string,
zecPrice: number,
transactionId: string,
theme: AppTheme,
};
const Component = ({
// confirmed,
// confirmations,
confirmed,
confirmations,
type,
date,
address,
amount,
fees,
zecPrice,
transactionId,
theme,
@ -110,6 +129,11 @@ const Component = ({
const receivedIcon = theme.mode === DARK ? ReceivedIconDark : ReceivedIconLight;
const sentIcon = theme.mode === DARK ? SentIconDark : SentIconLight;
const unconfirmedIcon = theme.mode === DARK ? UnconfirmedLight : UnconfirmedDark;
// TODO: style the tooltip correctly (overlay issue)
// const showUnconfirmed = !confirmed || confirmations < 1 || address === '(Shielded)';
const showUnconfirmed = false;
return (
<ModalComponent
@ -121,13 +145,18 @@ const Component = ({
onClick={toggleVisibility}
>
<RowComponent alignItems='center'>
<RowComponent alignItems='center'>
<RelativeRowComponent alignItems='center'>
<Icon src={isReceived ? receivedIcon : sentIcon} alt='Transaction Type Icon' />
<TransactionColumn>
<TransactionTypeLabel isReceived={isReceived} value={type} isBold />
<TransactionLabel value={transactionTime} isReceived={isReceived} />
</TransactionColumn>
</RowComponent>
{showUnconfirmed && (
<UnconfirmedStatusWrapper>
<UnconfirmedStatus src={unconfirmedIcon} />
</UnconfirmedStatusWrapper>
)}
</RelativeRowComponent>
<TransactionAddress value={address} />
</RowComponent>
<ColumnComponent alignItems='flex-end'>
@ -146,7 +175,8 @@ const Component = ({
amount={amount}
date={date}
address={address}
fees={fees}
confirmed={confirmed}
confirmations={confirmations}
transactionId={transactionId}
handleClose={toggleVisibility}
type={type}

View File

@ -87,7 +87,7 @@ const QRCodeContainer = styled.div`
background-color: ${props => props.theme.colors.qrCodeWrapperBg}
border: 1px solid ${props => props.theme.colors.qrCodeWrapperBorder}
border-radius: ${props => props.theme.boxBorderRadius};
padding: 20px;
padding: 20px 20px 20px 10px;
margin-bottom: 10px;
width: 100%;
`;
@ -164,8 +164,9 @@ const AddressDetailsLabel = styled.div`
const AddressDetailsWrapper = styled.div`
display: flex;
flex: 1;
flex-direction: column;
padding: 10px 20px 0 20px;
padding: 15px 20px 0 20px;
`;
const FormButton = styled(Button)`
@ -179,7 +180,7 @@ const FormButton = styled(Button)`
const Column = styled.div`
display: flex;
padding-bottom: 10px;
padding-bottom: 15px;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;

View File

@ -4,46 +4,86 @@ import React from 'react';
import styled, { withTheme } from 'styled-components';
import { TextComponent } from './text';
import { RowComponent } from './row';
import { formatNumber } from '../utils/format-number';
import { getCoinName } from '../utils/get-coin-name';
import { DARK } from '../constants/themes';
const Wrapper = styled.div`
display: flex;
flex-direction: column;
background-color: ${props => props.theme.colors.walletSummaryBg};
border: 1px solid ${props => props.theme.colors.walletSummaryBorder};
border-radius: ${props => props.theme.boxBorderRadius};
padding: 37px 45px;
min-height: 250px;
position: relative;
import ShieldDarkImage from '../assets/images/shield_dark.png';
import ShieldLightImage from '../assets/images/shield_light.png';
const OutsideWrapper = styled.div`
margin-top: ${props => props.theme.layoutContentPaddingTop};
`;
const AllAddresses = styled(TextComponent)`
margin-bottom: 2.5px;
font-size: ${props => `${props.theme.fontSize.small}em`};
const Wrapper = styled.div`
display: flex;
flex-direction: row;
background-color: ${props => props.theme.colors.walletSummaryBg};
border: 1px solid ${props => props.theme.colors.walletSummaryBorder};
border-radius: ${props => props.theme.boxBorderRadius};
padding: 30px 30px;
position: relative;
`;
const ValueBox = styled.div`
margin-bottom: 15px;
margin-right: 25px;
`;
const Label = styled(TextComponent)`
margin-top: 10px;
const OutsideLabel = styled(TextComponent)`
text-transform: uppercase;
color: ${props => props.theme.colors.transactionsDate};
font-size: ${props => `${props.theme.fontSize.regular * 0.9}em`};
font-weight: ${props => String(props.theme.fontWeight.bold)};
margin-bottom: 5px;
margin-left: -7.5px;
`;
const TotalContainer = styled.div`
min-width: 270px;
`;
const DetailContainer = styled.div`
min-width: 130px;
padding-right: 20px;
`;
const USDValue = styled(TextComponent)`
opacity: 0.5;
font-weight: ${props => String(props.theme.fontWeight.light)};
font-size: 16px;
`;
const ShieldedValue = styled(Label)`
const DefaultLabel = styled(TextComponent)`
margin-top: 5px;
margin-bottom: 0px;
color: ${props => props.theme.colors.walletSummaryTransparent};
`;
const MiddleLabel = styled(TextComponent)`
margin-top: 7px;
margin-bottom: 5px;
font-size: 18px;
`;
const ShieldedValue = styled(DefaultLabel)`
color: ${props => props.theme.colors.activeItem};
padding-left: 14px;
position: relative;
&:before {
position: absolute;
left: 0;
top: -1px;
content: '';
background: url(${props => (props.theme.mode === DARK ? ShieldDarkImage : ShieldLightImage)});
background-size: cover;
height: 12px;
width: 11px;
}
`;
const UnconfirmedLabel = styled(DefaultLabel)`
color: ${props => props.theme.colors.walletSummaryUnconfirmed};
`;
const UnconfirmedValue = styled(MiddleLabel)`
color: ${props => props.theme.colors.walletSummaryUnconfirmed};
`;
type Props = {
@ -64,50 +104,51 @@ export const Component = ({
theme,
}: Props) => {
const coinName = getCoinName();
return (
<Wrapper>
<AllAddresses value='ALL ADDRESSES' isBold />
<ValueBox>
<TextComponent
size={theme.fontSize.medium * 2.5}
value={`${coinName} ${formatNumber({ value: total })}`}
isBold
/>
<USDValue
value={`USD $${formatNumber({ value: total * zecPrice })}`}
size={theme.fontSize.medium * 2}
/>
</ValueBox>
<RowComponent>
<ValueBox>
<ShieldedValue value='&#9679; SHIELDED' isBold size={theme.fontSize.small} />
<OutsideWrapper>
<OutsideLabel value='Wallet Summary' />
<Wrapper>
<TotalContainer>
<TextComponent
size={theme.fontSize.medium * 2.4}
value={`${coinName} ${formatNumber({ value: total })}`}
isBold
/>
<USDValue
value={`USD $${formatNumber({ value: total * zecPrice })}`}
size={theme.fontSize.medium * 2}
/>
</TotalContainer>
<DetailContainer>
<ShieldedValue value='SHIELDED' isBold size={theme.fontSize.small} />
<MiddleLabel
value={`${coinName} ${formatNumber({ value: shielded })}`}
isBold
size={theme.fontSize.medium}
size='16px'
/>
<USDValue value={`USD $${formatNumber({ value: shielded * zecPrice })}`} />
</ValueBox>
<ValueBox>
<Label value='&#9679; TRANSPARENT' isBold size={theme.fontSize.small} />
<TextComponent
</DetailContainer>
<DetailContainer>
<DefaultLabel value='TRANSPARENT' isBold size={theme.fontSize.small} />
<MiddleLabel
value={`${coinName} ${formatNumber({ value: transparent })}`}
isBold
size={theme.fontSize.medium}
size='16px'
/>
<USDValue value={`USD $${formatNumber({ value: transparent * zecPrice })}`} />
</ValueBox>
<ValueBox>
<Label value='&#9679; UNCONFIRMED' isBold size={theme.fontSize.small} />
<TextComponent
value={`${coinName} ${formatNumber({ value: unconfirmed })}`}
</DetailContainer>
<DetailContainer>
<UnconfirmedLabel value='UNCONFIRMED' isBold size={theme.fontSize.small} />
<UnconfirmedValue
value={`${coinName} ${formatNumber({ value: transparent })}`}
isBold
size={theme.fontSize.medium}
size='16px'
/>
<USDValue value={`USD $${formatNumber({ value: unconfirmed * zecPrice })}`} />
</ValueBox>
</RowComponent>
</Wrapper>
</DetailContainer>
</Wrapper>
</OutsideWrapper>
);
};

View File

@ -124,6 +124,9 @@ export const DARK_COLORS = {
// Wallet Summary
walletSummaryBg: black,
walletSummaryBorder: black,
walletSummaryUnconfirmed: silver,
walletSummaryShielded: white,
walletSummaryTransparent: white,
// Wallet Address
walletAddressBg: black,

View File

@ -123,6 +123,9 @@ export const LIGHT_COLORS = {
// Wallet Summary
walletSummaryBg: white,
walletSummaryBorder: alto,
walletSummaryUnconfirmed: shipGray,
walletSummaryShielded: black,
walletSummaryTransparent: black,
// Wallet Address
walletAddressBg: white,

View File

@ -21,7 +21,7 @@ type Props = {
transactions: TransactionsList,
};
const UPDATE_INTERVAL = 10000;
const UPDATE_INTERVAL = 5000;
export class DashboardView extends PureComponent<Props> {
interval = null;

View File

@ -15,10 +15,13 @@ import MenuIconDark from '../assets/images/menu_icon_dark.svg';
import MenuIconLight from '../assets/images/menu_icon_light.svg';
import PlusIconDark from '../assets/images/plus_icon_dark.svg';
import PlusIconLight from '../assets/images/plus_icon_light.svg';
import ShieldGrayImage from '../assets/images/shield_dark_gray.png';
import type { addressType } from '../redux/modules/receive';
import type { MapStateToProps, MapDispatchToProps } from '../containers/receive';
const TRANSPARENT_ADDRESS_SUBLABEL = 'By using transaparent addresses you are forgiving any privacy properties to your transactions.';
const Row = styled(RowComponent)`
margin-bottom: 10px;
`;
@ -31,6 +34,16 @@ const Label = styled(InputLabelComponent)`
margin-bottom: 5px;
`;
const SubLabel = styled(InputLabelComponent)`
color: ${props => props.theme.colors.transactionsDate};
font-size: ${props => `${props.theme.fontSize.regular * 0.9}em`};
font-weight: ${props => String(props.theme.fontWeight.light)};
letter-spacing: 0.5px;
margin-bottom: 10px;
margin-top: 0;
padding-top: 8px;
`;
const ActionButton = styled.button`
background: none;
border: none;
@ -75,6 +88,22 @@ const RevealsMain = styled.div`
justify-content: flex-start;
`;
const ShieldedLabel = styled(Label)`
padding-left: 14px;
position: relative;
&:before {
position: absolute;
left: 0;
top: -1px;
content: '';
background: url(${ShieldGrayImage});
background-size: cover;
height: 12px;
width: 11px;
}
`;
type Props = MapDispatchToProps &
MapStateToProps & {
theme: AppTheme,
@ -119,7 +148,7 @@ class Component extends PureComponent<Props, State> {
return (
<div id='receive-wrapper'>
<Label value='Shielded Address' id='shielded-address-label' />
<ShieldedLabel value='Shielded Address' id='shielded-address-label' />
{shieldedAddresses.map(({ address, balance }) => (
<WalletAddress key={address} address={address} balance={balance} />
))}
@ -164,6 +193,7 @@ class Component extends PureComponent<Props, State> {
}}
>
<Label value='Transparent Address (not private)' />
<SubLabel value={TRANSPARENT_ADDRESS_SUBLABEL} />
{transparentAddresses.map(({ address, balance }) => (
<WalletAddress key={address} address={address} balance={balance} />
))}

View File

@ -387,6 +387,13 @@ const ZSuccessTransactionId = styled(TextComponent)`
word-break: break-all !important;
`;
const CustomFeeWarning = styled(TextComponent)`
padding: 15px 10px 0 0;
letter-spacing: 0.5px;
font-size: 12px;
color: ${props => props.theme.colors.error};
`;
type Props = {
match: Match,
theme: AppTheme,
@ -902,15 +909,17 @@ class Component extends PureComponent<Props, State> {
/>
</ColumnComponent>
</RowComponent>
<RowComponent>
{feeType === FEES.CUSTOM && (
<CustomFeeWarning value='Custom fees may compromise your privacy since fees are transparent.' />
)}
</RowComponent>
</FeeWrapper>
</animated.div>
))
}
</Transition>
</RevealsMain>
{feeType === FEES.CUSTOM && (
<TextComponent value='Custom fees may compromise your privacy since fees are transparent' />
)}
</FormWrapper>
<SendWrapper>
<InfoCard>

View File

@ -71,7 +71,6 @@ const ClipboardButton = styled(Clipboard)`
`;
const SettingsWrapper = styled.div`
margin-bottom: 20px;
min-width: 200px;
width: 70%;
max-width: 600px;

View File

@ -112,7 +112,6 @@ export class TransactionsView extends PureComponent<Props> {
confirmations={transaction.confirmations}
address={transaction.address}
amount={transaction.amount}
fees={transaction.fees}
date={transaction.date}
transactionId={transaction.transactionId}
type={transaction.type}