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