diff --git a/src/components/BalanceSummary/index.js b/src/components/BalanceSummary/index.js index 17bd93ef..0a032cf3 100644 --- a/src/components/BalanceSummary/index.js +++ b/src/components/BalanceSummary/index.js @@ -35,7 +35,8 @@ const BalanceSummary = ({ renderHeader, selectedTime, }: Props) => { - const unit = getFiatCurrencyByTicker(counterValue).units[0] + const currency = getFiatCurrencyByTicker(counterValue) + const account = accounts.length === 1 ? accounts[0] : undefined return ( - isAvailable ? ( - - ) : null + renderTooltip={ + isAvailable && !account + ? d => ( + + + + {d.date.toISOString().substr(0, 10)} + + + ) + : undefined } /> diff --git a/src/components/CalculateBalance.js b/src/components/CalculateBalance.js index 7c4337ce..fb544402 100644 --- a/src/components/CalculateBalance.js +++ b/src/components/CalculateBalance.js @@ -4,7 +4,7 @@ import { PureComponent } from 'react' import { connect } from 'react-redux' -import type { Account, BalanceHistory } from '@ledgerhq/live-common/lib/types' +import type { Account } from '@ledgerhq/live-common/lib/types' import { getBalanceHistorySum } from '@ledgerhq/live-common/lib/helpers/account' import CounterValues from 'helpers/countervalues' import { exchangeSettingsForAccountSelector, counterValueCurrencySelector } from 'reducers/settings' @@ -16,8 +16,14 @@ type OwnProps = { children: Props => *, } +type Item = { + date: Date, + value: number, + originalValue: number, +} + type Props = OwnProps & { - balanceHistory: BalanceHistory, + balanceHistory: Item[], balanceStart: number, balanceEnd: number, isAvailable: boolean, @@ -26,10 +32,18 @@ type Props = OwnProps & { const mapStateToProps = (state: State, props: OwnProps) => { const counterValueCurrency = counterValueCurrencySelector(state) let isAvailable = true + + // create array of original values, used to reconciliate + // with counter values after calculation + const originalValues = [] + const balanceHistory = getBalanceHistorySum( props.accounts, props.daysCount, (account, value, date) => { + // keep track of original value + originalValues.push(value) + const cv = CounterValues.calculateSelector(state, { value, date, @@ -43,7 +57,11 @@ const mapStateToProps = (state: State, props: OwnProps) => { } return cv }, + ).map((item, i) => + // reconciliate balance history with original values + ({ ...item, originalValue: originalValues[i] || 0 }), ) + return { isAvailable, balanceHistory, diff --git a/src/components/CounterValue/index.js b/src/components/CounterValue/index.js index 4b46b8a9..1874ee01 100644 --- a/src/components/CounterValue/index.js +++ b/src/components/CounterValue/index.js @@ -49,7 +49,9 @@ const mapStateToProps = (state: State, props: OwnProps) => { class CounterValue extends PureComponent { render() { const { value, counterValueCurrency, date, ...props } = this.props - if (!value && value !== 0) return null + if (!value && value !== 0) { + return null + } return ( )} diff --git a/src/components/DevTools.js b/src/components/DevTools.js index 1e6e1729..9baa004c 100644 --- a/src/components/DevTools.js +++ b/src/components/DevTools.js @@ -254,26 +254,8 @@ class DevTools extends PureComponent { color="#8884d8" height={50} hideAxis - interactive={false} + isInteractive={false} /> - {/* ( - - )} - /> */} ))} diff --git a/src/components/TopBar/ItemContainer.js b/src/components/TopBar/ItemContainer.js index a4019dde..d6b750e4 100644 --- a/src/components/TopBar/ItemContainer.js +++ b/src/components/TopBar/ItemContainer.js @@ -8,13 +8,13 @@ export default styled(Box).attrs({ px: 2, ml: 0, justifyContent: 'center', - cursor: p => (p.interactive ? 'pointer' : 'default'), + cursor: p => (p.isInteractive ? 'pointer' : 'default'), })` opacity: 0.7; &:hover { - opacity: ${p => (p.interactive ? 0.85 : 0.7)}; + opacity: ${p => (p.isInteractive ? 0.85 : 0.7)}; } &:active { - opacity: ${p => (p.interactive ? 1 : 0.7)}; + opacity: ${p => (p.isInteractive ? 1 : 0.7)}; } ` diff --git a/src/components/TopBar/index.js b/src/components/TopBar/index.js index 303e661c..57f2edab 100644 --- a/src/components/TopBar/index.js +++ b/src/components/TopBar/index.js @@ -93,7 +93,7 @@ class TopBar extends PureComponent { - + {hasPassword && ( // FIXME this should be a dedicated component. therefore this component don't need to connect() @@ -101,7 +101,7 @@ class TopBar extends PureComponent { - + diff --git a/src/components/base/Chart/Tooltip.js b/src/components/base/Chart/Tooltip.js index 54cc7900..ef97bc25 100644 --- a/src/components/base/Chart/Tooltip.js +++ b/src/components/base/Chart/Tooltip.js @@ -1,47 +1,35 @@ // @flow -import React from 'react' +import React, { Fragment } from 'react' +import styled from 'styled-components' -import type { Unit } from '@ledgerhq/live-common/lib/types' +import type { Account } from '@ledgerhq/live-common/lib/types' -import { colors as themeColors } from 'styles/theme' -import { TooltipContainer } from 'components/base/Tooltip' import FormattedVal from 'components/base/FormattedVal' +import Box from 'components/base/Box' import type { Item } from './types' -/** - * we use inline style for more perfs, as tooltip may re-render numerous times - */ - -const Arrow = () => ( - - - -) +const Container = styled(Box).attrs({ + px: 4, + py: 3, + align: 'center', +})` + background: white; + border: 1px solid #d8d8d8; + border-radius: 4px; + width: 150px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.03); +` const Tooltip = ({ - d, + item, renderTooltip, - fiat, - unit, + account, }: { - d: Item, + item: Item, renderTooltip?: Function, - fiat?: string, - unit?: Unit, + account?: Account, }) => (
- + {renderTooltip ? ( - renderTooltip(d) + renderTooltip(item) ) : ( - + + + {account && ( + + )} + + {item.date.toISOString().substr(0, 10)} + + )} - - +
) -Tooltip.defaultProps = { - renderTooltip: undefined, - fiat: undefined, - unit: undefined, -} - export default Tooltip diff --git a/src/components/base/Chart/handleMouseEvents.js b/src/components/base/Chart/handleMouseEvents.js index 9f39e8c3..9809b066 100644 --- a/src/components/base/Chart/handleMouseEvents.js +++ b/src/components/base/Chart/handleMouseEvents.js @@ -29,7 +29,7 @@ export default function handleMouseEvents({ renderTooltip?: Function, }) { const { MARGINS, HEIGHT, WIDTH, NODES, DATA, x, y } = ctx - const { hideAxis, unit } = props + const { account } = props const bisectDate = d3.bisector(d => d.parsedDate).left @@ -65,21 +65,15 @@ export default function handleMouseEvents({ NODES.tooltip .style('transition', '100ms cubic-bezier(.61,1,.53,1) opacity') .style('opacity', 1) - .style('transform', `translate3d(${MARGINS.left + x(d.parsedDate)}px, ${y(d.value)}px, 0)`) + .style('transform', `translate3d(${MARGINS.left + x(d.parsedDate)}px, 0, 0)`) NODES.focus.style('opacity', 1) - if (!hideAxis) { - NODES.xBar.style('opacity', 1) - NODES.yBar.style('opacity', 1) - } + NODES.xBar.style('opacity', 1) } function mouseOut() { NODES.tooltip.style('opacity', 0).style('transition', '100ms linear opacity') NODES.focus.style('opacity', 0) - if (!hideAxis) { - NODES.xBar.style('opacity', 0) - NODES.yBar.style('opacity', 0) - } + NODES.xBar.style('opacity', 0) } function mouseMove() { @@ -97,24 +91,17 @@ export default function handleMouseEvents({ renderToString( - + , ), ) - .style('transform', `translate3d(${MARGINS.left + x(d.parsedDate)}px, ${y(d.value)}px, 0)`) - if (!hideAxis) { - NODES.xBar - .attr('x1', x(d.parsedDate)) - .attr('x2', x(d.parsedDate)) - .attr('y1', HEIGHT) - .attr('y2', y(d.value)) - NODES.yBar - .attr('x1', 0) - .attr('x2', x(d.parsedDate)) - .attr('y1', y(d.value)) - .attr('y2', y(d.value)) - } + .style('transform', `translate3d(${MARGINS.left + x(d.parsedDate)}px, 0, 0)`) + NODES.xBar + .attr('x1', x(d.parsedDate)) + .attr('x2', x(d.parsedDate)) + .attr('y1', -30) // ensure that xbar is covered + .attr('y2', HEIGHT) } return node diff --git a/src/components/base/Chart/helpers.js b/src/components/base/Chart/helpers.js index 6f0256c7..3505fba9 100644 --- a/src/components/base/Chart/helpers.js +++ b/src/components/base/Chart/helpers.js @@ -16,7 +16,7 @@ export function generateColors(color) { focus: color, gradientStart: cColor.fade(0.7), gradientStop: cColor.fade(1), - focusBar: cColor.fade(0.5), + focusBar: '#d8d8d8', } } diff --git a/src/components/base/Chart/index.js b/src/components/base/Chart/index.js index b2ec4264..21cba1b6 100644 --- a/src/components/base/Chart/index.js +++ b/src/components/base/Chart/index.js @@ -18,7 +18,7 @@ * * @@ -37,7 +37,7 @@ import React, { PureComponent } from 'react' import * as d3 from 'd3' import noop from 'lodash/noop' -import type { Unit } from '@ledgerhq/live-common/lib/types' +import type { Account } from '@ledgerhq/live-common/lib/types' import refreshNodes from './refreshNodes' import refreshDraw from './refreshDraw' @@ -48,7 +48,7 @@ import type { Data } from './types' export type Props = { data: Data, // eslint-disable-line react/no-unused-prop-types - unit?: Unit, // eslint-disable-line react/no-unused-prop-types + account?: Account, // eslint-disable-line react/no-unused-prop-types id?: string, // eslint-disable-line react/no-unused-prop-types height?: number, @@ -56,7 +56,7 @@ export type Props = { color?: string, // eslint-disable-line react/no-unused-prop-types hideAxis?: boolean, // eslint-disable-line react/no-unused-prop-types dateFormat?: string, // eslint-disable-line react/no-unused-prop-types - interactive?: boolean, // eslint-disable-line react/no-unused-prop-types + isInteractive?: boolean, // eslint-disable-line react/no-unused-prop-types renderTooltip?: Function, // eslint-disable-line react/no-unused-prop-types } @@ -67,9 +67,8 @@ class Chart extends PureComponent { height: 400, hideAxis: false, id: 'chart', - interactive: true, + isInteractive: true, tickXScale: 'month', - unit: undefined, } componentDidMount() { @@ -113,7 +112,7 @@ class Chart extends PureComponent { this.refreshChart = prevProps => { const { _node: node, props } = this - const { data: raw, color, height, hideAxis, interactive, renderTooltip } = props + const { data: raw, color, height, hideAxis, isInteractive, renderTooltip } = props ctx.DATA = enrichData(raw) @@ -157,7 +156,7 @@ class Chart extends PureComponent { // Mouse handler mouseHandler && mouseHandler.remove() // eslint-disable-line no-unused-expressions - if (interactive) { + if (isInteractive) { mouseHandler = handleMouseEvents({ ctx, props, diff --git a/src/components/base/Chart/refreshDraw.js b/src/components/base/Chart/refreshDraw.js index 2b5381f3..eac16978 100644 --- a/src/components/base/Chart/refreshDraw.js +++ b/src/components/base/Chart/refreshDraw.js @@ -31,11 +31,11 @@ function getRenderTickX(selectedTime) { export default function refreshDraw({ ctx, props }: { ctx: CTX, props: Props }) { const { NODES, WIDTH, HEIGHT, MARGINS, COLORS, INVALIDATED, DATA, x, y } = ctx - const { hideAxis, interactive, tickXScale, unit } = props + const { hideAxis, isInteractive, tickXScale, account } = props const nbTicksX = getTickXCount(tickXScale) const renderTickX = getRenderTickX(tickXScale) - const renderTickY = t => (unit ? formatShort(unit, t) : t) + const renderTickY = t => (account ? formatShort(account.unit, t) : t) const area = d3 .area() @@ -62,12 +62,11 @@ export default function refreshDraw({ ctx, props }: { ctx: CTX, props: Props }) } if (INVALIDATED.color) { - if (interactive) { + if (isInteractive) { // Update focus bar colors NODES.xBar.attr('stroke', COLORS.focusBar) - NODES.yBar.attr('stroke', COLORS.focusBar) // Update dot color - NODES.focus.attr('fill', COLORS.focus) + NODES.focus.attr('stroke', COLORS.focus) } // Update gradient color NODES.gradientStart.attr('stop-color', COLORS.gradientStart) @@ -78,11 +77,10 @@ export default function refreshDraw({ ctx, props }: { ctx: CTX, props: Props }) } // Hide interactive things - if (interactive) { + if (isInteractive) { NODES.focus.style('opacity', 0) NODES.tooltip.style('opacity', 0) NODES.xBar.style('opacity', 0) - NODES.yBar.style('opacity', 0) } // Draw axis diff --git a/src/components/base/Chart/refreshNodes.js b/src/components/base/Chart/refreshNodes.js index 856bba10..3fde2f95 100644 --- a/src/components/base/Chart/refreshNodes.js +++ b/src/components/base/Chart/refreshNodes.js @@ -10,7 +10,7 @@ const debug = d('Chart') export default function refreshNodes({ ctx, node, props }: { ctx: CTX, node: any, props: Props }) { const { NODES, COLORS } = ctx - const { hideAxis, interactive, id } = props + const { hideAxis, isInteractive, id } = props // Container @@ -26,20 +26,11 @@ export default function refreshNodes({ ctx, node, props }: { ctx: CTX, node: any // Focus bars - ensure({ onlyIf: interactive, NODES, key: 'xBar' }, () => + ensure({ onlyIf: isInteractive, NODES, key: 'xBar' }, () => NODES.wrapper .append('line') .attr('stroke', COLORS.focusBar) - .attr('stroke-width', '1px') - .attr('stroke-dasharray', '3, 2'), - ) - - ensure({ onlyIf: interactive, NODES, key: 'yBar' }, () => - NODES.wrapper - .append('line') - .attr('stroke', COLORS.focusBar) - .attr('stroke-width', '1px') - .attr('stroke-dasharray', '3, 2'), + .attr('stroke-width', '1px'), ) // Gradient @@ -84,7 +75,7 @@ export default function refreshNodes({ ctx, node, props }: { ctx: CTX, node: any // Tooltip & focus point - ensure({ onlyIf: interactive, NODES, key: 'tooltip' }, () => + ensure({ onlyIf: isInteractive, NODES, key: 'tooltip' }, () => d3 .select(node) .append('div') @@ -94,11 +85,13 @@ export default function refreshNodes({ ctx, node, props }: { ctx: CTX, node: any .style('pointer-events', 'none'), ) - ensure({ onlyIf: interactive, NODES, key: 'focus' }, () => + ensure({ onlyIf: isInteractive, NODES, key: 'focus' }, () => NODES.wrapper .append('g') .append('circle') - .attr('fill', COLORS.focus) + .attr('fill', 'white') + .attr('stroke', COLORS.focus) + .attr('stroke-width', 2) .attr('r', 4), ) diff --git a/src/components/base/Chart/stories.js b/src/components/base/Chart/stories.js index 5f0c4076..e1f4e704 100644 --- a/src/components/base/Chart/stories.js +++ b/src/components/base/Chart/stories.js @@ -9,11 +9,17 @@ import { boolean, number } from '@storybook/addon-knobs' import { color } from '@storybook/addon-knobs/react' import Chart from 'components/base/Chart' +import Box from 'components/base/Box' const stories = storiesOf('Components/base', module) const data = generateRandomData(365) -const unit = getCryptoCurrencyById('bitcoin').units[0] +const currency = getCryptoCurrencyById('bitcoin') + +// $FlowFixMe +const fakeAccount = { + currency, +} type State = { start: number, @@ -32,23 +38,31 @@ class Wrapper extends Component { const { start, stop } = this.state return ( - - + + + + ) @@ -63,9 +77,11 @@ function generateRandomData(n) { const data = [] const chance = new Chance() while (!day.isSame(today)) { + const value = chance.integer({ min: 0.5e8, max: 1e8 }) data.push({ date: day.toDate(), - value: chance.integer({ min: 0.5e8, max: 1e8 }), + value, + originalValue: value, }) day.add(1, 'day') } diff --git a/src/components/base/Chart/types.js b/src/components/base/Chart/types.js index 0c22e948..7446ab14 100644 --- a/src/components/base/Chart/types.js +++ b/src/components/base/Chart/types.js @@ -3,6 +3,7 @@ export type Item = { date: Date, value: number, + originalValue: number, } type EnrichedItem = { diff --git a/static/i18n/fr/common.yml b/static/i18n/fr/common.yml index 7d654e50..e5dd6771 100644 --- a/static/i18n/fr/common.yml +++ b/static/i18n/fr/common.yml @@ -1,5 +1,7 @@ --- ok: Ok +yes: true +no: false confirm: Confirmer cancel: Annuler continue: Continue diff --git a/static/i18n/fr/onboarding.yml b/static/i18n/fr/onboarding.yml index c4f5322e..c3ee9cfa 100644 --- a/static/i18n/fr/onboarding.yml +++ b/static/i18n/fr/onboarding.yml @@ -26,25 +26,49 @@ selectDevice: title: Ledger Blue desc: Please replace it with the final wording once it’s done. selectPIN: - title: Select PIN code + title: Choose your PIN code desc: This is a long text, please replace it with the final wording once it’s done. Lorem ipsum dolor amet ledger lorem dolor ipsum amet instructions: - step1: Connect your Ledger Nano S to your computer using the supplied micro USB cable. - step2: Press both buttons simultaneously as instructed on your Ledger Nano S screen. - step3: Select Configure as new device on your Ledger Nano S by pressing the right button, located above the validation icon. + step1: Connect the Ledger Nano S to your computer. + step2: Press both buttons simultaneously as instructed on the screen. + step3: Press the right button to select Configure as new device. step4: Choose a PIN code between 4 and 8 digits long. + disclaimer: + note1: Choose your own PIN code. This code unlocks your device. + note2: An 8-digit PIN code offers an optimum level of security. + note3: Never use a device supplied with a PIN code and/or a 24-word recovery phrase. writeSeed: - title: 24-Word Recovery phrase - desc: The 24 words that constitute your recovery phrase will now be displayed one by one on the Ledger Nano S screen. These 24 words will be displayed only once during this initialization. + title: Save your recovery phrase + desc: Your recovery phrase is formed by 24 words. They will be displayed only once. instructions: step1: Copy the first word (Word \ - step2: Move to Word \ - step3: Repeat the process until all 24 words are copied on the Recovery sheet. - step4: To confirm, use the right or left button to select each of the 24 words in the right order. - step5: Validate each word by simultaneously pressing both buttons. + step2: Press the right button to display Word \ + step3: Confirm your recovery phrase press both buttons to validate each word displayed on the screen. + disclaimer: + note1: Carefully secure your 24 words out of sight. + note2: Ledger does not keep any backup of your 24 words. + note3: Make sure you are the sole holder of the 24-word recovery phrase. + note4: Never use a device supplied with a recovery phrase and/or a PIN code. genuineCheck: title: Check PIN / Seed / Authenticity desc: Your Ledger Nano S should now display Your device is now ready. Before getting started, please confirm that + steps: + step1: + title: You alone have chosen your PIN code + desc: This is a long text, please replace it with the final wording once it’s done. Lorem ipsum dolor amet ledger lorem dolor + step2: + title: You alone have initialized your recovery phrase + desc: This is a long text, please replace it with the final wording once it’s done. Lorem ipsum dolor amet ledger lorem dolor + step3: + title: Your device is a genuine Ledger device + desc: This is a long text, please replace it with the final wording once it’s done. Lorem ipsum dolor amet ledger lorem dolor + buttons: + genuineCheck: Genuine check + contactSupport: Contact Support + errorPage: + ledgerNano: + title: Something is wrong with your Ledger Nano S + desc: Your Ledger Nano S should now display Your device is now ready. Before getting started, please confirm that setPassword: title: This is the title of the screen. 1 line is the maximum desc: This is a long text, please replace it with the final wording once it’s done. Lorem ipsum dolor amet ledger lorem dolor ipsum amet diff --git a/static/i18n/fr/send.yml b/static/i18n/fr/send.yml index d16ec404..379bed20 100644 --- a/static/i18n/fr/send.yml +++ b/static/i18n/fr/send.yml @@ -12,6 +12,7 @@ steps: advancedOptions: Options avancées useRBF: Utiliser la transaction RBF message: Laisser un message (140) + rippleTag: Tag connectDevice: title: Connecter l'appareil verification: