Browse Source

Merge pull request #83 from meriadec/master

Accounts are now stored as List instead of Dictionaries
master
Loëck Vézien 7 years ago
committed by GitHub
parent
commit
36d12adb70
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 29
      src/actions/accounts.js
  2. 2
      src/actions/settings.js
  3. 6
      src/components/AccountPage.js
  4. 60
      src/components/DashboardPage.js
  5. 4
      src/components/IsUnlocked.js
  6. 2
      src/components/SelectAccount/index.js
  7. 8
      src/components/SideBar/index.js
  8. 2
      src/components/TopBar.js
  9. 16
      src/components/modals/AddAccount/index.js
  10. 14
      src/components/modals/SettingsAccount.js
  11. 38
      src/helpers/db.js
  12. 6
      src/internals/usb/wallet/accounts.js
  13. 4
      src/middlewares/db.js
  14. 136
      src/reducers/accounts.js
  15. 8
      src/renderer/events.js
  16. 6
      src/renderer/index.js
  17. 2
      src/types/common.js

29
src/actions/accounts.js

@ -1,9 +1,5 @@
// @flow // @flow
import { createAction } from 'redux-actions'
import type { Dispatch } from 'redux'
import db from 'helpers/db' import db from 'helpers/db'
import type { Account } from 'types/common' import type { Account } from 'types/common'
@ -14,24 +10,17 @@ export const addAccount: AddAccount = payload => ({
payload, payload,
}) })
export type EditAccount = Account => { type: string, payload: Account } export type UpdateAccount = Account => { type: string, payload: Account }
export const editAccount: AddAccount = payload => ({ export const updateAccount: AddAccount = payload => ({
type: 'DB:EDIT_ACCOUNT', type: 'DB:UPDATE_ACCOUNT',
payload, payload,
}) })
type FetchAccounts = () => { type: string } type FetchAccounts = () => { type: string }
export const fetchAccounts: FetchAccounts = () => ({ export const fetchAccounts: FetchAccounts = () => {
type: 'FETCH_ACCOUNTS', const payload = db.get('accounts')
payload: db('accounts'), return {
}) type: 'SET_ACCOUNTS',
payload,
const setAccountData = createAction('DB:SET_ACCOUNT_DATA', (accountID, data) => ({ }
accountID,
data,
}))
export const syncAccount: Function = account => async (dispatch: Dispatch<*>) => {
const { id, ...data } = account
dispatch(setAccountData(id, data))
} }

2
src/actions/settings.js

@ -11,7 +11,7 @@ export const saveSettings: SaveSettings = payload => ({
}) })
export const fetchSettings: Function = () => dispatch => { export const fetchSettings: Function = () => dispatch => {
const settings = db('settings') const settings = db.get('settings')
if (Object.keys(settings).length === 0) { if (Object.keys(settings).length === 0) {
return return
} }

6
src/components/AccountPage.js

@ -4,6 +4,7 @@ import React, { PureComponent, Fragment } from 'react'
import { compose } from 'redux' 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 { Redirect } from 'react-router'
import { MODAL_SEND, MODAL_RECEIVE, MODAL_SETTINGS_ACCOUNT } from 'constants' import { MODAL_SEND, MODAL_RECEIVE, MODAL_SETTINGS_ACCOUNT } from 'constants'
@ -42,6 +43,11 @@ class AccountPage extends PureComponent<Props> {
render() { render() {
const { account, accountData, openModal, t } = this.props const { account, accountData, openModal, t } = this.props
// Don't even throw if we jumped in wrong account route
if (!account) {
return <Redirect to="/" />
}
return ( return (
<Box flow={3}> <Box flow={3}>
<Box horizontal> <Box horizontal>

60
src/components/DashboardPage.js

@ -40,7 +40,7 @@ type Props = {
type State = { type State = {
tab: number, tab: number,
datas: Object, fakeDatas: Array<any>,
} }
const ACCOUNTS_BY_LINE = 3 const ACCOUNTS_BY_LINE = 3
@ -52,7 +52,7 @@ const itemsTimes = [
{ key: 'year', name: 'Last year' }, { key: 'year', name: 'Last year' },
] ]
const generateData = v => ({ const generateFakeData = v => ({
name: `Day ${v}`, name: `Day ${v}`,
value: random(10, 100), value: random(10, 100),
}) })
@ -60,11 +60,11 @@ const generateData = v => ({
class DashboardPage extends PureComponent<Props, State> { class DashboardPage extends PureComponent<Props, State> {
state = { state = {
tab: 0, tab: 0,
datas: this.generateDatas(), fakeDatas: this.generateFakeDatas(),
} }
componentDidMount() { componentDidMount() {
this.addDatasOnAccounts() this.addFakeDatasOnAccounts()
} }
componentWillUnmount() { componentWillUnmount() {
@ -74,42 +74,33 @@ class DashboardPage extends PureComponent<Props, State> {
getAccountsChunk() { getAccountsChunk() {
const { accounts } = this.props const { accounts } = this.props
const listAccounts = Object.values(accounts) // create shallow copy of accounts, to be mutated
const listAccounts = [...accounts]
while (listAccounts.length % ACCOUNTS_BY_LINE !== 0) listAccounts.push(null) while (listAccounts.length % ACCOUNTS_BY_LINE !== 0) listAccounts.push(null)
return chunk(listAccounts, ACCOUNTS_BY_LINE) return chunk(listAccounts, ACCOUNTS_BY_LINE)
} }
generateDatas() { generateFakeDatas() {
const { accounts } = this.props const { accounts } = this.props
return accounts.map(() => [...Array(25).keys()].map(v => generateFakeData(v + 1)))
return Object.keys(accounts).reduce((result, key) => {
result[key] = [...Array(25).keys()].map(v => generateData(v + 1))
return result
}, {})
} }
addDatasOnAccounts = () => { addFakeDatasOnAccounts = () => {
this._timeout = setTimeout(() => { this._timeout = setTimeout(() => {
const { accounts } = this.props const { accounts } = this.props
this.setState(prev => ({ this.setState(prev => ({
datas: { fakeDatas: accounts.reduce((res, acc, i) => {
...Object.keys(accounts).reduce((result, key) => { if (res[i]) {
if (result[key]) { const nextIndex = res[i].length
const nextIndex = result[key].length res[i][nextIndex] = generateFakeData(nextIndex)
}
result[key][nextIndex] = generateData(nextIndex) return res
} }, prev.fakeDatas),
return result
}, prev.datas),
},
})) }))
this.addDatasOnAccounts() this.addFakeDatasOnAccounts()
}, TIMEOUT_REFRESH_DATAS) }, TIMEOUT_REFRESH_DATAS)
} }
@ -119,9 +110,9 @@ class DashboardPage extends PureComponent<Props, State> {
render() { render() {
const { totalBalance, push, accounts } = this.props const { totalBalance, push, accounts } = this.props
const { tab, datas } = this.state const { tab, fakeDatas } = this.state
const totalAccounts = Object.keys(accounts).length const totalAccounts = accounts.length
return ( return (
<Box flow={4}> <Box flow={4}>
@ -171,17 +162,14 @@ class DashboardPage extends PureComponent<Props, State> {
<AreaChart <AreaChart
height={250} height={250}
data={takeRight( data={takeRight(
Object.keys(datas).reduce((result, key) => { fakeDatas.reduce((res, data) => {
const data = datas[key]
data.forEach((d, i) => { data.forEach((d, i) => {
result[i] = { res[i] = {
name: d.name, name: d.name,
value: (result[i] ? result[i].value : 0) + d.value, value: (res[i] ? res[i].value : 0) + d.value,
} }
}) })
return res
return result
}, []), }, []),
25, 25,
)} )}
@ -216,7 +204,7 @@ class DashboardPage extends PureComponent<Props, State> {
<Box grow align="center" justify="center"> <Box grow align="center" justify="center">
{account.data && formatBTC(account.data.balance)} {account.data && formatBTC(account.data.balance)}
</Box> </Box>
<BarChart height={100} data={takeRight(datas[account.id], 25)} /> <BarChart height={100} data={takeRight(fakeDatas[j], 25)} />
</Card> </Card>
), ),
)} )}

4
src/components/IsUnlocked/index.js → src/components/IsUnlocked.js

@ -81,6 +81,10 @@ class IsUnlocked extends Component<Props, State> {
return true return true
} }
if (!nextProps.isLocked && this.props.isLocked) {
return true
}
return nextProps.children !== this.props.children return nextProps.children !== this.props.children
} }

2
src/components/SelectAccount/index.js

@ -18,7 +18,7 @@ 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(getVisibleAccounts(state)).map(([, account]: [string, any]) => account), accounts: getVisibleAccounts(state),
}) })
const renderItem = item => ( const renderItem = item => (

8
src/components/SideBar/index.js

@ -111,11 +111,11 @@ class SideBar extends PureComponent<Props> {
</PlusBtn> </PlusBtn>
</CapsSubtitle> </CapsSubtitle>
<GrowScroll pb={2} px={2} flow={1}> <GrowScroll pb={2} px={2} flow={1}>
{Object.entries(accounts).map(([id, account]: [string, any]) => ( {accounts.map(account => (
<Item <Item
linkTo={`/account/${id}`} linkTo={`/account/${account.id}`}
desc={formatBTC(account.data.balance)} desc={formatBTC(account.data ? account.data.balance : 0)}
key={id} key={account.id}
icon={{ iconName: 'btc', prefix: 'fab' }} icon={{ iconName: 'btc', prefix: 'fab' }}
> >
{account.name} {account.name}

2
src/components/TopBar.js

@ -32,7 +32,7 @@ const Container = styled(Box).attrs({
` `
const mapStateToProps: MapStateToProps<*, *, *> = state => ({ const mapStateToProps: MapStateToProps<*, *, *> = state => ({
hasAccounts: Object.keys(getAccounts(state)).length > 0, hasAccounts: getAccounts(state).length > 0,
hasPassword: hasPassword(state), hasPassword: hasPassword(state),
}) })

16
src/components/modals/AddAccount/index.js

@ -5,6 +5,7 @@ import { connect } from 'react-redux'
import { compose } from 'redux' import { compose } from 'redux'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { ipcRenderer } from 'electron' import { ipcRenderer } from 'electron'
import differenceBy from 'lodash/differenceBy'
import { MODAL_ADD_ACCOUNT } from 'constants' import { MODAL_ADD_ACCOUNT } from 'constants'
@ -74,7 +75,7 @@ const Steps = {
</Box> </Box>
), ),
listAccounts: (props: Object) => { listAccounts: (props: Object) => {
const accounts = Object.entries(props.accounts).map(([, account]: [string, any]) => account) const { accounts } = props
const emptyAccounts = accounts.filter(account => account.transactions.length === 0) const emptyAccounts = accounts.filter(account => account.transactions.length === 0)
const existingAccounts = accounts.filter(account => account.transactions.length > 0) const existingAccounts = accounts.filter(account => account.transactions.length > 0)
const canCreateAccount = props.canCreateAccount && emptyAccounts.length === 1 const canCreateAccount = props.canCreateAccount && emptyAccounts.length === 1
@ -110,7 +111,7 @@ type Props = {
type State = { type State = {
inputValue: InputValue, inputValue: InputValue,
step: Step, step: Step,
accounts: Object, accounts: Accounts,
progress: null | Object, progress: null | Object,
} }
@ -129,7 +130,7 @@ const defaultState = {
inputValue: { inputValue: {
wallet: '', wallet: '',
}, },
accounts: {}, accounts: [],
progress: null, progress: null,
step: 'chooseWallet', step: 'chooseWallet',
} }
@ -146,12 +147,7 @@ class AddAccountModal extends PureComponent<Props, State> {
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.accounts) { if (nextProps.accounts) {
this.setState(prev => ({ this.setState(prev => ({
accounts: Object.keys(prev.accounts).reduce((result, value) => { accounts: differenceBy(prev.accounts, nextProps.accounts, 'id'),
if (!nextProps.accounts[value]) {
result[value] = prev.accounts[value]
}
return result
}, {}),
})) }))
} }
} }
@ -183,7 +179,7 @@ class AddAccountModal extends PureComponent<Props, State> {
sendEvent('usb', 'wallet.getAccounts', { sendEvent('usb', 'wallet.getAccounts', {
path: currentDevice.path, path: currentDevice.path,
wallet: inputValue.wallet, wallet: inputValue.wallet,
currentAccounts: Object.keys(accounts), currentAccounts: accounts.map(acc => acc.id),
}) })
} }

14
src/components/modals/SettingsAccount.js

@ -9,7 +9,7 @@ import { MODAL_SETTINGS_ACCOUNT } from 'constants'
import type { Account } from 'types/common' import type { Account } from 'types/common'
import { editAccount } from 'actions/accounts' import { updateAccount } from 'actions/accounts'
import { setDataModal, closeModal } from 'reducers/modals' import { setDataModal, closeModal } from 'reducers/modals'
import Box from 'components/base/Box' import Box from 'components/base/Box'
@ -27,14 +27,14 @@ type State = {
type Props = { type Props = {
closeModal: Function, closeModal: Function,
editAccount: Function, updateAccount: Function,
setDataModal: Function, setDataModal: Function,
push: Function, push: Function,
} }
const mapDispatchToProps = { const mapDispatchToProps = {
closeModal, closeModal,
editAccount, updateAccount,
setDataModal, setDataModal,
push, push,
} }
@ -91,9 +91,9 @@ class SettingsAccount extends PureComponent<Props, State> {
handleSubmitName = (account: Account) => (e: SyntheticEvent<HTMLFormElement>) => { handleSubmitName = (account: Account) => (e: SyntheticEvent<HTMLFormElement>) => {
e.preventDefault() e.preventDefault()
const { editAccount, setDataModal } = this.props const { updateAccount, setDataModal } = this.props
editAccount(account) updateAccount(account)
setDataModal(MODAL_SETTINGS_ACCOUNT, { account }) setDataModal(MODAL_SETTINGS_ACCOUNT, { account })
this.setState({ this.setState({
@ -102,9 +102,9 @@ class SettingsAccount extends PureComponent<Props, State> {
} }
handleArchiveAccount = (account: Account) => () => { handleArchiveAccount = (account: Account) => () => {
const { push, closeModal, editAccount } = this.props const { push, closeModal, updateAccount } = this.props
editAccount({ updateAccount({
...account, ...account,
archived: true, archived: true,
}) })

38
src/helpers/db.js

@ -2,23 +2,37 @@ import Store from 'electron-store'
const encryptionKey = {} const encryptionKey = {}
const store = type => const store = key =>
new Store({ new Store({
name: type, name: key,
defaults: {}, defaults: {
encryptionKey: encryptionKey[type], data: null,
},
encryptionKey: encryptionKey[key],
}) })
export function setEncryptionKey(type, value) { export function setEncryptionKey(key, value) {
encryptionKey[type] = value encryptionKey[key] = value
} }
export default (type, values) => { export default {
const db = store(type) // If the db doesn't exists for that key, init it, with the default value provided
init: (key, defaults) => {
const db = store(key)
const data = db.get('data')
if (!data) {
db.set('data', defaults)
}
},
if (values) { get: key => {
db.store = values const db = store(key)
} return db.get('data')
},
return db.store set: (key, val) => {
const db = store(key)
db.set('data', val)
return db.get('data')
},
} }

6
src/internals/usb/wallet/accounts.js

@ -105,7 +105,7 @@ export default async ({
return encodeBase58Check(xpub) return encodeBase58Check(xpub)
} }
const getAllAccounts = async (currentAccount = 0, accounts = {}) => { const getAllAccounts = async (currentAccount = 0, accounts = []) => {
const xpub58 = await getXpub58ByAccount({ account: currentAccount, network }) const xpub58 = await getXpub58ByAccount({ account: currentAccount, network })
if (currentAccounts.includes(xpub58)) { if (currentAccounts.includes(xpub58)) {
@ -122,10 +122,10 @@ export default async ({
const hasTransactions = account.transactions.length > 0 const hasTransactions = account.transactions.length > 0
accounts[xpub58] = { accounts.push({
id: xpub58, id: xpub58,
...account, ...account,
} })
if (hasTransactions) { if (hasTransactions) {
return getAllAccounts(currentAccount + 1, accounts) return getAllAccounts(currentAccount + 1, accounts)

4
src/middlewares/db.js

@ -17,6 +17,6 @@ export default store => next => action => {
const state = getState() const state = getState()
const { settings } = state const { settings } = state
db('settings', settings) db.set('settings', settings)
db('accounts', getAccounts(state)) db.set('accounts', getAccounts(state))
} }

136
src/reducers/accounts.js

@ -12,64 +12,85 @@ import type { Account, Accounts, AccountData } from 'types/common'
export type AccountsState = Accounts export type AccountsState = Accounts
const state: AccountsState = {} const state: AccountsState = []
function getAccount(account: Account) { function orderAccountsTransactions(account: Account) {
const transactions = get(account.data, 'transactions', []) const transactions = get(account.data, 'transactions', [])
transactions.sort((a, b) => new Date(b.received_at) - new Date(a.received_at)) transactions.sort((a, b) => new Date(b.received_at) - new Date(a.received_at))
return { return {
...account, ...account,
data: { data: {
...(account.data || {}), ...account.data,
transactions, transactions,
}, },
} }
} }
const defaultAccountData: AccountData = {
address: '',
balance: 0,
currentIndex: 0,
transactions: [],
}
const handlers: Object = { const handlers: Object = {
ADD_ACCOUNT: (state: AccountsState, { payload: account }: { payload: Account }) => ({ SET_ACCOUNTS: (
...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, state: AccountsState,
{ payload: { accountID, data } }: { payload: { accountID: string, data: AccountData } }, { payload: accounts }: { payload: Accounts },
): AccountsState => { ): AccountsState => accounts,
const account = state[accountID]
const { data: accountData } = account
const transactions = uniqBy(
[...get(accountData, 'transactions', []), ...data.transactions],
tx => tx.hash,
)
const currentIndex = data.currentIndex ? data.currentIndex : get(accountData, 'currentIndex', 0)
account.data = {
...accountData,
...data,
balance: transactions.reduce((result, v) => {
result += v.balance
return result
}, 0),
currentIndex,
transactions,
}
return { ADD_ACCOUNT: (
...state, state: AccountsState,
[accountID]: getAccount(account), { payload: account }: { payload: Account },
} ): AccountsState => {
account = orderAccountsTransactions({
...account,
data: {
...defaultAccountData,
...account.data,
},
})
return [...state, account]
}, },
UPDATE_ACCOUNT: (
state: AccountsState,
{ payload: account }: { payload: Account },
): AccountsState =>
state.map(existingAccount => {
if (existingAccount.id !== account.id) {
return existingAccount
}
const existingData = get(existingAccount, 'data', {})
const data = get(account, 'data', {})
const transactions = uniqBy(
[...get(existingData, 'transactions', []), ...get(data, 'transactions', [])],
tx => tx.hash,
)
const currentIndex = data.currentIndex
? data.currentIndex
: get(existingData, 'currentIndex', 0)
const updatedAccount = {
...existingAccount,
...account,
data: {
...existingData,
...data,
balance: transactions.reduce((result, v) => {
result += v.balance
return result
}, 0),
currentIndex,
transactions,
},
}
return orderAccountsTransactions(updatedAccount)
}),
} }
// Selectors // Selectors
@ -78,35 +99,24 @@ export function getTotalBalance(state: { accounts: AccountsState }) {
return reduce( return reduce(
state.accounts, state.accounts,
(result, account) => { (result, account) => {
result += account.data.balance result += get(account, 'data.balance', 0)
return result return result
}, },
0, 0,
) )
} }
export function getAccounts(state: { accounts: AccountsState }) { export function getAccounts(state: { accounts: AccountsState }): Array<Account> {
return Object.keys(state.accounts).reduce((result, key) => { return state.accounts
result[key] = getAccount(state.accounts[key])
return result
}, {})
} }
export function getVisibleAccounts(state: { accounts: AccountsState }) { export function getVisibleAccounts(state: { accounts: AccountsState }): Array<Account> {
const accounts = getAccounts(state) return getAccounts(state).filter(account => account.archived !== true)
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): Account | null {
return getAccounts(state)[id] const account = getAccounts(state).find(account => account.id === id)
return account || null
} }
export function getAccountData(state: State, id: string): AccountData | null { export function getAccountData(state: State, id: string): AccountData | null {
@ -114,7 +124,7 @@ export function getAccountData(state: State, id: string): AccountData | null {
} }
export function canCreateAccount(state: State): boolean { export function canCreateAccount(state: State): boolean {
return every(getAccounts(state), a => a.data.transactions.length > 0) return every(getAccounts(state), a => get(a, 'data.transactions.length', 0) > 0)
} }
export default handleActions(handlers, state) export default handleActions(handlers, state)

8
src/renderer/events.js

@ -10,7 +10,7 @@ import type { Accounts } from 'types/common'
import { CHECK_UPDATE_TIMEOUT, SYNC_ACCOUNT_TIMEOUT } from 'constants' import { CHECK_UPDATE_TIMEOUT, SYNC_ACCOUNT_TIMEOUT } from 'constants'
import { updateDevices, addDevice, removeDevice } from 'actions/devices' import { updateDevices, addDevice, removeDevice } from 'actions/devices'
import { syncAccount } from 'actions/accounts' import { updateAccount } from 'actions/accounts'
import { setUpdateStatus } from 'reducers/update' import { setUpdateStatus } from 'reducers/update'
import { getAccountData, getAccounts } from 'reducers/accounts' import { getAccountData, getAccounts } from 'reducers/accounts'
@ -41,11 +41,11 @@ export function sendSyncEvent(channel: string, msgType: string, data: any): any
export function startSyncAccounts(accounts: Accounts) { export function startSyncAccounts(accounts: Accounts) {
syncAccounts = true syncAccounts = true
sendEvent('accounts', 'sync.all', { sendEvent('accounts', 'sync.all', {
accounts: Object.entries(accounts).map(([id, account]: [string, any]) => { accounts: accounts.map(account => {
const currentIndex = get(account, 'data.currentIndex', 0) const currentIndex = get(account, 'data.currentIndex', 0)
const allAddresses = get(account, 'data.allAddresses', []) const allAddresses = get(account, 'data.allAddresses', [])
return { return {
id, id: account.id,
allAddresses, allAddresses,
currentIndex, currentIndex,
} }
@ -75,7 +75,7 @@ export default ({ store, locked }: { store: Object, locked: boolean }) => {
) )
if (currentAccountData.transactions.length !== transactions.length) { if (currentAccountData.transactions.length !== transactions.length) {
store.dispatch(syncAccount(account)) store.dispatch(updateAccount(account))
} }
} }
}, },

6
src/renderer/index.js

@ -16,6 +16,8 @@ import { fetchSettings } from 'actions/settings'
import { isLocked } from 'reducers/application' import { isLocked } from 'reducers/application'
import { getLanguage } from 'reducers/settings' import { getLanguage } from 'reducers/settings'
import db from 'helpers/db'
import App from 'components/App' import App from 'components/App'
import 'styles/global' import 'styles/global'
@ -25,6 +27,10 @@ if (__PROD__ && __SENTRY_URL__) {
window.addEventListener('unhandledrejection', event => Raven.captureException(event.reason)) window.addEventListener('unhandledrejection', event => Raven.captureException(event.reason))
} }
// init db with defaults if needed
db.init('accounts', [])
db.init('settings', {})
const history = createHistory() const history = createHistory()
const store = createStore(history) const store = createStore(history)
const rootNode = document.getElementById('app') const rootNode = document.getElementById('app')

2
src/types/common.js

@ -33,7 +33,7 @@ export type Account = {
data?: AccountData, data?: AccountData,
} }
export type Accounts = { [_: string]: Account } export type Accounts = Array<Account>
// -------------------- Settings // -------------------- Settings

Loading…
Cancel
Save