Gaëtan Renaudeau
7 years ago
committed by
GitHub
121 changed files with 3487 additions and 2457 deletions
@ -0,0 +1,15 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
// This is a test example for dev testing purpose.
|
||||
|
|
||||
|
import { Observable } from 'rxjs' |
||||
|
import { createCommand, Command } from 'helpers/ipc' |
||||
|
|
||||
|
const cmd: Command<void, string> = createCommand('ping', () => |
||||
|
Observable.create(o => { |
||||
|
o.next('pong') |
||||
|
o.complete() |
||||
|
}), |
||||
|
) |
||||
|
|
||||
|
export default cmd |
@ -1,45 +0,0 @@ |
|||||
// @flow
|
|
||||
/* eslint-disable react/no-multi-comp */ |
|
||||
|
|
||||
import { Component, PureComponent } from 'react' |
|
||||
import { connect } from 'react-redux' |
|
||||
|
|
||||
import type { Node } from 'react' |
|
||||
import type { Device } from 'types/common' |
|
||||
|
|
||||
import { getCurrentDevice } from 'reducers/devices' |
|
||||
|
|
||||
type Props = { |
|
||||
device: Device, |
|
||||
children: (device: Device) => Node, |
|
||||
} |
|
||||
|
|
||||
let prevents = 0 |
|
||||
export class PreventDeviceChangeRecheck extends PureComponent<{}> { |
|
||||
componentDidMount() { |
|
||||
prevents++ |
|
||||
} |
|
||||
componentWillUnmount() { |
|
||||
prevents-- |
|
||||
} |
|
||||
render() { |
|
||||
return null |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
class EnsureDevice extends Component<Props> { |
|
||||
shouldComponentUpdate(nextProps) { |
|
||||
if (prevents > 0) return false |
|
||||
return nextProps.device !== this.props.device |
|
||||
} |
|
||||
render() { |
|
||||
const { device, children } = this.props |
|
||||
return children(device) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const mapStateToProps = state => ({ |
|
||||
device: getCurrentDevice(state), |
|
||||
}) |
|
||||
|
|
||||
export default connect(mapStateToProps)(EnsureDevice) |
|
@ -0,0 +1,28 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { createStructuredSelector } from 'reselect' |
||||
|
import { hasCompletedOnboardingSelector } from 'reducers/settings' |
||||
|
import Onboarding from './Onboarding' |
||||
|
|
||||
|
type Props = { |
||||
|
hasCompletedOnboarding: boolean, |
||||
|
children: *, |
||||
|
} |
||||
|
|
||||
|
class OnboardingOrElse extends PureComponent<Props> { |
||||
|
render() { |
||||
|
const { hasCompletedOnboarding, children } = this.props |
||||
|
if (hasCompletedOnboarding) { |
||||
|
return children |
||||
|
} |
||||
|
return <Onboarding /> |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default connect( |
||||
|
createStructuredSelector({ |
||||
|
hasCompletedOnboarding: hasCompletedOnboardingSelector, |
||||
|
}), |
||||
|
)(OnboardingOrElse) |
@ -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 small 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 Switch from 'components/base/Switch' |
||||
|
|
||||
|
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'} /> |
||||
|
<Switch 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 Switch from 'components/base/Switch' |
||||
|
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 small onClick={this.handleOpenPasswordModal}> |
||||
|
{t('app:settings.profile.changePassword')} |
||||
|
</Button> |
||||
|
)} |
||||
|
<Switch 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,46 @@ |
|||||
|
// @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 |
||||
|
small |
||||
|
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 small 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; |
||||
|
align-items: 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 Switch from 'components/base/Switch' |
||||
|
|
||||
|
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'} /> |
||||
|
<Switch 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 Switch from 'components/base/Switch' |
||||
|
|
||||
|
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'} /> |
||||
|
<Switch 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; |
|
||||
` |
|
@ -0,0 +1,65 @@ |
|||||
|
// @flow
|
||||
|
// Sync continuously the accounts that have pending operations
|
||||
|
|
||||
|
import React, { Component } from 'react' |
||||
|
import logger from 'logger' |
||||
|
import { createStructuredSelector, createSelector } from 'reselect' |
||||
|
import { connect } from 'react-redux' |
||||
|
import type { Account } from '@ledgerhq/live-common/lib/types' |
||||
|
import { BridgeSyncConsumer } from 'bridge/BridgeSyncContext' |
||||
|
import type { Sync } from 'bridge/BridgeSyncContext' |
||||
|
import { accountsSelector } from 'reducers/accounts' |
||||
|
|
||||
|
const accountsWithPendingOperationsSelector = createSelector(accountsSelector, accounts => |
||||
|
accounts.filter(a => a.pendingOperations.length > 0), |
||||
|
) |
||||
|
|
||||
|
class SyncContPendingOpsConnected extends Component<{ |
||||
|
sync: Sync, |
||||
|
accounts: Account[], |
||||
|
priority: number, |
||||
|
interval: number, |
||||
|
}> { |
||||
|
componentDidMount() { |
||||
|
this.timeout = setTimeout(this.check, this.props.interval) |
||||
|
} |
||||
|
componentWillUnmount() { |
||||
|
clearTimeout(this.timeout) |
||||
|
} |
||||
|
check = () => { |
||||
|
const { sync, accounts, priority, interval } = this.props |
||||
|
setTimeout(this.check, interval) |
||||
|
if (accounts.length > 0) { |
||||
|
logger.log(`SyncContinouslyPendingOperations: found ${accounts.length} accounts`, accounts) |
||||
|
sync({ |
||||
|
type: 'SYNC_SOME_ACCOUNTS', |
||||
|
accountIds: accounts.map(a => a.id), |
||||
|
priority, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
timeout: * |
||||
|
render() { |
||||
|
return null |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const Effect = connect( |
||||
|
createStructuredSelector({ |
||||
|
accounts: accountsWithPendingOperationsSelector, |
||||
|
}), |
||||
|
)(SyncContPendingOpsConnected) |
||||
|
|
||||
|
const SyncContinuouslyPendingOperations = ({ |
||||
|
priority, |
||||
|
interval, |
||||
|
}: { |
||||
|
priority: number, |
||||
|
interval: number, |
||||
|
}) => ( |
||||
|
<BridgeSyncConsumer> |
||||
|
{sync => <Effect sync={sync} interval={interval} priority={priority} />} |
||||
|
</BridgeSyncConsumer> |
||||
|
) |
||||
|
|
||||
|
export default SyncContinuouslyPendingOperations |
@ -1,12 +1,12 @@ |
|||||
import React from 'react' |
import React from 'react' |
||||
import { storiesOf } from '@storybook/react' |
import { storiesOf } from '@storybook/react' |
||||
import { boolean } from '@storybook/addon-knobs' |
|
||||
import { action } from '@storybook/addon-actions' |
import { action } from '@storybook/addon-actions' |
||||
|
import { boolean } from '@storybook/addon-knobs' |
||||
|
|
||||
import CheckBox from 'components/base/CheckBox' |
import CheckBox from 'components/base/CheckBox' |
||||
|
|
||||
const stories = storiesOf('Components/base', module) |
const stories = storiesOf('Components/base', module) |
||||
|
|
||||
stories.add('CheckBox', () => ( |
stories.add('CheckBox', () => ( |
||||
<CheckBox isChecked={boolean('isChecked', false)} onChange={action('onChange')} /> |
<CheckBox isChecked={boolean('checked', false)} onChange={action('onChange')} /> |
||||
)) |
)) |
||||
|
@ -1,40 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import { PureComponent } from 'react' |
|
||||
|
|
||||
type Props = { |
|
||||
children: any, |
|
||||
} |
|
||||
|
|
||||
type State = { |
|
||||
shouldRender: boolean, |
|
||||
} |
|
||||
|
|
||||
class Defer extends PureComponent<Props, State> { |
|
||||
state = { |
|
||||
shouldRender: false, |
|
||||
} |
|
||||
|
|
||||
componentDidMount() { |
|
||||
this._mounted = true |
|
||||
|
|
||||
window.requestAnimationFrame(() => |
|
||||
window.requestAnimationFrame(() => this._mounted && this.setState({ shouldRender: true })), |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
componentWillUnmount() { |
|
||||
this._mounted = false |
|
||||
} |
|
||||
|
|
||||
_mounted = false |
|
||||
|
|
||||
render() { |
|
||||
const { children } = this.props |
|
||||
const { shouldRender } = this.state |
|
||||
|
|
||||
return shouldRender ? children : null |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default Defer |
|
@ -0,0 +1,25 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React from 'react' |
||||
|
|
||||
|
import { storiesOf } from '@storybook/react' |
||||
|
import { text, number, boolean } from '@storybook/addon-knobs' |
||||
|
|
||||
|
import Progress from 'components/base/Progress' |
||||
|
|
||||
|
const stories = storiesOf('Components/base', module) |
||||
|
|
||||
|
stories.add('Progress (infinite)', () => ( |
||||
|
<Progress |
||||
|
infinite={boolean('infinite', true)} |
||||
|
timing={number('timing (ms)', 3000)} |
||||
|
color={text('color (css or from theme)', 'wallet')} |
||||
|
/> |
||||
|
)) |
||||
|
stories.add('Progress', () => ( |
||||
|
<Progress |
||||
|
infinite={boolean('infinite', false)} |
||||
|
timing={number('timing (ms)', 3000)} |
||||
|
color={text('color (css or from theme)', 'wallet')} |
||||
|
/> |
||||
|
)) |
@ -0,0 +1,53 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React from 'react' |
||||
|
import noop from 'lodash/noop' |
||||
|
import styled from 'styled-components' |
||||
|
|
||||
|
import { Tabbable } from 'components/base/Box' |
||||
|
|
||||
|
const Base = styled(Tabbable).attrs({ |
||||
|
bg: p => (p.isChecked ? 'wallet' : 'lightFog'), |
||||
|
horizontal: true, |
||||
|
align: 'center', |
||||
|
})` |
||||
|
backround: red; |
||||
|
width: 50px; |
||||
|
height: 26px; |
||||
|
border-radius: 13px; |
||||
|
transition: 250ms linear background-color; |
||||
|
cursor: pointer; |
||||
|
&:focus { |
||||
|
outline: none; |
||||
|
} |
||||
|
` |
||||
|
|
||||
|
const Ball = styled.div` |
||||
|
width: 20px; |
||||
|
height: 20px; |
||||
|
border-radius: 50%; |
||||
|
background: white; |
||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2); |
||||
|
transition: 250ms ease-in-out transform; |
||||
|
transform: translate3d(${p => (p.isChecked ? '27px' : '3px')}, 0, 0); |
||||
|
` |
||||
|
|
||||
|
type Props = { |
||||
|
isChecked: boolean, |
||||
|
onChange?: Function, |
||||
|
} |
||||
|
|
||||
|
function Switch(props: Props) { |
||||
|
const { isChecked, onChange, ...p } = props |
||||
|
return ( |
||||
|
<Base isChecked={isChecked} onClick={() => onChange && onChange(!isChecked)} {...p}> |
||||
|
<Ball isChecked={isChecked} /> |
||||
|
</Base> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
Switch.defaultProps = { |
||||
|
onChange: noop, |
||||
|
} |
||||
|
|
||||
|
export default Switch |
@ -0,0 +1,12 @@ |
|||||
|
import React from 'react' |
||||
|
import { storiesOf } from '@storybook/react' |
||||
|
import { boolean } from '@storybook/addon-knobs' |
||||
|
import { action } from '@storybook/addon-actions' |
||||
|
|
||||
|
import Switch from 'components/base/Switch' |
||||
|
|
||||
|
const stories = storiesOf('Components/base', module) |
||||
|
|
||||
|
stories.add('Switch', () => ( |
||||
|
<Switch isChecked={boolean('isChecked', false)} onChange={action('onChange')} /> |
||||
|
)) |
@ -1,32 +1,57 @@ |
|||||
// @flow
|
// @flow
|
||||
|
|
||||
import React from 'react' |
import React, { Fragment } from 'react' |
||||
|
import styled from 'styled-components' |
||||
|
|
||||
import TrackPage from 'analytics/TrackPage' |
import TrackPage from 'analytics/TrackPage' |
||||
import Box from 'components/base/Box' |
import Box from 'components/base/Box' |
||||
import Button from 'components/base/Button' |
import Button from 'components/base/Button' |
||||
import IconCheckCircle from 'icons/CheckCircle' |
import IconCheckFull from 'icons/CheckFull' |
||||
|
import { CurrencyCircleIcon } from '../../../base/CurrencyBadge' |
||||
import type { StepProps } from '../index' |
import type { StepProps } from '../index' |
||||
|
|
||||
function StepFinish({ onCloseModal, onGoStep1, t }: StepProps) { |
const Title = styled(Box).attrs({ |
||||
|
ff: 'Museo Sans', |
||||
|
fontSize: 5, |
||||
|
mt: 2, |
||||
|
color: 'dark', |
||||
|
})` |
||||
|
text-align: center; |
||||
|
` |
||||
|
|
||||
|
const Text = styled(Box).attrs({ |
||||
|
ff: 'Open Sans', |
||||
|
fontSize: 4, |
||||
|
mt: 2, |
||||
|
})` |
||||
|
text-align: center; |
||||
|
` |
||||
|
|
||||
|
function StepFinish({ currency, t, checkedAccountsIds }: StepProps) { |
||||
return ( |
return ( |
||||
<Box align="center" py={6}> |
<Box align="center" py={6}> |
||||
<TrackPage category="AddAccounts" name="Step4" /> |
<TrackPage category="AddAccounts" name="Step4" /> |
||||
<Box color="positiveGreen"> |
{currency ? ( |
||||
<IconCheckCircle size={40} /> |
<Box color="positiveGreen" style={{ position: 'relative' }}> |
||||
</Box> |
<CurrencyCircleIcon size={50} currency={currency} /> |
||||
<Box p={4}>{t('app:addAccounts.success')}</Box> |
<IconCheckFull size={18} style={{ position: 'absolute', top: 0, right: 0 }} /> |
||||
<Box horizontal> |
</Box> |
||||
<Button mr={2} outline onClick={onGoStep1}> |
) : null} |
||||
{t('app:addAccounts.cta.addMore')} |
<Title>{t('app:addAccounts.success', { count: checkedAccountsIds.length })}</Title> |
||||
</Button> |
<Text>{t('app:addAccounts.successDescription', { count: checkedAccountsIds.length })}</Text> |
||||
<Button primary onClick={onCloseModal}> |
|
||||
{t('app:common.close')} |
|
||||
</Button> |
|
||||
</Box> |
|
||||
</Box> |
</Box> |
||||
) |
) |
||||
} |
} |
||||
|
|
||||
export default StepFinish |
export default StepFinish |
||||
|
|
||||
|
export const StepFinishFooter = ({ onCloseModal, onGoStep1, t }: StepProps) => ( |
||||
|
<Fragment> |
||||
|
<Button mr={2} outline onClick={onGoStep1}> |
||||
|
{t('app:addAccounts.cta.addMore')} |
||||
|
</Button> |
||||
|
<Button primary onClick={onCloseModal}> |
||||
|
{t('app:common.close')} |
||||
|
</Button> |
||||
|
</Fragment> |
||||
|
) |
||||
|
@ -0,0 +1,48 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import { translate } from 'react-i18next' |
||||
|
|
||||
|
import type { T } from 'types/common' |
||||
|
|
||||
|
import { MODAL_DISCLAIMER } from 'config/constants' |
||||
|
|
||||
|
import Modal, { ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal' |
||||
|
import Button from 'components/base/Button' |
||||
|
import Box from 'components/base/Box' |
||||
|
import { HandShield } from 'components/WarnBox' |
||||
|
|
||||
|
type Props = { |
||||
|
t: T, |
||||
|
} |
||||
|
|
||||
|
class DisclaimerModal extends PureComponent<Props> { |
||||
|
render() { |
||||
|
const { t } = this.props |
||||
|
|
||||
|
return ( |
||||
|
<Modal |
||||
|
name={MODAL_DISCLAIMER} |
||||
|
render={({ onClose }) => ( |
||||
|
<ModalBody onClose={onClose}> |
||||
|
<ModalTitle>{t('app:disclaimerModal.title')}</ModalTitle> |
||||
|
<ModalContent flow={4} ff="Open Sans|Regular" fontSize={4} color="smoke"> |
||||
|
<Box align="center" mt={4} pb={4}> |
||||
|
<HandShield size={55} /> |
||||
|
</Box> |
||||
|
<p>{t('app:disclaimerModal.desc_1')}</p> |
||||
|
<p>{t('app:disclaimerModal.desc_2')}</p> |
||||
|
</ModalContent> |
||||
|
<ModalFooter horizontal justifyContent="flex-end"> |
||||
|
<Button onClick={onClose} primary> |
||||
|
{t('app:disclaimerModal.cta')} |
||||
|
</Button> |
||||
|
</ModalFooter> |
||||
|
</ModalBody> |
||||
|
)} |
||||
|
/> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()(DisclaimerModal) |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue