diff --git a/app/assets/images/error_icon.png b/app/assets/images/error_icon.png new file mode 100644 index 0000000..f15d38a Binary files /dev/null and b/app/assets/images/error_icon.png differ diff --git a/app/assets/images/green_check.png b/app/assets/images/green_check.png new file mode 100644 index 0000000..51df71e Binary files /dev/null and b/app/assets/images/green_check.png differ diff --git a/app/assets/images/sync_icon.png b/app/assets/images/sync_icon.png new file mode 100644 index 0000000..82c6359 Binary files /dev/null and b/app/assets/images/sync_icon.png differ diff --git a/app/components/header.js b/app/components/header.js index 4dd2d84..5c2a085 100644 --- a/app/components/header.js +++ b/app/components/header.js @@ -6,6 +6,8 @@ import styled from 'styled-components'; import { ZcashLogo } from './zcash-logo'; import { TextComponent } from './text'; import { Divider } from './divider'; +import { RowComponent } from './row'; +import { StatusPill } from './status-pill'; const Wrapper = styled.div` height: ${props => props.theme.headerHeight}; @@ -38,6 +40,12 @@ const TitleWrapper = styled.div` padding-right: ${props => props.theme.layoutPaddingRight}; `; +const TitleRow = styled(RowComponent)` + align-items: center; + justify-content: space-between; + width: 100%; +`; + const Title = styled(TextComponent)` font-size: ${props => `${props.theme.fontSize.large}em`}; margin-top: 10px; @@ -57,7 +65,10 @@ export const HeaderComponent = ({ title }: Props) => ( - + <TitleRow alignItems='center' justifyContent='space-around'> + <Title value={title} /> + <StatusPill /> + </TitleRow> <Divider opacity={0.1} /> </TitleWrapper> </Wrapper> diff --git a/app/components/status-pill.js b/app/components/status-pill.js new file mode 100644 index 0000000..d4c8e53 --- /dev/null +++ b/app/components/status-pill.js @@ -0,0 +1,112 @@ +// @flow +import React, { Component } from 'react'; +import styled, { keyframes } from 'styled-components'; +import eres from 'eres'; + +import { TextComponent } from './text'; + +import rpc from '../../services/api'; + +import readyIcon from '../assets/images/green_check.png'; +import syncIcon from '../assets/images/sync_icon.png'; +import errorIcon from '../assets/images/error_icon.png'; + +const rotate = keyframes` + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +`; + +const Wrapper = styled.div` + align-items: center; + display: flex; + background-color: #000; + border-radius: 27px; + padding: 7px 13px; +`; + +const Icon = styled.img` + width: 12px; + height: 12px; + margin-right: 8px; + animation: 2s linear infinite; + animation-name: ${props => (props.animated ? rotate : 'none')}; +`; + +const StatusPillLabel = styled(TextComponent)` + color: ${props => props.theme.colors.statusPillLabel}; + font-weight: ${props => props.theme.fontWeight.bold}; + text-transform: uppercase; +`; + +type Props = {}; + +type State = { + type: string, + icon: string, + progress: number, + isSynching: boolean, +}; + +export class StatusPill extends Component<Props, State> { + timer: ?IntervalID = null; + + state = { + type: 'synching', + icon: syncIcon, + progress: 0, + isSynching: true, + }; + + componentDidMount() { + this.timer = setInterval(() => { + this.getBlockchainStatus(); + }, 2000); + } + + componentWillUnmount() { + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } + } + + getBlockchainStatus = async () => { + const [blockchainErr, blockchaininfo] = await eres( + rpc.getblockchaininfo(), + ); + + const newProgress = blockchaininfo.verificationprogress * 100; + + this.setState({ + progress: newProgress, + ...(newProgress > 99.99 ? { + type: 'ready', + icon: readyIcon, + isSynching: false, + } : {}), + }); + + if (blockchainErr) { + this.setState(() => ({ type: 'error', icon: errorIcon })); + } + } + + render() { + const { + type, icon, progress, isSynching, + } = this.state; + const showPercent = isSynching ? `(${progress.toFixed(2)}%)` : ''; + + return ( + <Wrapper> + <Icon src={icon} animated={isSynching} /> + <StatusPillLabel value={`${type} ${showPercent}`} /> + </Wrapper> + ); + } +} diff --git a/app/components/status-pill.mdx b/app/components/status-pill.mdx new file mode 100644 index 0000000..d013575 --- /dev/null +++ b/app/components/status-pill.mdx @@ -0,0 +1,22 @@ +--- +name: StatusPill +--- + +import { Playground, PropsTable } from 'docz' + +import { StatusPill } from './status-pill.js' +import { DoczWrapper } from '../theme.js' + +# StatusPill + +## Properties + +<PropsTable of={StatusPill} /> + +## Basic Usage + +<Playground> + <DoczWrapper> + {() => <StatusPill percent={83.} />} + </DoczWrapper> +</Playground> diff --git a/app/theme.js b/app/theme.js index 1bafe92..28ea0f0 100644 --- a/app/theme.js +++ b/app/theme.js @@ -24,6 +24,7 @@ const transactionReceived = '#6AEAC0'; const transactionsDate = '#777777'; const transactionsItemHovered = '#222222'; const selectButtonShadow = 'rgba(238,201,76,0.65)'; +const statusPillLabel = '#727272'; const transactionsDetailsLabel = transactionsDate; const appTheme = { @@ -68,6 +69,7 @@ const appTheme = { inputBackground: brandOne, selectButtonShadow, transactionsDetailsLabel, + statusPillLabel, }, sidebarWidth: '180px', headerHeight: '60px',