From f08efce182f0fcf62a85cb7c1b0f3c06ab8e63bc Mon Sep 17 00:00:00 2001 From: meriadec Date: Fri, 13 Apr 2018 14:14:09 +0200 Subject: [PATCH 01/18] Use Pills in SettingsPage, and remove Tabs component --- src/components/SettingsPage/index.js | 105 ++++++++++++++------------- src/components/base/Tabs/index.js | 68 ----------------- src/components/base/Tabs/stories.js | 31 -------- 3 files changed, 56 insertions(+), 148 deletions(-) delete mode 100644 src/components/base/Tabs/index.js delete mode 100644 src/components/base/Tabs/stories.js diff --git a/src/components/SettingsPage/index.js b/src/components/SettingsPage/index.js index 725549f2..cfcc4bbf 100644 --- a/src/components/SettingsPage/index.js +++ b/src/components/SettingsPage/index.js @@ -13,9 +13,8 @@ import type { FetchCounterValues } from 'actions/counterValues' import { saveSettings } from 'actions/settings' import { fetchCounterValues } from 'actions/counterValues' +import Pills from 'components/base/Pills' import Box from 'components/base/Box' -import Text from 'components/base/Text' -import Tabs from 'components/base/Tabs' import TabDisplay from './Display' import TabProfile from './Profile' @@ -48,7 +47,12 @@ class SettingsPage extends PureComponent { tab: 0, } - handleChangeTab = (tab: number) => this.setState({ tab }) + _items = [] + + handleChangeTab = (item: any) => { + const tab = this._items.indexOf(item) + this.setState({ tab }) + } handleSaveSettings = newSettings => { const { fetchCounterValues, saveSettings, i18n, settings } = this.props @@ -75,53 +79,56 @@ class SettingsPage extends PureComponent { onSaveSettings: this.handleSaveSettings, } + this._items = [ + { + key: 'display', + label: t('settings:tabs.display'), + value: () => , + }, + { + key: 'money', + label: t('settings:tabs.money'), + value: () => , + }, + { + key: 'material', + isDisabled: true, + label: t('settings:tabs.material'), + value: () =>
{'Matériel'}
, + }, + { + key: 'app', + isDisabled: true, + label: t('settings:tabs.app'), + value: () =>
{'App (beta)'}
, + }, + { + key: 'tools', + label: t('settings:tabs.tools'), + value: () => , + }, + { + key: 'blockchain', + isDisabled: true, + label: t('settings:tabs.blockchain'), + value: () =>
{'Blockchain'}
, + }, + { + key: 'profile', + label: t('settings:tabs.profile'), + value: () => , + }, + ] + + const item = this._items[tab] + return ( - - {t('settings:title')} - , - }, - { - key: 'money', - title: t('settings:tabs.money'), - render: () => , - }, - { - key: 'material', - isDisabled: true, - title: t('settings:tabs.material'), - render: () =>
{'Matériel'}
, - }, - { - key: 'app', - isDisabled: true, - title: t('settings:tabs.app'), - render: () =>
{'App (beta)'}
, - }, - { - key: 'tools', - title: t('settings:tabs.tools'), - render: () => , - }, - { - key: 'blockchain', - isDisabled: true, - title: t('settings:tabs.blockchain'), - render: () =>
{'Blockchain'}
, - }, - { - key: 'profile', - title: t('settings:tabs.profile'), - render: () => , - }, - ]} - /> + + + {t('settings:title')} + + + {item.value && item.value()} ) } diff --git a/src/components/base/Tabs/index.js b/src/components/base/Tabs/index.js deleted file mode 100644 index f2db1445..00000000 --- a/src/components/base/Tabs/index.js +++ /dev/null @@ -1,68 +0,0 @@ -// @flow - -import React, { Fragment } from 'react' -import styled from 'styled-components' - -import type { Element } from 'react' - -import Box, { Tabbable } from 'components/base/Box' - -const WrapperTab = styled(Box).attrs({ - horizontal: true, -})` - border-bottom: 1px solid ${p => p.theme.colors.fog}; -` - -const Tab = styled(Tabbable).attrs({ - flex: 1, - pb: 2, - alignItems: 'center', - justifyContent: 'center', - fontSize: 3, -})` - border-bottom: 2px solid transparent; - border-bottom-color: ${p => (p.isActive ? p.theme.colors.wallet : '')}; - color: ${p => - p.isActive - ? p.theme.colors.wallet - : p.isDisabled - ? p.theme.colors.grey - : p.theme.colors.graphite}; - margin-bottom: -1px; - outline: none; - cursor: ${p => (p.isActive ? 'default' : p.isDisabled ? 'not-allowed' : 'pointer')}; - max-width: 200px; -` - -type Item = { - key: string | number, - isDisabled?: boolean, - title: string | Element, - render: () => Element, -} - -type Props = { - items: Array, - index: number, - onTabClick: number => void, -} - -const Tabs = ({ items, index, onTabClick }: Props) => ( - - - {items.map((item, i) => ( - onTabClick(i)} - > - {item.title} - - ))} - - {items[index] && items[index].render()} - -) - -export default Tabs diff --git a/src/components/base/Tabs/stories.js b/src/components/base/Tabs/stories.js deleted file mode 100644 index bb89efcc..00000000 --- a/src/components/base/Tabs/stories.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react' - -import { number } from '@storybook/addon-knobs' -import { action } from '@storybook/addon-actions' -import { storiesOf } from '@storybook/react' - -import Tabs from 'components/base/Tabs' - -const stories = storiesOf('Components/base', module) - -stories.add('Tabs', () => ( -
{'first tab content'}
, - }, - { - key: 'second', - title: 'second tab', - render: () =>
{'second tab content'}
, - }, - ]} - /> -)) From 3c8bbfffad37b0ef25deef4e07a07ff5d9445d76 Mon Sep 17 00:00:00 2001 From: meriadec Date: Fri, 13 Apr 2018 14:31:19 +0200 Subject: [PATCH 02/18] Update OperationList hover color --- src/components/OperationsList/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/OperationsList/index.js b/src/components/OperationsList/index.js index e09c8f73..1684666a 100644 --- a/src/components/OperationsList/index.js +++ b/src/components/OperationsList/index.js @@ -16,6 +16,8 @@ import type { Account, Operation as OperationType } from '@ledgerhq/wallet-commo import noop from 'lodash/noop' import keyBy from 'lodash/keyBy' +import { rgba } from 'styles/helpers' + import type { T } from 'types/common' import { MODAL_OPERATION_DETAILS } from 'config/constants' @@ -71,7 +73,7 @@ const OperationRaw = styled(Box).attrs({ } &:hover { - background: ${p => p.theme.colors.lightFog}; + background: ${p => rgba(p.theme.colors.wallet, 0.04)}; } ` From 01313f5065a6d6a442d7b6626cb53cb8ac7496be Mon Sep 17 00:00:00 2001 From: meriadec Date: Fri, 13 Apr 2018 17:14:49 +0200 Subject: [PATCH 03/18] Integrate the new settings design --- src/components/SettingsPage/Display.js | 98 -------------- src/components/SettingsPage/Money.js | 88 ------------- src/components/SettingsPage/Profile.js | 124 ------------------ .../SettingsPage/SettingsSection.js | 95 ++++++++++++++ src/components/SettingsPage/index.js | 62 +++------ src/components/SettingsPage/sections/About.js | 62 +++++++++ .../SettingsPage/sections/Currencies.js | 53 ++++++++ .../SettingsPage/sections/Display.js | 93 +++++++++++++ .../SettingsPage/sections/Profile.js | 56 ++++++++ .../SettingsPage/{ => sections}/Tools.js | 0 src/components/base/Select/index.js | 12 +- src/icons/Currencies.js | 12 ++ src/icons/Display.js | 12 ++ src/icons/Help.js | 12 ++ src/types/common.js | 16 +-- static/i18n/en/settings.yml | 34 +++-- 16 files changed, 452 insertions(+), 377 deletions(-) delete mode 100644 src/components/SettingsPage/Display.js delete mode 100644 src/components/SettingsPage/Money.js delete mode 100644 src/components/SettingsPage/Profile.js create mode 100644 src/components/SettingsPage/SettingsSection.js create mode 100644 src/components/SettingsPage/sections/About.js create mode 100644 src/components/SettingsPage/sections/Currencies.js create mode 100644 src/components/SettingsPage/sections/Display.js create mode 100644 src/components/SettingsPage/sections/Profile.js rename src/components/SettingsPage/{ => sections}/Tools.js (100%) create mode 100644 src/icons/Currencies.js create mode 100644 src/icons/Display.js create mode 100644 src/icons/Help.js diff --git a/src/components/SettingsPage/Display.js b/src/components/SettingsPage/Display.js deleted file mode 100644 index db3f7dc6..00000000 --- a/src/components/SettingsPage/Display.js +++ /dev/null @@ -1,98 +0,0 @@ -// @flow - -import React, { PureComponent } from 'react' - -import type { SettingsDisplay, T } from 'types/common' - -import Box, { Card } from 'components/base/Box' -import Button from 'components/base/Button' -import Label from 'components/base/Label' -import Select from 'components/base/Select' - -type InputValue = SettingsDisplay - -type Props = { - t: T, - settings: SettingsDisplay, - onSaveSettings: Function, -} - -type State = { - inputValue: InputValue, -} - -class TabProfile extends PureComponent { - state = { - inputValue: { - language: this.props.settings.language, - }, - } - - getDatas() { - const { t } = this.props - - return { - languages: [ - { - key: 'en', - name: t('language:en'), - }, - { - key: 'fr', - name: t('language:fr'), - }, - ], - } - } - - handleChangeInput = (key: $Keys) => (value: $Values) => - this.setState(prev => ({ - inputValue: { - ...prev.inputValue, - [key]: value, - }, - })) - - handleSubmit = (e: SyntheticEvent) => { - e.preventDefault() - - const { onSaveSettings } = this.props - const { inputValue } = this.state - - onSaveSettings({ - ...inputValue, - }) - } - - render() { - const { t } = this.props - const { inputValue } = this.state - - const { languages } = this.getDatas() - - const currentLanguage = languages.find(l => l.key === inputValue.language) - - return ( -
- - - - this.handleChangeInput('counterValue')(item.key)} - renderSelected={item => item && item.name} - value={currentCounterValues} - items={counterValues} - /> - - - - - -
- ) - } -} - -export default TabProfile diff --git a/src/components/SettingsPage/Profile.js b/src/components/SettingsPage/Profile.js deleted file mode 100644 index 1b7b50fe..00000000 --- a/src/components/SettingsPage/Profile.js +++ /dev/null @@ -1,124 +0,0 @@ -// @flow - -import React, { PureComponent } from 'react' -import { connect } from 'react-redux' -import bcrypt from 'bcryptjs' - -import get from 'lodash/get' -import set from 'lodash/set' - -import { setEncryptionKey } from 'helpers/db' - -import type { SettingsProfile, T } from 'types/common' - -import { unlock } from 'reducers/application' - -import Box, { Card } from 'components/base/Box' -import Input from 'components/base/Input' -import CheckBox from 'components/base/CheckBox' -import Button from 'components/base/Button' -import Label from 'components/base/Label' - -type InputValue = SettingsProfile - -type Props = { - t: T, - settings: SettingsProfile, - onSaveSettings: Function, - unlock: Function, -} -type State = { - inputValue: InputValue, -} - -const mapDispatchToProps = { - unlock, -} - -class TabProfile extends PureComponent { - state = { - inputValue: { - password: { - ...this.props.settings.password, - value: undefined, - }, - }, - } - - handleChangeInput = (key: string) => (value: $Values) => - this.setState(prev => ({ - inputValue: { - ...set(prev.inputValue, key, value), - }, - })) - - handleSubmit = (e: SyntheticEvent) => { - e.preventDefault() - - const { onSaveSettings, unlock } = this.props - const { inputValue } = this.state - - const settings = { - ...inputValue, - password: { - ...inputValue.password, - value: '', - }, - } - - const password = get(inputValue, 'password', {}) - - if (password.state === true && password.value.trim() !== '') { - settings.password.value = bcrypt.hashSync(password.value, 8) - setEncryptionKey('accounts', password.value) - } else { - setEncryptionKey('accounts', undefined) - } - - unlock() - - onSaveSettings(settings) - } - - render() { - const { t } = this.props - const { inputValue } = this.state - - const isPasswordChecked = get(inputValue, 'password.state', false) - return ( -
- - - {get(inputValue, 'password.state') === true && ( - - - - - )} - - - - -
- ) - } -} - -export default connect(null, mapDispatchToProps)(TabProfile) diff --git a/src/components/SettingsPage/SettingsSection.js b/src/components/SettingsPage/SettingsSection.js new file mode 100644 index 00000000..d555dc5a --- /dev/null +++ b/src/components/SettingsPage/SettingsSection.js @@ -0,0 +1,95 @@ +// @flow + +import React from 'react' +import styled from 'styled-components' + +import { rgba } from 'styles/helpers' + +import Box, { Card } from 'components/base/Box' + +export const SettingsSection = styled(Card).attrs({ p: 0 })`` + +const SettingsSectionHeaderContainer = styled(Box).attrs({ + p: 4, + horizontal: true, + align: 'center', +})` + border-bottom: 1px solid ${p => p.theme.colors.lightFog}; + line-height: normal; +` + +const RoundIconContainer = styled(Box).attrs({ + align: 'center', + justify: 'center', + bg: p => rgba(p.theme.colors.wallet, 0.2), + color: 'wallet', +})` + height: 30px; + width: 30px; + border-radius: 50%; +` + +export const SettingsSectionBody = styled(Box)` + > * + * { + border-top: 1px solid ${p => p.theme.colors.lightFog}; + } +` + +export function SettingsSectionHeader({ + title, + desc, + icon, +}: { + title: string, + desc: string, + icon: any, +}) { + return ( + + {icon} + + + {title} + + + {desc} + + + + ) +} + +const SettingsSectionRowContainer = styled(Box).attrs({ p: 4, horizontal: true, align: 'center' })` + cursor: ${p => (p.onClick ? 'pointer' : '')}; +` + +export function SettingsSectionRow({ + title, + desc, + children, + onClick, +}: { + title: string, + desc: string, + children?: any, + onClick?: ?Function, +}) { + return ( + + + + {title} + + + {desc} + + + {children} + + ) +} + +SettingsSectionRow.defaultProps = { + children: null, + onClick: null, +} diff --git a/src/components/SettingsPage/index.js b/src/components/SettingsPage/index.js index cfcc4bbf..3c8bd4b6 100644 --- a/src/components/SettingsPage/index.js +++ b/src/components/SettingsPage/index.js @@ -4,7 +4,6 @@ import React, { PureComponent } from 'react' import { compose } from 'redux' import { connect } from 'react-redux' import { translate } from 'react-i18next' -import moment from 'moment' import type { Settings, T } from 'types/common' import type { SaveSettings } from 'actions/settings' @@ -16,10 +15,10 @@ import { fetchCounterValues } from 'actions/counterValues' import Pills from 'components/base/Pills' import Box from 'components/base/Box' -import TabDisplay from './Display' -import TabProfile from './Profile' -import TabTools from './Tools' -import TabMoney from './Money' +import SectionDisplay from './sections/Display' +import SectionCurrencies from './sections/Currencies' +import SectionProfile from './sections/Profile' +import SectionAbout from './sections/About' const mapStateToProps = state => ({ settings: state.settings, @@ -55,68 +54,45 @@ class SettingsPage extends PureComponent { } handleSaveSettings = newSettings => { - const { fetchCounterValues, saveSettings, i18n, settings } = this.props + const { fetchCounterValues, saveSettings, settings } = this.props saveSettings(newSettings) - if (newSettings.language !== settings.language) { - i18n.changeLanguage(newSettings.language) - moment.locale(newSettings.language) - } - if (newSettings.counterValue !== settings.counterValue) { fetchCounterValues() } } render() { - const { settings, t } = this.props + const { settings, t, i18n, saveSettings } = this.props const { tab } = this.state const props = { t, settings, - onSaveSettings: this.handleSaveSettings, + saveSettings, } this._items = [ { key: 'display', label: t('settings:tabs.display'), - value: () => , - }, - { - key: 'money', - label: t('settings:tabs.money'), - value: () => , - }, - { - key: 'material', - isDisabled: true, - label: t('settings:tabs.material'), - value: () =>
{'Matériel'}
, + value: () => , }, { - key: 'app', - isDisabled: true, - label: t('settings:tabs.app'), - value: () =>
{'App (beta)'}
, - }, - { - key: 'tools', - label: t('settings:tabs.tools'), - value: () => , - }, - { - key: 'blockchain', - isDisabled: true, - label: t('settings:tabs.blockchain'), - value: () =>
{'Blockchain'}
, + key: 'currencies', + label: t('settings:tabs.currencies'), + value: () => , }, { key: 'profile', label: t('settings:tabs.profile'), - value: () => , + value: () => , + }, + { + key: 'about', + label: t('settings:tabs.about'), + value: () => , }, ] @@ -124,10 +100,10 @@ class SettingsPage extends PureComponent { return ( - + {t('settings:title')} - + {item.value && item.value()} ) diff --git a/src/components/SettingsPage/sections/About.js b/src/components/SettingsPage/sections/About.js new file mode 100644 index 00000000..e8fba224 --- /dev/null +++ b/src/components/SettingsPage/sections/About.js @@ -0,0 +1,62 @@ +// @flow + +import React, { PureComponent } from 'react' +import { shell } from 'electron' + +import type { T } from 'types/common' + +import IconHelp from 'icons/Help' +import IconChevronRight from 'icons/ChevronRight' + +import { + SettingsSection as Section, + SettingsSectionHeader as Header, + SettingsSectionBody as Body, + SettingsSectionRow as Row, +} from '../SettingsSection' + +type Props = { + t: T, +} + +class SectionAbout extends PureComponent { + handleOpenLink = (url: string) => () => shell.openExternal(url) + + render() { + const { t } = this.props + return ( +
+
} + title={t('settings:tabs.about')} + desc="Lorem ipsum dolor sit amet" + /> + + + + + + + + + + + +
+ ) + } +} + +export default SectionAbout diff --git a/src/components/SettingsPage/sections/Currencies.js b/src/components/SettingsPage/sections/Currencies.js new file mode 100644 index 00000000..4a6590a4 --- /dev/null +++ b/src/components/SettingsPage/sections/Currencies.js @@ -0,0 +1,53 @@ +// @flow + +import React, { PureComponent } from 'react' + +import type { T } from 'types/common' + +import IconCurrencies from 'icons/Currencies' + +import { + SettingsSection as Section, + SettingsSectionHeader as Header, + SettingsSectionBody as Body, + SettingsSectionRow as Row, +} from '../SettingsSection' + +type Props = { + t: T, +} + +class TabCurrencies extends PureComponent { + render() { + const { t } = this.props + return ( +
+
} + title={t('settings:tabs.currencies')} + desc="Lorem ipsum dolor sit amet" + /> + + + + + + +
+ ) + } +} + +export default TabCurrencies diff --git a/src/components/SettingsPage/sections/Display.js b/src/components/SettingsPage/sections/Display.js new file mode 100644 index 00000000..d2373463 --- /dev/null +++ b/src/components/SettingsPage/sections/Display.js @@ -0,0 +1,93 @@ +// @flow + +import React, { PureComponent } from 'react' +import moment from 'moment' + +import type { Settings, T } from 'types/common' + +import Select from 'components/base/Select' +import IconDisplay from 'icons/Display' + +import { + SettingsSection as Section, + SettingsSectionHeader as Header, + SettingsSectionBody as Body, + SettingsSectionRow as Row, +} from '../SettingsSection' + +type Props = { + t: T, + settings: Settings, + saveSettings: Function, + i18n: Object, +} + +type State = { + cachedLanguageKey: string, +} + +class TabProfile extends PureComponent { + state = { + cachedLanguageKey: this.props.settings.language, + } + + getDatas() { + const { t } = this.props + return { + languages: [{ key: 'en', name: t('language:en') }, { key: 'fr', name: t('language:fr') }], + } + } + + handleChangeLanguage = (languageKey: string) => { + const { i18n, saveSettings } = this.props + this.setState({ cachedLanguageKey: languageKey }) + window.requestIdleCallback(() => { + i18n.changeLanguage(languageKey) + moment.locale(languageKey) + saveSettings({ language: languageKey }) + }) + } + + render() { + const { t } = this.props + const { cachedLanguageKey } = this.state + const { languages } = this.getDatas() + const currentLanguage = languages.find(l => l.key === cachedLanguageKey) + + return ( +
+
} + title={t('settings:tabs.display')} + desc="Lorem ipsum dolor sit amet" + /> + + + {'-'} + + + this.handleChangeCounterValue(item)} + itemToString={item => (item ? item.name : '')} + renderSelected={item => item && item.name} + items={fiats} + value={cachedCounterValue} + /> } + {...getInputProps({ placeholder })} /> ) : ( diff --git a/static/i18n/en/settings.yml b/static/i18n/en/settings.yml index 1e7843ec..69a9ef43 100644 --- a/static/i18n/en/settings.yml +++ b/static/i18n/en/settings.yml @@ -23,8 +23,16 @@ currencies: explorer: Blockchain explorer explorerDesc: Lorem ipsum dolor sit amet profile: - protectWithPassword: Protect local data with a password + username: Username + usernameDesc: Lorem ipsum dolor sit amet password: Password + passwordDesc: Lorem ipsum dolor sit amet + sync: Sync accounts + syncDesc: Lorem ipsum dolor sit amet + export: Export logs + exportDesc: Lorem ipsum dolor sit amet + reset: Reset application + resetDesc: Lorem ipsum dolor sit amet about: faq: FAQ faqDesc: Lorem ipsum dolor sit amet diff --git a/yarn.lock b/yarn.lock index 7448044d..eb9f1709 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8013,9 +8013,6 @@ ledger-test-library@KhalilBellakrid/ledger-test-library-nodejs#7d37482: dependencies: axios "^0.17.1" bindings "^1.3.0" - electron "^1.8.2" - electron-builder "^20.0.4" - electron-rebuild "^1.7.3" nan "^2.6.2" prebuild-install "^2.2.2" From 5a569bca22f4943695ae9c7dd673a246c318413a Mon Sep 17 00:00:00 2001 From: meriadec Date: Mon, 16 Apr 2018 10:25:56 +0200 Subject: [PATCH 05/18] Ability to change the username --- src/components/SettingsPage/index.js | 2 +- .../SettingsPage/sections/Profile.js | 34 +++++++++++++++---- src/components/TopBar.js | 6 ++-- src/reducers/settings.js | 1 + 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/components/SettingsPage/index.js b/src/components/SettingsPage/index.js index 3c8bd4b6..a4f45b67 100644 --- a/src/components/SettingsPage/index.js +++ b/src/components/SettingsPage/index.js @@ -43,7 +43,7 @@ type State = { class SettingsPage extends PureComponent { state = { - tab: 0, + tab: 2, } _items = [] diff --git a/src/components/SettingsPage/sections/Profile.js b/src/components/SettingsPage/sections/Profile.js index 57b7b5f9..8af6b884 100644 --- a/src/components/SettingsPage/sections/Profile.js +++ b/src/components/SettingsPage/sections/Profile.js @@ -3,11 +3,11 @@ import React, { PureComponent } from 'react' import { connect } from 'react-redux' -// import type { Settings, T } from 'types/common' -import type { T } from 'types/common' +import type { Settings, T } from 'types/common' import { unlock } from 'reducers/application' +import Input from 'components/base/Input' import IconUser from 'icons/User' import { @@ -19,22 +19,37 @@ import { type Props = { t: T, - // settings: Settings, - // onSaveSettings: Function, + settings: Settings, + saveSettings: Function, // unlock: Function, } type State = {} +const mapStateToProps = state => ({ + username: state.settings, +}) + const mapDispatchToProps = { unlock, } class TabProfile extends PureComponent { - state = {} + state = { + username: this.props.settings.username, + } + + handleChangeUsername = username => { + const { saveSettings } = this.props + this.setState({ username }) + window.requestIdleCallback(() => { + saveSettings({ username: username.trim() || 'Anonymous' }) + }) + } render() { const { t } = this.props + const { username } = this.state return (
@@ -45,7 +60,12 @@ class TabProfile extends PureComponent { /> - {'-'} + {'-'} @@ -65,4 +85,4 @@ class TabProfile extends PureComponent { } } -export default connect(null, mapDispatchToProps)(TabProfile) +export default connect(mapStateToProps, mapDispatchToProps)(TabProfile) diff --git a/src/components/TopBar.js b/src/components/TopBar.js index 990c8a91..5193475c 100644 --- a/src/components/TopBar.js +++ b/src/components/TopBar.js @@ -75,6 +75,7 @@ const DropDownItem = styled(DDItem).attrs({ ` const mapStateToProps = state => ({ + username: state.settings.username, hasAccounts: getAccounts(state).length > 0, hasPassword: hasPassword(state), }) @@ -88,6 +89,7 @@ type Props = { hasAccounts: boolean, hasPassword: boolean, lock: Function, + username: string, } type State = { @@ -145,7 +147,7 @@ class TopBar extends PureComponent { handleLock = () => this.props.lock() render() { - const { hasPassword, hasAccounts, t } = this.props + const { hasPassword, hasAccounts, username, t } = this.props const { sync } = this.state return ( @@ -197,7 +199,7 @@ class TopBar extends PureComponent { justifyContent="center" offsetTop={-2} > - {'Khalil Benihoud'} + {username} diff --git a/src/reducers/settings.js b/src/reducers/settings.js index 0ab84c52..9775eb56 100644 --- a/src/reducers/settings.js +++ b/src/reducers/settings.js @@ -10,6 +10,7 @@ import type { Settings } from 'types/common' export type SettingsState = Object const defaultState: SettingsState = { + username: 'Anonymous', counterValue: 'USD', language: 'en', orderAccounts: 'balance|asc', From 5708458a998de6f383ce23d0cc297e4496bbc5e0 Mon Sep 17 00:00:00 2001 From: meriadec Date: Mon, 16 Apr 2018 12:27:42 +0200 Subject: [PATCH 06/18] Refactor button style --- src/components/base/Button/index.js | 122 +++++++++++++++----------- src/components/base/Button/stories.js | 11 +++ 2 files changed, 80 insertions(+), 53 deletions(-) diff --git a/src/components/base/Button/index.js b/src/components/base/Button/index.js index 51f7116e..1cec72ff 100644 --- a/src/components/base/Button/index.js +++ b/src/components/base/Button/index.js @@ -9,36 +9,88 @@ import { darken, lighten } from 'styles/helpers' import fontFamily from 'styles/styled/fontFamily' +const buttonStyles = { + primary: { + default: p => ` + background: ${p.disabled ? lighten(p.theme.colors.wallet, 0.1) : p.theme.colors.wallet}; + color: white; + `, + hover: p => ` + background: ${lighten(p.theme.colors.wallet, 0.05)}; + `, + active: p => ` + background: ${darken(p.theme.colors.wallet, 0.1)}; + `, + }, + danger: { + default: p => ` + background: ${p.disabled ? lighten(p.theme.colors.alertRed, 0.3) : p.theme.colors.alertRed}; + color: white; + `, + hover: p => ` + background: ${lighten(p.theme.colors.alertRed, 0.1)}; + `, + active: p => ` + background: ${darken(p.theme.colors.alertRed, 0.1)}; + `, + }, + outline: { + default: p => ` + background: transparent; + border: 1px solid ${p.theme.colors.wallet}; + color: ${p.theme.colors.wallet}; + `, + active: p => ` + color: ${darken(p.theme.colors.wallet, 0.1)}; + border-color: ${darken(p.theme.colors.wallet, 0.1)}; + `, + }, + icon: { + default: () => ` + font-size: ${fontSize[3]}px; + padding-left: ${space[1]}px; + padding-right: ${space[1]}px; + `, + }, +} + +function getStyles(props, state) { + let output = `` + for (const s in buttonStyles) { + if (buttonStyles.hasOwnProperty(s) && props[s] === true) { + const style = buttonStyles[s][state] + if (style) { + output += style(props) + } + } + } + return output +} + const Base = styled.button.attrs({ ff: 'Museo Sans|Regular', fontSize: p => p.fontSize || 3, - px: p => (p.primary ? (p.small ? 2 : 3) : 2), + px: 2, + color: 'grey', })` ${space}; ${color}; ${fontSize}; ${fontWeight}; ${fontFamily}; + border: none; border-radius: ${p => p.theme.radii[1]}px; - border: ${p => (p.outline ? `1px solid ${p.theme.colors.wallet}` : 'none')}; - color: ${p => (p.outline ? p.theme.colors.wallet : '')}; cursor: ${p => (p.disabled ? 'default' : 'pointer')}; height: ${p => (p.small ? 30 : 36)}px; + pointer-events: ${p => (p.disabled ? 'none' : '')}; outline: none; + ${p => getStyles(p, 'default')}; &:hover { - background: ${p => (p.disabled ? '' : p.primary ? lighten(p.theme.colors.wallet, 0.05) : '')}; + ${p => getStyles(p, 'hover')}; } - &:active { - color: ${p => - p.primary - ? '' - : p.outline - ? darken(p.theme.colors.wallet, 0.1) - : darken(p.theme.colors.grey, 0.2)}; - border-color: ${p => (p.outline ? darken(p.theme.colors.wallet, 0.1) : '')}; - background: ${p => (p.primary ? darken(p.theme.colors.wallet, 0.1) : '')}; + ${p => getStyles(p, 'active')}; } ` @@ -46,54 +98,17 @@ type Props = { children?: any, icon?: string, primary?: boolean, + danger?: boolean, disabled?: boolean, onClick?: Function, small?: boolean, } -function getProps({ disabled, icon, primary }: Object) { - const props = (predicate, props, defaults = {}) => (predicate ? props : defaults) - - return { - color: 'grey', - ...props( - icon, - { - fontSize: 3, - px: 1, - }, - { - fontSize: 4, - px: 3, - }, - ), - ...props( - primary, - { - color: 'white', - bg: 'wallet', - }, - { - bg: 'transparent', - }, - ), - ...props(disabled, { - color: 'grey', - bg: 'lightFog', - }), - } -} - const Button = (props: Props) => { - const { onClick, children, primary, disabled } = props + const { onClick, children, disabled } = props return ( - + {children} ) @@ -106,6 +121,7 @@ Button.defaultProps = { onClick: noop, primary: false, small: false, + danger: false, } export default Button diff --git a/src/components/base/Button/stories.js b/src/components/base/Button/stories.js index 039b553a..78ee935b 100644 --- a/src/components/base/Button/stories.js +++ b/src/components/base/Button/stories.js @@ -47,6 +47,17 @@ stories.add('Button', () => ( + + danger + + + + + + + outline From e04514e782b452e58b3f3aec6f3f8476caae9587 Mon Sep 17 00:00:00 2001 From: meriadec Date: Mon, 16 Apr 2018 14:51:21 +0200 Subject: [PATCH 07/18] Add ConfirmModal component --- src/components/base/Modal/ConfirmModal.js | 59 ++++++++++++++++++++++ src/components/base/Modal/stories.js | 60 +++++++++++++++-------- 2 files changed, 99 insertions(+), 20 deletions(-) create mode 100644 src/components/base/Modal/ConfirmModal.js diff --git a/src/components/base/Modal/ConfirmModal.js b/src/components/base/Modal/ConfirmModal.js new file mode 100644 index 00000000..4fd3364f --- /dev/null +++ b/src/components/base/Modal/ConfirmModal.js @@ -0,0 +1,59 @@ +// @flow + +import React, { PureComponent } from 'react' +import { translate } from 'react-i18next' + +import type { T } from 'types/common' + +import Button from 'components/base/Button' +import Box from 'components/base/Box' + +import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from './index' + +type Props = { + isOpened: boolean, + isDanger: boolean, + title: string, + subTitle: string, + desc: string, + confirmText: string, + cancelText: string, + t: T, +} + +class ConfirmModal extends PureComponent { + render() { + const { isOpened, title, subTitle, desc, confirmText, cancelText, isDanger, t } = this.props + + const realConfirmText = confirmText || t('common:confirm') + const realCancelText = cancelText || t('common:cancel') + return ( + ( + + {title} + + {subTitle && ( + + {subTitle} + + )} + + {desc} + + + + + + + + )} + /> + ) + } +} + +export default translate()(ConfirmModal) diff --git a/src/components/base/Modal/stories.js b/src/components/base/Modal/stories.js index 0516912a..1b95d374 100644 --- a/src/components/base/Modal/stories.js +++ b/src/components/base/Modal/stories.js @@ -2,29 +2,49 @@ import React from 'react' import { storiesOf } from '@storybook/react' -import { boolean } from '@storybook/addon-knobs' +import { action } from '@storybook/addon-actions' +import { boolean, text } from '@storybook/addon-knobs' -import { Modal, ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal' +import { + Modal, + ModalBody, + ModalTitle, + ModalContent, + ModalFooter, + ConfirmModal, +} from 'components/base/Modal' import Box from 'components/base/Box' import Button from 'components/base/Button' const stories = storiesOf('Components/base', module) -stories.add('Modal', () => { - const isOpened = boolean('isOpened', true) - return ( - ( - - {'modal title'} - {'this is the modal content'} - - {'modal footer'} - - - - )} - /> - ) -}) +stories.add('Modal', () => ( + ( + + {'modal title'} + {'this is the modal content'} + + {'modal footer'} + + + + )} + /> +)) + +stories.add('ConfirmModal', () => ( + +)) From 2a97d8996540a438e30879ae54e6ce5282e2d7f5 Mon Sep 17 00:00:00 2001 From: meriadec Date: Mon, 16 Apr 2018 15:13:05 +0200 Subject: [PATCH 08/18] Connect username and hard reset --- src/components/DashboardPage/index.js | 6 ++- src/components/SettingsPage/index.js | 2 +- .../SettingsPage/sections/Display.js | 4 +- .../SettingsPage/sections/Profile.js | 54 ++++++++++++++----- src/components/base/Box/index.js | 7 +++ src/components/base/Button/index.js | 1 + src/components/base/Input/index.js | 4 +- src/components/base/Modal/ConfirmModal.js | 21 ++++++-- src/components/base/Modal/index.js | 1 + src/types/common.js | 1 + static/i18n/en/common.yml | 1 + static/i18n/en/settings.yml | 5 ++ 12 files changed, 85 insertions(+), 22 deletions(-) diff --git a/src/components/DashboardPage/index.js b/src/components/DashboardPage/index.js index 443f1101..215f87ef 100644 --- a/src/components/DashboardPage/index.js +++ b/src/components/DashboardPage/index.js @@ -33,6 +33,7 @@ import AccountCard from './AccountCard' import AccountsOrder from './AccountsOrder' const mapStateToProps = state => ({ + username: state.settings.username, accounts: getVisibleAccounts(state), counterValue: getCounterValueCode(state), }) @@ -48,6 +49,7 @@ type Props = { accounts: Account[], push: Function, counterValue: string, + username: string, } type State = { @@ -109,7 +111,7 @@ class DashboardPage extends PureComponent { _cacheBalance = null render() { - const { push, accounts, t, counterValue } = this.props + const { push, accounts, t, counterValue, username } = this.props const { accountsChunk, selectedTime, daysCount } = this.state const totalAccounts = accounts.length @@ -119,7 +121,7 @@ class DashboardPage extends PureComponent { - {t('dashboard:greetings', { name: 'Khalil' })} + {t('dashboard:greetings', { name: username })} {totalAccounts > 0 diff --git a/src/components/SettingsPage/index.js b/src/components/SettingsPage/index.js index a4f45b67..3c8bd4b6 100644 --- a/src/components/SettingsPage/index.js +++ b/src/components/SettingsPage/index.js @@ -43,7 +43,7 @@ type State = { class SettingsPage extends PureComponent { state = { - tab: 2, + tab: 0, } _items = [] diff --git a/src/components/SettingsPage/sections/Display.js b/src/components/SettingsPage/sections/Display.js index bec97e04..326ad6f8 100644 --- a/src/components/SettingsPage/sections/Display.js +++ b/src/components/SettingsPage/sections/Display.js @@ -31,7 +31,7 @@ type Props = { type State = { cachedLanguageKey: string, - cachedCounterValue: ?string, + cachedCounterValue: ?Object, } class TabProfile extends PureComponent { @@ -47,7 +47,7 @@ class TabProfile extends PureComponent { } } - handleChangeCounterValue = item => { + handleChangeCounterValue = (item: Object) => { const { saveSettings } = this.props this.setState({ cachedCounterValue: item.fiat }) window.requestIdleCallback(() => { diff --git a/src/components/SettingsPage/sections/Profile.js b/src/components/SettingsPage/sections/Profile.js index 8af6b884..bc02f62f 100644 --- a/src/components/SettingsPage/sections/Profile.js +++ b/src/components/SettingsPage/sections/Profile.js @@ -2,12 +2,16 @@ import React, { PureComponent } from 'react' import { connect } from 'react-redux' +import { remote } from 'electron' import type { Settings, T } from 'types/common' import { unlock } from 'reducers/application' +import db from 'helpers/db' import Input from 'components/base/Input' +import Button from 'components/base/Button' +import { ConfirmModal } from 'components/base/Modal' import IconUser from 'icons/User' import { @@ -17,6 +21,14 @@ import { SettingsSectionRow as Row, } from '../SettingsSection' +const mapStateToProps = state => ({ + username: state.settings, +}) + +const mapDispatchToProps = { + unlock, +} + type Props = { t: T, settings: Settings, @@ -24,19 +36,15 @@ type Props = { // unlock: Function, } -type State = {} - -const mapStateToProps = state => ({ - username: state.settings, -}) - -const mapDispatchToProps = { - unlock, +type State = { + isHardResetModalOpened: boolean, + username: string, } class TabProfile extends PureComponent { state = { username: this.props.settings.username, + isHardResetModalOpened: false, } handleChangeUsername = username => { @@ -47,9 +55,18 @@ class TabProfile extends PureComponent { }) } + handleOpenHardResetModal = () => this.setState({ isHardResetModalOpened: true }) + handleCloseHardResetModal = () => this.setState({ isHardResetModalOpened: false }) + + handleHardReset = () => { + db.resetAll() + remote.app.relaunch() + remote.app.exit() + } + render() { const { t } = this.props - const { username } = this.state + const { username, isHardResetModalOpened } = this.state return (
@@ -71,15 +88,28 @@ class TabProfile extends PureComponent { {'-'} - {'-'} + - {'-'} + - {'-'} + + +
) } diff --git a/src/components/base/Box/index.js b/src/components/base/Box/index.js index bfa54a6c..9900d28e 100644 --- a/src/components/base/Box/index.js +++ b/src/components/base/Box/index.js @@ -11,12 +11,18 @@ import { fontSize, justifyContent, space, + style, } from 'styled-system' import fontFamily from 'styles/styled/fontFamily' import Text from 'components/base/Text' +const textAlign = style({ + prop: 'textAlign', + cssProperty: 'textAlign', +}) + const Box = styled.div` ${alignItems}; ${borderRadius}; @@ -27,6 +33,7 @@ const Box = styled.div` ${fontSize}; ${justifyContent}; ${space}; + ${textAlign}; display: flex; flex-shrink: ${p => (p.noShrink === true ? '0' : p.shrink === true ? '1' : '')}; diff --git a/src/components/base/Button/index.js b/src/components/base/Button/index.js index 1cec72ff..3d0aed42 100644 --- a/src/components/base/Button/index.js +++ b/src/components/base/Button/index.js @@ -72,6 +72,7 @@ const Base = styled.button.attrs({ fontSize: p => p.fontSize || 3, px: 2, color: 'grey', + bg: 'transparent', })` ${space}; ${color}; diff --git a/src/components/base/Input/index.js b/src/components/base/Input/index.js index c8bb5e5a..b03206fd 100644 --- a/src/components/base/Input/index.js +++ b/src/components/base/Input/index.js @@ -95,7 +95,7 @@ class Input extends PureComponent { handleClick = () => this._input && this._input.focus() - handleFocus = e => { + handleFocus = (e: SyntheticInputEvent) => { const { onFocus } = this.props this.setState({ isFocus: true, @@ -103,7 +103,7 @@ class Input extends PureComponent { onFocus(e) } - handleBlur = e => { + handleBlur = (e: SyntheticInputEvent) => { const { onBlur } = this.props this.setState({ isFocus: false, diff --git a/src/components/base/Modal/ConfirmModal.js b/src/components/base/Modal/ConfirmModal.js index 4fd3364f..0dccc693 100644 --- a/src/components/base/Modal/ConfirmModal.js +++ b/src/components/base/Modal/ConfirmModal.js @@ -18,18 +18,33 @@ type Props = { desc: string, confirmText: string, cancelText: string, + onReject: Function, + onConfirm: Function, t: T, } class ConfirmModal extends PureComponent { render() { - const { isOpened, title, subTitle, desc, confirmText, cancelText, isDanger, t } = this.props + const { + isOpened, + title, + subTitle, + desc, + confirmText, + cancelText, + isDanger, + onReject, + onConfirm, + t, + ...props + } = this.props const realConfirmText = confirmText || t('common:confirm') const realCancelText = cancelText || t('common:cancel') return ( ( {title} @@ -44,8 +59,8 @@ class ConfirmModal extends PureComponent {
- - + diff --git a/src/components/base/Modal/index.js b/src/components/base/Modal/index.js index 48e9c22d..ee76bae0 100644 --- a/src/components/base/Modal/index.js +++ b/src/components/base/Modal/index.js @@ -21,6 +21,7 @@ import GrowScroll from 'components/base/GrowScroll' import Defer from 'components/base/Defer' export { default as ModalBody } from './ModalBody' +export { default as ConfirmModal } from './ConfirmModal' const springConfig = { stiffness: 320, diff --git a/src/types/common.js b/src/types/common.js index 1789bfcf..13d68fb8 100644 --- a/src/types/common.js +++ b/src/types/common.js @@ -14,6 +14,7 @@ export type Devices = Array export type Settings = { language: string, + username: string, counterValue: string, password: { isEnabled: boolean, diff --git a/static/i18n/en/common.yml b/static/i18n/en/common.yml index 9a052f48..d30d81a9 100644 --- a/static/i18n/en/common.yml +++ b/static/i18n/en/common.yml @@ -1,4 +1,5 @@ ok: Okay +confirm: Confirm cancel: Cancel chooseWalletPlaceholder: Choose a wallet... currency: Currency diff --git a/static/i18n/en/settings.yml b/static/i18n/en/settings.yml index 69a9ef43..6eec9641 100644 --- a/static/i18n/en/settings.yml +++ b/static/i18n/en/settings.yml @@ -33,6 +33,7 @@ profile: exportDesc: Lorem ipsum dolor sit amet reset: Reset application resetDesc: Lorem ipsum dolor sit amet + resetButton: Hard reset about: faq: FAQ faqDesc: Lorem ipsum dolor sit amet @@ -40,3 +41,7 @@ about: contactUsDesc: Lorem ipsum dolor sit amet terms: Terms and Privacy policy termsDesc: Lorem ipsum dolor sit amet +hardResetModal: + title: Hard reset + subTitle: Are you sure houston? + desc: Lorem ipsum dolor sit amet From 4d5a8709c204f3b7ba99fcfc5e32e95cc74a044e Mon Sep 17 00:00:00 2001 From: meriadec Date: Mon, 16 Apr 2018 16:56:14 +0200 Subject: [PATCH 09/18] Integrate the PasswordModal --- src/components/SettingsPage/PasswordModal.js | 77 +++++++++++++++++++ .../SettingsPage/sections/Profile.js | 36 ++++++--- src/components/base/CheckBox/index.js | 6 +- src/components/base/Label.js | 1 + static/i18n/en/settings.yml | 8 ++ 5 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 src/components/SettingsPage/PasswordModal.js diff --git a/src/components/SettingsPage/PasswordModal.js b/src/components/SettingsPage/PasswordModal.js new file mode 100644 index 00000000..f21394b8 --- /dev/null +++ b/src/components/SettingsPage/PasswordModal.js @@ -0,0 +1,77 @@ +// @flow + +import React, { PureComponent } from 'react' + +import Box from 'components/base/Box' +import Button from 'components/base/Button' +import Input from 'components/base/Input' +import Label from 'components/base/Label' +import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'components/base/Modal' + +import type { T } from 'types/common' + +type Props = { + t: T, + onClose: Function, +} + +class PasswordModal extends PureComponent { + render() { + const { t, onClose, ...props } = this.props + return ( + ( + + {t('settings:profile.passwordModalTitle')} + + + {t('settings:profile.passwordModalSubtitle')} + + + {t('settings:profile.passwordModalDesc')} + + + + + + + + + + + + + + + + + + + + + + )} + /> + ) + } +} + +export default PasswordModal diff --git a/src/components/SettingsPage/sections/Profile.js b/src/components/SettingsPage/sections/Profile.js index bc02f62f..d3580600 100644 --- a/src/components/SettingsPage/sections/Profile.js +++ b/src/components/SettingsPage/sections/Profile.js @@ -10,9 +10,12 @@ import { unlock } from 'reducers/application' import db from 'helpers/db' import Input from 'components/base/Input' +import CheckBox from 'components/base/CheckBox' +import Box from 'components/base/Box' import Button from 'components/base/Button' import { ConfirmModal } from 'components/base/Modal' import IconUser from 'icons/User' +import PasswordModal from '../PasswordModal' import { SettingsSection as Section, @@ -21,10 +24,6 @@ import { SettingsSectionRow as Row, } from '../SettingsSection' -const mapStateToProps = state => ({ - username: state.settings, -}) - const mapDispatchToProps = { unlock, } @@ -57,6 +56,8 @@ class TabProfile extends PureComponent { handleOpenHardResetModal = () => this.setState({ isHardResetModalOpened: true }) handleCloseHardResetModal = () => this.setState({ isHardResetModalOpened: false }) + handleOpenPasswordModal = () => this.setState({ isPasswordModalOpened: true }) + handleClosePasswordModal = () => this.setState({ isPasswordModalOpened: false }) handleHardReset = () => { db.resetAll() @@ -64,10 +65,18 @@ class TabProfile extends PureComponent { remote.app.exit() } - render() { - const { t } = this.props - const { username, isHardResetModalOpened } = this.state + handleChangePasswordCheck = isChecked => { + if (isChecked) { + this.handleOpenPasswordModal() + } else { + // console.log(`decrypting data`) + } + } + render() { + const { t, settings } = this.props + const { username, isHardResetModalOpened, isPasswordModalOpened } = this.state + const isPasswordEnabled = settings.password.isEnabled === true return (
{ /> - {'-'} + + {isPasswordEnabled && } + + @@ -110,9 +122,15 @@ class TabProfile extends PureComponent { subTitle={t('settings:hardResetModal.subTitle')} desc={t('settings:hardResetModal.desc')} /> + +
) } } -export default connect(mapStateToProps, mapDispatchToProps)(TabProfile) +export default connect(null, mapDispatchToProps)(TabProfile) diff --git a/src/components/base/CheckBox/index.js b/src/components/base/CheckBox/index.js index 0898313e..3d4a7cc2 100644 --- a/src/components/base/CheckBox/index.js +++ b/src/components/base/CheckBox/index.js @@ -24,13 +24,13 @@ const Base = styled(Tabbable).attrs({ ` const Ball = styled.div` - width: 22px; - height: 22px; + width: 20px; + height: 20px; border-radius: 50%; background: white; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.15); transition: 250ms ease-in-out transform; - transform: translate3d(${p => (p.isChecked ? '28px' : '0')}, 0, 0); + transform: translate3d(${p => (p.isChecked ? '28px' : '2px')}, 0, 0); ` type Props = { diff --git a/src/components/base/Label.js b/src/components/base/Label.js index c24d2cea..eca826c4 100644 --- a/src/components/base/Label.js +++ b/src/components/base/Label.js @@ -8,6 +8,7 @@ export default styled.label.attrs({ ff: 'Museo Sans|Regular', color: 'grey', align: 'center', + display: 'block', })` ${alignItems}; ${color}; diff --git a/static/i18n/en/settings.yml b/static/i18n/en/settings.yml index 6eec9641..d6bb007f 100644 --- a/static/i18n/en/settings.yml +++ b/static/i18n/en/settings.yml @@ -27,6 +27,14 @@ profile: usernameDesc: Lorem ipsum dolor sit amet password: Password passwordDesc: Lorem ipsum dolor sit amet + changePassword: Change password + passwordModalTitle: Password + passwordModalSubtitle: Set a password to lock your application + passwordModalDesc: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh diam. + passwordModalPasswordInput: Current password + passwordModalNewPasswordInput: New password + passwordModalRepeatPasswordInput: Repeat password + passwordModalSave: Save sync: Sync accounts syncDesc: Lorem ipsum dolor sit amet export: Export logs From d58273bb383da33377cdfdc190dbb4fd233fe855 Mon Sep 17 00:00:00 2001 From: meriadec Date: Mon, 16 Apr 2018 18:08:08 +0200 Subject: [PATCH 10/18] Work on set password --- src/components/IsUnlocked.js | 2 +- src/components/SettingsPage/PasswordModal.js | 156 ++++++++++++------ src/components/SettingsPage/index.js | 2 +- .../SettingsPage/sections/Profile.js | 24 ++- src/reducers/settings.js | 5 +- 5 files changed, 136 insertions(+), 53 deletions(-) diff --git a/src/components/IsUnlocked.js b/src/components/IsUnlocked.js index e184aaea..c70b5cea 100644 --- a/src/components/IsUnlocked.js +++ b/src/components/IsUnlocked.js @@ -1,10 +1,10 @@ // @flow +import bcrypt from 'bcryptjs' import React, { Component } from 'react' import { connect } from 'react-redux' import { compose } from 'redux' import { translate } from 'react-i18next' -import bcrypt from 'bcryptjs' import type { Account } from '@ledgerhq/wallet-common/lib/types' import type { Settings, T } from 'types/common' diff --git a/src/components/SettingsPage/PasswordModal.js b/src/components/SettingsPage/PasswordModal.js index f21394b8..1e653e81 100644 --- a/src/components/SettingsPage/PasswordModal.js +++ b/src/components/SettingsPage/PasswordModal.js @@ -1,6 +1,10 @@ // @flow import React, { PureComponent } from 'react' +import { connect } from 'react-redux' +import bcrypt from 'bcryptjs' + +import { unlock } from 'reducers/application' import Box from 'components/base/Box' import Button from 'components/base/Button' @@ -10,68 +14,128 @@ import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'compone import type { T } from 'types/common' +const mapDispatchToProps = { + unlock, +} + type Props = { t: T, onClose: Function, + unlock: Function, + isPasswordEnabled: boolean, + currentPasswordHash: string, + onChangePassword: Function, +} + +type State = { + currentPassword: string, + newPassword: string, + repeatPassword: string, } -class PasswordModal extends PureComponent { +class PasswordModal extends PureComponent { + state = { + currentPassword: '', + newPassword: '', + repeatPassword: '', + } + + handleSave = (e: SyntheticEvent) => { + if (e) { + e.preventDefault() + } + if (!this.isValid()) { + return + } + const { currentPassword, newPassword, repeatPassword } = this.state + const { isPasswordEnabled, currentPasswordHash, onChangePassword } = this.props + if (isPasswordEnabled) { + const calculatedPasswordHash = bcrypt.hashSync(currentPassword, 8) + if (calculatedPasswordHash !== currentPasswordHash) { + return + } + onChangePassword(newPassword) + } else if (newPassword === repeatPassword) { + onChangePassword(newPassword) + } + } + + handleInputChange = key => value => this.setState({ [key]: value }) + + isValid = () => { + const { newPassword, repeatPassword } = this.state + return newPassword && newPassword === repeatPassword + } + render() { - const { t, onClose, ...props } = this.props + const { t, isPasswordEnabled, onClose, ...props } = this.props + const isValid = this.isValid() return ( ( - - {t('settings:profile.passwordModalTitle')} - - - {t('settings:profile.passwordModalSubtitle')} - - - {t('settings:profile.passwordModalDesc')} - - - - - - - - - - - - +
+ + {t('settings:profile.passwordModalTitle')} + + + {t('settings:profile.passwordModalSubtitle')} + + + {t('settings:profile.passwordModalDesc')} + + {isPasswordEnabled && ( + + + + + )} + + + + + + + + - - - - - - - + + + + + + +
)} /> ) } } -export default PasswordModal +export default connect(null, mapDispatchToProps)(PasswordModal) diff --git a/src/components/SettingsPage/index.js b/src/components/SettingsPage/index.js index 3c8bd4b6..a4f45b67 100644 --- a/src/components/SettingsPage/index.js +++ b/src/components/SettingsPage/index.js @@ -43,7 +43,7 @@ type State = { class SettingsPage extends PureComponent { state = { - tab: 0, + tab: 2, } _items = [] diff --git a/src/components/SettingsPage/sections/Profile.js b/src/components/SettingsPage/sections/Profile.js index d3580600..c636cbbe 100644 --- a/src/components/SettingsPage/sections/Profile.js +++ b/src/components/SettingsPage/sections/Profile.js @@ -3,11 +3,12 @@ import React, { PureComponent } from 'react' import { connect } from 'react-redux' import { remote } from 'electron' +import bcrypt from 'bcryptjs' import type { Settings, T } from 'types/common' import { unlock } from 'reducers/application' -import db from 'helpers/db' +import db, { setEncryptionKey } from 'helpers/db' import Input from 'components/base/Input' import CheckBox from 'components/base/CheckBox' @@ -32,7 +33,6 @@ type Props = { t: T, settings: Settings, saveSettings: Function, - // unlock: Function, } type State = { @@ -73,6 +73,21 @@ class TabProfile extends PureComponent { } } + handleChangePassword = (password: ?string) => { + const { saveSettings, unlock } = this.props + const hash = bcrypt.hashSync(password, 8) + setEncryptionKey('accounts', password) + window.requestIdleCallback(() => { + saveSettings({ + password: { + isEnabled: hash !== undefined, + value: hash, + }, + }) + unlock() + }) + } + render() { const { t, settings } = this.props const { username, isHardResetModalOpened, isPasswordModalOpened } = this.state @@ -125,8 +140,11 @@ class TabProfile extends PureComponent {
) diff --git a/src/reducers/settings.js b/src/reducers/settings.js index 9775eb56..67c90041 100644 --- a/src/reducers/settings.js +++ b/src/reducers/settings.js @@ -15,7 +15,8 @@ const defaultState: SettingsState = { language: 'en', orderAccounts: 'balance|asc', password: { - state: false, + isEnabled: false, + value: '', }, } @@ -35,7 +36,7 @@ const handlers: Object = { } export const hasPassword = (state: Object) => - get(state.settings, 'password.state', defaultState.password.state) + get(state.settings, 'password.isEnabled', defaultState.password.isEnabled) export const getCounterValueCode = (state: Object) => get(state.settings, 'counterValue', defaultState.counterValue) From 2093dfed32b116758c46bd1f151f1ff82e159fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Tue, 17 Apr 2018 10:21:37 +0200 Subject: [PATCH 11/18] Add change password, fix unlocked when you don't have accounts --- package.json | 14 ++--- src/components/IsUnlocked.js | 4 +- src/components/SettingsPage/PasswordModal.js | 56 +++++++++---------- src/components/SettingsPage/index.js | 2 +- .../SettingsPage/sections/Profile.js | 54 +++++++++++------- src/components/base/CheckBox/index.js | 10 ++-- src/components/base/InputPassword/index.js | 35 +++++++----- src/reducers/accounts.js | 4 +- yarn.lock | 54 +++++++++++------- 9 files changed, 133 insertions(+), 100 deletions(-) diff --git a/package.json b/package.json index 6f711fb6..a65b74fe 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "bs58check": "^2.1.1", "color": "^3.0.0", "cross-env": "^5.1.4", - "d3": "^5.0.0", + "d3": "^5.0.1", "debug": "^3.1.0", "downshift": "^1.31.7", "electron-store": "^1.3.0", @@ -74,8 +74,8 @@ "qs": "^6.5.1", "raven": "^2.5.0", "raven-js": "^3.24.1", - "react": "^16.3.1", - "react-dom": "^16.3.1", + "react": "^16.3.2", + "react-dom": "^16.3.2", "react-flip-ticker": "^0.3.0", "react-i18next": "^7.6.0", "react-mortal": "^3.2.0", @@ -92,7 +92,7 @@ "smooth-scrollbar": "^8.2.7", "source-map": "0.7.2", "source-map-support": "^0.5.4", - "styled-components": "^3.2.5", + "styled-components": "^3.2.6", "styled-system": "^2.2.1", "tippy.js": "^2.5.2", "ws": "^5.1.1", @@ -117,7 +117,7 @@ "babel-loader": "^8.0.0-beta.2", "babel-plugin-module-resolver": "^3.1.1", "babel-plugin-styled-components": "^1.5.0", - "chalk": "^2.3.1", + "chalk": "^2.4.0", "chance": "^1.0.13", "concurrently": "^3.5.1", "dotenv": "^5.0.1", @@ -142,9 +142,9 @@ "js-yaml": "^3.10.0", "lint-staged": "^7.0.4", "node-loader": "^0.6.0", - "prettier": "^1.12.0", + "prettier": "^1.12.1", "react-hot-loader": "^4.0.1", - "react-test-renderer": "^16.3.1", + "react-test-renderer": "^16.3.2", "webpack": "^4.5.0", "webpack-bundle-analyzer": "^2.11.1", "webpack-cli": "^2.0.14", diff --git a/src/components/IsUnlocked.js b/src/components/IsUnlocked.js index c70b5cea..113f55e7 100644 --- a/src/components/IsUnlocked.js +++ b/src/components/IsUnlocked.js @@ -25,7 +25,7 @@ import { isLocked, unlock } from 'reducers/application' import { getCounterValueCode } from 'reducers/settings' import Box from 'components/base/Box' -import Input from 'components/base/Input' +import InputPassword from 'components/base/InputPassword' type InputValue = { password: string, @@ -141,7 +141,7 @@ class IsUnlocked extends Component {
- (this._input = n)} placeholder={t('common:password')} diff --git a/src/components/SettingsPage/PasswordModal.js b/src/components/SettingsPage/PasswordModal.js index 1e653e81..a5921584 100644 --- a/src/components/SettingsPage/PasswordModal.js +++ b/src/components/SettingsPage/PasswordModal.js @@ -8,7 +8,7 @@ import { unlock } from 'reducers/application' import Box from 'components/base/Box' import Button from 'components/base/Button' -import Input from 'components/base/Input' +import InputPassword from 'components/base/InputPassword' import Label from 'components/base/Label' import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'components/base/Modal' @@ -30,15 +30,15 @@ type Props = { type State = { currentPassword: string, newPassword: string, - repeatPassword: string, +} + +const INITIAL_STATE = { + currentPassword: '', + newPassword: '', } class PasswordModal extends PureComponent { - state = { - currentPassword: '', - newPassword: '', - repeatPassword: '', - } + state = INITIAL_STATE handleSave = (e: SyntheticEvent) => { if (e) { @@ -47,32 +47,35 @@ class PasswordModal extends PureComponent { if (!this.isValid()) { return } - const { currentPassword, newPassword, repeatPassword } = this.state + const { currentPassword, newPassword } = this.state const { isPasswordEnabled, currentPasswordHash, onChangePassword } = this.props if (isPasswordEnabled) { - const calculatedPasswordHash = bcrypt.hashSync(currentPassword, 8) - if (calculatedPasswordHash !== currentPasswordHash) { + if (!bcrypt.compareSync(currentPassword, currentPasswordHash)) { return } onChangePassword(newPassword) - } else if (newPassword === repeatPassword) { + } else { onChangePassword(newPassword) } } handleInputChange = key => value => this.setState({ [key]: value }) + handleReset = () => this.setState(INITIAL_STATE) + isValid = () => { - const { newPassword, repeatPassword } = this.state - return newPassword && newPassword === repeatPassword + const { newPassword } = this.state + return newPassword } render() { const { t, isPasswordEnabled, onClose, ...props } = this.props + const { currentPassword, newPassword } = this.state const isValid = this.isValid() return ( ( @@ -90,35 +93,28 @@ class PasswordModal extends PureComponent { - )} - - + {t('settings:profile.passwordModalNewPasswordInput')} + + )} + - - - - diff --git a/src/components/SettingsPage/index.js b/src/components/SettingsPage/index.js index a4f45b67..3c8bd4b6 100644 --- a/src/components/SettingsPage/index.js +++ b/src/components/SettingsPage/index.js @@ -43,7 +43,7 @@ type State = { class SettingsPage extends PureComponent { state = { - tab: 2, + tab: 0, } _items = [] diff --git a/src/components/SettingsPage/sections/Profile.js b/src/components/SettingsPage/sections/Profile.js index c636cbbe..6352b023 100644 --- a/src/components/SettingsPage/sections/Profile.js +++ b/src/components/SettingsPage/sections/Profile.js @@ -7,6 +7,8 @@ import bcrypt from 'bcryptjs' import type { Settings, T } from 'types/common' +import debounce from 'lodash/debounce' + import { unlock } from 'reducers/application' import db, { setEncryptionKey } from 'helpers/db' @@ -32,11 +34,13 @@ const mapDispatchToProps = { type Props = { t: T, settings: Settings, + unlock: Function, saveSettings: Function, } type State = { isHardResetModalOpened: boolean, + isPasswordModalOpened: boolean, username: string, } @@ -44,14 +48,29 @@ class TabProfile extends PureComponent { state = { username: this.props.settings.username, isHardResetModalOpened: false, + isPasswordModalOpened: false, + } + + setPassword = hash => { + const { saveSettings, unlock } = this.props + setEncryptionKey('accounts', hash) + saveSettings({ + password: { + isEnabled: hash !== undefined, + value: hash, + }, + }) + unlock() } + debounceSaveUsername = debounce( + v => this.props.saveSettings({ username: v.trim() || 'Anonymous' }), + 250, + ) + handleChangeUsername = username => { - const { saveSettings } = this.props this.setState({ username }) - window.requestIdleCallback(() => { - saveSettings({ username: username.trim() || 'Anonymous' }) - }) + this.debounceSaveUsername(username) } handleOpenHardResetModal = () => this.setState({ isHardResetModalOpened: true }) @@ -69,23 +88,16 @@ class TabProfile extends PureComponent { if (isChecked) { this.handleOpenPasswordModal() } else { - // console.log(`decrypting data`) + this.setPassword(undefined) } } handleChangePassword = (password: ?string) => { - const { saveSettings, unlock } = this.props - const hash = bcrypt.hashSync(password, 8) - setEncryptionKey('accounts', password) - window.requestIdleCallback(() => { - saveSettings({ - password: { - isEnabled: hash !== undefined, - value: hash, - }, - }) - unlock() - }) + if (password) { + const hash = bcrypt.hashSync(password, 8) + this.setPassword(hash) + this.handleClosePasswordModal() + } } render() { @@ -110,7 +122,11 @@ class TabProfile extends PureComponent { - {isPasswordEnabled && } + {isPasswordEnabled && ( + + )} @@ -140,7 +156,7 @@ class TabProfile extends PureComponent { (p.isChecked ? 'wallet' : 'fog'), + bg: p => (p.isChecked ? 'wallet' : 'lightFog'), horizontal: true, align: 'center', })` backround: red; width: 50px; - height: 24px; - border-radius: 16px; + height: 26px; + border-radius: 13px; transition: 250ms linear background-color; cursor: pointer; &:focus { @@ -28,9 +28,9 @@ const Ball = styled.div` height: 20px; border-radius: 50%; background: white; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.15); + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2); transition: 250ms ease-in-out transform; - transform: translate3d(${p => (p.isChecked ? '28px' : '2px')}, 0, 0); + transform: translate3d(${p => (p.isChecked ? '27px' : '3px')}, 0, 0); ` type Props = { diff --git a/src/components/base/InputPassword/index.js b/src/components/base/InputPassword/index.js index 9072f69c..692afd43 100644 --- a/src/components/base/InputPassword/index.js +++ b/src/components/base/InputPassword/index.js @@ -1,6 +1,6 @@ // @flow -import React, { PureComponent } from 'react' +import React, { Fragment, PureComponent } from 'react' import styled from 'styled-components' import { translate } from 'react-i18next' import zxcvbn from 'zxcvbn' @@ -50,6 +50,7 @@ type Props = { onChange: Function, t: T, value: string, + withStrength: boolean, } class InputPassword extends PureComponent { @@ -84,7 +85,7 @@ class InputPassword extends PureComponent { } render() { - const { t, value, maxLength } = this.props + const { t, value, maxLength, withStrength } = this.props const { passwordStrength, inputType } = this.state const hasValue = value.trim() !== '' @@ -102,19 +103,23 @@ class InputPassword extends PureComponent { } /> - - {[0, 1, 2, 3, 4].map(v => ( - = v} - /> - ))} - - {hasValue && ( - - {t(`password:warning_${passwordStrength}`)} - + {withStrength && ( + + + {[0, 1, 2, 3, 4].map(v => ( + = v} + /> + ))} + + {hasValue && ( + + {t(`password:warning_${passwordStrength}`)} + + )} + )} ) diff --git a/src/reducers/accounts.js b/src/reducers/accounts.js index 2a6bcb38..49c9633a 100644 --- a/src/reducers/accounts.js +++ b/src/reducers/accounts.js @@ -94,10 +94,10 @@ export function canCreateAccount(state: State): boolean { // such a simple thing, let's put any, right? I don't care. export function serializeAccounts(accounts: any): Account[] { // ensure that accounts are always wrapped in data key - if (accounts.length && !accounts[0].data) { + if (accounts && accounts.length && !accounts[0].data) { accounts = accounts.map(account => ({ data: account })) } - return accounts.map(accountModel.decode) + return accounts ? accounts.map(accountModel.decode) : [] } export function deserializeAccounts(accounts: Account[]) { diff --git a/yarn.lock b/yarn.lock index eb9f1709..ffd73253 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3362,6 +3362,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3 escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.0.tgz#a060a297a6b57e15b61ca63ce84995daa0fe6e52" + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + chalk@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f" @@ -4321,9 +4329,9 @@ d3-zoom@1: d3-selection "1" d3-transition "1" -d3@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/d3/-/d3-5.0.0.tgz#b373ce8ccc953cbe09cef4c1e7e2223b42441df9" +d3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/d3/-/d3-5.0.1.tgz#c74743964deff2eedf2a10e6119fbeb77825af7a" dependencies: d3-array "1" d3-axis "1" @@ -8013,6 +8021,9 @@ ledger-test-library@KhalilBellakrid/ledger-test-library-nodejs#7d37482: dependencies: axios "^0.17.1" bindings "^1.3.0" + electron "^1.8.2" + electron-builder "^20.0.4" + electron-rebuild "^1.7.3" nan "^2.6.2" prebuild-install "^2.2.2" @@ -9792,9 +9803,9 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" -prettier@^1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.0.tgz#d26fc5894b9230de97629b39cae225b503724ce8" +prettier@^1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.1.tgz#c1ad20e803e7749faf905a409d2367e06bbe7325" prettier@^1.5.3: version "1.11.1" @@ -10132,9 +10143,9 @@ react-docgen@^3.0.0-beta11: node-dir "^0.1.10" recast "^0.12.6" -react-dom@^16.3.1: - version "16.3.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.1.tgz#6a3c90a4fb62f915bdbcf6204422d93a7d4ca573" +react-dom@^16.3.2: + version "16.3.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" @@ -10205,6 +10216,10 @@ react-is@^16.3.1: version "16.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.1.tgz#ee66e6d8283224a83b3030e110056798488359ba" +react-is@^16.3.2: + version "16.3.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.2.tgz#f4d3d0e2f5fbb6ac46450641eb2e25bf05d36b22" + react-modal@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.3.2.tgz#b13da9490653a7c76bc0e9600323eb1079c620e7" @@ -10308,14 +10323,14 @@ react-style-proptype@^3.0.0: dependencies: prop-types "^15.5.4" -react-test-renderer@^16.3.1: - version "16.3.1" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.3.1.tgz#d9257936d8535bd40f57f3d5a84e7b0452fb17f2" +react-test-renderer@^16.3.2: + version "16.3.2" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.3.2.tgz#3d1ed74fda8db42521fdf03328e933312214749a" dependencies: fbjs "^0.8.16" object-assign "^4.1.1" prop-types "^15.6.0" - react-is "^16.3.1" + react-is "^16.3.2" react-textarea-autosize@^5.2.1: version "5.2.1" @@ -10353,9 +10368,9 @@ react@^16.0.0, react@^16.2.0: object-assign "^4.1.1" prop-types "^15.6.0" -react@^16.3.1: - version "16.3.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.3.1.tgz#4a2da433d471251c69b6033ada30e2ed1202cfd8" +react@^16.3.2: + version "16.3.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" @@ -11704,9 +11719,9 @@ style-loader@^0.20.3: loader-utils "^1.1.0" schema-utils "^0.4.5" -styled-components@^3.2.5: - version "3.2.5" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.2.5.tgz#b5d5d7d618ab240ff10602b5ca5886b8db3d0a0d" +styled-components@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.2.6.tgz#99e6e75a746bdedd295a17e03dd1493055a1cc3b" dependencies: buffer "^5.0.3" css-to-react-native "^2.0.3" @@ -11714,6 +11729,7 @@ styled-components@^3.2.5: hoist-non-react-statics "^2.5.0" is-plain-object "^2.0.1" prop-types "^15.5.4" + react-is "^16.3.1" stylis "^3.5.0" stylis-rule-sheet "^0.0.10" supports-color "^3.2.3" From 216059f61c3fc4694015ca3571f41dbba38cb872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Tue, 17 Apr 2018 10:49:49 +0200 Subject: [PATCH 12/18] Fix setEncryptionKey --- .../SettingsPage/sections/Profile.js | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/components/SettingsPage/sections/Profile.js b/src/components/SettingsPage/sections/Profile.js index 6352b023..62e2498b 100644 --- a/src/components/SettingsPage/sections/Profile.js +++ b/src/components/SettingsPage/sections/Profile.js @@ -51,16 +51,19 @@ class TabProfile extends PureComponent { isPasswordModalOpened: false, } - setPassword = hash => { + setPassword = password => { const { saveSettings, unlock } = this.props - setEncryptionKey('accounts', hash) - saveSettings({ - password: { - isEnabled: hash !== undefined, - value: hash, - }, + window.requestIdleCallback(() => { + setEncryptionKey('accounts', password) + const hash = password ? bcrypt.hashSync(password, 8) : undefined + saveSettings({ + password: { + isEnabled: hash !== undefined, + value: hash, + }, + }) + unlock() }) - unlock() } debounceSaveUsername = debounce( @@ -94,8 +97,7 @@ class TabProfile extends PureComponent { handleChangePassword = (password: ?string) => { if (password) { - const hash = bcrypt.hashSync(password, 8) - this.setPassword(hash) + this.setPassword(password) this.handleClosePasswordModal() } } From d35b62b33652a8c4795148db5b05a80bff502edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Tue, 17 Apr 2018 14:04:40 +0200 Subject: [PATCH 13/18] Update CurrentAddress design --- src/components/CurrentAddress/index.js | 94 +++++++++++-------- src/components/CurrentAddress/stories.js | 3 +- src/components/base/Modal/index.js | 11 +-- src/components/layout/Print.js | 7 +- .../modals/Receive/04-step-receive-funds.js | 6 +- static/i18n/en/currentAddress.yml | 1 + 6 files changed, 66 insertions(+), 56 deletions(-) diff --git a/src/components/CurrentAddress/index.js b/src/components/CurrentAddress/index.js index 977029d5..1b95a372 100644 --- a/src/components/CurrentAddress/index.js +++ b/src/components/CurrentAddress/index.js @@ -1,7 +1,7 @@ // @flow import React, { PureComponent } from 'react' -import { translate } from 'react-i18next' +import { Trans, translate } from 'react-i18next' import styled from 'styled-components' import noop from 'lodash/noop' @@ -25,20 +25,12 @@ import IconShield from 'icons/Shield' const Container = styled(Box).attrs({ borderRadius: 1, alignItems: 'center', - bg: p => (p.withQRCode ? 'lightGrey' : 'transparent'), - py: 5, + bg: p => + p.withQRCode ? (p.notValid ? rgba(p.theme.colors.alertRed, 0.02) : 'lightGrey') : 'transparent', + py: 4, px: 7, -})`` - -const WrapperAddress = styled(Box).attrs({ - alignItems: 'center', - borderRadius: 1, - py: p => (p.notValid ? 4 : 0), - px: 4, })` - background: ${p => (p.notValid ? rgba(p.theme.colors.alertRed, 0.05) : 'transparent')}; - border: ${p => (p.notValid ? `1px dashed ${rgba(p.theme.colors.alertRed, 0.26)}` : 'none')}; - width: 100%; + border: ${p => (p.notValid ? `1px dashed ${rgba(p.theme.colors.alertRed, 0.5)}` : 'none')}; ` const Address = styled(Box).attrs({ @@ -63,11 +55,17 @@ const Label = styled(Box).attrs({ fontSize: 4, flow: 1, horizontal: true, -})`` +})` + strong { + color: ${p => p.theme.colors.dark}; + font-weight: 600; + border-bottom: 1px dashed ${p => p.theme.colors.dark}; + } +` const Footer = styled(Box).attrs({ horizontal: true, - mt: 5, + mt: 4, })` text-transform: uppercase; width: 100%; @@ -77,9 +75,20 @@ const FooterButtonWrapper = styled(Box).attrs({ color: 'grey', alignItems: 'center', justifyContent: 'center', - grow: true, + borderRadius: 1, })` cursor: pointer; + height: 55px; + width: 55px; + + &:hover { + background-color: rgba(100, 144, 241, 0.1); + color: ${p => p.theme.colors.dark}; + + svg { + color: ${p => p.theme.colors.wallet}; + } + } ` const FooterButton = ({ @@ -91,15 +100,18 @@ const FooterButton = ({ label: string, onClick: Function, }) => ( - - {icon} - - {label} - - + + + {icon} + + {label} + + + ) type Props = { + accountName?: string, address: string, addressVerified?: boolean, amount?: string, @@ -130,6 +142,7 @@ class CurrentAddress extends PureComponent { render() { const { + accountName, address, addressVerified, amount, @@ -149,20 +162,27 @@ class CurrentAddress extends PureComponent { return ( - - {withQRCode && ( - - - - )} - -
- {address} -
-
+ {withQRCode && ( + + + + )} + +
+ {address} +
{withBadge && ( @@ -186,7 +206,7 @@ class CurrentAddress extends PureComponent { )} /> ( } diff --git a/src/components/CurrentAddress/stories.js b/src/components/CurrentAddress/stories.js index 188b4c00..bc665a32 100644 --- a/src/components/CurrentAddress/stories.js +++ b/src/components/CurrentAddress/stories.js @@ -2,7 +2,7 @@ import React from 'react' import { storiesOf } from '@storybook/react' -import { boolean } from '@storybook/addon-knobs' +import { boolean, text } from '@storybook/addon-knobs' import CurrentAddress from 'components/CurrentAddress' @@ -12,6 +12,7 @@ const stories = storiesOf('Components', module) stories.add('CurrentAddress', () => ( const Container = styled(Box).attrs({ color: 'grey', - alignItems: 'center', - justifyContent: 'flex-start', sticky: true, style: p => ({ pointerEvents: p.isVisible ? 'auto' : 'none', @@ -79,8 +77,6 @@ const Backdrop = styled(Box).attrs({ const Wrapper = styled(Tabbable).attrs({ bg: 'transparent', flow: 4, - mt: 100, - mb: 100, style: p => ({ opacity: p.op, transform: `scale3d(${p.scale}, ${p.scale}, ${p.scale})`, @@ -175,12 +171,7 @@ export class Modal extends Component { {(m, isVisible, isAnimated) => ( - + { if (!data) { return null } - const { address, amount } = data + const { address, amount, accountName } = data return ( (this._node = n)} - amount={amount} + accountName={accountName} address={address} + amount={amount} + innerRef={n => (this._node = n)} withQRCode /> ) diff --git a/src/components/modals/Receive/04-step-receive-funds.js b/src/components/modals/Receive/04-step-receive-funds.js index 9a51f66f..efe220f7 100644 --- a/src/components/modals/Receive/04-step-receive-funds.js +++ b/src/components/modals/Receive/04-step-receive-funds.js @@ -8,7 +8,6 @@ import type { T } from 'types/common' import Box from 'components/base/Box' import CurrentAddress from 'components/CurrentAddress' import Label from 'components/base/Label' -import SelectAccount from 'components/SelectAccount' import RequestAmount from 'components/RequestAmount' type Props = { @@ -22,10 +21,6 @@ type Props = { export default (props: Props) => ( - - - - ( /> <0>{{accountName}} From 88862249cec73710eb1f88d505d8cc718ccdbdfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Tue, 17 Apr 2018 18:04:15 +0200 Subject: [PATCH 14/18] Use react-router for switch tab in SettingsPage --- src/components/SelectAccount/index.js | 3 +- src/components/SelectCurrency/index.js | 60 ++++++++++++++ src/components/SelectCurrency/stories.js | 31 +++++++ .../SettingsPage/SettingsSection.js | 31 ++++++- src/components/SettingsPage/index.js | 82 +++++++++++-------- .../SettingsPage/sections/Currencies.js | 21 ++++- src/components/SideBar/Item.js | 10 ++- src/components/TopBar.js | 10 ++- src/components/base/Button/index.js | 19 +++-- src/components/base/CheckBox/index.js | 1 - src/components/layout/Default.js | 1 + static/i18n/en/common.yml | 1 + 12 files changed, 217 insertions(+), 53 deletions(-) create mode 100644 src/components/SelectCurrency/index.js create mode 100644 src/components/SelectCurrency/stories.js diff --git a/src/components/SelectAccount/index.js b/src/components/SelectAccount/index.js index c08ac6bc..823de202 100644 --- a/src/components/SelectAccount/index.js +++ b/src/components/SelectAccount/index.js @@ -3,9 +3,10 @@ import React from 'react' import { connect } from 'react-redux' import { translate } from 'react-i18next' -import noop from 'lodash/noop' import { getIconByCoinType } from '@ledgerhq/currencies/react' +import noop from 'lodash/noop' + import type { Account } from '@ledgerhq/wallet-common/lib/types' import type { T } from 'types/common' diff --git a/src/components/SelectCurrency/index.js b/src/components/SelectCurrency/index.js new file mode 100644 index 00000000..298a90e9 --- /dev/null +++ b/src/components/SelectCurrency/index.js @@ -0,0 +1,60 @@ +// @flow + +import React from 'react' +import { translate } from 'react-i18next' +import { getIconByCoinType } from '@ledgerhq/currencies/react' +import { listCurrencies } from '@ledgerhq/currencies' + +import noop from 'lodash/noop' + +import type { Currency } from '@ledgerhq/currencies' +import type { T } from 'types/common' + +import Select from 'components/base/Select' +import Box from 'components/base/Box' + +const renderItem = a => { + const { color, name, coinType } = a + const Icon = getIconByCoinType(coinType) + return ( + + {Icon && ( + + + + )} + + {name} + + + ) +} + +const currencies = listCurrencies() + +type Props = { + onChange: Function, + value?: Currency, + t: T, +} + +const SelectCurrency = ({ onChange, value, t, ...props }: Props) => ( +