Browse Source

Merge branch 'master' into migrate-live-common

master
Gaëtan Renaudeau 7 years ago
committed by GitHub
parent
commit
bdc476ef8e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 50
      src/components/IsUnlocked.js
  2. 4
      src/components/OperationsList/Operation.js
  3. 7
      src/components/SettingsPage/DisablePasswordModal.js
  4. 28
      src/components/SettingsPage/sections/Display.js
  5. 53
      src/components/WarnBox/index.js
  6. 18
      src/components/WarnBox/stories.js
  7. 2
      src/components/base/Input/index.js
  8. 7
      src/components/base/Select/index.js
  9. 53
      src/components/modals/Send/03-step-verification.js
  10. 57
      src/components/modals/Send/04-step-confirmation.js
  11. 9
      src/components/modals/Send/Footer.js
  12. 64
      src/components/modals/Send/index.js
  13. 245
      src/helpers/countries.json
  14. 12
      src/icons/CheckCircle.js
  15. 12
      src/icons/ExclamationCircleThin.js
  16. 42
      src/icons/LockScreen.js
  17. 2
      src/reducers/settings.js
  18. 5
      src/styles/helpers.js
  19. 5
      src/types/common.js
  20. 5
      static/i18n/en/common.yml
  21. 13
      static/i18n/en/send.yml
  22. 3
      static/i18n/en/settings.yml
  23. 4
      static/i18n/fr/manager.yml
  24. 1
      static/i18n/fr/password.yml
  25. 5
      static/i18n/fr/settings.yml
  26. 8
      yarn.lock

50
src/components/IsUnlocked.js

@ -4,10 +4,14 @@ import bcrypt from 'bcryptjs'
import React, { Component } from 'react' import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { compose } from 'redux' import { compose } from 'redux'
import styled from 'styled-components'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import type { Account } from '@ledgerhq/live-common/lib/types' import type { Account } from '@ledgerhq/live-common/lib/types'
import type { Settings, T } from 'types/common' import type { Settings, T } from 'types/common'
import IconLockScreen from 'icons/LockScreen'
import { ErrorMessageInput } from 'components/base/Input'
import get from 'lodash/get' import get from 'lodash/get'
@ -43,6 +47,7 @@ type Props = {
} }
type State = { type State = {
inputValue: InputValue, inputValue: InputValue,
incorrectPassword: boolean,
} }
const mapStateToProps = state => ({ const mapStateToProps = state => ({
@ -61,8 +66,27 @@ const defaultState = {
inputValue: { inputValue: {
password: '', password: '',
}, },
} incorrectPassword: false,
}
export const PageTitle = styled(Box).attrs({
width: 152,
height: 27,
ff: 'Museo Sans|Regular',
fontSize: 7,
color: 'dark',
})``
export const LockScreenDesc = styled(Box).attrs({
width: 340,
height: 36,
ff: 'Open Sans|Regular',
fontSize: 4,
textAlign: 'center',
color: 'smoke',
})`
margin: 10px auto 25px;
`
class IsUnlocked extends Component<Props, State> { class IsUnlocked extends Component<Props, State> {
state = { state = {
...defaultState, ...defaultState,
@ -121,6 +145,8 @@ class IsUnlocked extends Component<Props, State> {
this.setState({ this.setState({
...defaultState, ...defaultState,
}) })
} else {
this.setState({ incorrectPassword: true })
} }
} }
@ -133,22 +159,36 @@ class IsUnlocked extends Component<Props, State> {
_input: ?HTMLInputElement _input: ?HTMLInputElement
render() { render() {
const { inputValue } = this.state const { inputValue, incorrectPassword } = this.state
const { isLocked, t } = this.props const { isLocked, t } = this.props
if (isLocked) { if (isLocked) {
return ( return (
<Box sticky alignItems="center" justifyContent="center" onClick={this.handleFocusInput}> <Box sticky alignItems="center" justifyContent="center" onClick={this.handleFocusInput}>
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<Box> <Box align="center">
<IconLockScreen size={136} />
<PageTitle>{t('common:lockScreen.title')}</PageTitle>
<LockScreenDesc>
{t('common:lockScreen.subTitle')}
<br />
{t('common:lockScreen.description')}
</LockScreenDesc>
<Box style={{ minWidth: 230 }}>
<InputPassword <InputPassword
autoFocus autoFocus
innerRef={(n: any) => (this._input = n)} innerRef={(n: any) => (this._input = n)}
placeholder={t('common:password')} placeholder={t('common:lockScreen.inputPlaceholder')}
type="password" type="password"
onChange={this.handleChangeInput('password')} onChange={this.handleChangeInput('password')}
value={inputValue.password} value={inputValue.password}
/> />
{incorrectPassword && (
<ErrorMessageInput>
{t('password:errorMessageIncorrectPassword')}
</ErrorMessageInput>
)}
</Box>
</Box> </Box>
</form> </form>
</Box> </Box>

4
src/components/OperationsList/Operation.js

@ -49,6 +49,10 @@ const OperationRaw = styled(Box).attrs({
` `
const Address = ({ value }: { value: string }) => { const Address = ({ value }: { value: string }) => {
if (!value) {
return <Box />
}
const addrSize = value.length / 2 const addrSize = value.length / 2
const left = value.slice(0, 10) const left = value.slice(0, 10)

7
src/components/SettingsPage/DisablePasswordModal.js

@ -6,6 +6,7 @@ import bcrypt from 'bcryptjs'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import InputPassword from 'components/base/InputPassword' import InputPassword from 'components/base/InputPassword'
import Label from 'components/base/Label'
import { ErrorMessageInput } from 'components/base/Input' import { ErrorMessageInput } from 'components/base/Input'
import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'components/base/Modal' import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'components/base/Modal'
@ -72,14 +73,14 @@ class DisablePasswordModal extends PureComponent<Props, State> {
<ModalBody onClose={onClose}> <ModalBody onClose={onClose}>
<ModalTitle>{t('settings:profile.disablePasswordModalTitle')}</ModalTitle> <ModalTitle>{t('settings:profile.disablePasswordModalTitle')}</ModalTitle>
<ModalContent> <ModalContent>
<Box ff="Museo Sans|Regular" color="dark" textAlign="center" mb={2} mt={3}>
{t('settings:profile.disablePasswordModalSubtitle')}
</Box>
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" px={4}> <Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" px={4}>
{t('settings:profile.disablePasswordModalDesc')} {t('settings:profile.disablePasswordModalDesc')}
<Box px={7} mt={4} flow={3}> <Box px={7} mt={4} flow={3}>
{isPasswordEnabled && ( {isPasswordEnabled && (
<Box flow={1}> <Box flow={1}>
<Label htmlFor="password">
{t('settings:profile.disablePasswordModalInput')}
</Label>
<InputPassword <InputPassword
autoFocus autoFocus
type="password" type="password"

28
src/components/SettingsPage/sections/Display.js

@ -8,9 +8,10 @@ import type { Settings, T } from 'types/common'
import Select from 'components/base/Select' import Select from 'components/base/Select'
import RadioGroup from 'components/base/RadioGroup' import RadioGroup from 'components/base/RadioGroup'
import IconDisplay from 'icons/Display' import IconDisplay from 'icons/Display'
import COUNTRIES from 'helpers/countries.json'
import { import {
SettingsSection as Section, SettingsSection as Section,
SettingsSectionHeader as Header, SettingsSectionHeader as Header,
@ -27,20 +28,6 @@ const fiats = listFiatCurrencies()
name: `${fiat.name} - ${fiat.code}${fiat.symbol ? ` (${fiat.symbol})` : ''}`, name: `${fiat.name} - ${fiat.code}${fiat.symbol ? ` (${fiat.symbol})` : ''}`,
})) }))
/* temporary subset of countries */
const COUNTRIES = [
{ name: 'China', key: 'CN' },
{ name: 'France', key: 'FR' },
{ name: 'India', key: 'IN' },
{ name: 'Italy', key: 'IT' },
{ name: 'Japan', key: 'JP' },
{ name: 'Russian Federation', key: 'RU' },
{ name: 'Singapore', key: 'SG' },
{ name: 'Switzerland', key: 'CH' },
{ name: 'United Kingdom', key: 'GB' },
{ name: 'United States', key: 'US' },
]
type Props = { type Props = {
t: T, t: T,
settings: Settings, settings: Settings,
@ -52,7 +39,7 @@ type State = {
cachedMarketIndicator: string, cachedMarketIndicator: string,
cachedLanguageKey: string, cachedLanguageKey: string,
cachedCounterValue: ?Object, cachedCounterValue: ?Object,
cachedRegion: Object, cachedRegion: string,
} }
class TabProfile extends PureComponent<Props, State> { class TabProfile extends PureComponent<Props, State> {
@ -102,7 +89,7 @@ class TabProfile extends PureComponent<Props, State> {
}) })
} }
handleChangeRegion = (region: Object) => { handleChangeRegion = (region: string) => {
const { saveSettings } = this.props const { saveSettings } = this.props
this.setState({ cachedRegion: region }) this.setState({ cachedRegion: region })
window.requestIdleCallback(() => { window.requestIdleCallback(() => {
@ -131,7 +118,7 @@ class TabProfile extends PureComponent<Props, State> {
} = this.state } = this.state
const { languages } = this.getDatas() const { languages } = this.getDatas()
const currentLanguage = languages.find(l => l.key === cachedLanguageKey) const currentLanguage = languages.find(l => l.key === cachedLanguageKey)
const currentRegion = COUNTRIES.find(r => r.key === cachedRegion.key) const currentRegion = COUNTRIES.find(r => r.key === cachedRegion)
return ( return (
<Section> <Section>
@ -171,9 +158,8 @@ class TabProfile extends PureComponent<Props, State> {
<Select <Select
searchable searchable
fuseOptions={{ keys: ['name'] }} fuseOptions={{ keys: ['name'] }}
style={{ minWidth: 130 }} maxHeight={200}
small onChange={item => this.handleChangeRegion(item.key)}
onChange={item => this.handleChangeRegion(item)}
renderSelected={item => item && item.name} renderSelected={item => item && item.name}
value={currentRegion} value={currentRegion}
items={COUNTRIES} items={COUNTRIES}

53
src/components/WarnBox/index.js

@ -0,0 +1,53 @@
// @flow
import * as React from 'react'
import styled from 'styled-components'
import Box from 'components/base/Box'
const Container = styled(Box).attrs({
color: 'graphite',
borderRadius: 1,
px: 4,
py: 3,
horizontal: true,
ff: 'Open Sans',
fontSize: 4,
flow: 4,
})`
border: solid 1px;
border-color: ${p => p.theme.colors.fog};
align-items: center;
`
const svg = (
<svg width="36" height="43">
<g fill="none">
<path
fill="#4B84FF"
fillOpacity=".08"
d="M18.177 2C18.06 24.126 18 37.184 18 41.174h.354C27.74 38.96 35 29.762 35 19.884V9.154L18.177 2z"
/>
<path
stroke="#142533"
strokeWidth="2"
d="M18 2L1 9.153v10.73c0 9.88 7.158 19.077 16.821 21.29h.358C27.663 38.96 35 29.764 35 19.884V9.154L18 2z"
/>
<path
fill="#4B84FF"
d="M23.733 15.036c-.568 0-1.03.448-1.03 1.001l-.019 4.488s.002.313-.308.313c-.316 0-.307-.313-.307-.313v-6.474c0-.553-.456-.982-1.024-.982-.57 0-.974.43-.974.982v6.474s-.035.316-.34.316c-.303 0-.327-.316-.327-.316v-7.553c0-.552-.428-.972-.996-.972-.569 0-1 .42-1 .972v7.553s-.016.303-.344.303c-.321 0-.323-.303-.323-.303v-5.611c0-.553-.445-.9-1.013-.9-.57 0-.985.347-.985.9v8.2s-.056.365-.594-.237c-1.378-1.543-2.097-1.85-2.097-1.85s-1.31-.622-1.889.503c-.42.816.025.86.712 1.861.607.888 2.529 3.221 2.529 3.221s2.28 3.126 5.355 3.126c0 0 6.024.25 6.024-5.544l-.021-8.157c0-.553-.46-1.001-1.03-1.001"
/>
</g>
</svg>
)
type Props = {
children: React.Node,
}
export default (props: Props) => (
<Container>
<Box mx={1}>{svg}</Box>
<Box shrink>{props.children}</Box>
</Container>
)

18
src/components/WarnBox/stories.js

@ -0,0 +1,18 @@
// @flow
import React from 'react'
import { storiesOf } from '@storybook/react'
import { text } from '@storybook/addon-knobs'
import WarnBox from 'components/WarnBox'
const stories = storiesOf('Components', module)
stories.add('WarnBox', () => (
<WarnBox>
{text(
'children',
'Nulla ornare ligula nec velit fermentum accumsan. Mauris sagittis iaculis pretium. Maecenas tincidunt tortor ullamcorper neque scelerisque lacinia sit amet sit amet elit. Quisque vulputate at tellus ut fringilla. Sed varius neque accumsan nunc consequat semper. In interdum euismod velit, sed pulvinar justo finibus ac. Nullam euismod felis non pellentesque fermentum. Nullam sed libero eu ligula porta accumsan eget et neque. Sed varius lobortis vestibulum. Morbi efficitur leo at augue venenatis, vitae faucibus ante lobortis. Nunc tincidunt, sem eget ultricies convallis, dolor est gravida sem, non vestibulum urna lorem a justo. Quisque ultrices feugiat arcu, sit amet tristique tellus maximus in. Phasellus ultricies mattis erat vitae laoreet. Fusce ac dignissim dui. Etiam semper purus nisi, eu semper tortor mollis nec.',
)}
</WarnBox>
))

2
src/components/base/Input/index.js

@ -38,7 +38,7 @@ const Base = styled.input.attrs({
` `
export const ErrorMessageInput = styled(Box).attrs({ export const ErrorMessageInput = styled(Box).attrs({
alignItems: 'flex-end', alignItems: 'flex-start',
color: 'alertRed', color: 'alertRed',
ff: 'Open Sans|SemiBold', ff: 'Open Sans|SemiBold',
fontSize: 3, fontSize: 3,

7
src/components/base/Select/index.js

@ -320,8 +320,8 @@ class Select extends PureComponent<Props> {
<AngleDown mr={-1} /> <AngleDown mr={-1} />
</TriggerBtn> </TriggerBtn>
)} )}
{isOpen && <div hidden={!isOpen}>
(searchable ? ( {searchable ? (
<Search <Search
value={inputValue} value={inputValue}
items={items} items={items}
@ -332,7 +332,8 @@ class Select extends PureComponent<Props> {
/> />
) : ( ) : (
this.renderItems(items, selectedItem, downshiftProps) this.renderItems(items, selectedItem, downshiftProps)
))} )}
</div>
</Container> </Container>
) )
}} }}

53
src/components/modals/Send/03-step-verification.js

@ -1,7 +1,54 @@
// @flow
import React from 'react' import React from 'react'
import styled from 'styled-components'
import Box from 'components/base/Box'
import WarnBox from 'components/WarnBox'
import { multiline } from 'styles/helpers'
import DeviceCheckAddress from 'components/DeviceCheckAddress'
import DeviceConfirm from 'components/DeviceConfirm'
import type { Account } from '@ledgerhq/wallet-common/lib/types'
import type { Device, T } from 'types/common'
const Container = styled(Box).attrs({
alignItems: 'center',
fontSize: 4,
pb: 4,
})``
const Info = styled(Box).attrs({
ff: 'Open Sans|SemiBold',
color: 'dark',
mt: 6,
mb: 4,
px: 5,
})`
text-align: center;
`
function StepVerification() { type Props = {
return <div>step verification</div> account: ?Account,
device: ?Device,
onValidate: Function,
t: T,
} }
export default StepVerification export default (props: Props) => (
<Container>
<WarnBox>{multiline(props.t('send:steps.verification.warning'))}</WarnBox>
<Info>{props.t('send:steps.verification.body')}</Info>
{// TODO: Actually create a tx
// DeviceCheckAddress used as a placeholder in the meantime
props.account &&
props.device && (
<DeviceCheckAddress
account={props.account}
device={props.device}
onCheck={props.onValidate}
render={({ isVerified }) => <DeviceConfirm notValid={isVerified === false} />}
/>
)}
</Container>
)

57
src/components/modals/Send/04-step-confirmation.js

@ -1,7 +1,60 @@
// @flow
import React from 'react' import React from 'react'
import styled from 'styled-components'
function StepConfirmation() { import IconCheckCircle from 'icons/CheckCircle'
return <div>step confirmation</div> import IconExclamationCircleThin from 'icons/ExclamationCircleThin'
import Box from 'components/base/Box'
import { multiline } from 'styles/helpers'
import { colors } from 'styles/theme'
import type { T } from 'types/common'
const Container = styled(Box).attrs({
alignItems: 'center',
justifyContent: 'center',
grow: true,
color: 'dark',
})`
height: 220px;
`
const Title = styled(Box).attrs({
ff: 'Museo Sans',
fontSize: 5,
mt: 4,
})`
text-align: center;
`
const Text = styled(Box).attrs({
ff: 'Open Sans',
fontSize: 4,
mt: 2,
})`
text-align: center;
`
type Props = {
txValidated: boolean,
t: T,
}
function StepConfirmation(props: Props) {
const { t, txValidated } = props
const Icon = txValidated ? IconCheckCircle : IconExclamationCircleThin
const iconColor = txValidated ? colors.positiveGreen : colors.alertRed
const tPrefix = txValidated ? 'send:steps.confirmation.success' : 'send:steps.confirmation.error'
return (
<Container>
<span style={{ color: iconColor }}>
<Icon size={43} />
</span>
<Title>{t(`${tPrefix}.title`)}</Title>
<Text>{multiline(t(`${tPrefix}.text`))}</Text>
</Container>
)
} }
export default StepConfirmation export default StepConfirmation

9
src/components/modals/Send/Footer.js

@ -20,11 +20,14 @@ type Props = {
fees: number, fees: number,
onNext: Function, onNext: Function,
canNext: boolean, canNext: boolean,
showTotal: boolean,
} }
function Footer({ account, amount, fees, t, onNext, canNext }: Props) { function Footer({ account, amount, fees, t, onNext, canNext, showTotal }: Props) {
return ( return (
<ModalFooter horizontal alignItems="center"> <ModalFooter>
<Box horizontal alignItems="center" justifyContent="flex-end" flow={2}>
{showTotal && (
<Box grow> <Box grow>
<Label>{t('send:totalSpent')}</Label> <Label>{t('send:totalSpent')}</Label>
<Box horizontal flow={2} align="center"> <Box horizontal flow={2} align="center">
@ -53,9 +56,11 @@ function Footer({ account, amount, fees, t, onNext, canNext }: Props) {
</Box> </Box>
</Box> </Box>
</Box> </Box>
)}
<Button primary onClick={onNext} disabled={!canNext}> <Button primary onClick={onNext} disabled={!canNext}>
{'Next'} {'Next'}
</Button> </Button>
</Box>
</ModalFooter> </ModalFooter>
) )
} }

64
src/components/modals/Send/index.js

@ -37,6 +37,7 @@ type State = {
fees: number, fees: number,
isRBF: boolean, isRBF: boolean,
recipientAddress: string, recipientAddress: string,
txValidated: null | boolean,
stepIndex: number, stepIndex: number,
} }
@ -59,6 +60,7 @@ const INITIAL_STATE = {
fees: 0, fees: 0,
isRBF: false, isRBF: false,
recipientAddress: '', recipientAddress: '',
txValidated: null,
stepIndex: 0, stepIndex: 0,
} }
@ -136,6 +138,15 @@ class SendModal extends PureComponent<Props, State> {
}) })
} }
handleValidate = isValidated => {
const { stepIndex } = this.state
this.setState({
txValidated: isValidated,
stepIndex: stepIndex + 1,
})
}
createChangeHandler = key => value => { createChangeHandler = key => value => {
const patch = { [key]: value } const patch = { [key]: value }
// ensure max is always restecped when changing fees // ensure max is always restecped when changing fees
@ -158,34 +169,47 @@ class SendModal extends PureComponent<Props, State> {
renderStep = () => { renderStep = () => {
const { t } = this.props const { t } = this.props
const { stepIndex, amount, deviceSelected, ...otherState } = this.state const { stepIndex, deviceSelected, txValidated, ...otherState } = this.state
const step = this._steps[stepIndex] const step = this._steps[stepIndex]
if (!step) { if (!step) {
return null return null
} }
const { Comp } = step const { Comp } = step
const props = (predicate, props) => (predicate ? props : {})
const stepProps = { const stepProps = {
...otherState, ...otherState,
t, t,
amount,
account: this._account, account: this._account,
...props(stepIndex === 1, {
accountName: this._account ? this._account.name : undefined,
deviceSelected,
onChangeDevice: this.handleChangeDevice,
onStatusChange: this.handleChangeStatus,
}),
} }
return <Comp onChange={this.createChangeHandler} {...stepProps} {...this.props} /> switch (stepIndex) {
case 1:
stepProps.accountName = this._account ? this._account.name : undefined
stepProps.deviceSelected = deviceSelected
stepProps.onChangeDevice = this.handleChangeDevice
stepProps.onStatusChange = this.handleChangeStatus
break
case 2:
stepProps.device = deviceSelected
stepProps.onValidate = this.handleValidate
break
case 3:
if (txValidated !== null) {
stepProps.txValidated = txValidated
}
break
default:
break
}
return <Comp onChange={this.createChangeHandler} {...stepProps} />
} }
render() { render() {
const { accounts, t } = this.props const { accounts, t } = this.props
const { stepIndex, amount, account, fees } = this.state const { stepIndex, amount, account, fees, txValidated } = this.state
const canNext = this.canNext() const canNext = this.canNext()
const canPrev = this.canPrev() const canPrev = this.canPrev()
@ -210,20 +234,30 @@ class SendModal extends PureComponent<Props, State> {
{this.renderStep()} {this.renderStep()}
</ModalContent> </ModalContent>
{this._account && {this._account &&
stepIndex !== 3 && ( stepIndex < 2 && (
<Footer <Footer
canNext={canNext} canNext={canNext}
onNext={this.handleNextStep} onNext={this.handleNextStep}
account={this._account} account={this._account}
amount={amount} amount={amount}
fees={fees} fees={fees}
showTotal={stepIndex === 0}
t={t} t={t}
/> />
)} )}
{stepIndex === 3 && ( {stepIndex === 3 && (
<ModalFooter horizontal alignItems="center" justifyContent="flex-end" flow={2}> <ModalFooter horizontal alignItems="center" justifyContent="flex-end" flow={2}>
<Button onClick={onClose}>{t('common:close')}</Button> <Button onClick={onClose}>{t('common:close')}</Button>
<Button primary>{t('send:steps.confirmation.cta')}</Button> {txValidated ? (
// TODO: actually go to operations details
<Button onClick={onClose} primary>
{t('send:steps.confirmation.success.cta')}
</Button>
) : (
<Button onClick={() => this.setState({ stepIndex: 0 })} primary>
{t('send:steps.confirmation.error.cta')}
</Button>
)}
</ModalFooter> </ModalFooter>
)} )}
</ModalBody> </ModalBody>

245
src/helpers/countries.json

@ -0,0 +1,245 @@
[
{ "name": "Afghanistan", "key": "AF" },
{ "name": "Åland Islands", "key": "AX" },
{ "name": "Albania", "key": "AL" },
{ "name": "Algeria", "key": "DZ" },
{ "name": "American Samoa", "key": "AS" },
{ "name": "AndorrA", "key": "AD" },
{ "name": "Angola", "key": "AO" },
{ "name": "Anguilla", "key": "AI" },
{ "name": "Antarctica", "key": "AQ" },
{ "name": "Antigua and Barbuda", "key": "AG" },
{ "name": "Argentina", "key": "AR" },
{ "name": "Armenia", "key": "AM" },
{ "name": "Aruba", "key": "AW" },
{ "name": "Australia", "key": "AU" },
{ "name": "Austria", "key": "AT" },
{ "name": "Azerbaijan", "key": "AZ" },
{ "name": "Bahamas", "key": "BS" },
{ "name": "Bahrain", "key": "BH" },
{ "name": "Bangladesh", "key": "BD" },
{ "name": "Barbados", "key": "BB" },
{ "name": "Belarus", "key": "BY" },
{ "name": "Belgium", "key": "BE" },
{ "name": "Belize", "key": "BZ" },
{ "name": "Benin", "key": "BJ" },
{ "name": "Bermuda", "key": "BM" },
{ "name": "Bhutan", "key": "BT" },
{ "name": "Bolivia", "key": "BO" },
{ "name": "Bosnia and Herzegovina", "key": "BA" },
{ "name": "Botswana", "key": "BW" },
{ "name": "Bouvet Island", "key": "BV" },
{ "name": "Brazil", "key": "BR" },
{ "name": "British Indian Ocean Territory", "key": "IO" },
{ "name": "Brunei Darussalam", "key": "BN" },
{ "name": "Bulgaria", "key": "BG" },
{ "name": "Burkina Faso", "key": "BF" },
{ "name": "Burundi", "key": "BI" },
{ "name": "Cambodia", "key": "KH" },
{ "name": "Cameroon", "key": "CM" },
{ "name": "Canada", "key": "CA" },
{ "name": "Cape Verde", "key": "CV" },
{ "name": "Cayman Islands", "key": "KY" },
{ "name": "Central African Republic", "key": "CF" },
{ "name": "Chad", "key": "TD" },
{ "name": "Chile", "key": "CL" },
{ "name": "China", "key": "CN" },
{ "name": "Christmas Island", "key": "CX" },
{ "name": "Cocos (Keeling) Islands", "key": "CC" },
{ "name": "Colombia", "key": "CO" },
{ "name": "Comoros", "key": "KM" },
{ "name": "Congo", "key": "CG" },
{ "name": "Congo, The Democratic Republic of the", "key": "CD" },
{ "name": "Cook Islands", "key": "CK" },
{ "name": "Costa Rica", "key": "CR" },
{ "name": "Cote D'Ivoire", "key": "CI" },
{ "name": "Croatia", "key": "HR" },
{ "name": "Cuba", "key": "CU" },
{ "name": "Cyprus", "key": "CY" },
{ "name": "Czech Republic", "key": "CZ" },
{ "name": "Denmark", "key": "DK" },
{ "name": "Djibouti", "key": "DJ" },
{ "name": "Dominica", "key": "DM" },
{ "name": "Dominican Republic", "key": "DO" },
{ "name": "Ecuador", "key": "EC" },
{ "name": "Egypt", "key": "EG" },
{ "name": "El Salvador", "key": "SV" },
{ "name": "Equatorial Guinea", "key": "GQ" },
{ "name": "Eritrea", "key": "ER" },
{ "name": "Estonia", "key": "EE" },
{ "name": "Ethiopia", "key": "ET" },
{ "name": "Falkland Islands (Malvinas)", "key": "FK" },
{ "name": "Faroe Islands", "key": "FO" },
{ "name": "Fiji", "key": "FJ" },
{ "name": "Finland", "key": "FI" },
{ "name": "France", "key": "FR" },
{ "name": "French Guiana", "key": "GF" },
{ "name": "French Polynesia", "key": "PF" },
{ "name": "French Southern Territories", "key": "TF" },
{ "name": "Gabon", "key": "GA" },
{ "name": "Gambia", "key": "GM" },
{ "name": "Georgia", "key": "GE" },
{ "name": "Germany", "key": "DE" },
{ "name": "Ghana", "key": "GH" },
{ "name": "Gibraltar", "key": "GI" },
{ "name": "Greece", "key": "GR" },
{ "name": "Greenland", "key": "GL" },
{ "name": "Grenada", "key": "GD" },
{ "name": "Guadeloupe", "key": "GP" },
{ "name": "Guam", "key": "GU" },
{ "name": "Guatemala", "key": "GT" },
{ "name": "Guernsey", "key": "GG" },
{ "name": "Guinea", "key": "GN" },
{ "name": "Guinea-Bissau", "key": "GW" },
{ "name": "Guyana", "key": "GY" },
{ "name": "Haiti", "key": "HT" },
{ "name": "Heard Island and Mcdonald Islands", "key": "HM" },
{ "name": "Holy See (Vatican City State)", "key": "VA" },
{ "name": "Honduras", "key": "HN" },
{ "name": "Hong Kong", "key": "HK" },
{ "name": "Hungary", "key": "HU" },
{ "name": "Iceland", "key": "IS" },
{ "name": "India", "key": "IN" },
{ "name": "Indonesia", "key": "ID" },
{ "name": "Iran, Islamic Republic Of", "key": "IR" },
{ "name": "Iraq", "key": "IQ" },
{ "name": "Ireland", "key": "IE" },
{ "name": "Isle of Man", "key": "IM" },
{ "name": "Israel", "key": "IL" },
{ "name": "Italy", "key": "IT" },
{ "name": "Jamaica", "key": "JM" },
{ "name": "Japan", "key": "JP" },
{ "name": "Jersey", "key": "JE" },
{ "name": "Jordan", "key": "JO" },
{ "name": "Kazakhstan", "key": "KZ" },
{ "name": "Kenya", "key": "KE" },
{ "name": "Kiribati", "key": "KI" },
{ "name": "Korea, Democratic People'S Republic of", "key": "KP" },
{ "name": "Korea, Republic of", "key": "KR" },
{ "name": "Kuwait", "key": "KW" },
{ "name": "Kyrgyzstan", "key": "KG" },
{ "name": "Lao People'S Democratic Republic", "key": "LA" },
{ "name": "Latvia", "key": "LV" },
{ "name": "Lebanon", "key": "LB" },
{ "name": "Lesotho", "key": "LS" },
{ "name": "Liberia", "key": "LR" },
{ "name": "Libyan Arab Jamahiriya", "key": "LY" },
{ "name": "Liechtenstein", "key": "LI" },
{ "name": "Lithuania", "key": "LT" },
{ "name": "Luxembourg", "key": "LU" },
{ "name": "Macao", "key": "MO" },
{ "name": "Macedonia, The Former Yugoslav Republic of", "key": "MK" },
{ "name": "Madagascar", "key": "MG" },
{ "name": "Malawi", "key": "MW" },
{ "name": "Malaysia", "key": "MY" },
{ "name": "Maldives", "key": "MV" },
{ "name": "Mali", "key": "ML" },
{ "name": "Malta", "key": "MT" },
{ "name": "Marshall Islands", "key": "MH" },
{ "name": "Martinique", "key": "MQ" },
{ "name": "Mauritania", "key": "MR" },
{ "name": "Mauritius", "key": "MU" },
{ "name": "Mayotte", "key": "YT" },
{ "name": "Mexico", "key": "MX" },
{ "name": "Micronesia, Federated States of", "key": "FM" },
{ "name": "Moldova, Republic of", "key": "MD" },
{ "name": "Monaco", "key": "MC" },
{ "name": "Mongolia", "key": "MN" },
{ "name": "Montserrat", "key": "MS" },
{ "name": "Morocco", "key": "MA" },
{ "name": "Mozambique", "key": "MZ" },
{ "name": "Myanmar", "key": "MM" },
{ "name": "Namibia", "key": "NA" },
{ "name": "Nauru", "key": "NR" },
{ "name": "Nepal", "key": "NP" },
{ "name": "Netherlands", "key": "NL" },
{ "name": "Netherlands Antilles", "key": "AN" },
{ "name": "New Caledonia", "key": "NC" },
{ "name": "New Zealand", "key": "NZ" },
{ "name": "Nicaragua", "key": "NI" },
{ "name": "Niger", "key": "NE" },
{ "name": "Nigeria", "key": "NG" },
{ "name": "Niue", "key": "NU" },
{ "name": "Norfolk Island", "key": "NF" },
{ "name": "Northern Mariana Islands", "key": "MP" },
{ "name": "Norway", "key": "NO" },
{ "name": "Oman", "key": "OM" },
{ "name": "Pakistan", "key": "PK" },
{ "name": "Palau", "key": "PW" },
{ "name": "Palestinian Territory, Occupied", "key": "PS" },
{ "name": "Panama", "key": "PA" },
{ "name": "Papua New Guinea", "key": "PG" },
{ "name": "Paraguay", "key": "PY" },
{ "name": "Peru", "key": "PE" },
{ "name": "Philippines", "key": "PH" },
{ "name": "Pitcairn", "key": "PN" },
{ "name": "Poland", "key": "PL" },
{ "name": "Portugal", "key": "PT" },
{ "name": "Puerto Rico", "key": "PR" },
{ "name": "Qatar", "key": "QA" },
{ "name": "Reunion", "key": "RE" },
{ "name": "Romania", "key": "RO" },
{ "name": "Russian Federation", "key": "RU" },
{ "name": "RWANDA", "key": "RW" },
{ "name": "Saint Helena", "key": "SH" },
{ "name": "Saint Kitts and Nevis", "key": "KN" },
{ "name": "Saint Lucia", "key": "LC" },
{ "name": "Saint Pierre and Miquelon", "key": "PM" },
{ "name": "Saint Vincent and the Grenadines", "key": "VC" },
{ "name": "Samoa", "key": "WS" },
{ "name": "San Marino", "key": "SM" },
{ "name": "Sao Tome and Principe", "key": "ST" },
{ "name": "Saudi Arabia", "key": "SA" },
{ "name": "Senegal", "key": "SN" },
{ "name": "Serbia and Montenegro", "key": "CS" },
{ "name": "Seychelles", "key": "SC" },
{ "name": "Sierra Leone", "key": "SL" },
{ "name": "Singapore", "key": "SG" },
{ "name": "Slovakia", "key": "SK" },
{ "name": "Slovenia", "key": "SI" },
{ "name": "Solomon Islands", "key": "SB" },
{ "name": "Somalia", "key": "SO" },
{ "name": "South Africa", "key": "ZA" },
{ "name": "South Georgia and the South Sandwich Islands", "key": "GS" },
{ "name": "Spain", "key": "ES" },
{ "name": "Sri Lanka", "key": "LK" },
{ "name": "Sudan", "key": "SD" },
{ "name": "Suriname", "key": "SR" },
{ "name": "Svalbard and Jan Mayen", "key": "SJ" },
{ "name": "Swaziland", "key": "SZ" },
{ "name": "Sweden", "key": "SE" },
{ "name": "Switzerland", "key": "CH" },
{ "name": "Syrian Arab Republic", "key": "SY" },
{ "name": "Taiwan, Province of China", "key": "TW" },
{ "name": "Tajikistan", "key": "TJ" },
{ "name": "Tanzania, United Republic of", "key": "TZ" },
{ "name": "Thailand", "key": "TH" },
{ "name": "Timor-Leste", "key": "TL" },
{ "name": "Togo", "key": "TG" },
{ "name": "Tokelau", "key": "TK" },
{ "name": "Tonga", "key": "TO" },
{ "name": "Trinidad and Tobago", "key": "TT" },
{ "name": "Tunisia", "key": "TN" },
{ "name": "Turkey", "key": "TR" },
{ "name": "Turkmenistan", "key": "TM" },
{ "name": "Turks and Caicos Islands", "key": "TC" },
{ "name": "Tuvalu", "key": "TV" },
{ "name": "Uganda", "key": "UG" },
{ "name": "Ukraine", "key": "UA" },
{ "name": "United Arab Emirates", "key": "AE" },
{ "name": "United Kingdom", "key": "GB" },
{ "name": "United States", "key": "US" },
{ "name": "United States Minor Outlying Islands", "key": "UM" },
{ "name": "Uruguay", "key": "UY" },
{ "name": "Uzbekistan", "key": "UZ" },
{ "name": "Vanuatu", "key": "VU" },
{ "name": "Venezuela", "key": "VE" },
{ "name": "Viet Nam", "key": "VN" },
{ "name": "Virgin Islands, British", "key": "VG" },
{ "name": "Virgin Islands, U.S.", "key": "VI" },
{ "name": "Wallis and Futuna", "key": "WF" },
{ "name": "Western Sahara", "key": "EH" },
{ "name": "Yemen", "key": "YE" },
{ "name": "Zambia", "key": "ZM" },
{ "name": "Zimbabwe", "key": "ZW" }
]

12
src/icons/CheckCircle.js

@ -0,0 +1,12 @@
// @flow
import React from 'react'
export default ({ size, ...p }: { size: number }) => (
<svg viewBox="0 0 24 24" height={size} width={size} {...p}>
<path
fill="currentColor"
d="M12 .375C5.58.375.375 5.58.375 12S5.58 23.625 12 23.625 23.625 18.42 23.625 12 18.42.375 12 .375zm0 21.75C6.438 22.125 1.875 17.622 1.875 12 1.875 6.438 6.378 1.875 12 1.875c5.562 0 10.125 4.503 10.125 10.125 0 5.562-4.503 10.125-10.125 10.125zm6.639-12.889l-8.46 8.392a.562.562 0 0 1-.796-.003l-4.025-4.058a.562.562 0 0 1 .003-.795l.4-.397a.562.562 0 0 1 .795.004l3.233 3.259 7.661-7.6a.562.562 0 0 1 .796.003l.396.4a.562.562 0 0 1-.003.795z"
/>
</svg>
)

12
src/icons/ExclamationCircleThin.js

@ -0,0 +1,12 @@
// @flow
import React from 'react'
export default ({ size, ...p }: { size: number }) => (
<svg viewBox="0 0 24 24" height={size} width={size} {...p}>
<path
fill="currentColor"
d="M12 1.875c5.56 0 10.125 4.504 10.125 10.125A10.122 10.122 0 0 1 12 22.125C6.41 22.125 1.875 17.599 1.875 12 1.875 6.412 6.403 1.875 12 1.875zm0-1.5C5.58.375.375 5.582.375 12 .375 18.422 5.58 23.625 12 23.625S23.625 18.422 23.625 12C23.625 5.582 18.42.375 12 .375zM11.461 6h1.078c.32 0 .575.266.562.586l-.329 7.875a.562.562 0 0 1-.562.539h-.42a.563.563 0 0 1-.563-.54L10.9 6.587A.563.563 0 0 1 11.461 6zM12 15.938a1.312 1.312 0 1 0 0 2.624 1.312 1.312 0 0 0 0-2.625z"
/>
</svg>
)

42
src/icons/LockScreen.js

@ -0,0 +1,42 @@
// @flow
/* this icon is a placeholder for now */
import React from 'react'
export default ({ size, ...p }: { size: number }) => (
<svg width={size} height={size} {...p}>
<defs>
<filter
id="a"
width="178.9%"
height="178.9%"
x="-39.4%"
y="-37.2%"
filterUnits="objectBoundingBox"
>
<feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="11.5" />
<feColorMatrix
in="shadowBlurOuter1"
result="shadowMatrixOuter1"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0787760417 0"
/>
<feMerge>
<feMergeNode in="shadowMatrixOuter1" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<g fill="none" fillRule="evenodd" filter="url(#a)" transform="translate(23 21)">
<rect width="90" height="90" fill="#FFF" rx="20" />
<path fill="#6490F1" d="M45 13.36c-17.475 0-31.64 14.165-31.64 31.64S27.524 76.64 45 76.64" />
<path fill="#142533" fillOpacity=".1" d="M13.36 45c0 17.475 14.165 31.64 31.64 31.64V45" />
<path
fill="#142533"
fillOpacity=".1"
d="M22.845 67.59c5.708 5.598 13.528 9.05 22.155 9.05V45"
/>
</g>
</svg>
)

2
src/reducers/settings.js

@ -36,7 +36,7 @@ const defaultState: SettingsState = {
}, },
marketIndicator: 'western', marketIndicator: 'western',
currenciesSettings: {}, currenciesSettings: {},
region: { key: 'US', name: 'United States' }, region: 'US',
} }
const CURRENCY_DEFAULTS_SETTINGS: CurrencySettings = { const CURRENCY_DEFAULTS_SETTINGS: CurrencySettings = {

5
src/styles/helpers.js

@ -1,6 +1,8 @@
// @flow // @flow
import React from 'react'
import Color from 'color' import Color from 'color'
import uniqueId from 'lodash/uniqueId'
import staticPath from 'helpers/staticPath' import staticPath from 'helpers/staticPath'
import { colors, fontFamilies } from 'styles/theme' import { colors, fontFamilies } from 'styles/theme'
@ -53,6 +55,9 @@ export const fontFace = ({
} }
` `
export const multiline = (str: string) =>
str.split('\n').map(line => <p key={uniqueId()}>{line}</p>)
export function getMarketColor({ export function getMarketColor({
marketIndicator, marketIndicator,
isNegative, isNegative,

5
src/types/common.js

@ -40,10 +40,7 @@ export type Settings = {
}, },
marketIndicator: 'eastern' | 'western', marketIndicator: 'eastern' | 'western',
currenciesSettings: CurrenciesSettings, currenciesSettings: CurrenciesSettings,
region: { region: string,
key: string,
name: string,
},
} }
export type T = (?string, ?Object) => string export type T = (?string, ?Object) => string

5
static/i18n/en/common.yml

@ -18,3 +18,8 @@ retry: Retry
close: Close close: Close
eastern: Eastern eastern: Eastern
western: Western western: Western
lockScreen:
title: Welcome Back
subTitle: Your application is locked
description: Please enter your password to continue
inputPlaceholder: Type your password

13
static/i18n/en/send.yml

@ -15,6 +15,19 @@ steps:
title: Connect device title: Connect device
verification: verification:
title: Verification title: Verification
warning: |
You are about to validate a transaction.
Be careful, we strongly recommand you to verify that the informations on your Ledger device are correct.
body: Once you have checked everything is ok, you can validate securely the transaction on your device.
confirmation: confirmation:
title: Confirmation title: Confirmation
success:
title: Transaction successfully completed
text: You may have to wait few confirmations unitl the transaction appear
cta: View operation details cta: View operation details
error:
title: Transaction aborted
text: |
The transaction have been aborted on your device.
You can try again the operation.
cta: Retry operation

3
static/i18n/en/settings.yml

@ -36,9 +36,8 @@ profile:
passwordModalRepeatPasswordInput: Repeat password passwordModalRepeatPasswordInput: Repeat password
passwordModalSave: Save passwordModalSave: Save
disablePasswordModalTitle: Disable Password disablePasswordModalTitle: Disable Password
disablePasswordModalSubtitle: Type your password
disablePasswordModalDesc: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh diam. disablePasswordModalDesc: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh diam.
disablePasswordModalInput: Type your password disablePasswordModalInput: Current password
disablePasswordModalSave: Save disablePasswordModalSave: Save
sync: Sync accounts sync: Sync accounts
syncDesc: Lorem ipsum dolor sit amet syncDesc: Lorem ipsum dolor sit amet

4
static/i18n/fr/manager.yml

@ -4,3 +4,7 @@ tabs:
device: Mon appareil device: Mon appareil
install: Installer install: Installer
allApps: Toutes les applications allApps: Toutes les applications
plugYourDevice:
title: Plug your device
desc: Lorem Ipsum is simply dummy text of the printing and typesetting industry’s standard dummy text
cta: Plug my device

1
static/i18n/fr/password.yml

@ -4,3 +4,4 @@ warning_1: Warning 1
warning_2: Warning 2 warning_2: Warning 2
warning_3: Warning 3 warning_3: Warning 3
warning_4: Warning 4 warning_4: Warning 4
errorMessageIncorrectPassword: The password you entered is incorrect.

5
static/i18n/fr/settings.yml

@ -36,6 +36,11 @@ profile:
passwordModalNewPasswordInput: Nouveau mot de passe passwordModalNewPasswordInput: Nouveau mot de passe
passwordModalRepeatPasswordInput: Répéter mot de passe passwordModalRepeatPasswordInput: Répéter mot de passe
passwordModalSave: Sauvegarder passwordModalSave: Sauvegarder
disablePasswordModalTitle: Disable Password
disablePasswordModalSubtitle: Type your password
disablePasswordModalDesc: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh diam.
disablePasswordModalInput: Type your password
disablePasswordModalSave: Save
sync: Synchroniser les comptes sync: Synchroniser les comptes
syncDesc: Lorem ipsum dolor sit amet syncDesc: Lorem ipsum dolor sit amet
export: Exporter le journal export: Exporter le journal

8
yarn.lock

@ -5018,6 +5018,14 @@ electron@1.8.4, electron@^1.8.2:
electron-download "^3.0.1" electron-download "^3.0.1"
extract-zip "^1.0.3" extract-zip "^1.0.3"
electron@^1.8.2:
version "1.8.6"
resolved "https://registry.yarnpkg.com/electron/-/electron-1.8.6.tgz#6d45e377b321a35b78a794b64e40b2cf8face6ae"
dependencies:
"@types/node" "^8.0.24"
electron-download "^3.0.1"
extract-zip "^1.0.3"
elegant-spinner@^1.0.1: elegant-spinner@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"

Loading…
Cancel
Save