Meriadec Pillet
7 years ago
committed by
GitHub
52 changed files with 1589 additions and 799 deletions
@ -0,0 +1,60 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
import { translate } from 'react-i18next' |
|||
import { getIconByCoinType } from '@ledgerhq/currencies/react' |
|||
import { listCurrencies } from '@ledgerhq/currencies' |
|||
|
|||
import noop from 'lodash/noop' |
|||
|
|||
import type { Currency } from '@ledgerhq/currencies' |
|||
import type { T } from 'types/common' |
|||
|
|||
import Select from 'components/base/Select' |
|||
import Box from 'components/base/Box' |
|||
|
|||
const renderItem = a => { |
|||
const { color, name, coinType } = a |
|||
const Icon = getIconByCoinType(coinType) |
|||
return ( |
|||
<Box grow horizontal alignItems="center" flow={2}> |
|||
{Icon && ( |
|||
<Box style={{ width: 16, height: 16, color }}> |
|||
<Icon size={16} /> |
|||
</Box> |
|||
)} |
|||
<Box grow ff="Open Sans|SemiBold" color="dark" fontSize={4}> |
|||
{name} |
|||
</Box> |
|||
</Box> |
|||
) |
|||
} |
|||
|
|||
const currencies = listCurrencies() |
|||
|
|||
type Props = { |
|||
onChange: Function, |
|||
value?: Currency, |
|||
t: T, |
|||
} |
|||
|
|||
const SelectCurrency = ({ onChange, value, t, ...props }: Props) => ( |
|||
<Select |
|||
{...props} |
|||
value={value} |
|||
renderSelected={renderItem} |
|||
renderItem={renderItem} |
|||
keyProp="coinType" |
|||
items={currencies.sort((a, b) => (a.name < b.name ? -1 : 1))} |
|||
placeholder={t('common:selectCurrency')} |
|||
fontSize={4} |
|||
onChange={onChange} |
|||
/> |
|||
) |
|||
|
|||
SelectCurrency.defaultProps = { |
|||
onChange: noop, |
|||
value: undefined, |
|||
} |
|||
|
|||
export default translate()(SelectCurrency) |
@ -0,0 +1,31 @@ |
|||
// @flow
|
|||
|
|||
import React, { Component } from 'react' |
|||
import { storiesOf } from '@storybook/react' |
|||
import { action } from '@storybook/addon-actions' |
|||
|
|||
import SelectCurrency from 'components/SelectCurrency' |
|||
|
|||
const stories = storiesOf('Components', module) |
|||
|
|||
class Wrapper extends Component<any, any> { |
|||
state = { |
|||
value: '', |
|||
} |
|||
|
|||
handleChange = item => { |
|||
this.setState({ value: item }) |
|||
action('onChange')(item) |
|||
} |
|||
|
|||
render() { |
|||
const { render } = this.props |
|||
const { value } = this.state |
|||
|
|||
return render({ onChange: this.handleChange, value }) |
|||
} |
|||
} |
|||
|
|||
stories.add('SelectCurrency', () => ( |
|||
<Wrapper render={({ onChange, value }) => <SelectCurrency onChange={onChange} value={value} />} /> |
|||
)) |
@ -1,98 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
|
|||
import type { SettingsDisplay, T } from 'types/common' |
|||
|
|||
import Box, { Card } from 'components/base/Box' |
|||
import Button from 'components/base/Button' |
|||
import Label from 'components/base/Label' |
|||
import Select from 'components/base/Select' |
|||
|
|||
type InputValue = SettingsDisplay |
|||
|
|||
type Props = { |
|||
t: T, |
|||
settings: SettingsDisplay, |
|||
onSaveSettings: Function, |
|||
} |
|||
|
|||
type State = { |
|||
inputValue: InputValue, |
|||
} |
|||
|
|||
class TabProfile extends PureComponent<Props, State> { |
|||
state = { |
|||
inputValue: { |
|||
language: this.props.settings.language, |
|||
}, |
|||
} |
|||
|
|||
getDatas() { |
|||
const { t } = this.props |
|||
|
|||
return { |
|||
languages: [ |
|||
{ |
|||
key: 'en', |
|||
name: t('language:en'), |
|||
}, |
|||
{ |
|||
key: 'fr', |
|||
name: t('language:fr'), |
|||
}, |
|||
], |
|||
} |
|||
} |
|||
|
|||
handleChangeInput = (key: $Keys<InputValue>) => (value: $Values<InputValue>) => |
|||
this.setState(prev => ({ |
|||
inputValue: { |
|||
...prev.inputValue, |
|||
[key]: value, |
|||
}, |
|||
})) |
|||
|
|||
handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => { |
|||
e.preventDefault() |
|||
|
|||
const { onSaveSettings } = this.props |
|||
const { inputValue } = this.state |
|||
|
|||
onSaveSettings({ |
|||
...inputValue, |
|||
}) |
|||
} |
|||
|
|||
render() { |
|||
const { t } = this.props |
|||
const { inputValue } = this.state |
|||
|
|||
const { languages } = this.getDatas() |
|||
|
|||
const currentLanguage = languages.find(l => l.key === inputValue.language) |
|||
|
|||
return ( |
|||
<form onSubmit={this.handleSubmit}> |
|||
<Card flow={3}> |
|||
<Box flow={1}> |
|||
<Label>{t('settings:display.language')}</Label> |
|||
<Select |
|||
onChange={item => this.handleChangeInput('language')(item.key)} |
|||
renderSelected={item => item && item.name} |
|||
value={currentLanguage} |
|||
items={languages} |
|||
/> |
|||
</Box> |
|||
<Box horizontal justifyContent="flex-end"> |
|||
<Button primary type="submit"> |
|||
{t('common:save')} |
|||
</Button> |
|||
</Box> |
|||
</Card> |
|||
</form> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default TabProfile |
@ -1,88 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import { getFiatUnit } from '@ledgerhq/currencies' |
|||
|
|||
import type { SettingsMoney, T } from 'types/common' |
|||
|
|||
import Box, { Card } from 'components/base/Box' |
|||
import Button from 'components/base/Button' |
|||
import Label from 'components/base/Label' |
|||
import Select from 'components/base/Select' |
|||
|
|||
const counterValues = ['USD', 'EUR', 'JPY', 'GBP'].sort().map(c => { |
|||
const { name } = getFiatUnit(c) |
|||
return { |
|||
key: c, |
|||
name, |
|||
} |
|||
}) |
|||
|
|||
type InputValue = SettingsMoney |
|||
|
|||
type Props = { |
|||
t: T, |
|||
settings: SettingsMoney, |
|||
onSaveSettings: Function, |
|||
} |
|||
|
|||
type State = { |
|||
inputValue: InputValue, |
|||
} |
|||
|
|||
class TabProfile extends PureComponent<Props, State> { |
|||
state = { |
|||
inputValue: { |
|||
counterValue: this.props.settings.counterValue, |
|||
}, |
|||
} |
|||
|
|||
handleChangeInput = (key: $Keys<InputValue>) => (value: $Values<InputValue>) => |
|||
this.setState(prev => ({ |
|||
inputValue: { |
|||
...prev.inputValue, |
|||
[key]: value, |
|||
}, |
|||
})) |
|||
|
|||
handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => { |
|||
e.preventDefault() |
|||
|
|||
const { onSaveSettings } = this.props |
|||
const { inputValue } = this.state |
|||
|
|||
onSaveSettings({ |
|||
...inputValue, |
|||
}) |
|||
} |
|||
|
|||
render() { |
|||
const { t } = this.props |
|||
const { inputValue } = this.state |
|||
|
|||
const currentCounterValues = counterValues.find(l => l.key === inputValue.counterValue) |
|||
|
|||
return ( |
|||
<form onSubmit={this.handleSubmit}> |
|||
<Card flow={3}> |
|||
<Box flow={1}> |
|||
<Label>{t('settings:display.counterValue')}</Label> |
|||
<Select |
|||
onChange={item => this.handleChangeInput('counterValue')(item.key)} |
|||
renderSelected={item => item && item.name} |
|||
value={currentCounterValues} |
|||
items={counterValues} |
|||
/> |
|||
</Box> |
|||
<Box horizontal justifyContent="flex-end"> |
|||
<Button primary type="submit"> |
|||
{t('common:save')} |
|||
</Button> |
|||
</Box> |
|||
</Card> |
|||
</form> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default TabProfile |
@ -0,0 +1,137 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import { connect } from 'react-redux' |
|||
import bcrypt from 'bcryptjs' |
|||
|
|||
import { unlock } from 'reducers/application' |
|||
|
|||
import Box from 'components/base/Box' |
|||
import Button from 'components/base/Button' |
|||
import InputPassword from 'components/base/InputPassword' |
|||
import Label from 'components/base/Label' |
|||
import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'components/base/Modal' |
|||
|
|||
import type { T } from 'types/common' |
|||
|
|||
const mapDispatchToProps = { |
|||
unlock, |
|||
} |
|||
|
|||
type Props = { |
|||
t: T, |
|||
onClose: Function, |
|||
unlock: Function, |
|||
isPasswordEnabled: boolean, |
|||
currentPasswordHash: string, |
|||
onChangePassword: Function, |
|||
} |
|||
|
|||
type State = { |
|||
currentPassword: string, |
|||
newPassword: string, |
|||
} |
|||
|
|||
const INITIAL_STATE = { |
|||
currentPassword: '', |
|||
newPassword: '', |
|||
} |
|||
|
|||
class PasswordModal extends PureComponent<Props, State> { |
|||
state = INITIAL_STATE |
|||
|
|||
handleSave = (e: SyntheticEvent<HTMLFormElement>) => { |
|||
if (e) { |
|||
e.preventDefault() |
|||
} |
|||
if (!this.isValid()) { |
|||
return |
|||
} |
|||
const { currentPassword, newPassword } = this.state |
|||
const { isPasswordEnabled, currentPasswordHash, onChangePassword } = this.props |
|||
if (isPasswordEnabled) { |
|||
if (!bcrypt.compareSync(currentPassword, currentPasswordHash)) { |
|||
return |
|||
} |
|||
onChangePassword(newPassword) |
|||
} else { |
|||
onChangePassword(newPassword) |
|||
} |
|||
} |
|||
|
|||
handleInputChange = key => value => this.setState({ [key]: value }) |
|||
|
|||
handleReset = () => this.setState(INITIAL_STATE) |
|||
|
|||
isValid = () => { |
|||
const { newPassword } = this.state |
|||
return newPassword |
|||
} |
|||
|
|||
render() { |
|||
const { t, isPasswordEnabled, onClose, ...props } = this.props |
|||
const { currentPassword, newPassword } = this.state |
|||
const isValid = this.isValid() |
|||
return ( |
|||
<Modal |
|||
{...props} |
|||
onHide={this.handleReset} |
|||
onClose={onClose} |
|||
render={({ onClose }) => ( |
|||
<form onSubmit={this.handleSave}> |
|||
<ModalBody onClose={onClose}> |
|||
<ModalTitle>{t('settings:profile.passwordModalTitle')}</ModalTitle> |
|||
<ModalContent> |
|||
<Box ff="Museo Sans|Regular" color="dark" textAlign="center" mb={2} mt={3}> |
|||
{t('settings:profile.passwordModalSubtitle')} |
|||
</Box> |
|||
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" px={4}> |
|||
{t('settings:profile.passwordModalDesc')} |
|||
<Box px={7} mt={4} flow={3}> |
|||
{isPasswordEnabled && ( |
|||
<Box flow={1}> |
|||
<Label htmlFor="password"> |
|||
{t('settings:profile.passwordModalPasswordInput')} |
|||
</Label> |
|||
<InputPassword |
|||
type="password" |
|||
placeholder={t('settings:profile.passwordModalPasswordInput')} |
|||
autoFocus |
|||
id="password" |
|||
onChange={this.handleInputChange('currentPassword')} |
|||
value={currentPassword} |
|||
/> |
|||
</Box> |
|||
)} |
|||
<Box flow={1}> |
|||
{isPasswordEnabled && ( |
|||
<Label htmlFor="newPassword"> |
|||
{t('settings:profile.passwordModalNewPasswordInput')} |
|||
</Label> |
|||
)} |
|||
<InputPassword |
|||
placeholder={t('settings:profile.passwordModalNewPasswordInput')} |
|||
id="newPassword" |
|||
onChange={this.handleInputChange('newPassword')} |
|||
value={newPassword} |
|||
withStrength |
|||
/> |
|||
</Box> |
|||
</Box> |
|||
</Box> |
|||
</ModalContent> |
|||
<ModalFooter horizontal align="center" justify="flex-end" flow={2}> |
|||
<Button onClick={onClose}>{t('common:cancel')}</Button> |
|||
<Button primary onClick={this.handleSave} disabled={!isValid}> |
|||
{t('settings:profile.passwordModalSave')} |
|||
</Button> |
|||
</ModalFooter> |
|||
</ModalBody> |
|||
</form> |
|||
)} |
|||
/> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default connect(null, mapDispatchToProps)(PasswordModal) |
@ -1,124 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import { connect } from 'react-redux' |
|||
import bcrypt from 'bcryptjs' |
|||
|
|||
import get from 'lodash/get' |
|||
import set from 'lodash/set' |
|||
|
|||
import { setEncryptionKey } from 'helpers/db' |
|||
|
|||
import type { SettingsProfile, T } from 'types/common' |
|||
|
|||
import { unlock } from 'reducers/application' |
|||
|
|||
import Box, { Card } from 'components/base/Box' |
|||
import Input from 'components/base/Input' |
|||
import CheckBox from 'components/base/CheckBox' |
|||
import Button from 'components/base/Button' |
|||
import Label from 'components/base/Label' |
|||
|
|||
type InputValue = SettingsProfile |
|||
|
|||
type Props = { |
|||
t: T, |
|||
settings: SettingsProfile, |
|||
onSaveSettings: Function, |
|||
unlock: Function, |
|||
} |
|||
type State = { |
|||
inputValue: InputValue, |
|||
} |
|||
|
|||
const mapDispatchToProps = { |
|||
unlock, |
|||
} |
|||
|
|||
class TabProfile extends PureComponent<Props, State> { |
|||
state = { |
|||
inputValue: { |
|||
password: { |
|||
...this.props.settings.password, |
|||
value: undefined, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
handleChangeInput = (key: string) => (value: $Values<InputValue>) => |
|||
this.setState(prev => ({ |
|||
inputValue: { |
|||
...set(prev.inputValue, key, value), |
|||
}, |
|||
})) |
|||
|
|||
handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => { |
|||
e.preventDefault() |
|||
|
|||
const { onSaveSettings, unlock } = this.props |
|||
const { inputValue } = this.state |
|||
|
|||
const settings = { |
|||
...inputValue, |
|||
password: { |
|||
...inputValue.password, |
|||
value: '', |
|||
}, |
|||
} |
|||
|
|||
const password = get(inputValue, 'password', {}) |
|||
|
|||
if (password.state === true && password.value.trim() !== '') { |
|||
settings.password.value = bcrypt.hashSync(password.value, 8) |
|||
setEncryptionKey('accounts', password.value) |
|||
} else { |
|||
setEncryptionKey('accounts', undefined) |
|||
} |
|||
|
|||
unlock() |
|||
|
|||
onSaveSettings(settings) |
|||
} |
|||
|
|||
render() { |
|||
const { t } = this.props |
|||
const { inputValue } = this.state |
|||
|
|||
const isPasswordChecked = get(inputValue, 'password.state', false) |
|||
return ( |
|||
<form onSubmit={this.handleSubmit}> |
|||
<Card flow={3}> |
|||
<label> |
|||
<Box |
|||
horizontal |
|||
alignItems="center" |
|||
flow={2} |
|||
style={{ cursor: 'pointer' }} |
|||
onClick={() => this.handleChangeInput('password.state')(!isPasswordChecked)} |
|||
> |
|||
<CheckBox isChecked={isPasswordChecked} /> |
|||
<div>{t('settings:profile.protectWithPassword')}</div> |
|||
</Box> |
|||
</label> |
|||
{get(inputValue, 'password.state') === true && ( |
|||
<Box flow={1}> |
|||
<Label>{t('settings:profile.password')}</Label> |
|||
<Input |
|||
value={get(inputValue, 'password.value', '')} |
|||
onChange={this.handleChangeInput('password.value')} |
|||
type="password" |
|||
/> |
|||
</Box> |
|||
)} |
|||
<Box horizontal justifyContent="flex-end"> |
|||
<Button primary type="submit"> |
|||
{t('common:save')} |
|||
</Button> |
|||
</Box> |
|||
</Card> |
|||
</form> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default connect(null, mapDispatchToProps)(TabProfile) |
@ -0,0 +1,120 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
import styled from 'styled-components' |
|||
|
|||
import { rgba } from 'styles/helpers' |
|||
|
|||
import Box, { Card } from 'components/base/Box' |
|||
|
|||
export const SettingsSection = styled(Card).attrs({ p: 0 })`` |
|||
|
|||
const SettingsSectionHeaderContainer = styled(Box).attrs({ |
|||
p: 4, |
|||
horizontal: true, |
|||
align: 'center', |
|||
})` |
|||
border-bottom: 1px solid ${p => p.theme.colors.lightFog}; |
|||
line-height: normal; |
|||
` |
|||
|
|||
const RoundIconContainer = styled(Box).attrs({ |
|||
align: 'center', |
|||
justify: 'center', |
|||
bg: p => rgba(p.theme.colors.wallet, 0.2), |
|||
color: 'wallet', |
|||
})` |
|||
height: 30px; |
|||
width: 30px; |
|||
border-radius: 50%; |
|||
` |
|||
|
|||
export const SettingsSectionBody = styled(Box)` |
|||
> * + * { |
|||
&:after { |
|||
background: ${p => p.theme.colors.lightFog}; |
|||
content: ''; |
|||
display: block; |
|||
height: 1px; |
|||
left: ${p => p.theme.space[4]}px; |
|||
position: absolute; |
|||
right: ${p => p.theme.space[4]}px; |
|||
top: 0; |
|||
} |
|||
} |
|||
` |
|||
|
|||
export function SettingsSectionHeader({ |
|||
title, |
|||
desc, |
|||
icon, |
|||
renderRight, |
|||
}: { |
|||
title: string, |
|||
desc: string, |
|||
icon: any, |
|||
renderRight?: any, |
|||
}) { |
|||
return ( |
|||
<SettingsSectionHeaderContainer> |
|||
<RoundIconContainer mr={3}>{icon}</RoundIconContainer> |
|||
<Box grow> |
|||
<Box ff="Museo Sans|Regular" color="dark"> |
|||
{title} |
|||
</Box> |
|||
<Box ff="Open Sans" fontSize={3}> |
|||
{desc} |
|||
</Box> |
|||
</Box> |
|||
{renderRight && ( |
|||
<Box alignItems="center" justifyContent="flex-end"> |
|||
{renderRight} |
|||
</Box> |
|||
)} |
|||
</SettingsSectionHeaderContainer> |
|||
) |
|||
} |
|||
|
|||
SettingsSectionHeader.defaultProps = { |
|||
renderRight: undefined, |
|||
} |
|||
|
|||
const SettingsSectionRowContainer = styled(Box).attrs({ |
|||
p: 4, |
|||
horizontal: true, |
|||
align: 'center', |
|||
relative: true, |
|||
})` |
|||
cursor: ${p => (p.onClick ? 'pointer' : '')}; |
|||
` |
|||
|
|||
export function SettingsSectionRow({ |
|||
title, |
|||
desc, |
|||
children, |
|||
onClick, |
|||
}: { |
|||
title: string, |
|||
desc: string, |
|||
children?: any, |
|||
onClick?: ?Function, |
|||
}) { |
|||
return ( |
|||
<SettingsSectionRowContainer onClick={onClick}> |
|||
<Box grow> |
|||
<Box ff="Open Sans|SemiBold" color="dark" fontSize={4}> |
|||
{title} |
|||
</Box> |
|||
<Box ff="Open Sans" fontSize={3} color="grey"> |
|||
{desc} |
|||
</Box> |
|||
</Box> |
|||
<Box>{children}</Box> |
|||
</SettingsSectionRowContainer> |
|||
) |
|||
} |
|||
|
|||
SettingsSectionRow.defaultProps = { |
|||
children: null, |
|||
onClick: null, |
|||
} |
@ -0,0 +1,62 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import { shell } from 'electron' |
|||
|
|||
import type { T } from 'types/common' |
|||
|
|||
import IconHelp from 'icons/Help' |
|||
import IconChevronRight from 'icons/ChevronRight' |
|||
|
|||
import { |
|||
SettingsSection as Section, |
|||
SettingsSectionHeader as Header, |
|||
SettingsSectionBody as Body, |
|||
SettingsSectionRow as Row, |
|||
} from '../SettingsSection' |
|||
|
|||
type Props = { |
|||
t: T, |
|||
} |
|||
|
|||
class SectionAbout extends PureComponent<Props> { |
|||
handleOpenLink = (url: string) => () => shell.openExternal(url) |
|||
|
|||
render() { |
|||
const { t } = this.props |
|||
return ( |
|||
<Section> |
|||
<Header |
|||
icon={<IconHelp size={16} />} |
|||
title={t('settings:tabs.about')} |
|||
desc="Lorem ipsum dolor sit amet" |
|||
/> |
|||
<Body> |
|||
<Row |
|||
onClick={this.handleOpenLink('http://google.com')} |
|||
title={t('settings:about.faq')} |
|||
desc={t('settings:about.faqDesc')} |
|||
> |
|||
<IconChevronRight size={16} /> |
|||
</Row> |
|||
<Row |
|||
onClick={this.handleOpenLink('http://google.com')} |
|||
title={t('settings:about.contactUs')} |
|||
desc={t('settings:about.contactUsDesc')} |
|||
> |
|||
<IconChevronRight size={16} /> |
|||
</Row> |
|||
<Row |
|||
onClick={this.handleOpenLink('http://google.com')} |
|||
title={t('settings:about.terms')} |
|||
desc={t('settings:about.termsDesc')} |
|||
> |
|||
<IconChevronRight size={16} /> |
|||
</Row> |
|||
</Body> |
|||
</Section> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default SectionAbout |
@ -0,0 +1,72 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
|
|||
import { listCurrencies } from '@ledgerhq/currencies' |
|||
|
|||
import type { Currency } from '@ledgerhq/currencies' |
|||
import type { T } from 'types/common' |
|||
|
|||
import SelectCurrency from 'components/SelectCurrency' |
|||
|
|||
import IconCurrencies from 'icons/Currencies' |
|||
|
|||
import { |
|||
SettingsSection as Section, |
|||
SettingsSectionHeader as Header, |
|||
SettingsSectionBody as Body, |
|||
SettingsSectionRow as Row, |
|||
} from '../SettingsSection' |
|||
|
|||
type Props = { |
|||
t: T, |
|||
} |
|||
|
|||
type State = { |
|||
currency: Currency, |
|||
} |
|||
|
|||
class TabCurrencies extends PureComponent<Props, State> { |
|||
state = { |
|||
currency: listCurrencies()[0], |
|||
} |
|||
|
|||
handleChangeCurrency = (currency: Currency) => this.setState({ currency }) |
|||
|
|||
render() { |
|||
const { t } = this.props |
|||
const { currency } = this.state |
|||
return ( |
|||
<Section> |
|||
<Header |
|||
icon={<IconCurrencies size={16} />} |
|||
title={t('settings:tabs.currencies')} |
|||
desc="Lorem ipsum dolor sit amet" |
|||
renderRight={ |
|||
<SelectCurrency small value={currency} onChange={this.handleChangeCurrency} /> |
|||
} |
|||
/> |
|||
<Body> |
|||
<Row |
|||
title={t('settings:currencies.confirmationsToSpend')} |
|||
desc={t('settings:currencies.confirmationsToSpendDesc')} |
|||
/> |
|||
<Row |
|||
title={t('settings:currencies.confirmationsNb')} |
|||
desc={t('settings:currencies.confirmationsNbDesc')} |
|||
/> |
|||
<Row |
|||
title={t('settings:currencies.transactionsFees')} |
|||
desc={t('settings:currencies.transactionsFeesDesc')} |
|||
/> |
|||
<Row |
|||
title={t('settings:currencies.explorer')} |
|||
desc={t('settings:currencies.explorerDesc')} |
|||
/> |
|||
</Body> |
|||
</Section> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default TabCurrencies |
@ -0,0 +1,120 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import moment from 'moment' |
|||
import { listFiats } from '@ledgerhq/currencies' |
|||
|
|||
import type { Settings, T } from 'types/common' |
|||
|
|||
import Select from 'components/base/Select' |
|||
import IconDisplay from 'icons/Display' |
|||
|
|||
import { |
|||
SettingsSection as Section, |
|||
SettingsSectionHeader as Header, |
|||
SettingsSectionBody as Body, |
|||
SettingsSectionRow as Row, |
|||
} from '../SettingsSection' |
|||
|
|||
const fiats = listFiats().map(fiat => ({ |
|||
key: fiat.code, |
|||
fiat, |
|||
name: `${fiat.name} - ${fiat.code}${fiat.symbol ? ` (${fiat.symbol})` : ''}`, |
|||
})) |
|||
|
|||
type Props = { |
|||
t: T, |
|||
settings: Settings, |
|||
saveSettings: Function, |
|||
i18n: Object, |
|||
} |
|||
|
|||
type State = { |
|||
cachedLanguageKey: string, |
|||
cachedCounterValue: ?Object, |
|||
} |
|||
|
|||
class TabProfile extends PureComponent<Props, State> { |
|||
state = { |
|||
cachedLanguageKey: this.props.settings.language, |
|||
cachedCounterValue: fiats.find(fiat => fiat.fiat.code === this.props.settings.counterValue), |
|||
} |
|||
|
|||
getDatas() { |
|||
const { t } = this.props |
|||
return { |
|||
languages: [{ key: 'en', name: t('language:en') }, { key: 'fr', name: t('language:fr') }], |
|||
} |
|||
} |
|||
|
|||
handleChangeCounterValue = (item: Object) => { |
|||
const { saveSettings } = this.props |
|||
this.setState({ cachedCounterValue: item.fiat }) |
|||
window.requestIdleCallback(() => { |
|||
saveSettings({ counterValue: item.fiat.code }) |
|||
}) |
|||
} |
|||
|
|||
handleChangeLanguage = (languageKey: string) => { |
|||
const { i18n, saveSettings } = this.props |
|||
this.setState({ cachedLanguageKey: languageKey }) |
|||
window.requestIdleCallback(() => { |
|||
i18n.changeLanguage(languageKey) |
|||
moment.locale(languageKey) |
|||
saveSettings({ language: languageKey }) |
|||
}) |
|||
} |
|||
|
|||
render() { |
|||
const { t } = this.props |
|||
const { cachedLanguageKey, cachedCounterValue } = this.state |
|||
const { languages } = this.getDatas() |
|||
const currentLanguage = languages.find(l => l.key === cachedLanguageKey) |
|||
|
|||
return ( |
|||
<Section> |
|||
<Header |
|||
icon={<IconDisplay size={16} />} |
|||
title={t('settings:tabs.display')} |
|||
desc="Lorem ipsum dolor sit amet" |
|||
/> |
|||
<Body> |
|||
<Row |
|||
title={t('settings:display.counterValue')} |
|||
desc={t('settings:display.counterValueDesc')} |
|||
> |
|||
<Select |
|||
searchable |
|||
fuseOptions={{ keys: ['name'] }} |
|||
style={{ minWidth: 250 }} |
|||
small |
|||
onChange={item => this.handleChangeCounterValue(item)} |
|||
itemToString={item => (item ? item.name : '')} |
|||
renderSelected={item => item && item.name} |
|||
items={fiats} |
|||
value={cachedCounterValue} |
|||
/> |
|||
</Row> |
|||
<Row title={t('settings:display.language')} desc={t('settings:display.languageDesc')}> |
|||
<Select |
|||
style={{ minWidth: 130 }} |
|||
small |
|||
onChange={item => this.handleChangeLanguage(item.key)} |
|||
renderSelected={item => item && item.name} |
|||
value={currentLanguage} |
|||
items={languages} |
|||
/> |
|||
</Row> |
|||
<Row title={t('settings:display.region')} desc={t('settings:display.regionDesc')}> |
|||
{'-'} |
|||
</Row> |
|||
<Row title={t('settings:display.stock')} desc={t('settings:display.stockDesc')}> |
|||
{'-'} |
|||
</Row> |
|||
</Body> |
|||
</Section> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default TabProfile |
@ -0,0 +1,172 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import { connect } from 'react-redux' |
|||
import { remote } from 'electron' |
|||
import bcrypt from 'bcryptjs' |
|||
|
|||
import type { Settings, T } from 'types/common' |
|||
|
|||
import debounce from 'lodash/debounce' |
|||
|
|||
import { unlock } from 'reducers/application' |
|||
import db, { setEncryptionKey } from 'helpers/db' |
|||
|
|||
import Input from 'components/base/Input' |
|||
import CheckBox from 'components/base/CheckBox' |
|||
import Box from 'components/base/Box' |
|||
import Button from 'components/base/Button' |
|||
import { ConfirmModal } from 'components/base/Modal' |
|||
import IconUser from 'icons/User' |
|||
import PasswordModal from '../PasswordModal' |
|||
|
|||
import { |
|||
SettingsSection as Section, |
|||
SettingsSectionHeader as Header, |
|||
SettingsSectionBody as Body, |
|||
SettingsSectionRow as Row, |
|||
} from '../SettingsSection' |
|||
|
|||
const mapDispatchToProps = { |
|||
unlock, |
|||
} |
|||
|
|||
type Props = { |
|||
t: T, |
|||
settings: Settings, |
|||
unlock: Function, |
|||
saveSettings: Function, |
|||
} |
|||
|
|||
type State = { |
|||
isHardResetModalOpened: boolean, |
|||
isPasswordModalOpened: boolean, |
|||
username: string, |
|||
} |
|||
|
|||
class TabProfile extends PureComponent<Props, State> { |
|||
state = { |
|||
username: this.props.settings.username, |
|||
isHardResetModalOpened: false, |
|||
isPasswordModalOpened: false, |
|||
} |
|||
|
|||
setPassword = password => { |
|||
const { saveSettings, unlock } = this.props |
|||
window.requestIdleCallback(() => { |
|||
setEncryptionKey('accounts', password) |
|||
const hash = password ? bcrypt.hashSync(password, 8) : undefined |
|||
saveSettings({ |
|||
password: { |
|||
isEnabled: hash !== undefined, |
|||
value: hash, |
|||
}, |
|||
}) |
|||
unlock() |
|||
}) |
|||
} |
|||
|
|||
debounceSaveUsername = debounce( |
|||
v => this.props.saveSettings({ username: v.trim() || 'Anonymous' }), |
|||
250, |
|||
) |
|||
|
|||
handleChangeUsername = username => { |
|||
this.setState({ username }) |
|||
this.debounceSaveUsername(username) |
|||
} |
|||
|
|||
handleOpenHardResetModal = () => this.setState({ isHardResetModalOpened: true }) |
|||
handleCloseHardResetModal = () => this.setState({ isHardResetModalOpened: false }) |
|||
handleOpenPasswordModal = () => this.setState({ isPasswordModalOpened: true }) |
|||
handleClosePasswordModal = () => this.setState({ isPasswordModalOpened: false }) |
|||
|
|||
handleHardReset = () => { |
|||
db.resetAll() |
|||
remote.app.relaunch() |
|||
remote.app.exit() |
|||
} |
|||
|
|||
handleChangePasswordCheck = isChecked => { |
|||
if (isChecked) { |
|||
this.handleOpenPasswordModal() |
|||
} else { |
|||
this.setPassword(undefined) |
|||
} |
|||
} |
|||
|
|||
handleChangePassword = (password: ?string) => { |
|||
if (password) { |
|||
this.setPassword(password) |
|||
this.handleClosePasswordModal() |
|||
} |
|||
} |
|||
|
|||
render() { |
|||
const { t, settings } = this.props |
|||
const { username, isHardResetModalOpened, isPasswordModalOpened } = this.state |
|||
const isPasswordEnabled = settings.password.isEnabled === true |
|||
return ( |
|||
<Section> |
|||
<Header |
|||
icon={<IconUser size={16} />} |
|||
title={t('settings:tabs.profile')} |
|||
desc="Lorem ipsum dolor sit amet" |
|||
/> |
|||
<Body> |
|||
<Row title={t('settings:profile.username')} desc={t('settings:profile.usernameDesc')}> |
|||
<Input |
|||
small |
|||
placeholder={t('settings:profile.username')} |
|||
onChange={this.handleChangeUsername} |
|||
value={username} |
|||
/> |
|||
</Row> |
|||
<Row title={t('settings:profile.password')} desc={t('settings:profile.passwordDesc')}> |
|||
<Box horizontal flow={2} align="center"> |
|||
{isPasswordEnabled && ( |
|||
<Button onClick={this.handleOpenPasswordModal}> |
|||
{t('settings:profile.changePassword')} |
|||
</Button> |
|||
)} |
|||
<CheckBox isChecked={isPasswordEnabled} onChange={this.handleChangePasswordCheck} /> |
|||
</Box> |
|||
</Row> |
|||
<Row title={t('settings:profile.sync')} desc={t('settings:profile.syncDesc')}> |
|||
<Button primary>{t('settings:profile.sync')}</Button> |
|||
</Row> |
|||
<Row title={t('settings:profile.export')} desc={t('settings:profile.exportDesc')}> |
|||
<Button primary>{t('settings:profile.export')}</Button> |
|||
</Row> |
|||
<Row title={t('settings:profile.reset')} desc={t('settings:profile.resetDesc')}> |
|||
<Button danger onClick={this.handleOpenHardResetModal}> |
|||
{t('settings:profile.resetButton')} |
|||
</Button> |
|||
</Row> |
|||
</Body> |
|||
|
|||
<ConfirmModal |
|||
isDanger |
|||
isOpened={isHardResetModalOpened} |
|||
onClose={this.handleCloseHardResetModal} |
|||
onReject={this.handleCloseHardResetModal} |
|||
onConfirm={this.handleHardReset} |
|||
title={t('settings:hardResetModal.title')} |
|||
subTitle={t('settings:hardResetModal.subTitle')} |
|||
desc={t('settings:hardResetModal.desc')} |
|||
/> |
|||
|
|||
<PasswordModal |
|||
t={t} |
|||
isOpened={isPasswordModalOpened} |
|||
onClose={this.handleClosePasswordModal} |
|||
onChangePassword={this.handleChangePassword} |
|||
isPasswordEnabled={isPasswordEnabled} |
|||
currentPasswordHash={settings.password.value} |
|||
/> |
|||
</Section> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default connect(null, mapDispatchToProps)(TabProfile) |
@ -0,0 +1,74 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import { translate } from 'react-i18next' |
|||
|
|||
import type { T } from 'types/common' |
|||
|
|||
import Button from 'components/base/Button' |
|||
import Box from 'components/base/Box' |
|||
|
|||
import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from './index' |
|||
|
|||
type Props = { |
|||
isOpened: boolean, |
|||
isDanger: boolean, |
|||
title: string, |
|||
subTitle: string, |
|||
desc: string, |
|||
confirmText: string, |
|||
cancelText: string, |
|||
onReject: Function, |
|||
onConfirm: Function, |
|||
t: T, |
|||
} |
|||
|
|||
class ConfirmModal extends PureComponent<Props> { |
|||
render() { |
|||
const { |
|||
isOpened, |
|||
title, |
|||
subTitle, |
|||
desc, |
|||
confirmText, |
|||
cancelText, |
|||
isDanger, |
|||
onReject, |
|||
onConfirm, |
|||
t, |
|||
...props |
|||
} = this.props |
|||
|
|||
const realConfirmText = confirmText || t('common:confirm') |
|||
const realCancelText = cancelText || t('common:cancel') |
|||
return ( |
|||
<Modal |
|||
isOpened={isOpened} |
|||
{...props} |
|||
render={({ onClose }) => ( |
|||
<ModalBody onClose={onClose}> |
|||
<ModalTitle>{title}</ModalTitle> |
|||
<ModalContent> |
|||
{subTitle && ( |
|||
<Box ff="Museo Sans|Regular" color="dark" textAlign="center" mb={2} mt={3}> |
|||
{subTitle} |
|||
</Box> |
|||
)} |
|||
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center"> |
|||
{desc} |
|||
</Box> |
|||
</ModalContent> |
|||
<ModalFooter horizontal align="center" justify="flex-end" flow={2}> |
|||
<Button onClick={onReject}>{realCancelText}</Button> |
|||
<Button onClick={onConfirm} primary={!isDanger} danger={isDanger}> |
|||
{realConfirmText} |
|||
</Button> |
|||
</ModalFooter> |
|||
</ModalBody> |
|||
)} |
|||
/> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default translate()(ConfirmModal) |
@ -1,68 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import React, { Fragment } from 'react' |
|||
import styled from 'styled-components' |
|||
|
|||
import type { Element } from 'react' |
|||
|
|||
import Box, { Tabbable } from 'components/base/Box' |
|||
|
|||
const WrapperTab = styled(Box).attrs({ |
|||
horizontal: true, |
|||
})` |
|||
border-bottom: 1px solid ${p => p.theme.colors.fog}; |
|||
` |
|||
|
|||
const Tab = styled(Tabbable).attrs({ |
|||
flex: 1, |
|||
pb: 2, |
|||
alignItems: 'center', |
|||
justifyContent: 'center', |
|||
fontSize: 3, |
|||
})` |
|||
border-bottom: 2px solid transparent; |
|||
border-bottom-color: ${p => (p.isActive ? p.theme.colors.wallet : '')}; |
|||
color: ${p => |
|||
p.isActive |
|||
? p.theme.colors.wallet |
|||
: p.isDisabled |
|||
? p.theme.colors.grey |
|||
: p.theme.colors.graphite}; |
|||
margin-bottom: -1px; |
|||
outline: none; |
|||
cursor: ${p => (p.isActive ? 'default' : p.isDisabled ? 'not-allowed' : 'pointer')}; |
|||
max-width: 200px; |
|||
` |
|||
|
|||
type Item = { |
|||
key: string | number, |
|||
isDisabled?: boolean, |
|||
title: string | Element<any>, |
|||
render: () => Element<any>, |
|||
} |
|||
|
|||
type Props = { |
|||
items: Array<Item>, |
|||
index: number, |
|||
onTabClick: number => void, |
|||
} |
|||
|
|||
const Tabs = ({ items, index, onTabClick }: Props) => ( |
|||
<Fragment> |
|||
<WrapperTab> |
|||
{items.map((item, i) => ( |
|||
<Tab |
|||
key={item.key} |
|||
isDisabled={item.isDisabled} |
|||
isActive={index === i} |
|||
onClick={item.isDisabled ? void 0 : () => onTabClick(i)} |
|||
> |
|||
{item.title} |
|||
</Tab> |
|||
))} |
|||
</WrapperTab> |
|||
{items[index] && items[index].render()} |
|||
</Fragment> |
|||
) |
|||
|
|||
export default Tabs |
@ -1,31 +0,0 @@ |
|||
import React from 'react' |
|||
|
|||
import { number } from '@storybook/addon-knobs' |
|||
import { action } from '@storybook/addon-actions' |
|||
import { storiesOf } from '@storybook/react' |
|||
|
|||
import Tabs from 'components/base/Tabs' |
|||
|
|||
const stories = storiesOf('Components/base', module) |
|||
|
|||
stories.add('Tabs', () => ( |
|||
<Tabs |
|||
index={number('index', 0, { |
|||
min: 0, |
|||
max: 1, |
|||
})} |
|||
onTabClick={action('onTabClick')} |
|||
items={[ |
|||
{ |
|||
key: 'first', |
|||
title: 'first tab', |
|||
render: () => <div>{'first tab content'}</div>, |
|||
}, |
|||
{ |
|||
key: 'second', |
|||
title: 'second tab', |
|||
render: () => <div>{'second tab content'}</div>, |
|||
}, |
|||
]} |
|||
/> |
|||
)) |
@ -0,0 +1,12 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
|
|||
export default ({ size, ...p }: { size: number }) => ( |
|||
<svg viewBox="0 0 16 16" height={size} width={size} {...p}> |
|||
<path |
|||
fill="currentColor" |
|||
d="M8 5.581c-3.865 0-7-1.083-7-2.79C1 1.083 4.135 0 8 0s7 1.083 7 2.79c0 1.708-3.135 2.791-7 2.791zm-7-2.79c0-.309.241-.558.538-.558.298 0 .539.25.539.558v10.418c0 .28.516.704 1.517 1.05 1.142.396 2.714.625 4.406.625 1.692 0 3.264-.23 4.406-.625 1.001-.346 1.517-.77 1.517-1.05V2.791c0-.309.241-.558.539-.558.297 0 .538.25.538.558v10.418C15 14.921 11.88 16 8 16s-7-1.08-7-2.79V2.79zM13.923 8c0-.308.241-.558.539-.558.297 0 .538.25.538.558 0 1.711-3.12 2.79-7 2.79S1 9.712 1 8c0-.308.241-.558.538-.558.298 0 .539.25.539.558 0 .28.516.704 1.517 1.05 1.142.395 2.714.624 4.406.624 1.692 0 3.264-.229 4.406-.624 1.001-.346 1.517-.77 1.517-1.05zM8 4.465c1.682 0 3.254-.23 4.399-.625 1.004-.347 1.524-.772 1.524-1.05 0-.277-.52-.702-1.524-1.048-1.145-.396-2.717-.626-4.399-.626s-3.254.23-4.399.626c-1.004.346-1.524.771-1.524 1.049 0 .277.52.702 1.524 1.049 1.145.395 2.717.625 4.399.625z" |
|||
/> |
|||
</svg> |
|||
) |
@ -0,0 +1,12 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
|
|||
export default ({ size, ...p }: { size: number }) => ( |
|||
<svg viewBox="0 0 16 16" width={size} height={size} {...p}> |
|||
<path |
|||
fill="currentColor" |
|||
d="M3.2 2.26c-.552 0-1 .471-1 1.051v6.547c0 .58.448 1.05 1 1.05h9.6c.552 0 1-.47 1-1.05V3.31c0-.58-.448-1.05-1-1.05H3.2zm5.4 9.909v1.57h1.96c.331 0 .6.283.6.63 0 .349-.269.631-.6.631H5.44c-.331 0-.6-.282-.6-.63 0-.348.269-.63.6-.63H7.4v-1.571H3.2c-1.215 0-2.2-1.035-2.2-2.311V3.31C1 2.035 1.985 1 3.2 1h9.6C14.015 1 15 2.035 15 3.311v6.547c0 1.276-.985 2.311-2.2 2.311H8.6z" |
|||
/> |
|||
</svg> |
|||
) |
@ -0,0 +1,12 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
|
|||
export default ({ size, ...p }: { size: number }) => ( |
|||
<svg viewBox="0 0 16 16" height={size} width={size} {...p}> |
|||
<path |
|||
fill="currentColor" |
|||
d="M3.3 4.007a6.167 6.167 0 1 0 .707-.707l2.135 2.135a3.167 3.167 0 1 1-.707.707L3.3 4.007zM8 15.167A7.167 7.167 0 1 1 8 .833a7.167 7.167 0 0 1 0 14.334zm0-5a2.167 2.167 0 1 0 0-4.334 2.167 2.167 0 0 0 0 4.334zm1.387-4.054c0-.128.048-.256.146-.353l2.353-2.354a.5.5 0 0 1 .708.708L10.24 6.467a.5.5 0 0 1-.707-.707l2.827-2.827a.5.5 0 0 1 .707.707L10.24 6.467a.5.5 0 0 1-.853-.354zm.146 4.127a.5.5 0 0 1 .707-.707l2.827 2.827a.5.5 0 1 1-.707.707L9.533 10.24zM3.64 13.067a.5.5 0 1 1-.707-.707L5.76 9.533a.5.5 0 1 1 .707.707L3.64 13.067z" |
|||
/> |
|||
</svg> |
|||
) |
@ -1 +1,2 @@ |
|||
label: Current address |
|||
labelFrom: Address from <1><0>{{accountName}}</0></1> |
|||
|
@ -1,16 +1,55 @@ |
|||
title: Settings |
|||
tabs: |
|||
display: Display |
|||
money: Money |
|||
material: Material |
|||
app: App (beta) |
|||
tools: Tools |
|||
blockchain: Blockchain |
|||
currencies: Currencies |
|||
profile: Profile |
|||
about: About |
|||
display: |
|||
language: Language |
|||
counterValue: Counter Value |
|||
orderAccounts: Order accounts |
|||
language: Interface language |
|||
languageDesc: Lorem ipsum dolor sit amet |
|||
counterValue: Countervalue |
|||
counterValueDesc: Lorem ipsum dolor sit amet |
|||
region: Region |
|||
regionDesc: Lorem ipsum dolor sit amet |
|||
stock: Stock market indicators |
|||
stockDesc: Lorem ipsum dolor sit amet |
|||
currencies: |
|||
confirmationsToSpend: Confirmations to spend |
|||
confirmationsToSpendDesc: Lorem ipsum dolor sit amet |
|||
confirmationsNb: Number of confirmations |
|||
confirmationsNbDesc: Lorem ipsum dolor sit amet |
|||
transactionsFees: Transactions fees |
|||
transactionsFeesDesc: Lorem ipsum dolor sit amet |
|||
explorer: Blockchain explorer |
|||
explorerDesc: Lorem ipsum dolor sit amet |
|||
profile: |
|||
protectWithPassword: Protect local data with a password |
|||
username: Username |
|||
usernameDesc: Lorem ipsum dolor sit amet |
|||
password: Password |
|||
passwordDesc: Lorem ipsum dolor sit amet |
|||
changePassword: Change password |
|||
passwordModalTitle: Password |
|||
passwordModalSubtitle: Set a password to lock your application |
|||
passwordModalDesc: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh diam. |
|||
passwordModalPasswordInput: Current password |
|||
passwordModalNewPasswordInput: New password |
|||
passwordModalRepeatPasswordInput: Repeat password |
|||
passwordModalSave: Save |
|||
sync: Sync accounts |
|||
syncDesc: Lorem ipsum dolor sit amet |
|||
export: Export logs |
|||
exportDesc: Lorem ipsum dolor sit amet |
|||
reset: Reset application |
|||
resetDesc: Lorem ipsum dolor sit amet |
|||
resetButton: Hard reset |
|||
about: |
|||
faq: FAQ |
|||
faqDesc: Lorem ipsum dolor sit amet |
|||
contactUs: Contact us |
|||
contactUsDesc: Lorem ipsum dolor sit amet |
|||
terms: Terms and Privacy policy |
|||
termsDesc: Lorem ipsum dolor sit amet |
|||
hardResetModal: |
|||
title: Hard reset |
|||
subTitle: Are you sure houston? |
|||
desc: Lorem ipsum dolor sit amet |
|||
|
Loading…
Reference in new issue