From 713a4463fdd80fd5f4f6efc2b16621dcb4a1e81b Mon Sep 17 00:00:00 2001 From: Kristof Csillag Date: Thu, 2 Nov 2023 01:42:32 +0100 Subject: [PATCH 1/4] Add missing brand-light-blue color --- src/styles/theme/colors.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/theme/colors.ts b/src/styles/theme/colors.ts index 92c0060e6..e2f41b5f4 100644 --- a/src/styles/theme/colors.ts +++ b/src/styles/theme/colors.ts @@ -9,6 +9,7 @@ export const COLORS = { brandExtraDark: '#000062', brandExtraLight: '#e5e5ef', brandLight: '#6665d8', + brandLightBlue: '#E8F5FF', brandMedium: '#0092f6', brandMedium15: '#d9effe', brightGray2: '#ececec', From 2b037876f0547156760a8beff7041cc0ada24a7e Mon Sep 17 00:00:00 2001 From: Kristof Csillag Date: Thu, 2 Nov 2023 01:43:05 +0100 Subject: [PATCH 2/4] Add first draft of event filter switch --- .../RuntimeEvents/EventListFilterSwitch.tsx | 71 +++++++++++++++++++ src/locales/en/translation.json | 4 ++ 2 files changed, 75 insertions(+) create mode 100644 src/app/components/RuntimeEvents/EventListFilterSwitch.tsx diff --git a/src/app/components/RuntimeEvents/EventListFilterSwitch.tsx b/src/app/components/RuntimeEvents/EventListFilterSwitch.tsx new file mode 100644 index 000000000..c7ce3f9c8 --- /dev/null +++ b/src/app/components/RuntimeEvents/EventListFilterSwitch.tsx @@ -0,0 +1,71 @@ +import { FC } from 'react' +import Chip from '@mui/material/Chip' +import CheckIcon from '@mui/icons-material/Check' +import { useTranslation } from 'react-i18next' +import { COLORS } from '../../../styles/theme/colors' +import { TFunction } from 'i18next' +import Box from '@mui/material/Box' + +export const EventFilterMode = { + All: 'all', + NonTX: 'nonTX', +} as const + +const eventFilterModes: EventFilterMode[] = [EventFilterMode.All, EventFilterMode.NonTX] + +const getEventFilterModeName = (t: TFunction, mode: EventFilterMode): string => { + switch (mode) { + case 'all': + return t('runtimeEvent.filter.all') + case 'nonTX': + return t('runtimeEvent.filter.nonTx') + } +} + +const Pill: FC<{ label: string; selected: boolean; onSelect: () => void }> = ({ + label, + selected, + onSelect, +}) => { + return ( + : undefined} + label={label} + onClick={onSelect} + sx={{ + display: 'flex', + height: 30, + padding: '3px 10px', + alignItems: 'center', + borderRadius: 9, + border: `1px solid ${COLORS.brandMedium}`, + background: selected ? COLORS.brandMedium : COLORS.brandLightBlue, + color: selected ? 'white' : 'black', + }} + /> + ) +} + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type EventFilterMode = (typeof EventFilterMode)[keyof typeof EventFilterMode] + +type EventFilterSwitchProps = { + selected?: EventFilterMode + onSelectionChange: (selection: EventFilterMode) => void +} + +export const EventFilterSwitch: FC = ({ selected, onSelectionChange }) => { + const { t } = useTranslation() + return ( + + {eventFilterModes.map(mode => ( + onSelectionChange(mode)} + /> + ))} + + ) +} diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 051a958d6..f19edaf87 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -292,6 +292,10 @@ "consensusDeposit": "Deposit from consensus", "consensusWithdrawal": "Withdrawal to consensus", "evmLog": "EVM log message", + "filter": { + "all": "All events", + "nonTx": "Non-transactional" + }, "gasUsed": "Gas used", "fields": { "amount": "Amount", From 7fe64d201ff5463c07d9824578a496d051eb3331 Mon Sep 17 00:00:00 2001 From: Csillag Kristof Date: Tue, 31 Oct 2023 22:32:22 +0100 Subject: [PATCH 3/4] Show block-level events in block details W.I.P. because filtering out tx-level events is done on the client --- .changelog/990.feature.md | 1 + src/app/pages/BlockDetailPage/EventsCard.tsx | 82 ++++++++++++++++++++ src/app/pages/BlockDetailPage/index.tsx | 2 + src/locales/en/translation.json | 2 + 4 files changed, 87 insertions(+) create mode 100644 .changelog/990.feature.md create mode 100644 src/app/pages/BlockDetailPage/EventsCard.tsx diff --git a/.changelog/990.feature.md b/.changelog/990.feature.md new file mode 100644 index 000000000..9c024abc9 --- /dev/null +++ b/.changelog/990.feature.md @@ -0,0 +1 @@ +Show block-level evens in block details diff --git a/src/app/pages/BlockDetailPage/EventsCard.tsx b/src/app/pages/BlockDetailPage/EventsCard.tsx new file mode 100644 index 000000000..2f8d1e118 --- /dev/null +++ b/src/app/pages/BlockDetailPage/EventsCard.tsx @@ -0,0 +1,82 @@ +import { FC, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { ScrollingCard } from '../../components/PageLayout/ScrollingCard' +import CardHeader from '@mui/material/CardHeader' +import CardContent from '@mui/material/CardContent' + +import { Layer, RuntimeEventType, useGetRuntimeEvents } from '../../../oasis-nexus/api' +import { ErrorBoundary } from '../../components/ErrorBoundary' +import { AppErrors } from '../../../types/errors' +import { SearchScope } from '../../../types/searchScope' +import { RuntimeEventsDetailedList } from '../../components/RuntimeEvents/RuntimeEventsDetailedList' +import { AddressSwitchOption } from '../../components/AddressSwitch' +import { EventFilterMode, EventFilterSwitch } from '../../components/RuntimeEvents/EventListFilterSwitch' +import { EmptyState } from '../../components/EmptyState' + +export const eventsContainerId = 'events' + +const EventsList: FC<{ scope: SearchScope; blockHeight: number; filterMode: EventFilterMode }> = ({ + scope, + blockHeight, + filterMode, +}) => { + const { t } = useTranslation() + if (scope.layer === Layer.consensus) { + // Loading events for consensus blocks is not yet supported. + // Should use useGetConsensusEvents() + throw AppErrors.UnsupportedLayer + } + const eventsQuery = useGetRuntimeEvents(scope.network, scope.layer, { + block: blockHeight, + // TODO: search for tx_hash = null + limit: 100, // We want to avoid pagination here, if possible + }) + + const { isLoading, isError, data } = eventsQuery + + const events = data?.data.events.filter( + event => + !event.tx_hash && // TODO: remove filtering here if it's implemented using the query parameters + (filterMode === EventFilterMode.All || event.type !== RuntimeEventType.accountstransfer), + ) + + if (!events?.length && !isLoading) { + return ( + + ) + } + + return ( + + ) +} + +export const EventsCard: FC<{ scope: SearchScope; blockHeight: number }> = ({ scope, blockHeight }) => { + const [filterMode, setFilterMode] = useState(EventFilterMode.All) + const { t } = useTranslation() + return ( + + } + /> + + + + + + + ) +} diff --git a/src/app/pages/BlockDetailPage/index.tsx b/src/app/pages/BlockDetailPage/index.tsx index 19211a05f..230ac226b 100644 --- a/src/app/pages/BlockDetailPage/index.tsx +++ b/src/app/pages/BlockDetailPage/index.tsx @@ -18,6 +18,7 @@ import { BlockLink, BlockHashLink } from '../../components/Blocks/BlockLink' import { RouteUtils } from '../../utils/route-utils' import { useRequiredScopeParam } from '../../hooks/useScopeParam' import { DashboardLink } from '../ParatimeDashboardPage/DashboardLink' +import { EventsCard } from './EventsCard' export const BlockDetailPage: FC = () => { const { t } = useTranslation() @@ -43,6 +44,7 @@ export const BlockDetailPage: FC = () => { {!!block?.num_transactions && } + ) } diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index f19edaf87..378a986bc 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -286,6 +286,8 @@ }, "runtimeEvent": { "cantLoadEvents": "Unfortunately we couldn't load the list of events. Please try again later.", + "noEvents": "No events", + "cantFindMatchingEvents": "We can't find any matching events.", "accountsburn": "Tokens burnt", "accountsmint": "Tokens minted", "accountstransfer": "Transfer", From b8c35b057fb4cbe4d12a4cac2dd5d942893780da Mon Sep 17 00:00:00 2001 From: Kristof Csillag Date: Wed, 8 Nov 2023 00:49:18 +0100 Subject: [PATCH 4/4] Also apply filtering to account events --- .../AccountDetailsPage/AccountEventsCard.tsx | 17 +++++++++++++++-- src/app/pages/AccountDetailsPage/hook.ts | 8 ++++++-- src/app/pages/AccountDetailsPage/index.tsx | 19 ++++++++++++++++--- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/app/pages/AccountDetailsPage/AccountEventsCard.tsx b/src/app/pages/AccountDetailsPage/AccountEventsCard.tsx index a7e048d27..fd148700b 100644 --- a/src/app/pages/AccountDetailsPage/AccountEventsCard.tsx +++ b/src/app/pages/AccountDetailsPage/AccountEventsCard.tsx @@ -5,19 +5,32 @@ import { useTranslation } from 'react-i18next' import { RuntimeEventsDetailedList } from '../../components/RuntimeEvents/RuntimeEventsDetailedList' import { SearchScope } from '../../../types/searchScope' import { AddressSwitchOption } from '../../components/AddressSwitch' +import { EventFilterMode, EventFilterSwitch } from '../../components/RuntimeEvents/EventListFilterSwitch' type AccountEventProps = { scope: SearchScope isLoading: boolean + filterMode: EventFilterMode + setFilterMode: (mode: EventFilterMode) => void isError: boolean events: RuntimeEvent[] | undefined } -export const AccountEventsCard: FC = ({ scope, isLoading, isError, events }) => { +export const AccountEventsCard: FC = ({ + scope, + isLoading, + filterMode, + setFilterMode, + isError, + events, +}) => { const { t } = useTranslation() return ( - + } + > { const { network, layer } = scope @@ -60,7 +62,7 @@ export const useAccountTransactions = (scope: SearchScope, address: string) => { } } -export const useAccountEvents = (scope: SearchScope, address: string) => { +export const useAccountEvents = (scope: SearchScope, address: string, filterMode: EventFilterMode) => { const { network, layer } = scope if (layer === Layer.consensus) { throw AppErrors.UnsupportedLayer @@ -72,6 +74,8 @@ export const useAccountEvents = (scope: SearchScope, address: string) => { // TODO: implement filtering for non-transactional events }) const { isFetched, isLoading, isError, data } = query - const events = data?.data.events + const events = data?.data.events.filter( + event => filterMode === EventFilterMode.All || event.type !== RuntimeEventType.accountstransfer, + ) return { isFetched, isLoading, isError, events } } diff --git a/src/app/pages/AccountDetailsPage/index.tsx b/src/app/pages/AccountDetailsPage/index.tsx index b442c5d0c..67ef94525 100644 --- a/src/app/pages/AccountDetailsPage/index.tsx +++ b/src/app/pages/AccountDetailsPage/index.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react' +import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' import { useHref, useLoaderData, useOutletContext } from 'react-router-dom' import { PageLayout } from '../../components/PageLayout' @@ -17,6 +17,7 @@ import { getTokenTypePluralName } from '../../../types/tokens' import { SearchScope } from '../../../types/searchScope' import { AccountDetailsCard } from './AccountDetailsCard' import { AccountEventsCard } from './AccountEventsCard' +import { EventFilterMode } from '../../components/RuntimeEvents/EventListFilterSwitch' export type AccountDetailsContext = { scope: SearchScope @@ -35,8 +36,13 @@ export const AccountDetailsPage: FC = () => { const { token, isLoading: isTokenLoading } = useTokenInfo(scope, address, isContract) const tokenPriceInfo = useTokenPrice(account?.ticker || Ticker.ROSE) + const [eventFilterMode, setEventFilterMode] = useState(EventFilterMode.All) - const { isLoading: areEventsLoading, isError: isEventsError, events } = useAccountEvents(scope, address) + const { + isLoading: areEventsLoading, + isError: isEventsError, + events, + } = useAccountEvents(scope, address, eventFilterMode) const tokenTransfersLink = useHref(`token-transfers#${accountTokenTransfersContainerId}`) const erc20Link = useHref(`tokens/erc-20#${accountTokenContainerId}`) @@ -76,7 +82,14 @@ export const AccountDetailsPage: FC = () => { ]} context={context} /> - + ) }