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 { storiesOf } from '@storybook/react' |
|||
import { boolean } from '@storybook/addon-knobs' |
|||
import { action } from '@storybook/addon-actions' |
|||
import { boolean } from '@storybook/addon-knobs' |
|||
|
|||
import CheckBox from 'components/base/CheckBox' |
|||
|
|||
const stories = storiesOf('Components/base', module) |
|||
|
|||
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
|
|||
|
|||
import React from 'react' |
|||
import React, { Fragment } from 'react' |
|||
import styled from 'styled-components' |
|||
|
|||
import TrackPage from 'analytics/TrackPage' |
|||
import Box from 'components/base/Box' |
|||
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' |
|||
|
|||
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 ( |
|||
<Box align="center" py={6}> |
|||
<TrackPage category="AddAccounts" name="Step4" /> |
|||
<Box color="positiveGreen"> |
|||
<IconCheckCircle size={40} /> |
|||
</Box> |
|||
<Box p={4}>{t('app:addAccounts.success')}</Box> |
|||
<Box horizontal> |
|||
<Button mr={2} outline onClick={onGoStep1}> |
|||
{t('app:addAccounts.cta.addMore')} |
|||
</Button> |
|||
<Button primary onClick={onCloseModal}> |
|||
{t('app:common.close')} |
|||
</Button> |
|||
</Box> |
|||
{currency ? ( |
|||
<Box color="positiveGreen" style={{ position: 'relative' }}> |
|||
<CurrencyCircleIcon size={50} currency={currency} /> |
|||
<IconCheckFull size={18} style={{ position: 'absolute', top: 0, right: 0 }} /> |
|||
</Box> |
|||
) : null} |
|||
<Title>{t('app:addAccounts.success', { count: checkedAccountsIds.length })}</Title> |
|||
<Text>{t('app:addAccounts.successDescription', { count: checkedAccountsIds.length })}</Text> |
|||
</Box> |
|||
) |
|||
} |
|||
|
|||
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