Browse Source

Merge pull request #303 from NastiaS/LIVE-112

wip live-112 settings page updates
master
Meriadec Pillet 7 years ago
committed by GitHub
parent
commit
7afd4399a6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 121
      src/components/SettingsPage/DisablePasswordModal.js
  2. 22
      src/components/SettingsPage/PasswordModal.js
  3. 14
      src/components/SettingsPage/sections/About.js
  4. 43
      src/components/SettingsPage/sections/Display.js
  5. 32
      src/components/SettingsPage/sections/Profile.js
  6. 7
      src/components/base/Input/index.js
  7. 12
      src/icons/ExternalLink.js
  8. 1
      src/reducers/settings.js
  9. 4
      src/types/common.js
  10. 1
      static/i18n/en/password.yml
  11. 5
      static/i18n/en/settings.yml

121
src/components/SettingsPage/DisablePasswordModal.js

@ -0,0 +1,121 @@
// @flow
import React, { PureComponent } from 'react'
import bcrypt from 'bcryptjs'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import InputPassword from 'components/base/InputPassword'
import { ErrorMessageInput } from 'components/base/Input'
import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'components/base/Modal'
import type { T } from 'types/common'
type Props = {
t: T,
onClose: Function,
isPasswordEnabled: boolean,
currentPasswordHash: string,
onChangePassword: Function,
}
type State = {
currentPassword: string,
incorrectPassword: boolean,
}
const INITIAL_STATE = {
currentPassword: '',
incorrectPassword: false,
}
class DisablePasswordModal extends PureComponent<Props, State> {
state = INITIAL_STATE
disablePassword = (e: SyntheticEvent<HTMLFormElement>) => {
if (e) {
e.preventDefault()
}
const { currentPassword } = this.state
const { isPasswordEnabled, currentPasswordHash, onChangePassword } = this.props
if (isPasswordEnabled) {
if (!bcrypt.compareSync(currentPassword, currentPasswordHash)) {
this.setState({ incorrectPassword: true })
return
}
onChangePassword('')
} else {
onChangePassword('')
}
}
handleInputChange = (key: string) => (value: string) => {
if (this.state.incorrectPassword) {
this.setState({ incorrectPassword: false })
}
this.setState({ [key]: value })
}
handleReset = () => this.setState(INITIAL_STATE)
render() {
const { t, isPasswordEnabled, onClose, ...props } = this.props
const { currentPassword, incorrectPassword } = this.state
return (
<Modal
{...props}
onHide={this.handleReset}
onClose={onClose}
render={({ onClose }) => (
<form onSubmit={this.disablePassword}>
<ModalBody onClose={onClose}>
<ModalTitle>{t('settings:profile.disablePasswordModalTitle')}</ModalTitle>
<ModalContent>
<Box ff="Museo Sans|Regular" color="dark" textAlign="center" mb={2} mt={3}>
{t('settings:profile.disablePasswordModalSubtitle')}
</Box>
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" px={4}>
{t('settings:profile.disablePasswordModalDesc')}
<Box px={7} mt={4} flow={3}>
{isPasswordEnabled && (
<Box flow={1}>
<InputPassword
autoFocus
type="password"
placeholder={t('settings:profile.disablePasswordModalInput')}
id="password"
onChange={this.handleInputChange('currentPassword')}
value={currentPassword}
/>
{incorrectPassword && (
<ErrorMessageInput>
{t('password:errorMessageIncorrectPassword')}
</ErrorMessageInput>
)}
</Box>
)}
</Box>
</Box>
</ModalContent>
<ModalFooter horizontal align="center" justify="flex-end" flow={2}>
<Button type="button" onClick={onClose}>
{t('common:cancel')}
</Button>
<Button
primary
onClick={this.disablePassword}
disabled={!currentPassword && !incorrectPassword}
>
{t('settings:profile.disablePasswordModalSave')}
</Button>
</ModalFooter>
</ModalBody>
</form>
)}
/>
)
}
}
export default DisablePasswordModal

22
src/components/SettingsPage/PasswordModal.js

@ -11,6 +11,7 @@ import Button from 'components/base/Button'
import InputPassword from 'components/base/InputPassword'
import Label from 'components/base/Label'
import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'components/base/Modal'
import { ErrorMessageInput } from 'components/base/Input'
import type { T } from 'types/common'
@ -30,11 +31,13 @@ type Props = {
type State = {
currentPassword: string,
newPassword: string,
incorrectPassword: boolean,
}
const INITIAL_STATE = {
currentPassword: '',
newPassword: '',
incorrectPassword: false,
}
class PasswordModal extends PureComponent<Props, State> {
@ -51,6 +54,7 @@ class PasswordModal extends PureComponent<Props, State> {
const { isPasswordEnabled, currentPasswordHash, onChangePassword } = this.props
if (isPasswordEnabled) {
if (!bcrypt.compareSync(currentPassword, currentPasswordHash)) {
this.setState({ incorrectPassword: true })
return
}
onChangePassword(newPassword)
@ -59,7 +63,12 @@ class PasswordModal extends PureComponent<Props, State> {
}
}
handleInputChange = key => value => this.setState({ [key]: value })
handleInputChange = (key: string) => (value: string) => {
if (this.state.incorrectPassword) {
this.setState({ incorrectPassword: false })
}
this.setState({ [key]: value })
}
handleReset = () => this.setState(INITIAL_STATE)
@ -70,7 +79,7 @@ class PasswordModal extends PureComponent<Props, State> {
render() {
const { t, isPasswordEnabled, onClose, ...props } = this.props
const { currentPassword, newPassword } = this.state
const { currentPassword, newPassword, incorrectPassword } = this.state
const isValid = this.isValid()
return (
<Modal
@ -101,6 +110,11 @@ class PasswordModal extends PureComponent<Props, State> {
onChange={this.handleInputChange('currentPassword')}
value={currentPassword}
/>
{incorrectPassword && (
<ErrorMessageInput>
{t('password:errorMessageIncorrectPassword')}
</ErrorMessageInput>
)}
</Box>
)}
<Box flow={1}>
@ -122,7 +136,9 @@ class PasswordModal extends PureComponent<Props, State> {
</Box>
</ModalContent>
<ModalFooter horizontal align="center" justify="flex-end" flow={2}>
<Button onClick={onClose}>{t('common:cancel')}</Button>
<Button type="button" onClick={onClose}>
{t('common:cancel')}
</Button>
<Button primary onClick={this.handleSave} disabled={!isValid}>
{t('settings:profile.passwordModalSave')}
</Button>

14
src/components/SettingsPage/sections/About.js

@ -6,7 +6,7 @@ import { shell } from 'electron'
import type { T } from 'types/common'
import IconHelp from 'icons/Help'
import IconChevronRight from 'icons/ChevronRight'
import IconExternalLink from 'icons/ExternalLink'
import {
SettingsSection as Section,
@ -33,25 +33,25 @@ class SectionAbout extends PureComponent<Props> {
/>
<Body>
<Row
onClick={this.handleOpenLink('http://google.com')}
onClick={this.handleOpenLink('https://support.ledgerwallet.com/hc/en-us')}
title={t('settings:about.faq')}
desc={t('settings:about.faqDesc')}
>
<IconChevronRight size={16} />
<IconExternalLink size={16} />
</Row>
<Row
onClick={this.handleOpenLink('http://google.com')}
onClick={this.handleOpenLink('https://support.ledgerwallet.com/hc/en-us/requests/new')}
title={t('settings:about.contactUs')}
desc={t('settings:about.contactUsDesc')}
>
<IconChevronRight size={16} />
<IconExternalLink size={16} />
</Row>
<Row
onClick={this.handleOpenLink('http://google.com')}
onClick={this.handleOpenLink('https://www.ledgerwallet.com/terms')}
title={t('settings:about.terms')}
desc={t('settings:about.termsDesc')}
>
<IconChevronRight size={16} />
<IconExternalLink size={16} />
</Row>
</Body>
</Section>

43
src/components/SettingsPage/sections/Display.js

@ -24,6 +24,20 @@ const fiats = listFiats().map(fiat => ({
name: `${fiat.name} - ${fiat.code}${fiat.symbol ? ` (${fiat.symbol})` : ''}`,
}))
/* temporary subset of countries */
const COUNTRIES = [
{ name: 'China', key: 'CN' },
{ name: 'France', key: 'FR' },
{ name: 'India', key: 'IN' },
{ name: 'Italy', key: 'IT' },
{ name: 'Japan', key: 'JP' },
{ name: 'Russian Federation', key: 'RU' },
{ name: 'Singapore', key: 'SG' },
{ name: 'Switzerland', key: 'CH' },
{ name: 'United Kingdom', key: 'GB' },
{ name: 'United States', key: 'US' },
]
type Props = {
t: T,
settings: Settings,
@ -35,6 +49,7 @@ type State = {
cachedMarketIndicator: string,
cachedLanguageKey: string,
cachedCounterValue: ?Object,
cachedRegion: Object,
}
class TabProfile extends PureComponent<Props, State> {
@ -42,6 +57,7 @@ class TabProfile extends PureComponent<Props, State> {
cachedMarketIndicator: this.props.settings.marketIndicator,
cachedLanguageKey: this.props.settings.language,
cachedCounterValue: fiats.find(fiat => fiat.fiat.code === this.props.settings.counterValue),
cachedRegion: this.props.settings.region,
}
getDatas() {
@ -83,6 +99,14 @@ class TabProfile extends PureComponent<Props, State> {
})
}
handleChangeRegion = (region: Object) => {
const { saveSettings } = this.props
this.setState({ cachedRegion: region })
window.requestIdleCallback(() => {
saveSettings({ region })
})
}
handleChangeMarketIndicator = (item: Object) => {
const { saveSettings } = this.props
const marketIndicator = item.key
@ -96,9 +120,15 @@ class TabProfile extends PureComponent<Props, State> {
render() {
const { t } = this.props
const { cachedMarketIndicator, cachedLanguageKey, cachedCounterValue } = this.state
const {
cachedMarketIndicator,
cachedLanguageKey,
cachedCounterValue,
cachedRegion,
} = this.state
const { languages } = this.getDatas()
const currentLanguage = languages.find(l => l.key === cachedLanguageKey)
const currentRegion = COUNTRIES.find(r => r.key === cachedRegion.key)
return (
<Section>
@ -135,7 +165,16 @@ class TabProfile extends PureComponent<Props, State> {
/>
</Row>
<Row title={t('settings:display.region')} desc={t('settings:display.regionDesc')}>
{'-'}
<Select
searchable
fuseOptions={{ keys: ['name'] }}
style={{ minWidth: 130 }}
small
onChange={item => this.handleChangeRegion(item)}
renderSelected={item => item && item.name}
value={currentRegion}
items={COUNTRIES}
/>
</Row>
<Row title={t('settings:display.stock')} desc={t('settings:display.stockDesc')}>
<RadioGroup

32
src/components/SettingsPage/sections/Profile.js

@ -19,6 +19,7 @@ import Button from 'components/base/Button'
import { ConfirmModal } from 'components/base/Modal'
import IconUser from 'icons/User'
import PasswordModal from '../PasswordModal'
import DisablePasswordModal from '../DisablePasswordModal'
import {
SettingsSection as Section,
@ -41,6 +42,7 @@ type Props = {
type State = {
isHardResetModalOpened: boolean,
isPasswordModalOpened: boolean,
isDisablePasswordModalOpened: boolean,
username: string,
}
@ -49,6 +51,7 @@ class TabProfile extends PureComponent<Props, State> {
username: this.props.settings.username,
isHardResetModalOpened: false,
isPasswordModalOpened: false,
isDisablePasswordModalOpened: false,
}
setPassword = password => {
@ -80,6 +83,8 @@ class TabProfile extends PureComponent<Props, State> {
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 })
handleHardReset = () => {
db.resetAll()
@ -91,7 +96,7 @@ class TabProfile extends PureComponent<Props, State> {
if (isChecked) {
this.handleOpenPasswordModal()
} else {
this.setPassword(undefined)
this.handleDisablePassowrd()
}
}
@ -99,12 +104,20 @@ class TabProfile extends PureComponent<Props, State> {
if (password) {
this.setPassword(password)
this.handleClosePasswordModal()
} else {
this.setPassword(undefined)
this.handleCloseDisablePasswordModal()
}
}
render() {
const { t, settings } = this.props
const { username, isHardResetModalOpened, isPasswordModalOpened } = this.state
const {
username,
isHardResetModalOpened,
isPasswordModalOpened,
isDisablePasswordModalOpened,
} = this.state
const isPasswordEnabled = settings.password.isEnabled === true
return (
<Section>
@ -132,12 +145,6 @@ class TabProfile extends PureComponent<Props, State> {
<CheckBox isChecked={isPasswordEnabled} onChange={this.handleChangePasswordCheck} />
</Box>
</Row>
<Row title={t('settings:profile.sync')} desc={t('settings:profile.syncDesc')}>
<Button primary>{t('settings:profile.sync')}</Button>
</Row>
<Row title={t('settings:profile.export')} desc={t('settings:profile.exportDesc')}>
<Button primary>{t('settings:profile.export')}</Button>
</Row>
<Row title={t('settings:profile.reset')} desc={t('settings:profile.resetDesc')}>
<Button danger onClick={this.handleOpenHardResetModal}>
{t('settings:profile.resetButton')}
@ -164,6 +171,15 @@ class TabProfile extends PureComponent<Props, State> {
isPasswordEnabled={isPasswordEnabled}
currentPasswordHash={settings.password.value}
/>
<DisablePasswordModal
t={t}
isOpened={isDisablePasswordModalOpened}
onClose={this.handleCloseDisablePasswordModal}
onChangePassword={this.handleChangePassword}
isPasswordEnabled={isPasswordEnabled}
currentPasswordHash={settings.password.value}
/>
</Section>
)
}

7
src/components/base/Input/index.js

@ -37,6 +37,13 @@ const Base = styled.input.attrs({
}
`
export const ErrorMessageInput = styled(Box).attrs({
alignItems: 'flex-end',
color: 'alertRed',
ff: 'Open Sans|SemiBold',
fontSize: 3,
})``
export const Textarea = styled.textarea.attrs({
p: 2,
fontSize: 4,

12
src/icons/ExternalLink.js

@ -0,0 +1,12 @@
// @flow
import React from 'react'
export default ({ size, ...p }: { size: number }) => (
<svg viewBox="0 0 16 16" height={size} width={size} {...p}>
<path
fill="currentColor"
d="M12.19 2.75H10a.75.75 0 0 1 0-1.5h4a.75.75 0 0 1 .75.75v4a.75.75 0 1 1-1.5 0V3.81L7.197 9.865a.75.75 0 0 1-1.06-1.061l6.052-6.053zm-.94 5.917a.75.75 0 1 1 1.5 0v4c0 1.15-.933 2.083-2.083 2.083H3.333a2.083 2.083 0 0 1-2.083-2.083V5.333c0-1.15.933-2.083 2.083-2.083h4a.75.75 0 1 1 0 1.5h-4a.583.583 0 0 0-.583.583v7.334c0 .322.261.583.583.583h7.334a.583.583 0 0 0 .583-.583v-4z"
/>
</svg>
)

1
src/reducers/settings.js

@ -23,6 +23,7 @@ const defaultState: SettingsState = {
},
marketIndicator: 'western',
currenciesSettings: {},
region: { key: 'US', name: 'United States' },
}
const CURRENCY_DEFAULTS_SETTINGS: CurrencySettings = {

4
src/types/common.js

@ -40,6 +40,10 @@ export type Settings = {
},
marketIndicator: 'eastern' | 'western',
currenciesSettings: CurrenciesSettings,
region: {
key: string,
name: string,
},
}
export type T = (?string, ?Object) => string

1
static/i18n/en/password.yml

@ -3,3 +3,4 @@ warning_1: Warning 1
warning_2: Warning 2
warning_3: Warning 3
warning_4: Warning 4
errorMessageIncorrectPassword: The password you entered is incorrect.

5
static/i18n/en/settings.yml

@ -35,6 +35,11 @@ profile:
passwordModalNewPasswordInput: New password
passwordModalRepeatPasswordInput: Repeat password
passwordModalSave: Save
disablePasswordModalTitle: Disable Password
disablePasswordModalSubtitle: Type your password
disablePasswordModalDesc: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh diam.
disablePasswordModalInput: Type your password
disablePasswordModalSave: Save
sync: Sync accounts
syncDesc: Lorem ipsum dolor sit amet
export: Export logs

Loading…
Cancel
Save