Meriadec Pillet
7 years ago
committed by
GitHub
26 changed files with 1049 additions and 685 deletions
@ -0,0 +1,25 @@ |
|||||
|
// @flow
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import { shell } from 'electron' |
||||
|
import IconExternalLink from 'icons/ExternalLink' |
||||
|
import { Tabbable } from 'components/base/Box' |
||||
|
import { SettingsSectionRow } from './SettingsSection' |
||||
|
|
||||
|
export default class AboutRowItem extends PureComponent<{ |
||||
|
url: string, |
||||
|
title: string, |
||||
|
desc: string, |
||||
|
}> { |
||||
|
onClick = () => shell.openExternal(this.props.url) |
||||
|
|
||||
|
render() { |
||||
|
const { title, desc } = this.props |
||||
|
return ( |
||||
|
<SettingsSectionRow title={title} desc={desc}> |
||||
|
<Tabbable p={2} borderRadius={1} onClick={this.onClick}> |
||||
|
<IconExternalLink style={{ cursor: 'pointer' }} size={16} /> |
||||
|
</Tabbable> |
||||
|
</SettingsSectionRow> |
||||
|
) |
||||
|
} |
||||
|
} |
@ -0,0 +1,72 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Fragment, PureComponent } from 'react' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import type { T } from 'types/common' |
||||
|
import { remote } from 'electron' |
||||
|
import { cleanAccountsCache } from 'actions/accounts' |
||||
|
import db from 'helpers/db' |
||||
|
import { delay } from 'helpers/promise' |
||||
|
import Button from 'components/base/Button' |
||||
|
import { ConfirmModal } from 'components/base/Modal' |
||||
|
|
||||
|
const mapDispatchToProps = { |
||||
|
cleanAccountsCache, |
||||
|
} |
||||
|
|
||||
|
type Props = { |
||||
|
t: T, |
||||
|
cleanAccountsCache: () => *, |
||||
|
} |
||||
|
|
||||
|
type State = { |
||||
|
opened: boolean, |
||||
|
} |
||||
|
|
||||
|
class CleanButton extends PureComponent<Props, State> { |
||||
|
state = { |
||||
|
opened: false, |
||||
|
} |
||||
|
|
||||
|
open = () => this.setState({ opened: true }) |
||||
|
|
||||
|
close = () => this.setState({ opened: false }) |
||||
|
|
||||
|
action = async () => { |
||||
|
this.props.cleanAccountsCache() |
||||
|
await delay(500) |
||||
|
db.cleanCache() |
||||
|
remote.getCurrentWindow().webContents.reload() |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { t } = this.props |
||||
|
const { opened } = this.state |
||||
|
return ( |
||||
|
<Fragment> |
||||
|
<Button primary onClick={this.open} event="ClearCacheIntent"> |
||||
|
{t('app:settings.profile.softReset')} |
||||
|
</Button> |
||||
|
|
||||
|
<ConfirmModal |
||||
|
isDanger |
||||
|
isOpened={opened} |
||||
|
onClose={this.close} |
||||
|
onReject={this.close} |
||||
|
onConfirm={this.action} |
||||
|
title={t('app:settings.softResetModal.title')} |
||||
|
subTitle={t('app:settings.softResetModal.subTitle')} |
||||
|
desc={t('app:settings.softResetModal.desc')} |
||||
|
/> |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()( |
||||
|
connect( |
||||
|
null, |
||||
|
mapDispatchToProps, |
||||
|
)(CleanButton), |
||||
|
) |
@ -0,0 +1,53 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Fragment, PureComponent } from 'react' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { createStructuredSelector } from 'reselect' |
||||
|
import { |
||||
|
counterValueCurrencySelector, |
||||
|
counterValueExchangeSelector, |
||||
|
intermediaryCurrency, |
||||
|
} from 'reducers/settings' |
||||
|
import { setCounterValueExchange } from 'actions/settings' |
||||
|
import type { Currency } from '@ledgerhq/live-common/lib/types' |
||||
|
import SelectExchange from 'components/SelectExchange' |
||||
|
|
||||
|
type Props = { |
||||
|
counterValueCurrency: Currency, |
||||
|
counterValueExchange: ?string, |
||||
|
setCounterValueExchange: (?string) => void, |
||||
|
} |
||||
|
|
||||
|
class CounterValueExchangeSelect extends PureComponent<Props> { |
||||
|
handleChangeExchange = (exchange: *) => |
||||
|
this.props.setCounterValueExchange(exchange ? exchange.id : null) |
||||
|
|
||||
|
render() { |
||||
|
const { counterValueCurrency, counterValueExchange } = this.props |
||||
|
|
||||
|
return ( |
||||
|
<Fragment> |
||||
|
{counterValueCurrency ? ( |
||||
|
<SelectExchange |
||||
|
small |
||||
|
from={intermediaryCurrency} |
||||
|
to={counterValueCurrency} |
||||
|
exchangeId={counterValueExchange} |
||||
|
onChange={this.handleChangeExchange} |
||||
|
minWidth={200} |
||||
|
/> |
||||
|
) : null} |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default connect( |
||||
|
createStructuredSelector({ |
||||
|
counterValueCurrency: counterValueCurrencySelector, |
||||
|
counterValueExchange: counterValueExchangeSelector, |
||||
|
}), |
||||
|
{ |
||||
|
setCounterValueExchange, |
||||
|
}, |
||||
|
)(CounterValueExchangeSelect) |
@ -0,0 +1,58 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Fragment, PureComponent } from 'react' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { createStructuredSelector } from 'reselect' |
||||
|
import { listFiatCurrencies } from '@ledgerhq/live-common/lib/helpers/currencies' |
||||
|
import type { Currency } from '@ledgerhq/live-common/lib/types' |
||||
|
import { setCounterValue } from 'actions/settings' |
||||
|
import { counterValueCurrencySelector } from 'reducers/settings' |
||||
|
import Select from 'components/base/Select' |
||||
|
|
||||
|
const fiats = listFiatCurrencies() |
||||
|
.map(f => f.units[0]) |
||||
|
// For now we take first unit, in the future we'll need to figure out something else
|
||||
|
.map(fiat => ({ |
||||
|
value: fiat.code, |
||||
|
label: `${fiat.name} - ${fiat.code}${fiat.symbol ? ` (${fiat.symbol})` : ''}`, |
||||
|
fiat, |
||||
|
})) |
||||
|
|
||||
|
type Props = { |
||||
|
counterValueCurrency: Currency, |
||||
|
setCounterValue: string => void, |
||||
|
} |
||||
|
|
||||
|
class CounterValueSelect extends PureComponent<Props> { |
||||
|
handleChangeCounterValue = (item: Object) => { |
||||
|
const { setCounterValue } = this.props |
||||
|
setCounterValue(item.fiat.code) |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { counterValueCurrency } = this.props |
||||
|
const cvOption = fiats.find(f => f.value === counterValueCurrency.ticker) |
||||
|
|
||||
|
return ( |
||||
|
<Fragment> |
||||
|
{/* TODO Track */} |
||||
|
<Select |
||||
|
small |
||||
|
minWidth={250} |
||||
|
onChange={this.handleChangeCounterValue} |
||||
|
itemToString={item => (item ? item.name : '')} |
||||
|
renderSelected={item => item && item.name} |
||||
|
options={fiats} |
||||
|
value={cvOption} |
||||
|
/> |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default connect( |
||||
|
createStructuredSelector({ |
||||
|
counterValueCurrency: counterValueCurrencySelector, |
||||
|
}), |
||||
|
{ setCounterValue }, |
||||
|
)(CounterValueSelect) |
@ -0,0 +1,39 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Fragment, PureComponent } from 'react' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { createStructuredSelector } from 'reselect' |
||||
|
import { setDeveloperMode } from 'actions/settings' |
||||
|
import { developerModeSelector } from 'reducers/settings' |
||||
|
import Track from 'analytics/Track' |
||||
|
import CheckBox from 'components/base/CheckBox' |
||||
|
|
||||
|
const mapStateToProps = createStructuredSelector({ |
||||
|
developerMode: developerModeSelector, |
||||
|
}) |
||||
|
|
||||
|
const mapDispatchToProps = { |
||||
|
setDeveloperMode, |
||||
|
} |
||||
|
|
||||
|
type Props = { |
||||
|
developerMode: boolean, |
||||
|
setDeveloperMode: boolean => void, |
||||
|
} |
||||
|
|
||||
|
class DevModeButton extends PureComponent<Props> { |
||||
|
render() { |
||||
|
const { developerMode, setDeveloperMode } = this.props |
||||
|
return ( |
||||
|
<Fragment> |
||||
|
<Track onUpdate event={developerMode ? 'DevModeEnabled' : 'DevModeDisabled'} /> |
||||
|
<CheckBox isChecked={developerMode} onChange={setDeveloperMode} /> |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default connect( |
||||
|
mapStateToProps, |
||||
|
mapDispatchToProps, |
||||
|
)(DevModeButton) |
@ -0,0 +1,133 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Fragment, PureComponent } from 'react' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import type { T } from 'types/common' |
||||
|
import { createStructuredSelector } from 'reselect' |
||||
|
import bcrypt from 'bcryptjs' |
||||
|
import { setEncryptionKey } from 'helpers/db' |
||||
|
import { cleanAccountsCache } from 'actions/accounts' |
||||
|
import { saveSettings } from 'actions/settings' |
||||
|
import { storeSelector } from 'reducers/settings' |
||||
|
import type { SettingsState } from 'reducers/settings' |
||||
|
import { unlock } from 'reducers/application' // FIXME should be in actions
|
||||
|
import Track from 'analytics/Track' |
||||
|
import CheckBox from 'components/base/CheckBox' |
||||
|
import Box from 'components/base/Box' |
||||
|
import Button from 'components/base/Button' |
||||
|
import PasswordModal from './PasswordModal' |
||||
|
import DisablePasswordModal from './DisablePasswordModal' |
||||
|
|
||||
|
const mapStateToProps = createStructuredSelector({ |
||||
|
// FIXME in future we should use dedicated password selector and a savePassword action (you don't know the shape of settings)
|
||||
|
settings: storeSelector, |
||||
|
}) |
||||
|
|
||||
|
const mapDispatchToProps = { |
||||
|
unlock, |
||||
|
cleanAccountsCache, |
||||
|
saveSettings, |
||||
|
} |
||||
|
|
||||
|
type Props = { |
||||
|
t: T, |
||||
|
unlock: () => void, |
||||
|
settings: SettingsState, |
||||
|
saveSettings: Function, |
||||
|
} |
||||
|
|
||||
|
type State = { |
||||
|
isPasswordModalOpened: boolean, |
||||
|
isDisablePasswordModalOpened: boolean, |
||||
|
} |
||||
|
|
||||
|
class DisablePasswordButton extends PureComponent<Props, State> { |
||||
|
state = { |
||||
|
isPasswordModalOpened: false, |
||||
|
isDisablePasswordModalOpened: 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() |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
handleOpenPasswordModal = () => this.setState({ isPasswordModalOpened: true }) |
||||
|
handleClosePasswordModal = () => this.setState({ isPasswordModalOpened: false }) |
||||
|
handleDisablePassowrd = () => this.setState({ isDisablePasswordModalOpened: true }) |
||||
|
handleCloseDisablePasswordModal = () => this.setState({ isDisablePasswordModalOpened: false }) |
||||
|
|
||||
|
handleChangePasswordCheck = isChecked => { |
||||
|
if (isChecked) { |
||||
|
this.handleOpenPasswordModal() |
||||
|
} else { |
||||
|
this.handleDisablePassowrd() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
handleChangePassword = (password: ?string) => { |
||||
|
if (password) { |
||||
|
this.setPassword(password) |
||||
|
this.handleClosePasswordModal() |
||||
|
} else { |
||||
|
this.setPassword(undefined) |
||||
|
this.handleCloseDisablePasswordModal() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { t, settings } = this.props |
||||
|
const { isDisablePasswordModalOpened, isPasswordModalOpened } = this.state |
||||
|
const isPasswordEnabled = settings.password.isEnabled === true |
||||
|
return ( |
||||
|
<Fragment> |
||||
|
<Track onUpdate event={isPasswordEnabled ? 'PasswordEnabled' : 'PasswordDisabled'} /> |
||||
|
|
||||
|
<Box horizontal flow={2} align="center"> |
||||
|
{isPasswordEnabled && ( |
||||
|
<Button onClick={this.handleOpenPasswordModal}> |
||||
|
{t('app:settings.profile.changePassword')} |
||||
|
</Button> |
||||
|
)} |
||||
|
<CheckBox isChecked={isPasswordEnabled} onChange={this.handleChangePasswordCheck} /> |
||||
|
</Box> |
||||
|
|
||||
|
<PasswordModal |
||||
|
t={t} |
||||
|
isOpened={isPasswordModalOpened} |
||||
|
onClose={this.handleClosePasswordModal} |
||||
|
onChangePassword={this.handleChangePassword} |
||||
|
isPasswordEnabled={isPasswordEnabled} |
||||
|
currentPasswordHash={settings.password.value} |
||||
|
/> |
||||
|
|
||||
|
<DisablePasswordModal |
||||
|
t={t} |
||||
|
isOpened={isDisablePasswordModalOpened} |
||||
|
onClose={this.handleCloseDisablePasswordModal} |
||||
|
onChangePassword={this.handleChangePassword} |
||||
|
isPasswordEnabled={isPasswordEnabled} |
||||
|
currentPasswordHash={settings.password.value} |
||||
|
/> |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()( |
||||
|
connect( |
||||
|
mapStateToProps, |
||||
|
mapDispatchToProps, |
||||
|
)(DisablePasswordButton), |
||||
|
) |
@ -0,0 +1,61 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Fragment, PureComponent } from 'react' |
||||
|
import moment from 'moment' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import type { T } from 'types/common' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { setLanguage } from 'actions/settings' |
||||
|
import { langAndRegionSelector } from 'reducers/settings' |
||||
|
import languageKeys from 'config/languages' |
||||
|
import Select from 'components/base/Select' |
||||
|
|
||||
|
type Props = { |
||||
|
t: T, |
||||
|
useSystem: boolean, |
||||
|
language: string, |
||||
|
setLanguage: (?string) => void, |
||||
|
i18n: Object, |
||||
|
} |
||||
|
|
||||
|
class LanguageSelect extends PureComponent<Props> { |
||||
|
handleChangeLanguage = ({ value: languageKey }: *) => { |
||||
|
const { i18n, setLanguage } = this.props |
||||
|
i18n.changeLanguage(languageKey) |
||||
|
moment.locale(languageKey) |
||||
|
setLanguage(languageKey) |
||||
|
} |
||||
|
|
||||
|
languages = [{ value: null, label: this.props.t(`language:system`) }].concat( |
||||
|
languageKeys.map(key => ({ value: key, label: this.props.t(`language:${key}`) })), |
||||
|
) |
||||
|
|
||||
|
render() { |
||||
|
const { language, useSystem } = this.props |
||||
|
const currentLanguage = useSystem |
||||
|
? this.languages[0] |
||||
|
: this.languages.find(l => l.value === language) |
||||
|
return ( |
||||
|
<Fragment> |
||||
|
<Select |
||||
|
small |
||||
|
minWidth={250} |
||||
|
isSearchable={false} |
||||
|
onChange={this.handleChangeLanguage} |
||||
|
renderSelected={item => item && item.name} |
||||
|
value={currentLanguage} |
||||
|
options={this.languages} |
||||
|
/> |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()( |
||||
|
connect( |
||||
|
langAndRegionSelector, |
||||
|
{ |
||||
|
setLanguage, |
||||
|
}, |
||||
|
)(LanguageSelect), |
||||
|
) |
@ -0,0 +1,48 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import type { T } from 'types/common' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { createStructuredSelector } from 'reselect' |
||||
|
import { setMarketIndicator } from 'actions/settings' |
||||
|
import { marketIndicatorSelector } from 'reducers/settings' |
||||
|
import RadioGroup from 'components/base/RadioGroup' |
||||
|
|
||||
|
type Props = { |
||||
|
t: T, |
||||
|
setMarketIndicator: (*) => *, |
||||
|
marketIndicator: *, |
||||
|
} |
||||
|
|
||||
|
class MarketIndicatorRadio extends PureComponent<Props> { |
||||
|
indicators = [ |
||||
|
{ |
||||
|
label: this.props.t('app:common.eastern'), |
||||
|
key: 'eastern', |
||||
|
}, |
||||
|
{ |
||||
|
label: this.props.t('app:common.western'), |
||||
|
key: 'western', |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
onChange = (item: Object) => { |
||||
|
const { setMarketIndicator } = this.props |
||||
|
setMarketIndicator(item.key) |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { marketIndicator } = this.props |
||||
|
return ( |
||||
|
<RadioGroup items={this.indicators} activeKey={marketIndicator} onChange={this.onChange} /> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()( |
||||
|
connect( |
||||
|
createStructuredSelector({ marketIndicator: marketIndicatorSelector }), |
||||
|
{ setMarketIndicator }, |
||||
|
)(MarketIndicatorRadio), |
||||
|
) |
@ -0,0 +1,67 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Fragment, PureComponent } from 'react' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { createSelector } from 'reselect' |
||||
|
import { setRegion } from 'actions/settings' |
||||
|
import { langAndRegionSelector, counterValueCurrencySelector } from 'reducers/settings' |
||||
|
import type { Currency } from '@ledgerhq/live-common/lib/types' |
||||
|
import type { T } from 'types/common' |
||||
|
|
||||
|
import regionsByKey from 'helpers/regions.json' |
||||
|
import Select from 'components/base/Select' |
||||
|
|
||||
|
const regions = Object.keys(regionsByKey).map(key => { |
||||
|
const [language, region] = key.split('-') |
||||
|
return { value: key, language, region, label: regionsByKey[key] } |
||||
|
}) |
||||
|
|
||||
|
type Props = { |
||||
|
t: T, |
||||
|
counterValueCurrency: Currency, |
||||
|
useSystem: boolean, |
||||
|
language: string, |
||||
|
region: ?string, |
||||
|
setRegion: (?string) => void, |
||||
|
} |
||||
|
|
||||
|
class RegionSelect extends PureComponent<Props> { |
||||
|
handleChangeRegion = ({ region }: *) => { |
||||
|
const { setRegion } = this.props |
||||
|
setRegion(region) |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { language, region } = this.props |
||||
|
|
||||
|
const regionsFiltered = regions.filter(item => language === item.language) |
||||
|
const currentRegion = regionsFiltered.find(item => item.region === region) || regionsFiltered[0] |
||||
|
|
||||
|
return ( |
||||
|
<Fragment> |
||||
|
<Select |
||||
|
small |
||||
|
minWidth={250} |
||||
|
onChange={this.handleChangeRegion} |
||||
|
renderSelected={item => item && item.name} |
||||
|
value={currentRegion} |
||||
|
options={regionsFiltered} |
||||
|
/> |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default connect( |
||||
|
createSelector( |
||||
|
langAndRegionSelector, |
||||
|
counterValueCurrencySelector, |
||||
|
(langAndRegion, counterValueCurrency) => ({ |
||||
|
...langAndRegion, |
||||
|
counterValueCurrency, |
||||
|
}), |
||||
|
), |
||||
|
{ |
||||
|
setRegion, |
||||
|
}, |
||||
|
)(RegionSelect) |
@ -0,0 +1,45 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import type { T } from 'types/common' |
||||
|
import { shell } from 'electron' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { openModal } from 'reducers/modals' |
||||
|
import { MODAL_RELEASES_NOTES } from 'config/constants' |
||||
|
import Button from 'components/base/Button' |
||||
|
|
||||
|
type Props = { |
||||
|
t: T, |
||||
|
openModal: Function, |
||||
|
} |
||||
|
|
||||
|
const mapDispatchToProps = { |
||||
|
openModal, |
||||
|
} |
||||
|
|
||||
|
class ReleaseNotesButton extends PureComponent<Props> { |
||||
|
handleOpenLink = (url: string) => shell.openExternal(url) |
||||
|
|
||||
|
render() { |
||||
|
const { t, openModal } = this.props |
||||
|
const version = __APP_VERSION__ |
||||
|
return ( |
||||
|
<Button |
||||
|
primary |
||||
|
onClick={() => { |
||||
|
openModal(MODAL_RELEASES_NOTES, version) |
||||
|
}} |
||||
|
> |
||||
|
{t('app:settings.about.releaseNotesBtn')} |
||||
|
</Button> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()( |
||||
|
connect( |
||||
|
null, |
||||
|
mapDispatchToProps, |
||||
|
)(ReleaseNotesButton), |
||||
|
) |
@ -0,0 +1,82 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Fragment, PureComponent } from 'react' |
||||
|
import styled from 'styled-components' |
||||
|
import { remote } from 'electron' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import type { T } from 'types/common' |
||||
|
import hardReset from 'helpers/hardReset' |
||||
|
import Box from 'components/base/Box' |
||||
|
import Button from 'components/base/Button' |
||||
|
import { ConfirmModal } from 'components/base/Modal' |
||||
|
import IconTriangleWarning from 'icons/TriangleWarning' |
||||
|
|
||||
|
type Props = { |
||||
|
t: T, |
||||
|
} |
||||
|
|
||||
|
type State = { |
||||
|
opened: boolean, |
||||
|
pending: boolean, |
||||
|
} |
||||
|
|
||||
|
class ResetButton extends PureComponent<Props, State> { |
||||
|
state = { |
||||
|
opened: false, |
||||
|
pending: false, |
||||
|
} |
||||
|
|
||||
|
open = () => this.setState({ opened: true }) |
||||
|
close = () => this.setState({ opened: false }) |
||||
|
|
||||
|
action = async () => { |
||||
|
this.setState({ pending: true }) |
||||
|
try { |
||||
|
await hardReset() |
||||
|
remote.getCurrentWindow().webContents.reloadIgnoringCache() |
||||
|
} catch (err) { |
||||
|
this.setState({ pending: false }) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { t } = this.props |
||||
|
const { opened, pending } = this.state |
||||
|
return ( |
||||
|
<Fragment> |
||||
|
<Button danger onClick={this.open} event="HardResetIntent"> |
||||
|
{t('app:settings.profile.hardReset')} |
||||
|
</Button> |
||||
|
|
||||
|
<ConfirmModal |
||||
|
isDanger |
||||
|
isLoading={pending} |
||||
|
isOpened={opened} |
||||
|
onClose={this.close} |
||||
|
onReject={this.close} |
||||
|
onConfirm={this.action} |
||||
|
confirmText={t('app:common.reset')} |
||||
|
title={t('app:settings.hardResetModal.title')} |
||||
|
desc={t('app:settings.hardResetModal.desc')} |
||||
|
renderIcon={() => ( |
||||
|
// FIXME why not pass in directly the DOM 🤷🏻
|
||||
|
<IconWrapperCircle color="alertRed"> |
||||
|
<IconTriangleWarning width={23} height={21} /> |
||||
|
</IconWrapperCircle> |
||||
|
)} |
||||
|
/> |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const IconWrapperCircle = styled(Box)` |
||||
|
width: 50px; |
||||
|
height: 50px; |
||||
|
border-radius: 50%; |
||||
|
background: #ea2e4919; |
||||
|
text-align: center; |
||||
|
justify-content: center; |
||||
|
` |
||||
|
|
||||
|
export default translate()(ResetButton) |
@ -0,0 +1,39 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Fragment, PureComponent } from 'react' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { createStructuredSelector } from 'reselect' |
||||
|
import { setSentryLogs } from 'actions/settings' |
||||
|
import { sentryLogsSelector } from 'reducers/settings' |
||||
|
import Track from 'analytics/Track' |
||||
|
import CheckBox from 'components/base/CheckBox' |
||||
|
|
||||
|
const mapStateToProps = createStructuredSelector({ |
||||
|
sentryLogs: sentryLogsSelector, |
||||
|
}) |
||||
|
|
||||
|
const mapDispatchToProps = { |
||||
|
setSentryLogs, |
||||
|
} |
||||
|
|
||||
|
type Props = { |
||||
|
sentryLogs: boolean, |
||||
|
setSentryLogs: boolean => void, |
||||
|
} |
||||
|
|
||||
|
class SentryLogsButton extends PureComponent<Props> { |
||||
|
render() { |
||||
|
const { sentryLogs, setSentryLogs } = this.props |
||||
|
return ( |
||||
|
<Fragment> |
||||
|
<Track onUpdate event={sentryLogs ? 'SentryEnabled' : 'SentryDisabled'} /> |
||||
|
<CheckBox isChecked={sentryLogs} onChange={setSentryLogs} /> |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default connect( |
||||
|
mapStateToProps, |
||||
|
mapDispatchToProps, |
||||
|
)(SentryLogsButton) |
@ -0,0 +1,38 @@ |
|||||
|
// @flow
|
||||
|
import React, { Fragment, PureComponent } from 'react' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { createStructuredSelector } from 'reselect' |
||||
|
import { setShareAnalytics } from 'actions/settings' |
||||
|
import { shareAnalyticsSelector } from 'reducers/settings' |
||||
|
import Track from 'analytics/Track' |
||||
|
import CheckBox from 'components/base/CheckBox' |
||||
|
|
||||
|
const mapStateToProps = createStructuredSelector({ |
||||
|
shareAnalytics: shareAnalyticsSelector, |
||||
|
}) |
||||
|
|
||||
|
const mapDispatchToProps = { |
||||
|
setShareAnalytics, |
||||
|
} |
||||
|
|
||||
|
type Props = { |
||||
|
shareAnalytics: boolean, |
||||
|
setShareAnalytics: boolean => void, |
||||
|
} |
||||
|
|
||||
|
class ShareAnalytics extends PureComponent<Props> { |
||||
|
render() { |
||||
|
const { shareAnalytics, setShareAnalytics } = this.props |
||||
|
return ( |
||||
|
<Fragment> |
||||
|
<Track onUpdate event={shareAnalytics ? 'AnalyticsEnabled' : 'AnalyticsDisabled'} /> |
||||
|
<CheckBox isChecked={shareAnalytics} onChange={setShareAnalytics} /> |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default connect( |
||||
|
mapStateToProps, |
||||
|
mapDispatchToProps, |
||||
|
)(ShareAnalytics) |
@ -0,0 +1,114 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Fragment, PureComponent } from 'react' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { createStructuredSelector } from 'reselect' |
||||
|
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' |
||||
|
import type { T } from 'types/common' |
||||
|
import { saveSettings } from 'actions/settings' |
||||
|
import { intermediaryCurrency, currencySettingsSelector, storeSelector } from 'reducers/settings' |
||||
|
import type { SettingsState, CurrencySettings } from 'reducers/settings' |
||||
|
import { currencySettingsDefaults } from 'helpers/SettingsDefaults' |
||||
|
import StepperNumber from 'components/base/StepperNumber' |
||||
|
import ExchangeSelect from 'components/SelectExchange' |
||||
|
|
||||
|
import { SettingsSectionRow as Row } from '../SettingsSection' |
||||
|
|
||||
|
type Props = { |
||||
|
t: T, |
||||
|
currency: CryptoCurrency, |
||||
|
currencySettings: CurrencySettings, |
||||
|
// FIXME: the stuff bellow to be to be gone!
|
||||
|
settings: SettingsState, |
||||
|
saveSettings: ($Shape<SettingsState>) => void, |
||||
|
} |
||||
|
|
||||
|
class CurrencyRows extends PureComponent<Props> { |
||||
|
handleChangeConfirmationsToSpend = (nb: number) => |
||||
|
this.updateCurrencySettings('confirmationsToSpend', nb) |
||||
|
|
||||
|
handleChangeConfirmationsNb = (nb: number) => this.updateCurrencySettings('confirmationsNb', nb) |
||||
|
|
||||
|
handleChangeExchange = (exchange: *) => |
||||
|
this.updateCurrencySettings('exchange', exchange ? exchange.id : null) |
||||
|
|
||||
|
updateCurrencySettings = (key: string, val: *) => { |
||||
|
// FIXME this really should be a dedicated action
|
||||
|
const { settings, saveSettings, currency } = this.props |
||||
|
const currencySettings = settings.currenciesSettings[currency.id] |
||||
|
let newCurrenciesSettings = [] |
||||
|
if (!currencySettings) { |
||||
|
newCurrenciesSettings = { |
||||
|
...settings.currenciesSettings, |
||||
|
[currency.id]: { |
||||
|
[key]: val, |
||||
|
}, |
||||
|
} |
||||
|
} else { |
||||
|
newCurrenciesSettings = { |
||||
|
...settings.currenciesSettings, |
||||
|
[currency.id]: { |
||||
|
...currencySettings, |
||||
|
[key]: val, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
saveSettings({ currenciesSettings: newCurrenciesSettings }) |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { currency, t, currencySettings } = this.props |
||||
|
const { confirmationsNb, exchange } = currencySettings |
||||
|
const defaults = currencySettingsDefaults(currency) |
||||
|
return ( |
||||
|
<Fragment> |
||||
|
{currency !== intermediaryCurrency ? ( |
||||
|
<Row |
||||
|
title={t('app:settings.currencies.exchange', { |
||||
|
ticker: currency.ticker, |
||||
|
})} |
||||
|
desc={t('app:settings.currencies.exchangeDesc', { |
||||
|
currencyName: currency.name, |
||||
|
})} |
||||
|
> |
||||
|
<ExchangeSelect |
||||
|
small |
||||
|
from={currency} |
||||
|
to={intermediaryCurrency} |
||||
|
exchangeId={exchange} |
||||
|
onChange={this.handleChangeExchange} |
||||
|
minWidth={200} |
||||
|
/> |
||||
|
</Row> |
||||
|
) : null} |
||||
|
{defaults.confirmationsNb ? ( |
||||
|
<Row |
||||
|
title={t('app:settings.currencies.confirmationsNb')} |
||||
|
desc={t('app:settings.currencies.confirmationsNbDesc')} |
||||
|
> |
||||
|
<StepperNumber |
||||
|
min={defaults.confirmationsNb.min} |
||||
|
max={defaults.confirmationsNb.max} |
||||
|
step={1} |
||||
|
onChange={this.handleChangeConfirmationsNb} |
||||
|
value={confirmationsNb} |
||||
|
/> |
||||
|
</Row> |
||||
|
) : null} |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()( |
||||
|
connect( |
||||
|
createStructuredSelector({ |
||||
|
currencySettings: currencySettingsSelector, |
||||
|
settings: storeSelector, |
||||
|
}), |
||||
|
{ |
||||
|
saveSettings, |
||||
|
}, |
||||
|
)(CurrencyRows), |
||||
|
) |
@ -1,284 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import React, { PureComponent } from 'react' |
|
||||
import { connect } from 'react-redux' |
|
||||
import styled from 'styled-components' |
|
||||
import { remote } from 'electron' |
|
||||
import bcrypt from 'bcryptjs' |
|
||||
|
|
||||
import { cleanAccountsCache } from 'actions/accounts' |
|
||||
import { unlock } from 'reducers/application' // FIXME should be in actions
|
|
||||
import db, { setEncryptionKey } from 'helpers/db' |
|
||||
import { delay } from 'helpers/promise' |
|
||||
import hardReset from 'helpers/hardReset' |
|
||||
|
|
||||
import type { SettingsState } from 'reducers/settings' |
|
||||
import type { T } from 'types/common' |
|
||||
|
|
||||
import Track from 'analytics/Track' |
|
||||
import TrackPage from 'analytics/TrackPage' |
|
||||
import ExportLogsBtn from 'components/ExportLogsBtn' |
|
||||
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 IconTriangleWarning from 'icons/TriangleWarning' |
|
||||
import IconUser from 'icons/User' |
|
||||
import PasswordModal from '../PasswordModal' |
|
||||
import DisablePasswordModal from '../DisablePasswordModal' |
|
||||
|
|
||||
import { |
|
||||
SettingsSection as Section, |
|
||||
SettingsSectionHeader as Header, |
|
||||
SettingsSectionBody as Body, |
|
||||
SettingsSectionRow as Row, |
|
||||
} from '../SettingsSection' |
|
||||
|
|
||||
const mapDispatchToProps = { |
|
||||
unlock, |
|
||||
cleanAccountsCache, |
|
||||
} |
|
||||
|
|
||||
type Props = { |
|
||||
t: T, |
|
||||
settings: SettingsState, |
|
||||
unlock: Function, |
|
||||
saveSettings: Function, |
|
||||
cleanAccountsCache: () => *, |
|
||||
} |
|
||||
|
|
||||
type State = { |
|
||||
isHardResetModalOpened: boolean, |
|
||||
isSoftResetModalOpened: boolean, |
|
||||
isPasswordModalOpened: boolean, |
|
||||
isDisablePasswordModalOpened: boolean, |
|
||||
isHardResetting: boolean, |
|
||||
} |
|
||||
|
|
||||
class TabProfile extends PureComponent<Props, State> { |
|
||||
state = { |
|
||||
isHardResetModalOpened: false, |
|
||||
isSoftResetModalOpened: false, |
|
||||
isPasswordModalOpened: false, |
|
||||
isDisablePasswordModalOpened: false, |
|
||||
isHardResetting: 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() |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
handleOpenSoftResetModal = () => this.setState({ isSoftResetModalOpened: true }) |
|
||||
handleCloseSoftResetModal = () => this.setState({ isSoftResetModalOpened: false }) |
|
||||
handleOpenHardResetModal = () => this.setState({ isHardResetModalOpened: true }) |
|
||||
handleCloseHardResetModal = () => this.setState({ isHardResetModalOpened: false }) |
|
||||
handleOpenPasswordModal = () => this.setState({ isPasswordModalOpened: true }) |
|
||||
handleClosePasswordModal = () => this.setState({ isPasswordModalOpened: false }) |
|
||||
handleDisablePassowrd = () => this.setState({ isDisablePasswordModalOpened: true }) |
|
||||
handleCloseDisablePasswordModal = () => this.setState({ isDisablePasswordModalOpened: false }) |
|
||||
|
|
||||
handleSoftReset = async () => { |
|
||||
this.props.cleanAccountsCache() |
|
||||
await delay(500) |
|
||||
db.cleanCache() |
|
||||
remote.getCurrentWindow().webContents.reload() |
|
||||
} |
|
||||
|
|
||||
handleHardReset = async () => { |
|
||||
this.setState({ isHardResetting: true }) |
|
||||
try { |
|
||||
await hardReset() |
|
||||
remote.getCurrentWindow().webContents.reloadIgnoringCache() |
|
||||
} catch (err) { |
|
||||
this.setState({ isHardResetting: false }) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
handleChangePasswordCheck = isChecked => { |
|
||||
if (isChecked) { |
|
||||
this.handleOpenPasswordModal() |
|
||||
} else { |
|
||||
this.handleDisablePassowrd() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
handleChangePassword = (password: ?string) => { |
|
||||
if (password) { |
|
||||
this.setPassword(password) |
|
||||
this.handleClosePasswordModal() |
|
||||
} else { |
|
||||
this.setPassword(undefined) |
|
||||
this.handleCloseDisablePasswordModal() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
handleDeveloperMode = developerMode => { |
|
||||
this.props.saveSettings({ |
|
||||
developerMode, |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
hardResetIconRender = () => ( |
|
||||
<IconWrapperCircle color="alertRed"> |
|
||||
<IconTriangleWarning width={23} height={21} /> |
|
||||
</IconWrapperCircle> |
|
||||
) |
|
||||
|
|
||||
render() { |
|
||||
const { t, settings, saveSettings } = this.props |
|
||||
const { |
|
||||
isSoftResetModalOpened, |
|
||||
isHardResetModalOpened, |
|
||||
isPasswordModalOpened, |
|
||||
isDisablePasswordModalOpened, |
|
||||
isHardResetting, |
|
||||
} = this.state |
|
||||
const isPasswordEnabled = settings.password.isEnabled === true |
|
||||
return ( |
|
||||
<Section> |
|
||||
<TrackPage category="Settings" name="Profile" /> |
|
||||
<Header |
|
||||
icon={<IconUser size={16} />} |
|
||||
title={t('app:settings.tabs.profile')} |
|
||||
desc={t('app:settings.display.desc')} |
|
||||
/> |
|
||||
<Body> |
|
||||
<Row |
|
||||
title={t('app:settings.profile.password')} |
|
||||
desc={t('app:settings.profile.passwordDesc')} |
|
||||
> |
|
||||
<Track onUpdate event={isPasswordEnabled ? 'PasswordEnabled' : 'PasswordDisabled'} /> |
|
||||
<Box horizontal flow={2} align="center"> |
|
||||
{isPasswordEnabled && ( |
|
||||
<Button onClick={this.handleOpenPasswordModal}> |
|
||||
{t('app:settings.profile.changePassword')} |
|
||||
</Button> |
|
||||
)} |
|
||||
<CheckBox isChecked={isPasswordEnabled} onChange={this.handleChangePasswordCheck} /> |
|
||||
</Box> |
|
||||
</Row> |
|
||||
<Row |
|
||||
title={t('app:settings.profile.reportErrors')} |
|
||||
desc={t('app:settings.profile.reportErrorsDesc')} |
|
||||
> |
|
||||
<Track onUpdate event={settings.sentryLogs ? 'SentryEnabled' : 'SentryDisabled'} /> |
|
||||
<CheckBox |
|
||||
isChecked={settings.sentryLogs} |
|
||||
onChange={sentryLogs => saveSettings({ sentryLogs })} |
|
||||
/> |
|
||||
</Row> |
|
||||
<Row |
|
||||
title={t('app:settings.profile.analytics')} |
|
||||
desc={t('app:settings.profile.analyticsDesc')} |
|
||||
> |
|
||||
<Track |
|
||||
onUpdate |
|
||||
event={settings.shareAnalytics ? 'AnalyticsEnabled' : 'AnalyticsDisabled'} |
|
||||
/> |
|
||||
<CheckBox |
|
||||
isChecked={settings.shareAnalytics} |
|
||||
onChange={shareAnalytics => saveSettings({ shareAnalytics })} |
|
||||
/> |
|
||||
</Row> |
|
||||
<Row |
|
||||
title={t('app:settings.profile.developerMode')} |
|
||||
desc={t('app:settings.profile.developerModeDesc')} |
|
||||
> |
|
||||
<Track onUpdate event={settings.developerMode ? 'DevModeEnabled' : 'DevModeDisabled'} /> |
|
||||
<CheckBox |
|
||||
isChecked={settings.developerMode} |
|
||||
onChange={developerMode => saveSettings({ developerMode })} |
|
||||
/> |
|
||||
</Row> |
|
||||
<Row |
|
||||
title={t('app:settings.profile.softResetTitle')} |
|
||||
desc={t('app:settings.profile.softResetDesc')} |
|
||||
> |
|
||||
<Button primary onClick={this.handleOpenSoftResetModal} event="ClearCacheIntent"> |
|
||||
{t('app:settings.profile.softReset')} |
|
||||
</Button> |
|
||||
</Row> |
|
||||
<Row title={t('app:settings.exportLogs.title')} desc={t('app:settings.exportLogs.desc')}> |
|
||||
<ExportLogsBtn /> |
|
||||
</Row> |
|
||||
<Row |
|
||||
title={t('app:settings.profile.hardResetTitle')} |
|
||||
desc={t('app:settings.profile.hardResetDesc')} |
|
||||
> |
|
||||
<Button danger onClick={this.handleOpenHardResetModal} event="HardResetIntent"> |
|
||||
{t('app:settings.profile.hardReset')} |
|
||||
</Button> |
|
||||
</Row> |
|
||||
</Body> |
|
||||
|
|
||||
<ConfirmModal |
|
||||
isDanger |
|
||||
isOpened={isSoftResetModalOpened} |
|
||||
onClose={this.handleCloseSoftResetModal} |
|
||||
onReject={this.handleCloseSoftResetModal} |
|
||||
onConfirm={this.handleSoftReset} |
|
||||
title={t('app:settings.softResetModal.title')} |
|
||||
subTitle={t('app:settings.softResetModal.subTitle')} |
|
||||
desc={t('app:settings.softResetModal.desc')} |
|
||||
/> |
|
||||
|
|
||||
<ConfirmModal |
|
||||
isDanger |
|
||||
isLoading={isHardResetting} |
|
||||
isOpened={isHardResetModalOpened} |
|
||||
onClose={this.handleCloseHardResetModal} |
|
||||
onReject={this.handleCloseHardResetModal} |
|
||||
onConfirm={this.handleHardReset} |
|
||||
confirmText={t('app:common.reset')} |
|
||||
title={t('app:settings.hardResetModal.title')} |
|
||||
desc={t('app:settings.hardResetModal.desc')} |
|
||||
renderIcon={this.hardResetIconRender} |
|
||||
/> |
|
||||
|
|
||||
<PasswordModal |
|
||||
t={t} |
|
||||
isOpened={isPasswordModalOpened} |
|
||||
onClose={this.handleClosePasswordModal} |
|
||||
onChangePassword={this.handleChangePassword} |
|
||||
isPasswordEnabled={isPasswordEnabled} |
|
||||
currentPasswordHash={settings.password.value} |
|
||||
/> |
|
||||
|
|
||||
<DisablePasswordModal |
|
||||
t={t} |
|
||||
isOpened={isDisablePasswordModalOpened} |
|
||||
onClose={this.handleCloseDisablePasswordModal} |
|
||||
onChangePassword={this.handleChangePassword} |
|
||||
isPasswordEnabled={isPasswordEnabled} |
|
||||
currentPasswordHash={settings.password.value} |
|
||||
/> |
|
||||
</Section> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default connect( |
|
||||
null, |
|
||||
mapDispatchToProps, |
|
||||
)(TabProfile) |
|
||||
|
|
||||
// TODO: need a helper file for common styles across the app
|
|
||||
export const IconWrapperCircle = styled(Box).attrs({})` |
|
||||
width: 50px; |
|
||||
height: 50px; |
|
||||
border-radius: 50%; |
|
||||
background: #ea2e4919; |
|
||||
text-align: -webkit-center; |
|
||||
justify-content: center; |
|
||||
` |
|
Loading…
Reference in new issue