Browse Source

Merge pull request #68 from loeck/master

Add feature for rename and archive account
master
Loëck Vézien 7 years ago
committed by GitHub
parent
commit
b039bf4757
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/actions/accounts.js
  2. 10
      src/components/AccountPage.js
  3. 6
      src/components/AppRegionDrag.js
  4. 4
      src/components/DashboardPage.js
  5. 5
      src/components/ReceiveBox.js
  6. 4
      src/components/SelectAccount/index.js
  7. 7
      src/components/SideBar/Item.js
  8. 44
      src/components/SideBar/index.js
  9. 34
      src/components/TopBar.js
  10. 18
      src/components/Wrapper.js
  11. 1
      src/components/base/Button/index.js
  12. 13
      src/components/base/Modal/index.js
  13. 2
      src/components/base/Modal/stories.js
  14. 186
      src/components/modals/SettingsAccount.js
  15. 1
      src/components/modals/index.js
  16. 1
      src/constants.js
  17. 2
      src/helpers/btc.js
  18. 1
      src/main/app.js
  19. 20
      src/reducers/accounts.js
  20. 11
      src/reducers/modals.js
  21. 13
      src/styles/global.js
  22. 1
      src/types/common.js

6
src/actions/accounts.js

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

10
src/components/AccountPage.js

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

6
src/components/AppRegionDrag.js

@ -4,7 +4,9 @@ import styled from 'styled-components'
export default styled.div`
-webkit-app-region: drag;
background: ${p => p.theme.colors.white};
height: 40px;
z-index: 21;
position: absolute;
top: 0;
left: 0;
right: 0;
`

4
src/components/DashboardPage.js

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

5
src/components/ReceiveBox.js

@ -20,10 +20,11 @@ const AddressBox = styled(Box).attrs({
bg: 'cream',
p: 2,
})`
text-align: center;
word-break: break-all;
border-radius: 3px;
cursor: text;
text-align: center;
user-select: text;
word-break: break-all;
`
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 { getAccounts } from 'reducers/accounts'
import { getVisibleAccounts } from 'reducers/accounts'
import Select from 'components/base/Select'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
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 => (

7
src/components/SideBar/Item.js

@ -30,12 +30,11 @@ const mapDispatchToProps = {
const Container = styled(Box).attrs({
horizontal: true,
align: 'center',
color: 'lead',
p: 2,
flow: 2,
})`
cursor: pointer;
color: ${p => (p.isActive ? p.theme.colors.white : '')};
color: ${p => (p.isActive ? '#1d2027' : '#b8b8b8')};
background: ${p => (p.isActive ? 'rgba(255, 255, 255, 0.05)' : '')};
box-shadow: ${p =>
p.isActive ? `${p.theme.colors.blue} 4px 0 0 inset` : `${p.theme.colors.blue} 0 0 0 inset`};
@ -71,9 +70,7 @@ function Item({ children, desc, icon, linkTo, push, location, modal, openModal }
>
{icon && <Icon fontSize={3} color={isActive ? 'blue' : void 0} name={icon} />}
<div>
<Text fontWeight="bold" fontSize={1}>
{children}
</Text>
<Text fontSize={1}>{children}</Text>
{desc && (
<Box color="steel" fontSize={0}>
{desc}

44
src/components/SideBar/index.js

@ -12,13 +12,13 @@ import type { MapStateToProps } from 'react-redux'
import type { Accounts, T } from 'types/common'
import { openModal } from 'reducers/modals'
import { getAccounts } from 'reducers/accounts'
import { getVisibleAccounts } from 'reducers/accounts'
import { formatBTC } from 'helpers/format'
import { rgba } from 'styles/helpers'
import Box from 'components/base/Box'
import GrowScroll from 'components/base/GrowScroll'
import Icon from 'components/base/Icon'
import Item from './Item'
const CapsSubtitle = styled(Box).attrs({
@ -34,21 +34,9 @@ const CapsSubtitle = styled(Box).attrs({
const Container = styled(Box).attrs({
noShrink: true,
})`
background-color: ${p => rgba(p.theme.colors[p.bg], process.platform === 'darwin' ? 0.4 : 1)};
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 = {
t: T,
accounts: Accounts,
@ -56,7 +44,7 @@ type Props = {
}
const mapStateToProps: MapStateToProps<*, *, *> = state => ({
accounts: getAccounts(state),
accounts: getVisibleAccounts(state),
})
const mapDispatchToProps = {
@ -68,8 +56,8 @@ class SideBar extends PureComponent<Props> {
const { t, accounts, openModal } = this.props
return (
<Container bg="night">
<GrowScroll flow={4} py={4}>
<Container bg="white">
<Box flow={4} pt={4} grow>
<Box flow={2}>
<CapsSubtitle>{t('sidebar.menu')}</CapsSubtitle>
<div>
@ -87,20 +75,26 @@ class SideBar extends PureComponent<Props> {
</Item>
</div>
</Box>
<Box flow={2}>
<CapsSubtitle>{t('sidebar.accounts')}</CapsSubtitle>
<div>
<Box flow={2} grow>
<CapsSubtitle horizontal align="center">
<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]) => (
<Item linkTo={`/account/${id}`} desc={formatBTC(account.data.balance)} key={id}>
{account.name}
</Item>
))}
</div>
</GrowScroll>
</Box>
<BtnAddAccount onClick={() => openModal(MODAL_ADD_ACCOUNT)}>
{t('addAccount.title')}
</BtnAddAccount>
</GrowScroll>
</Box>
</Container>
)
}

34
src/components/TopBar.js

@ -2,6 +2,7 @@
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import styled from 'styled-components'
import { ipcRenderer } from 'electron'
import type { MapStateToProps, MapDispatchToProps } from 'react-redux'
@ -16,6 +17,34 @@ import { hasPassword } from 'reducers/settings'
import Box from 'components/base/Box'
const Container = styled(Box).attrs({
borderColor: '#e2e2e2',
borderWidth: 1,
borderBottom: true,
noShrink: true,
px: 3,
align: 'center',
horizontal: true,
})`
height: 60px;
position: absolute;
overflow: hidden;
left: 0;
right: 0;
top: 0;
z-index: 20;
`
const Filter = styled.div`
background: ${p => p.theme.colors.cream};
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: -1;
`
const mapStateToProps: MapStateToProps<*, *, *> = state => ({
hasAccounts: Object.keys(getAccounts(state)).length > 0,
currentDevice: getCurrentDevice(state),
@ -100,7 +129,7 @@ class TopBar extends PureComponent<Props, State> {
const { sync } = this.state
return (
<Box bg="white" px={2} noShrink style={{ height: 60, zIndex: 20 }} align="center" horizontal>
<Container>
<Box grow>
{hasAccounts &&
(sync.progress === true
@ -111,7 +140,8 @@ class TopBar extends PureComponent<Props, State> {
{hasPassword && <LockApplication onLock={this.handleLock} />}
<CountDevices count={devices.length} />
</Box>
</Box>
<Filter />
</Container>
)
}
}

18
src/components/Wrapper.js

@ -31,19 +31,17 @@ class Wrapper extends Component<{}> {
<ModalComponent key={name} />
))}
<Box grow horizontal>
<Box grow horizontal bg="white">
<SideBar />
<Box shrink grow bg="cream" color="grey">
<Box shrink grow bg="cream" color="grey" relative>
<TopBar />
<Box grow relative>
{__PROD__ && <UpdateNotifier />}
<GrowScroll p={4}>
<Route path="/" exact component={DashboardPage} />
<Route path="/settings" component={SettingsPage} />
<Route path="/account/:id" component={AccountPage} />
</GrowScroll>
</Box>
{__PROD__ && <UpdateNotifier />}
<GrowScroll p={3} style={{ paddingTop: 80 }}>
<Route path="/" exact component={DashboardPage} />
<Route path="/settings" component={SettingsPage} />
<Route path="/account/:id" component={AccountPage} />
</GrowScroll>
</Box>
</Box>
</Fragment>

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

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

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

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

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

@ -10,11 +10,9 @@ const stories = storiesOf('Modal', module)
stories.add('basic', () => {
const isOpened = boolean('isOpened', true)
const preventSideMargin = boolean('preventSideMargin', true)
return (
<Modal
isOpened={isOpened}
preventSideMargin={preventSideMargin}
render={({ onClose }) => (
<Fragment>
<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 Receive from './Receive'
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_SEND = 'MODAL_SEND'
export const MODAL_RECEIVE = 'MODAL_RECEIVE'
export const MODAL_SETTINGS_ACCOUNT = 'MODAL_SETTINGS_ACCOUNT'

2
src/helpers/btc.js

@ -46,7 +46,7 @@ export async function getAccount({
hdnode,
segwit,
network,
asyncDelay = 100,
asyncDelay = 500,
}: {
allAddresses?: Array<string>,
currentIndex?: number,

1
src/main/app.js

@ -14,7 +14,6 @@ function createMainWindow() {
? {
frame: false,
titleBarStyle: 'hiddenInset',
vibrancy: 'ultra-dark', // https://github.com/electron/electron/issues/10521
}
: {}),
center: true,

20
src/reducers/accounts.js

@ -33,6 +33,13 @@ const handlers: Object = {
...state,
[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,
SET_ACCOUNT_DATA: (
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) {
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
export const openModal = createAction('MODAL_OPEN', (name, data = {}) => ({ name, data }))
export const closeModal = createAction('MODAL_CLOSE', name => ({ name }))
export const setDataModal = createAction('MODAL_SET_DATA', (name, data = {}) => ({ name, data }))
// Selectors

13
src/styles/global.js

@ -14,21 +14,17 @@ injectGlobal`
font: inherit;
color: inherit;
user-select: none;
cursor: inherit;
min-width: 0;
// it will surely make problem in the future... to be inspected.
flex-shrink: 0;
}
html {
-ms-overflow-style: -ms-autohiding-scrollbar;
}
body {
line-height: 1.5;
font-size: 16px;
cursor: default;
font-family: "Open Sans", Arial, Helvetica, sans-serif;
font-size: 16px;
line-height: 1.5;
}
#app {
@ -46,12 +42,13 @@ injectGlobal`
}
.scrollbar-thumb {
background: rgba(102, 102, 102, 0.5) !important;
background: rgb(102, 102, 102) !important;
padding: 2px;
background-clip: content-box !important;
}
.scrollbar-track {
background: transparent !important;
transition: opacity 0.2s ease-in-out !important;
z-index: 20 !important;
}
`

1
src/types/common.js

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

Loading…
Cancel
Save