Browse Source

Add feature for rename and archive account

master
Loëck Vézien 7 years ago
parent
commit
ca05dd93cb
No known key found for this signature in database GPG Key ID: CBCDCE384E853AC4
  1. 6
      src/actions/accounts.js
  2. 10
      src/components/AccountPage.js
  3. 4
      src/components/DashboardPage.js
  4. 5
      src/components/ReceiveBox.js
  5. 4
      src/components/SelectAccount/index.js
  6. 41
      src/components/SideBar/index.js
  7. 5
      src/components/TopBar.js
  8. 1
      src/components/base/Button/index.js
  9. 13
      src/components/base/Modal/index.js
  10. 2
      src/components/base/Modal/stories.js
  11. 186
      src/components/modals/SettingsAccount.js
  12. 1
      src/components/modals/index.js
  13. 1
      src/constants.js
  14. 3
      src/main/app.js
  15. 20
      src/reducers/accounts.js
  16. 11
      src/reducers/modals.js
  17. 10
      src/styles/global.js
  18. 1
      src/types/common.js

6
src/actions/accounts.js

@ -14,6 +14,12 @@ export const addAccount: AddAccount = payload => ({
payload, payload,
}) })
export type EditAccount = Account => { type: string, payload: Account }
export const editAccount: AddAccount = payload => ({
type: 'DB:EDIT_ACCOUNT',
payload,
})
type FetchAccounts = () => { type: string } type FetchAccounts = () => { type: string }
export const fetchAccounts: FetchAccounts = () => ({ export const fetchAccounts: FetchAccounts = () => ({
type: 'FETCH_ACCOUNTS', type: 'FETCH_ACCOUNTS',

10
src/components/AccountPage.js

@ -5,7 +5,7 @@ import { compose } from 'redux'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { MODAL_SEND, MODAL_RECEIVE } from 'constants' import { MODAL_SEND, MODAL_RECEIVE, MODAL_SETTINGS_ACCOUNT } from 'constants'
import type { MapStateToProps } from 'react-redux' import type { MapStateToProps } from 'react-redux'
import type { T, Account, AccountData } from 'types/common' import type { T, Account, AccountData } from 'types/common'
@ -46,7 +46,7 @@ class AccountPage extends PureComponent<Props> {
<Box flow={3}> <Box flow={3}>
<Box horizontal> <Box horizontal>
<Box> <Box>
<Text fontSize={4}>{`${account.name} account`}</Text> <Text fontSize={4}>{account.name}</Text>
</Box> </Box>
<Box horizontal align="center" justify="flex-end" grow flow={20}> <Box horizontal align="center" justify="flex-end" grow flow={20}>
<Box> <Box>
@ -70,7 +70,11 @@ class AccountPage extends PureComponent<Props> {
</Button> </Button>
</Box> </Box>
<Box> <Box>
<Button icon="sliders-h" color="white" /> <Button
icon="sliders-h"
color="mouse"
onClick={() => openModal(MODAL_SETTINGS_ACCOUNT, { account })}
/>
</Box> </Box>
</Box> </Box>
</Box> </Box>

4
src/components/DashboardPage.js

@ -15,7 +15,7 @@ import type { Accounts, T } from 'types/common'
import { formatBTC } from 'helpers/format' import { formatBTC } from 'helpers/format'
import { openModal } from 'reducers/modals' import { openModal } from 'reducers/modals'
import { getTotalBalance, getAccounts } from 'reducers/accounts' import { getTotalBalance, getVisibleAccounts } from 'reducers/accounts'
import Box, { Card } from 'components/base/Box' import Box, { Card } from 'components/base/Box'
import Text from 'components/base/Text' import Text from 'components/base/Text'
@ -23,7 +23,7 @@ import Select from 'components/base/Select'
import Tabs from 'components/base/Tabs' import Tabs from 'components/base/Tabs'
const mapStateToProps: MapStateToProps<*, *, *> = state => ({ const mapStateToProps: MapStateToProps<*, *, *> = state => ({
accounts: getAccounts(state), accounts: getVisibleAccounts(state),
totalBalance: getTotalBalance(state), totalBalance: getTotalBalance(state),
}) })

5
src/components/ReceiveBox.js

@ -20,10 +20,11 @@ const AddressBox = styled(Box).attrs({
bg: 'cream', bg: 'cream',
p: 2, p: 2,
})` })`
text-align: center;
word-break: break-all;
border-radius: 3px; border-radius: 3px;
cursor: text;
text-align: center;
user-select: text; user-select: text;
word-break: break-all;
` `
const Action = styled(Box).attrs({ const Action = styled(Box).attrs({

4
src/components/SelectAccount/index.js

@ -11,14 +11,14 @@ import type { T, Account } from 'types/common'
import { formatBTC } from 'helpers/format' import { formatBTC } from 'helpers/format'
import { getAccounts } from 'reducers/accounts' import { getVisibleAccounts } from 'reducers/accounts'
import Select from 'components/base/Select' import Select from 'components/base/Select'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Text from 'components/base/Text' import Text from 'components/base/Text'
const mapStateToProps: MapStateToProps<*, *, *> = state => ({ const mapStateToProps: MapStateToProps<*, *, *> = state => ({
accounts: Object.entries(getAccounts(state)).map(([, account]: [string, any]) => account), accounts: Object.entries(getVisibleAccounts(state)).map(([, account]: [string, any]) => account),
}) })
const renderItem = item => ( const renderItem = item => (

41
src/components/SideBar/index.js

@ -12,12 +12,13 @@ import type { MapStateToProps } from 'react-redux'
import type { Accounts, T } from 'types/common' import type { Accounts, T } from 'types/common'
import { openModal } from 'reducers/modals' import { openModal } from 'reducers/modals'
import { getAccounts } from 'reducers/accounts' import { getVisibleAccounts } from 'reducers/accounts'
import { formatBTC } from 'helpers/format' import { formatBTC } from 'helpers/format'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import GrowScroll from 'components/base/GrowScroll' import GrowScroll from 'components/base/GrowScroll'
import Icon from 'components/base/Icon'
import Item from './Item' import Item from './Item'
const CapsSubtitle = styled(Box).attrs({ const CapsSubtitle = styled(Box).attrs({
@ -33,21 +34,9 @@ const CapsSubtitle = styled(Box).attrs({
const Container = styled(Box).attrs({ const Container = styled(Box).attrs({
noShrink: true, noShrink: true,
})` })`
margin-top: 40px;
width: ${p => p.theme.sizes.sideBarWidth}px; width: ${p => p.theme.sizes.sideBarWidth}px;
` `
const BtnAddAccount = styled(Box).attrs({
align: 'center',
color: 'steel',
})`
border-radius: 5px;
border: 1px dashed ${p => p.theme.colors.steel};
cursor: pointer;
margin: 30px 30px 0 30px;
padding: 5px;
`
type Props = { type Props = {
t: T, t: T,
accounts: Accounts, accounts: Accounts,
@ -55,7 +44,7 @@ type Props = {
} }
const mapStateToProps: MapStateToProps<*, *, *> = state => ({ const mapStateToProps: MapStateToProps<*, *, *> = state => ({
accounts: getAccounts(state), accounts: getVisibleAccounts(state),
}) })
const mapDispatchToProps = { const mapDispatchToProps = {
@ -68,7 +57,7 @@ class SideBar extends PureComponent<Props> {
return ( return (
<Container bg="white"> <Container bg="white">
<GrowScroll flow={4} py={3}> <Box flow={4} pt={4} grow>
<Box flow={2}> <Box flow={2}>
<CapsSubtitle>{t('sidebar.menu')}</CapsSubtitle> <CapsSubtitle>{t('sidebar.menu')}</CapsSubtitle>
<div> <div>
@ -86,20 +75,26 @@ class SideBar extends PureComponent<Props> {
</Item> </Item>
</div> </div>
</Box> </Box>
<Box flow={2}> <Box flow={2} grow>
<CapsSubtitle>{t('sidebar.accounts')}</CapsSubtitle> <CapsSubtitle horizontal align="center">
<div> <Box grow>{t('sidebar.accounts')}</Box>
<Box>
<Icon
name="plus-circle"
style={{ cursor: 'pointer' }}
onClick={() => openModal(MODAL_ADD_ACCOUNT)}
/>
</Box>
</CapsSubtitle>
<GrowScroll pb={2}>
{Object.entries(accounts).map(([id, account]: [string, any]) => ( {Object.entries(accounts).map(([id, account]: [string, any]) => (
<Item linkTo={`/account/${id}`} desc={formatBTC(account.data.balance)} key={id}> <Item linkTo={`/account/${id}`} desc={formatBTC(account.data.balance)} key={id}>
{account.name} {account.name}
</Item> </Item>
))} ))}
</div> </GrowScroll>
</Box> </Box>
<BtnAddAccount onClick={() => openModal(MODAL_ADD_ACCOUNT)}> </Box>
{t('addAccount.title')}
</BtnAddAccount>
</GrowScroll>
</Container> </Container>
) )
} }

5
src/components/TopBar.js

@ -36,13 +36,12 @@ const Container = styled(Box).attrs({
` `
const Filter = styled.div` const Filter = styled.div`
background: ${p => p.theme.colors.cream};
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
left: 0; left: 0;
margin: -300px;
bottom: 0; bottom: 0;
backdrop-filter: blur(20px);
z-index: -1; z-index: -1;
` `
@ -131,7 +130,6 @@ class TopBar extends PureComponent<Props, State> {
return ( return (
<Container> <Container>
<Filter />
<Box grow> <Box grow>
{hasAccounts && {hasAccounts &&
(sync.progress === true (sync.progress === true
@ -142,6 +140,7 @@ class TopBar extends PureComponent<Props, State> {
{hasPassword && <LockApplication onLock={this.handleLock} />} {hasPassword && <LockApplication onLock={this.handleLock} />}
<CountDevices count={devices.length} /> <CountDevices count={devices.length} />
</Box> </Box>
<Filter />
</Container> </Container>
) )
} }

1
src/components/base/Button/index.js

@ -54,6 +54,7 @@ function getProps({ disabled, icon, primary }: Object) {
withShadow: true, withShadow: true,
}, },
{ {
bg: 'transparent',
borderColor: 'mouse', borderColor: 'mouse',
borderWidth: 1, borderWidth: 1,
}, },

13
src/components/base/Modal/index.js

@ -21,7 +21,6 @@ type Props = {
isOpened?: boolean, isOpened?: boolean,
onClose: Function, onClose: Function,
preventBackdropClick?: boolean, preventBackdropClick?: boolean,
preventSideMargin?: boolean,
render: Function, render: Function,
data?: any, data?: any,
} }
@ -54,7 +53,6 @@ const Container = styled(Box).attrs({
}), }),
})` })`
position: fixed; position: fixed;
top: ${process.platform === 'darwin' ? 40 : 0}px;
z-index: 20; z-index: 20;
` `
@ -78,7 +76,6 @@ const Wrapper = styled(Box).attrs({
transform: `translate3d(0, ${p.offset}px, 0)`, transform: `translate3d(0, ${p.offset}px, 0)`,
}), }),
})` })`
margin-left: ${p => (p.preventSideMargin ? 0 : p.theme.sizes.sideBarWidth)}px;
width: 570px; width: 570px;
z-index: 2; z-index: 2;
` `
@ -108,12 +105,11 @@ export class Modal extends PureComponent<Props> {
static defaultProps = { static defaultProps = {
onClose: noop, onClose: noop,
preventBackdropClick: false, preventBackdropClick: false,
preventSideMargin: false,
isOpened: false, isOpened: false,
} }
render() { render() {
const { preventBackdropClick, preventSideMargin, isOpened, onClose, render, data } = this.props const { preventBackdropClick, isOpened, onClose, render, data } = this.props
return ( return (
<Mortal <Mortal
isOpened={isOpened} isOpened={isOpened}
@ -127,12 +123,7 @@ export class Modal extends PureComponent<Props> {
<Container isVisible={isVisible}> <Container isVisible={isVisible}>
<Backdrop op={m.opacity} /> <Backdrop op={m.opacity} />
<GrowScroll full align="center" onClick={preventBackdropClick ? undefined : onClose}> <GrowScroll full align="center" onClick={preventBackdropClick ? undefined : onClose}>
<Wrapper <Wrapper op={m.opacity} offset={m.y} onClick={e => e.stopPropagation()}>
preventSideMargin={preventSideMargin}
op={m.opacity}
offset={m.y}
onClick={e => e.stopPropagation()}
>
{render({ data, onClose })} {render({ data, onClose })}
</Wrapper> </Wrapper>
</GrowScroll> </GrowScroll>

2
src/components/base/Modal/stories.js

@ -10,11 +10,9 @@ const stories = storiesOf('Modal', module)
stories.add('basic', () => { stories.add('basic', () => {
const isOpened = boolean('isOpened', true) const isOpened = boolean('isOpened', true)
const preventSideMargin = boolean('preventSideMargin', true)
return ( return (
<Modal <Modal
isOpened={isOpened} isOpened={isOpened}
preventSideMargin={preventSideMargin}
render={({ onClose }) => ( render={({ onClose }) => (
<Fragment> <Fragment>
<ModalBody>Hey!</ModalBody> <ModalBody>Hey!</ModalBody>

186
src/components/modals/SettingsAccount.js

@ -0,0 +1,186 @@
// @flow
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import get from 'lodash/get'
import { push } from 'react-router-redux'
import { MODAL_SETTINGS_ACCOUNT } from 'constants'
import type { Account } from 'types/common'
import { editAccount } from 'actions/accounts'
import { setDataModal, closeModal } from 'reducers/modals'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import Input from 'components/base/Input'
import Modal, { ModalBody } from 'components/base/Modal'
import Text from 'components/base/Text'
import Icon from 'components/base/Icon'
type State = {
accountName: string,
editName: boolean,
nameHovered: boolean,
}
type Props = {
closeModal: Function,
editAccount: Function,
setDataModal: Function,
push: Function,
}
const mapDispatchToProps = {
closeModal,
editAccount,
setDataModal,
push,
}
const defaultState = {
editName: false,
accountName: '',
nameHovered: false,
}
class SettingsAccount extends PureComponent<Props, State> {
state = {
...defaultState,
}
getAccount(data: Object) {
const { accountName } = this.state
const account = get(data, 'account', {})
return {
...account,
...(accountName !== ''
? {
name: accountName,
}
: {}),
}
}
handleHoveredName = (state: boolean) => () =>
this.setState({
nameHovered: state,
})
handleEditName = (state: boolean) => () =>
this.setState({
nameHovered: false,
editName: state,
})
handleChangeName = (value: string) =>
this.setState({
accountName: value,
})
handleCancelEditName = (data: Object) => () => {
this.handleEditName(false)()
this.setState({
accountName: get(data, 'account.name', ''),
})
}
handleSubmitName = (account: Account) => (e: SyntheticEvent<HTMLFormElement>) => {
e.preventDefault()
const { editAccount, setDataModal } = this.props
editAccount(account)
setDataModal(MODAL_SETTINGS_ACCOUNT, { account })
this.setState({
editName: false,
})
}
handleArchiveAccount = (account: Account) => () => {
const { push, closeModal, editAccount } = this.props
editAccount({
...account,
archived: true,
})
closeModal(MODAL_SETTINGS_ACCOUNT)
push('/')
}
handleClose = () =>
this.setState({
...defaultState,
})
render() {
const { editName, nameHovered } = this.state
return (
<Modal
name={MODAL_SETTINGS_ACCOUNT}
onClose={this.handleClose}
render={({ data, onClose }) => {
const account = this.getAccount(data)
return (
<ModalBody onClose={onClose} flow={3}>
<Text fontSize={4} color="steel">
Account settings
</Text>
<Box
align="center"
flow={2}
horizontal
onMouseEnter={this.handleHoveredName(true)}
onMouseLeave={this.handleHoveredName(false)}
>
<Box>
{editName ? (
<form onSubmit={this.handleSubmitName(account)}>
<Box align="center" horizontal flow={2}>
<Box>
<Input value={account.name} onChange={this.handleChangeName} />
</Box>
<Box flow={2} horizontal>
<Button type="button" onClick={this.handleCancelEditName(data)}>
Cancel
</Button>
<Button type="submit" primary>
Ok
</Button>
</Box>
</Box>
</form>
) : (
account.name
)}
</Box>
{!editName &&
nameHovered && (
<Box onClick={this.handleEditName(true)} style={{ cursor: 'pointer' }}>
<Icon name="edit" />
</Box>
)}
</Box>
<Box horizontal grow align="flex-end" flow={2}>
<Box grow>
<Button onClick={this.handleArchiveAccount(account)}>Archive account</Button>
</Box>
<Box grow>
<Button primary>Go to account</Button>
</Box>
</Box>
</ModalBody>
)
}}
/>
)
}
}
export default connect(null, mapDispatchToProps)(SettingsAccount)

1
src/components/modals/index.js

@ -1,3 +1,4 @@
export AddAccount from './AddAccount' export AddAccount from './AddAccount'
export Receive from './Receive' export Receive from './Receive'
export Send from './Send' export Send from './Send'
export SettingsAccount from './SettingsAccount'

1
src/constants.js

@ -4,3 +4,4 @@ export const SYNC_ACCOUNT_TIMEOUT = 3e3
export const MODAL_ADD_ACCOUNT = 'MODAL_ADD_ACCOUNT' export const MODAL_ADD_ACCOUNT = 'MODAL_ADD_ACCOUNT'
export const MODAL_SEND = 'MODAL_SEND' export const MODAL_SEND = 'MODAL_SEND'
export const MODAL_RECEIVE = 'MODAL_RECEIVE' export const MODAL_RECEIVE = 'MODAL_RECEIVE'
export const MODAL_SETTINGS_ACCOUNT = 'MODAL_SETTINGS_ACCOUNT'

3
src/main/app.js

@ -22,9 +22,6 @@ function createMainWindow() {
width: MIN_WIDTH, width: MIN_WIDTH,
minHeight: MIN_HEIGHT, minHeight: MIN_HEIGHT,
minWidth: MIN_WIDTH, minWidth: MIN_WIDTH,
webPreferences: {
blinkFeatures: 'CSSBackdropFilter',
},
} }
const window = new BrowserWindow(windowOptions) const window = new BrowserWindow(windowOptions)

20
src/reducers/accounts.js

@ -33,6 +33,13 @@ const handlers: Object = {
...state, ...state,
[account.id]: getAccount(account), [account.id]: getAccount(account),
}), }),
EDIT_ACCOUNT: (state: AccountsState, { payload: account }: { payload: Account }) => ({
...state,
[account.id]: {
...state[account.id],
...getAccount(account),
},
}),
FETCH_ACCOUNTS: (state: AccountsState, { payload: accounts }: { payload: Accounts }) => accounts, FETCH_ACCOUNTS: (state: AccountsState, { payload: accounts }: { payload: Accounts }) => accounts,
SET_ACCOUNT_DATA: ( SET_ACCOUNT_DATA: (
state: AccountsState, state: AccountsState,
@ -85,6 +92,19 @@ export function getAccounts(state: { accounts: AccountsState }) {
}, {}) }, {})
} }
export function getVisibleAccounts(state: { accounts: AccountsState }) {
const accounts = getAccounts(state)
return Object.keys(accounts).reduce((result, key) => {
const account = accounts[key]
if (account.archived !== true) {
result[key] = account
}
return result
}, {})
}
export function getAccountById(state: { accounts: AccountsState }, id: string) { export function getAccountById(state: { accounts: AccountsState }, id: string) {
return getAccounts(state)[id] return getAccounts(state)[id]
} }

11
src/reducers/modals.js

@ -41,12 +41,23 @@ const handlers = {
}, },
} }
}, },
MODAL_SET_DATA: (state, { payload }: { payload: OpenPayload }) => {
const { name, data } = payload
return {
...state,
[name]: {
...state[name],
data,
},
}
},
} }
// Actions // Actions
export const openModal = createAction('MODAL_OPEN', (name, data = {}) => ({ name, data })) export const openModal = createAction('MODAL_OPEN', (name, data = {}) => ({ name, data }))
export const closeModal = createAction('MODAL_CLOSE', name => ({ name })) export const closeModal = createAction('MODAL_CLOSE', name => ({ name }))
export const setDataModal = createAction('MODAL_SET_DATA', (name, data = {}) => ({ name, data }))
// Selectors // Selectors

10
src/styles/global.js

@ -14,21 +14,17 @@ injectGlobal`
font: inherit; font: inherit;
color: inherit; color: inherit;
user-select: none; user-select: none;
cursor: inherit;
min-width: 0; min-width: 0;
// it will surely make problem in the future... to be inspected. // it will surely make problem in the future... to be inspected.
flex-shrink: 0; flex-shrink: 0;
} }
html {
-ms-overflow-style: -ms-autohiding-scrollbar;
}
body { body {
line-height: 1.5; cursor: default;
font-size: 16px;
font-family: "Open Sans", Arial, Helvetica, sans-serif; font-family: "Open Sans", Arial, Helvetica, sans-serif;
font-size: 16px;
line-height: 1.5;
} }
#app { #app {

1
src/types/common.js

@ -27,6 +27,7 @@ export type AccountData = {
export type Account = { export type Account = {
id: string, id: string,
archived?: boolean,
name: string, name: string,
type: string, type: string,
data?: AccountData, data?: AccountData,

Loading…
Cancel
Save