diff --git a/src/components/DashboardPage/index.js b/src/components/DashboardPage/index.js index 82692d8b..a9ba5ec8 100644 --- a/src/components/DashboardPage/index.js +++ b/src/components/DashboardPage/index.js @@ -3,6 +3,7 @@ import React, { PureComponent, Fragment } from 'react' import uniq from 'lodash/uniq' import { compose } from 'redux' +import IconExternalLink from 'icons/ExternalLink' import { translate } from 'react-i18next' import { connect } from 'react-redux' import { push } from 'react-router-redux' @@ -31,10 +32,14 @@ import Box from 'components/base/Box' import PillsDaysCount from 'components/PillsDaysCount' import OperationsList from 'components/OperationsList' import StickyBackToTop from 'components/StickyBackToTop' +import styled from 'styled-components' +import { openURL } from 'helpers/linking' import EmptyState from './EmptyState' import CurrentGreetings from './CurrentGreetings' import SummaryDesc from './SummaryDesc' import AccountCardList from './AccountCardList' +import TopBanner, { FakeLink } from '../TopBanner' +import { urls } from '../../config/urls' const mapStateToProps = createStructuredSelector({ accounts: accountsSelector, @@ -84,7 +89,20 @@ class DashboardPage extends PureComponent { return ( - + + + openURL(urls.nanoX)}>{t('common.learnMore')} + ), + }} + bannerId={'promoteMobile'} + dismissable + /> + { ) } } +// This forces only one visible top banner at a time +const TopBannerContainer = styled.div` + & > *:not(:first-child) { + display: none; + } +` export default compose( connect( diff --git a/src/components/TopBanner.js b/src/components/TopBanner.js new file mode 100644 index 00000000..0dd0ca6d --- /dev/null +++ b/src/components/TopBanner.js @@ -0,0 +1,129 @@ +// @flow + +import React, { PureComponent } from 'react' +import styled from 'styled-components' +import { connect } from 'react-redux' +import Box from 'components/base/Box' +import { radii } from 'styles/theme' +import IconCross from 'icons/Cross' +import { createStructuredSelector } from 'reselect' +import { dismissBanner } from '../actions/settings' +import { dismissedBannersSelector } from '../reducers/settings' + +export type Content = { + Icon?: React$ComponentType<*>, + message: React$Node, + right?: React$Node, +} + +type Props = { + content?: Content, + status: string, + dismissable: boolean, + bannerId?: string, + dismissedBanners: string[], + dismissBanner: string => void, +} + +const mapStateToProps = createStructuredSelector({ + dismissedBanners: dismissedBannersSelector, +}) + +const mapDispatchToProps = { + dismissBanner, +} + +class TopBanner extends PureComponent { + static defaultProps = { + status: '', + dismissable: false, + } + + onDismiss = () => { + const { bannerId, dismissBanner } = this.props + + if (bannerId) { + dismissBanner(bannerId) + } + } + + render() { + const { dismissedBanners, bannerId, dismissable, content, status } = this.props + + if (!content || (bannerId && dismissedBanners.includes(bannerId))) return null + + const { Icon, message, right } = content + + return ( + + {Icon && ( + + + + )} + {message} + {right} + {dismissable && ( + + + + )} + + ) + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(TopBanner) + +const IconContainer = styled.div` + margin-right: 15px; + display: flex; + align-items: center; +` + +const colorForStatus = { + error: 'alertRed', +} + +const Container = styled(Box).attrs({ + horizontal: true, + align: 'center', + py: '8px', + px: 3, + bg: p => colorForStatus[p.status] || 'wallet', + color: 'white', + mt: -20, + mb: 20, + fontSize: 4, + ff: 'Open Sans|SemiBold', +})` + border-radius: ${radii[1]}px; +` + +const RightContainer = styled.div` + margin-left: auto; +` + +export const FakeLink = styled.span` + color: white; + text-decoration: underline; + cursor: pointer; +` + +const CloseContainer = styled(Box).attrs({ + color: 'white', +})` + z-index: 1; + margin-left: 10px; + cursor: pointer; + &:hover { + color: ${p => p.theme.colors.graphite}; + } + + &:active { + color: ${p => p.theme.colors.graphite}; + } +` diff --git a/src/components/Updater/Banner.js b/src/components/Updater/Banner.js index 9c30daae..40758af4 100644 --- a/src/components/Updater/Banner.js +++ b/src/components/Updater/Banner.js @@ -1,21 +1,20 @@ // @flow import React, { PureComponent } from 'react' -import styled from 'styled-components' import { Trans } from 'react-i18next' import { urls } from 'config/urls' -import { radii } from 'styles/theme' import { openURL } from 'helpers/linking' import Spinner from 'components/base/Spinner' -import Box from 'components/base/Box' import IconUpdate from 'icons/Update' import IconDonjon from 'icons/Donjon' import IconWarning from 'icons/TriangleWarning' import { withUpdaterContext } from './UpdaterContext' import type { UpdaterContextType } from './UpdaterContext' +import TopBanner, { FakeLink } from '../TopBanner' +import type { Content } from '../TopBanner' type Props = { context: UpdaterContextType, @@ -23,25 +22,11 @@ type Props = { export const VISIBLE_STATUS = ['download-progress', 'checking', 'check-success', 'error'] -type Content = { - Icon: React$ComponentType<*>, - message: React$Node, - Right?: React$ComponentType<*>, -} - -type RightProps = { - downloadProgress: number, // eslint-disable-line react/no-unused-prop-types - quitAndInstall: () => void, // eslint-disable-line react/no-unused-prop-types - reDownload: () => void, // eslint-disable-line react/no-unused-prop-types -} - -const CONTENT_BY_STATUS: { [_: string]: Content } = { +const CONTENT_BY_STATUS = (quitAndInstall, reDownload, progress): { [string]: Content } => ({ 'download-progress': { Icon: Spinner, message: , - Right: ({ downloadProgress }: RightProps) => ( - - ), + right: , }, checking: { Icon: IconDonjon, @@ -50,7 +35,7 @@ const CONTENT_BY_STATUS: { [_: string]: Content } = { 'check-success': { Icon: IconUpdate, message: , - Right: ({ quitAndInstall }: RightProps) => ( + right: ( @@ -59,13 +44,13 @@ const CONTENT_BY_STATUS: { [_: string]: Content } = { error: { Icon: IconWarning, message: , - Right: ({ reDownload }: RightProps) => ( + right: ( ), }, -} +}) class UpdaterTopBanner extends PureComponent { reDownload = () => { @@ -75,66 +60,15 @@ class UpdaterTopBanner extends PureComponent { render() { const { context } = this.props const { status, quitAndInstall, downloadProgress } = context - if (!VISIBLE_STATUS.includes(status)) return null - const content: ?Content = CONTENT_BY_STATUS[status] + const content: ?Content = CONTENT_BY_STATUS(quitAndInstall, this.reDownload, downloadProgress)[ + status + ] if (!content) return null - const { Icon, message, Right } = content - - return ( - - {Icon && ( - - {/* $FlowFixMe let me do my stuff, flow */} - - - )} - {message} - {Right && ( - - - - )} - - ) + return } } -const IconContainer = styled.div` - margin-right: 15px; - display: flex; - align-items: center; -` - -const Container = styled(Box).attrs({ - horizontal: true, - align: 'center', - py: '8px', - px: 3, - bg: p => (p.status === 'error' ? 'alertRed' : 'wallet'), - color: 'white', - mt: -20, - mb: 20, - fontSize: 4, - ff: 'Open Sans|SemiBold', -})` - border-radius: ${radii[1]}px; -` - -const FakeLink = styled.span` - color: white; - text-decoration: underline; - cursor: pointer; -` - -const RightContainer = styled.div` - margin-left: auto; -` - export default withUpdaterContext(UpdaterTopBanner) diff --git a/src/config/urls.js b/src/config/urls.js index 9a194a1b..8ac91581 100644 --- a/src/config/urls.js +++ b/src/config/urls.js @@ -8,6 +8,9 @@ export const urls = { github: 'https://github.com/LedgerHQ/ledger-live-desktop', reddit: 'https://www.reddit.com/r/ledgerwallet/', + // Products + nanoX: 'https://www.ledger.com/pages/ledger-nano-x', + // Ledger support faq: 'https://support.ledgerwallet.com/hc/en-us', terms: 'https://www.ledger.com/pages/terms-of-use-and-disclaimer', diff --git a/static/i18n/en/app.json b/static/i18n/en/app.json index 89b2aa4c..3f6fc47d 100644 --- a/static/i18n/en/app.json +++ b/static/i18n/en/app.json @@ -111,6 +111,9 @@ } } }, + "banners": { + "promoteMobile": "Enjoy the Ledger Live experience, now available on mobile with the Ledger Nano X" + }, "dashboard": { "title": "Portfolio", "emptyAccountTile": {