Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- ## [[2.3.3](https://github.com/multiversx/mx-explorer-dapp/pull/213)] - 2026-01-22

- [Websocket Transfers](https://github.com/multiversx/mx-explorer-dapp/pull/210)

- ## [[2.3.2](https://github.com/multiversx/mx-explorer-dapp/pull/208)] - 2025-11-27

- [Handle deprecatedRelayedV1V2](https://github.com/multiversx/mx-explorer-dapp/pull/208)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "mx-explorer-dapp",
"description": "MultiversX Blockchain Explorer",
"version": "2.3.2",
"version": "2.3.3",
"author": "MultiversX",
"license": "GPL-3.0-or-later",
"repository": "multiversx/mx-explorer-dapp",
Expand Down
2 changes: 1 addition & 1 deletion src/assets/scss/common/variables/_bootstrap-variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ $dropdown-link-color: $neutral-400;
$dropdown-link-hover-color: $body-color;
$dropdown-link-hover-bg: $neutral-700;
$dropdown-link-active-color: $body-color;
$dropdown-link-active-bg: $neutral-900;
$dropdown-link-active-bg: $neutral-980;
$dropdown-link-disabled-color: $neutral-700;
$dropdown-header-color: $neutral-700;
// scss-docs-end dropdown-variables
Expand Down
19 changes: 6 additions & 13 deletions src/components/Chart/ChartLine.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import BigNumber from 'bignumber.js';
import classNames from 'classnames';
import moment from 'moment';
import {
ResponsiveContainer,
XAxis,
YAxis,
Line,
LineChart,
Tooltip,
CartesianGrid
} from 'recharts';
import { XAxis, YAxis, Line, Tooltip, CartesianGrid } from 'recharts';

import { getColors } from 'helpers';
import { ChartLineContainer } from './ChartLineContainer';
import { ChartTooltip } from './ChartTooltip';
import { formatYAxis } from './helpers/formatYAxis';
import { getChartMergedData } from './helpers/getChartMergedData';
Expand Down Expand Up @@ -82,8 +75,8 @@ export const ChartLine = ({
'has-only-start-end-tick': hasOnlyStartEndTick
})}
>
<ResponsiveContainer width={width ?? '100%'} height={height ?? '100%'}>
<LineChart data={chartData}>
<ChartLineContainer width={width} height={height} data={chartData}>
<>
<defs>
<linearGradient id='transparent' x1='0' y1='0' x2='0' y2='1'>
<stop offset='100%' stopColor='transparent' stopOpacity={0} />
Expand Down Expand Up @@ -190,8 +183,8 @@ export const ChartLine = ({
}
/>
)}
</LineChart>
</ResponsiveContainer>
</>
</ChartLineContainer>
</div>
);
};
28 changes: 28 additions & 0 deletions src/components/Chart/ChartLineContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { JSXElementConstructor, memo, ReactElement } from 'react';
import { ResponsiveContainer, LineChart } from 'recharts';
import { MergedChartDataType } from './helpers/types';

interface ChartLineContainerUIType {
children: ReactElement<unknown, string | JSXElementConstructor<any>>;
data: MergedChartDataType[];
width?: string | number;
height?: string | number;
}

export const ChartLineContainer = memo(
({ children, data, width, height }: ChartLineContainerUIType) => {
if (typeof width === 'number' && typeof height === 'number') {
return (
<LineChart data={data} width={width} height={height}>
{children}
</LineChart>
);
}

return (
<ResponsiveContainer width={width ?? '100%'} height={height ?? '100%'}>
<LineChart data={data}>{children}</LineChart>
</ResponsiveContainer>
);
}
);
86 changes: 44 additions & 42 deletions src/components/NftPreview/NftPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,50 +83,52 @@ export const NftPreview = ({ token }: { token: NftType }) => {
<div className='nft-details d-flex flex-column text-start'>
<ul className='list-unstyled mb-0'>
{token.uris.map((uri, i) => {
if (uri !== null && uri !== undefined) {
const link = Buffer.from(String(uri), 'base64').toString();
const { stringWithLinks, found } = scamFlag(link, token.scamInfo);
if (!uri) {
return null;
}

return (
<li key={i}>
<FontAwesomeIcon
icon={faCaretRight}
size='xs'
className='text-neutral-400 me-2'
/>
{link.startsWith(
'https://ipfs.io/ipfs/'
) /* && token.isWhitelistedStorage === true */ ? (
<ModalLink
href={found ? stringWithLinks : link}
target='_blank'
rel='noreferrer nofollow noopener'
className='text-break-all'
>
<Thumbnail link={found ? '' : link} token={token} index={i}>
{found ? stringWithLinks : link}
const link = Buffer.from(String(uri), 'base64').toString();
const { stringWithLinks, found } = scamFlag(link, token.scamInfo);

return (
<li key={i}>
<FontAwesomeIcon
icon={faCaretRight}
size='xs'
className='text-neutral-400 me-2'
/>
{link.startsWith(
'https://ipfs.io/ipfs/'
) /* && token.isWhitelistedStorage === true */ ? (
<ModalLink
href={found ? stringWithLinks : link}
target='_blank'
rel='noreferrer nofollow noopener'
className='text-break-all'
>
<Thumbnail link={found ? '' : link} token={token} index={i}>
{found ? stringWithLinks : link}
</Thumbnail>
</ModalLink>
) : (
<span className='text-break'>
{found ? (
<Anchorme
linkComponent={ModalLink}
target='_blank'
rel='noreferrer nofollow noopener'
>
{stringWithLinks}
</Anchorme>
) : (
<Thumbnail link={link} token={token} index={i}>
<span>{link}</span>
</Thumbnail>
</ModalLink>
) : (
<span className='text-break'>
{found ? (
<Anchorme
linkComponent={ModalLink}
target='_blank'
rel='noreferrer nofollow noopener'
>
{stringWithLinks}
</Anchorme>
) : (
<Thumbnail link={link} token={token} index={i}>
<span>{link}</span>
</Thumbnail>
)}
</span>
)}
</li>
);
} else return null;
)}
</span>
)}
</li>
);
})}
</ul>
{/* {token.isWhitelistedStorage === false && (
Expand Down
1 change: 1 addition & 0 deletions src/helpers/processData/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './processGrowthPrice';
export * from './processGrowthSearch';
export * from './processGrowthStaking';
export * from './processGrowthTransactions';
export * from './processListUpdates';
export * from './processNodesIdentities';
export * from './processNodesOverview';
export * from './processNodesVersions';
Expand Down
42 changes: 42 additions & 0 deletions src/helpers/processData/processListUpdates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { PAGE_SIZE } from 'appConstants';

interface ProcessListUpdatesProps {
existing: any[];
incoming: any[];
uniqueKey: string;
size?: number;
}

export const processListUpdates = ({
existing = [],
incoming = [],
uniqueKey,
size = PAGE_SIZE
}: ProcessListUpdatesProps) => {
const existingSet = new Set(existing.map((entry) => entry[uniqueKey]));
const updated = new Map<string, any>();
const result: any[] = [];

for (const entry of existing) {
updated.set(entry[uniqueKey], entry);
}
for (const entry of incoming) {
updated.set(entry[uniqueKey], { ...entry, isNew: true });
}

for (const entry of incoming) {
if (!existingSet.has(entry[uniqueKey])) {
result.push(updated.get(entry[uniqueKey])!);
}
}

for (const entry of existing) {
if (entry[uniqueKey] === undefined) {
continue;
}
result.push(updated.get(entry[uniqueKey])!);
}

// keep the resulting set the same size as the page size prop
return result.slice(0, size);
};
36 changes: 16 additions & 20 deletions src/hooks/adapter/requests/useTokenRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export const useTokenRequests = () => {
return {
/* Tokens */

getToken: (tokenId: string, { signal, timeout }: AxiosParamsApiType = {}) =>
provider({ url: `/tokens/${tokenId}`, signal, timeout }),
getToken: (token: string, { signal, timeout }: AxiosParamsApiType = {}) =>
provider({ url: `/tokens/${token}`, signal, timeout }),

getTokens: ({ signal, timeout, ...params }: GetTokensType) =>
provider({
Expand All @@ -34,13 +34,13 @@ export const useTokenRequests = () => {
}),

getTokenTransactions: ({
tokenId,
token,
signal,
timeout,
...params
}: GetTransactionsType & GetTokenResourceType) =>
provider({
url: `/tokens/${tokenId}/transactions`,
url: `/tokens/${token}/transactions`,
signal,
timeout,
params: getTransactionsParams({
Expand All @@ -49,13 +49,13 @@ export const useTokenRequests = () => {
}),

getTokenTransactionsCount: ({
tokenId,
token,
signal,
timeout,
...params
}: GetTransactionsType & GetTokenResourceType) =>
provider({
url: `/tokens/${tokenId}/transactions/c`,
url: `/tokens/${token}/transactions/c`,
signal,
timeout,
params: getTransactionsParams({
Expand All @@ -65,13 +65,13 @@ export const useTokenRequests = () => {
}),

getTokenTransfers: ({
tokenId,
token,
signal,
timeout,
...params
}: GetTransactionsType & GetTokenResourceType) =>
provider({
url: `/tokens/${tokenId}/transfers`,
url: `/tokens/${token}/transfers`,
signal,
timeout,
params: getTransactionsParams({
Expand All @@ -80,13 +80,13 @@ export const useTokenRequests = () => {
}),

getTokenTransfersCount: ({
tokenId,
token,
signal,
timeout,
...params
}: GetTransactionsType & GetTokenResourceType) =>
provider({
url: `/tokens/${tokenId}/transfers/c`,
url: `/tokens/${token}/transfers/c`,
signal,
timeout,
params: getTransactionsParams({
Expand All @@ -96,32 +96,28 @@ export const useTokenRequests = () => {
}),

getTokenAccounts: ({
tokenId,
token,
signal,
timeout,
...params
}: GetTokensType & GetTokenResourceType) =>
provider({
url: `/tokens/${tokenId}/accounts`,
url: `/tokens/${token}/accounts`,
signal,
timeout,
params: getTokensParams({ ...params })
}),

getTokenAccountsCount: ({
tokenId,
signal,
timeout
}: GetTokenResourceType) =>
getTokenAccountsCount: ({ token, signal, timeout }: GetTokenResourceType) =>
provider({
url: `/tokens/${tokenId}/accounts/count`,
url: `/tokens/${token}/accounts/count`,
signal,
timeout
}),

getTokenSupply: ({ tokenId, signal, timeout }: GetTokenResourceType) =>
getTokenSupply: ({ token, signal, timeout }: GetTokenResourceType) =>
provider({
url: `/tokens/${tokenId}/supply`,
url: `/tokens/${token}/supply`,
signal,
timeout
})
Expand Down
1 change: 1 addition & 0 deletions src/hooks/fetch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export * from './useFetchNodesVersions';
export * from './useFetchShards';
export * from './useFetchStats';
export * from './useFetchTransactions';
export * from './useFetchCustomTransfers';
export * from './useFetchTransactionsInPool';
export * from './useFetchWebsocketConfig';
10 changes: 9 additions & 1 deletion src/hooks/fetch/useFetchApiData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export interface FetchApiDataProps {
event?: WebsocketEventsEnum;
websocketConfig?: Record<string, any>;
urlParams?: Record<string, any>;
uuid?: string;
isRefreshPaused?: boolean;
isCustomUpdate?: boolean;
}

export const useFetchApiData = ({
Expand All @@ -35,6 +37,8 @@ export const useFetchApiData = ({
event,
websocketConfig = {},
urlParams = {},
uuid = '',
isCustomUpdate,
isRefreshPaused = false
}: FetchApiDataProps) => {
const { page, size } = useGetPage();
Expand All @@ -61,7 +65,11 @@ export const useFetchApiData = ({
useRegisterWebsocketListener({
subscription,
event,
config: { from: 0, size: PAGE_SIZE, ...websocketConfig },
uuid,
config: {
...(isCustomUpdate ? {} : { from: 0, size: PAGE_SIZE }),
...websocketConfig
},
onWebsocketEvent,
isPaused
});
Expand Down
Loading
Loading