diff --git a/.changeset/grumpy-olives-yell.md b/.changeset/grumpy-olives-yell.md new file mode 100644 index 00000000..5b88f376 --- /dev/null +++ b/.changeset/grumpy-olives-yell.md @@ -0,0 +1,5 @@ +--- +'@smartcontractkit/operator-ui': minor +--- + +Change the Account Balance section to accommodate multiple accounts on different chains. diff --git a/src/pages/Notifications.tsx b/src/pages/Notifications.tsx index 7631708f..fda991a3 100644 --- a/src/pages/Notifications.tsx +++ b/src/pages/Notifications.tsx @@ -54,7 +54,7 @@ interface Props extends OwnProps, StateProps {} export const Notifications: React.FC = ({ errors, successes }) => { return (
- {errors.length > 0 && } + {errors?.length > 0 && } {successes.length > 0 && }
) diff --git a/src/reducers/notifications.ts b/src/reducers/notifications.ts index 74cbc999..0a01b201 100644 --- a/src/reducers/notifications.ts +++ b/src/reducers/notifications.ts @@ -50,9 +50,9 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { } case NotifyActionType.NOTIFY_ERROR: { const errors = action.error.errors - const notifications = errors.map((e) => - buildJsonApiErrorNotification(action, e), - ) + const notifications = errors?.map(function (e) { + return buildJsonApiErrorNotification(action, e) + }) return { ...state, diff --git a/src/screens/Dashboard/AccountBalance.test.tsx b/src/screens/Dashboard/AccountBalance.test.tsx index ecdaeaa6..41e04fc8 100644 --- a/src/screens/Dashboard/AccountBalance.test.tsx +++ b/src/screens/Dashboard/AccountBalance.test.tsx @@ -36,7 +36,7 @@ function fetchAccountBalancesQuery( } describe('Activity', () => { - it('renders the first account balance', async () => { + it('renders the first and second account balance', async () => { const payload = buildETHKeys() const mocks: MockedResponse[] = [fetchAccountBalancesQuery(payload)] @@ -45,7 +45,7 @@ describe('Activity', () => { await waitForLoading() expect(await findByText(payload[0].address)).toBeInTheDocument() - expect(queryByText(payload[1].address)).toBeNull() + expect(queryByText(payload[1].address)).toBeInTheDocument() }) it('renders GQL query errors', async () => { diff --git a/src/screens/Dashboard/AccountBalanceCard.test.tsx b/src/screens/Dashboard/AccountBalanceCard.test.tsx index 87b6a376..d5b4e0ea 100644 --- a/src/screens/Dashboard/AccountBalanceCard.test.tsx +++ b/src/screens/Dashboard/AccountBalanceCard.test.tsx @@ -36,9 +36,6 @@ describe('AccountBalanceCard', () => { queryByText(fromJuels(ethKey.linkBalance as string)), ).toBeInTheDocument() expect(queryByText(ethKey.ethBalance as string)).toBeInTheDocument() - - // Does not appear if there is only one account - expect(queryByRole('link', { name: /view more accounts/i })).toBeNull() }) it('renders the empty balances for an account', () => { @@ -68,10 +65,6 @@ describe('AccountBalanceCard', () => { }, }, }) - - expect( - queryByRole('link', { name: /view more accounts/i }), - ).toBeInTheDocument() }) it('renders no content', () => { diff --git a/src/screens/Dashboard/AccountBalanceCard.tsx b/src/screens/Dashboard/AccountBalanceCard.tsx index 6a21fc27..cd6d4e62 100644 --- a/src/screens/Dashboard/AccountBalanceCard.tsx +++ b/src/screens/Dashboard/AccountBalanceCard.tsx @@ -3,19 +3,13 @@ import React from 'react' import { gql } from '@apollo/client' import Card from '@material-ui/core/Card' -import CardActions from '@material-ui/core/CardActions' -import CardContent from '@material-ui/core/CardContent' -import CardHeader from '@material-ui/core/CardHeader' import Grid from '@material-ui/core/Grid' +import { DetailsCardItemValue } from 'src/components/Cards/DetailsCard' +import { ChainAccountBalanceCard } from 'screens/Dashboard/ChainAccountBalanceCard' +import { EthKey } from 'types/generated/graphql' +import CardHeader from '@material-ui/core/CardHeader' import CircularProgress from '@material-ui/core/CircularProgress' -import { fromJuels } from 'src/utils/tokens/link' -import { - DetailsCardItemTitle, - DetailsCardItemValue, -} from 'src/components/Cards/DetailsCard' -import Link from 'src/components/Link' - export const ACCOUNT_BALANCES_PAYLOAD__RESULTS_FIELDS = gql` fragment AccountBalancesPayload_ResultsFields on EthKey { address @@ -34,73 +28,65 @@ export interface Props { errorMsg?: string } +export type KeysByChainID = { + [chainID: string]: Array +} + export const AccountBalanceCard: React.FC = ({ data, errorMsg, loading, }) => { const results = data?.ethKeys.results - const ethKey = results && results.length > 0 ? results[0] : undefined + const keysByChain: KeysByChainID = {} + results?.forEach(function (key) { + if (keysByChain[key.chain.id] === undefined) { + keysByChain[key.chain.id] = [] + } + keysByChain[key.chain.id].push(key as EthKey) + }) return ( - - - - - {loading && ( - - - - )} - - {errorMsg && ( - - - - )} - - {ethKey && ( - <> - - - - - - - - - - - - - - - - )} + {errorMsg && ( + <> + + + + + + )} - {!ethKey && !loading && !errorMsg && ( - - - - )} + {loading && ( + + - - {results && results.length > 1 && ( - - - View more accounts - - )} + + {!results?.length && !loading && !errorMsg && ( + <> + + + + + + )} + + {results && + Object.keys(keysByChain).map(function (chainID, index) { + return ( + + ) + })} ) } diff --git a/src/screens/Dashboard/ChainAccountBalanceCard.test.tsx b/src/screens/Dashboard/ChainAccountBalanceCard.test.tsx new file mode 100644 index 00000000..7fa8c7b8 --- /dev/null +++ b/src/screens/Dashboard/ChainAccountBalanceCard.test.tsx @@ -0,0 +1,69 @@ +import * as React from 'react' +import { renderWithRouter, screen } from 'support/test-utils' +import { ChainAccountBalanceCard } from 'screens/Dashboard/ChainAccountBalanceCard' +import { EthKey } from 'types/generated/graphql' +import { buildETHKey } from 'support/factories/gql/fetchAccountBalances' +import { fromJuels } from 'utils/tokens/link' + +const { findByText, queryByText } = screen + +describe('ChainAccountBalanceCard', () => { + function renderComponent( + keys: Array, + chainID: string, + hideHeaderTitle: boolean, + ) { + renderWithRouter( + , + ) + } + + it('renders the card with one address', async () => { + const key = buildETHKey() + + renderComponent([key] as Array, '111', false) + + expect(await findByText('Account Balances')).toBeInTheDocument() + expect(await findByText('Chain ID 111')).toBeInTheDocument() + expect(await findByText('Address')).toBeInTheDocument() + expect(await findByText('Native Token Balance')).toBeInTheDocument() + expect(await findByText('LINK Balance')).toBeInTheDocument() + + expect(await findByText(key.address)).toBeInTheDocument() + expect(await findByText(key.ethBalance as string)).toBeInTheDocument() + expect( + await findByText(fromJuels(key.linkBalance as string)), + ).toBeInTheDocument() + }) + + it('renders the card with two addresses', async () => { + const keys = [ + buildETHKey(), + buildETHKey({ + address: '0x0000000000000000000000000000000000000002', + linkBalance: '0.123', + ethBalance: '0.321', + }), + ] + + renderComponent(keys as Array, '12345321', true) + + expect(await queryByText('Account Balances')).toBeNull() + expect(await findByText('Chain ID 12345321')).toBeInTheDocument() + + expect(await findByText(keys[0].address)).toBeInTheDocument() + expect(await findByText(keys[1].address)).toBeInTheDocument() + expect(await findByText(keys[0].ethBalance as string)).toBeInTheDocument() + expect(await findByText(keys[1].ethBalance as string)).toBeInTheDocument() + expect( + await findByText(fromJuels(keys[0].linkBalance as string)), + ).toBeInTheDocument() + expect( + await findByText(fromJuels(keys[1].linkBalance as string)), + ).toBeInTheDocument() + }) +}) diff --git a/src/screens/Dashboard/ChainAccountBalanceCard.tsx b/src/screens/Dashboard/ChainAccountBalanceCard.tsx new file mode 100644 index 00000000..15289698 --- /dev/null +++ b/src/screens/Dashboard/ChainAccountBalanceCard.tsx @@ -0,0 +1,83 @@ +import React from 'react' +import { EthKey } from 'types/generated/graphql' +import Grid from '@material-ui/core/Grid' +import { + DetailsCardItemTitle, + DetailsCardItemValue, +} from 'components/Cards/DetailsCard' +import { fromJuels } from 'utils/tokens/link' +import CardContent from '@material-ui/core/CardContent' +import CardHeader from '@material-ui/core/CardHeader' +import List from '@material-ui/core/List' +import ListItem from '@material-ui/core/ListItem' +import ListItemText from '@material-ui/core/ListItemText' +import Divider from '@material-ui/core/Divider' + +export interface Props { + keys: Array + chainID: string + hideHeaderTitle: boolean +} +export const ChainAccountBalanceCard: React.FC = ({ + keys, + chainID, + hideHeaderTitle, +}) => { + return ( + <> + + + + + {keys && + keys.map((key, i) => { + return ( + <> + + + + + + + + + + + + + + + + + + } + > + + {/* Don't show divider on the last element */} + {i + 1 < keys.length && } + + ) + })} + + + + ) +} diff --git a/src/screens/Dashboard/DashboardView.test.tsx b/src/screens/Dashboard/DashboardView.test.tsx index d9e57ae2..7c01c13b 100644 --- a/src/screens/Dashboard/DashboardView.test.tsx +++ b/src/screens/Dashboard/DashboardView.test.tsx @@ -19,7 +19,7 @@ describe('DashboardView', () => { ) expect(await findByText('Activity')).toBeInTheDocument() - expect(await findByText('Account Balance')).toBeInTheDocument() + expect(await findByText('Account Balances')).toBeInTheDocument() expect(await findByText('Recent Jobs')).toBeInTheDocument() }) })