Commit b54be82e authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #2690 from blockscout/release/v2-0-0

Fixes for release `v2.0.0`
parents a76c14ca eb585d45
...@@ -15,7 +15,9 @@ on: ...@@ -15,7 +15,9 @@ on:
description: Image platforms (you can specify multiple platforms separated by comma) description: Image platforms (you can specify multiple platforms separated by comma)
required: false required: false
type: string type: string
default: linux/amd64 default: |
linux/amd64
linux/arm64/v8
workflow_call: workflow_call:
inputs: inputs:
tags: tags:
...@@ -30,7 +32,9 @@ on: ...@@ -30,7 +32,9 @@ on:
description: Image platforms (you can specify multiple platforms separated by comma) description: Image platforms (you can specify multiple platforms separated by comma)
required: false required: false
type: string type: string
default: linux/amd64 default: |
linux/amd64
linux/arm64/v8
jobs: jobs:
run: run:
...@@ -40,9 +44,6 @@ jobs: ...@@ -40,9 +44,6 @@ jobs:
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Will automatically make nice tags, see the table here https://github.com/docker/metadata-action#basic # Will automatically make nice tags, see the table here https://github.com/docker/metadata-action#basic
- name: Docker meta - name: Docker meta
id: meta id: meta
...@@ -55,13 +56,6 @@ jobs: ...@@ -55,13 +56,6 @@ jobs:
type=ref,event=tag type=ref,event=tag
${{ inputs.tags }} ${{ inputs.tags }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Add SHORT_SHA env property with commit short sha - name: Add SHORT_SHA env property with commit short sha
run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
...@@ -73,6 +67,17 @@ jobs: ...@@ -73,6 +67,17 @@ jobs:
echo "ref_type: $REF_TYPE" echo "ref_type: $REF_TYPE"
echo "ref_name: $REF_NAME" echo "ref_name: $REF_NAME"
- name: Setup repo
uses: blockscout/actions/.github/actions/setup-multiarch-buildx@no-metadata
id: setup
with:
docker-image: ghcr.io/blockscout/frontend
docker-username: ${{ github.actor }}
docker-password: ${{ secrets.GITHUB_TOKEN }}
docker-remote-multi-platform: true
docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }}
docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
......
...@@ -59,7 +59,7 @@ module.exports = { ...@@ -59,7 +59,7 @@ module.exports = {
{ {
userAgent: '*', userAgent: '*',
allow: '/', allow: '/',
disallow: ['/auth/*', '/login', '/chakra', '/sprite', '/account/*', '/api/*', '/node-api/*'], disallow: ['/auth/*', '/login', '/chakra', '/sprite', '/account/*'],
}, },
], ],
}, },
......
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.78 7.28a.75.75 0 0 0-1.06-1.06l-6.97 6.97-3.47-3.47a.75.75 0 0 0-1.06 1.06l4 4a.75.75 0 0 0 1.06 0l7.5-7.5Z" fill="currentColor"/>
</svg>
import { useCallback, useRef, useEffect } from 'react'; import { useCallback, useRef, useEffect } from 'react';
import type { PreSubmitTransactionResponse, PreVerifyContractResponse } from '@blockscout/points-types'; import type { PreSubmitTransactionResponse } from '@blockscout/points-types';
import config from 'configs/app'; import config from 'configs/app';
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
...@@ -74,17 +74,11 @@ export default function useRewardsActivity() { ...@@ -74,17 +74,11 @@ export default function useRewardsActivity() {
[ makeRequest ], [ makeRequest ],
); );
const trackContract = useCallback(async(address: string) => { const trackContract = useCallback(async(address: string) =>
return ( makeRequest('rewards_user_activity_track_contract', {
await makeRequest('rewards_user_activity_track_contract', {
address, address,
chain_id: config.chain.id ?? '', chain_id: config.chain.id ?? '',
}) }),
) as PreVerifyContractResponse | undefined;
}, [ makeRequest ]);
const trackContractConfirm = useCallback((token: string) =>
makeRequest('rewards_user_activity_track_contract_confirm', { token }),
[ makeRequest ], [ makeRequest ],
); );
...@@ -115,7 +109,6 @@ export default function useRewardsActivity() { ...@@ -115,7 +109,6 @@ export default function useRewardsActivity() {
trackTransaction, trackTransaction,
trackTransactionConfirm, trackTransactionConfirm,
trackContract, trackContract,
trackContractConfirm,
trackUsage, trackUsage,
}; };
} }
...@@ -6,7 +6,7 @@ import useApiQuery from 'lib/api/useApiQuery'; ...@@ -6,7 +6,7 @@ import useApiQuery from 'lib/api/useApiQuery';
import useAccount from './useAccount'; import useAccount from './useAccount';
export default function useAccountWithDomain(isEnabled: boolean) { export default function useAccountWithDomain(isEnabled: boolean) {
const { address } = useAccount(); const { address, isConnecting } = useAccount();
const isQueryEnabled = config.features.nameService.isEnabled && Boolean(address) && Boolean(isEnabled); const isQueryEnabled = config.features.nameService.isEnabled && Boolean(address) && Boolean(isEnabled);
...@@ -25,7 +25,7 @@ export default function useAccountWithDomain(isEnabled: boolean) { ...@@ -25,7 +25,7 @@ export default function useAccountWithDomain(isEnabled: boolean) {
return { return {
address: isEnabled ? address : undefined, address: isEnabled ? address : undefined,
domain: domainQuery.data?.domain?.name, domain: domainQuery.data?.domain?.name,
isLoading: isQueryEnabled && domainQuery.isLoading, isLoading: (isQueryEnabled && domainQuery.isLoading) || isConnecting,
}; };
}, [ address, domainQuery.data?.domain?.name, domainQuery.isLoading, isEnabled, isQueryEnabled ]); }, [ address, domainQuery.data?.domain?.name, domainQuery.isLoading, isEnabled, isQueryEnabled, isConnecting ]);
} }
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
| "contracts/regular" | "contracts/regular"
| "contracts/verified_many" | "contracts/verified_many"
| "contracts/verified" | "contracts/verified"
| "copy_check"
| "copy" | "copy"
| "cross" | "cross"
| "delete" | "delete"
......
...@@ -43,7 +43,7 @@ export const Image = React.forwardRef<HTMLImageElement, ImageProps>( ...@@ -43,7 +43,7 @@ export const Image = React.forwardRef<HTMLImageElement, ImageProps>(
onError={ handleLoadError } onError={ handleLoadError }
onLoad={ handleLoadSuccess } onLoad={ handleLoadSuccess }
{ ...rest } { ...rest }
display={ loading ? 'none' : rest.display || 'inline-block' } display={ loading ? 'none' : rest.display || 'block' }
/> />
</> </>
); );
......
...@@ -234,10 +234,11 @@ export interface SelectProps extends SelectRootProps { ...@@ -234,10 +234,11 @@ export interface SelectProps extends SelectRootProps {
portalled?: boolean; portalled?: boolean;
loading?: boolean; loading?: boolean;
errorText?: string; errorText?: string;
contentProps?: SelectContentProps;
} }
export const Select = React.forwardRef<HTMLDivElement, SelectProps>((props, ref) => { export const Select = React.forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
const { collection, placeholder, portalled = true, loading, errorText, ...rest } = props; const { collection, placeholder, portalled = true, loading, errorText, contentProps, ...rest } = props;
return ( return (
<SelectRoot <SelectRoot
ref={ ref } ref={ ref }
...@@ -253,7 +254,7 @@ export const Select = React.forwardRef<HTMLDivElement, SelectProps>((props, ref) ...@@ -253,7 +254,7 @@ export const Select = React.forwardRef<HTMLDivElement, SelectProps>((props, ref)
errorText={ errorText } errorText={ errorText }
/> />
</SelectControl> </SelectControl>
<SelectContent portalled={ portalled }> <SelectContent portalled={ portalled } { ...contentProps }>
{ collection.items.map((item: SelectOption) => ( { collection.items.map((item: SelectOption) => (
<SelectItem item={ item } key={ item.value }> <SelectItem item={ item } key={ item.value }>
{ item.label } { item.label }
......
...@@ -35,13 +35,30 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>( ...@@ -35,13 +35,30 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
lazyMount = true, lazyMount = true,
unmountOnExit = true, unmountOnExit = true,
triggerProps, triggerProps,
closeDelay = 100,
openDelay = 100,
interactive,
...rest ...rest
} = props; } = props;
const [ open, setOpen ] = React.useState<boolean>(defaultOpen); const [ open, setOpen ] = React.useState<boolean>(defaultOpen);
const timeoutRef = React.useRef<number | null>(null);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const triggerRef = useClickAway<HTMLButtonElement>(() => setOpen(false));
const handleClickAway = React.useCallback((event: Event) => {
if (interactive) {
const closest = (event.target as HTMLElement)?.closest('.chakra-tooltip__positioner');
if (closest) {
return;
}
}
timeoutRef.current = window.setTimeout(() => {
setOpen(false);
}, closeDelay);
}, [ closeDelay, interactive ]);
const triggerRef = useClickAway<HTMLButtonElement>(handleClickAway);
const handleOpenChange = React.useCallback((details: { open: boolean }) => { const handleOpenChange = React.useCallback((details: { open: boolean }) => {
setOpen(details.open); setOpen(details.open);
...@@ -49,8 +66,25 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>( ...@@ -49,8 +66,25 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
}, [ onOpenChange ]); }, [ onOpenChange ]);
const handleTriggerClick = React.useCallback(() => { const handleTriggerClick = React.useCallback(() => {
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
}
timeoutRef.current = window.setTimeout(() => {
setOpen((prev) => !prev); setOpen((prev) => !prev);
}, [ ]); }, open ? closeDelay : openDelay);
}, [ open, openDelay, closeDelay ]);
const handleContentClick = React.useCallback((event: React.MouseEvent<HTMLDivElement>) => {
event.stopPropagation();
}, []);
React.useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
if (disabled || (disableOnMobile && isMobile)) return children; if (disabled || (disableOnMobile && isMobile)) return children;
...@@ -68,22 +102,23 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>( ...@@ -68,22 +102,23 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
return ( return (
<ChakraTooltip.Root <ChakraTooltip.Root
openDelay={ 100 } openDelay={ openDelay }
// FIXME: chakra closes tooltip too fast, so Playwright is not able to make a screenshot of its content // FIXME: chakra closes tooltip too fast, so Playwright is not able to make a screenshot of its content
// so we need to increase the close delay in Playwright environment // so we need to increase the close delay in Playwright environment
closeDelay={ config.app.isPw ? 10_000 : 100 } closeDelay={ config.app.isPw ? 10_000 : closeDelay }
open={ open } open={ open }
onOpenChange={ handleOpenChange } onOpenChange={ handleOpenChange }
closeOnClick={ false } closeOnClick={ false }
closeOnPointerDown={ true } closeOnPointerDown={ false }
variant={ variant } variant={ variant }
lazyMount={ lazyMount } lazyMount={ lazyMount }
unmountOnExit={ unmountOnExit } unmountOnExit={ unmountOnExit }
interactive={ interactive }
{ ...rest } { ...rest }
positioning={ positioning } positioning={ positioning }
> >
<ChakraTooltip.Trigger <ChakraTooltip.Trigger
ref={ triggerRef } ref={ open ? triggerRef : null }
asChild asChild
onClick={ isMobile ? handleTriggerClick : undefined } onClick={ isMobile ? handleTriggerClick : undefined }
{ ...triggerProps } { ...triggerProps }
...@@ -94,6 +129,7 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>( ...@@ -94,6 +129,7 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
<ChakraTooltip.Positioner> <ChakraTooltip.Positioner>
<ChakraTooltip.Content <ChakraTooltip.Content
ref={ ref } ref={ ref }
onClick={ interactive ? handleContentClick : undefined }
{ ...(selected ? { 'data-selected': true } : {}) } { ...(selected ? { 'data-selected': true } : {}) }
{ ...contentProps } { ...contentProps }
> >
......
...@@ -71,6 +71,7 @@ const AdaptiveTabs = (props: Props) => { ...@@ -71,6 +71,7 @@ const AdaptiveTabs = (props: Props) => {
stickyEnabled={ stickyEnabled } stickyEnabled={ stickyEnabled }
activeTab={ activeTab } activeTab={ activeTab }
isLoading={ isLoading } isLoading={ isLoading }
variant={ variant }
/> />
{ tabs.map((tab) => { { tabs.map((tab) => {
const value = getTabValue(tab); const value = getTabValue(tab);
......
...@@ -9,6 +9,7 @@ import useIsMobile from 'lib/hooks/useIsMobile'; ...@@ -9,6 +9,7 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import { useIsSticky } from '../..//hooks/useIsSticky'; import { useIsSticky } from '../..//hooks/useIsSticky';
import { Skeleton } from '../../chakra/skeleton'; import { Skeleton } from '../../chakra/skeleton';
import type { TabsProps } from '../../chakra/tabs';
import { TabsCounter, TabsList, TabsTrigger } from '../../chakra/tabs'; import { TabsCounter, TabsList, TabsTrigger } from '../../chakra/tabs';
import AdaptiveTabsMenu from './AdaptiveTabsMenu'; import AdaptiveTabsMenu from './AdaptiveTabsMenu';
import useAdaptiveTabs from './useAdaptiveTabs'; import useAdaptiveTabs from './useAdaptiveTabs';
...@@ -32,6 +33,7 @@ export interface BaseProps { ...@@ -32,6 +33,7 @@ export interface BaseProps {
interface Props extends BaseProps { interface Props extends BaseProps {
activeTab: string; activeTab: string;
variant: TabsProps['variant'];
} }
const HIDDEN_ITEM_STYLES: HTMLChakraProps<'button'> = { const HIDDEN_ITEM_STYLES: HTMLChakraProps<'button'> = {
...@@ -41,19 +43,17 @@ const HIDDEN_ITEM_STYLES: HTMLChakraProps<'button'> = { ...@@ -41,19 +43,17 @@ const HIDDEN_ITEM_STYLES: HTMLChakraProps<'button'> = {
visibility: 'hidden', visibility: 'hidden',
}; };
const getItemStyles = (index: number, tabsCut: number | undefined) => { const getItemStyles = (index: number, tabsCut: number | undefined, isLoading: boolean | undefined) => {
if (tabsCut === undefined) { if (tabsCut === undefined || isLoading) {
return HIDDEN_ITEM_STYLES as never; return HIDDEN_ITEM_STYLES as never;
} }
return index < tabsCut ? {} : HIDDEN_ITEM_STYLES as never; return index < tabsCut ? {} : HIDDEN_ITEM_STYLES as never;
}; };
const getMenuStyles = (tabsLength: number, tabsCut: number | undefined) => { const getMenuStyles = (tabsLength: number, tabsCut: number | undefined, isLoading: boolean | undefined) => {
if (tabsCut === undefined) { if (tabsCut === undefined || isLoading) {
return { return HIDDEN_ITEM_STYLES;
opacity: 0,
};
} }
return tabsCut >= tabsLength ? HIDDEN_ITEM_STYLES : {}; return tabsCut >= tabsLength ? HIDDEN_ITEM_STYLES : {};
...@@ -71,6 +71,7 @@ const AdaptiveTabsList = (props: Props) => { ...@@ -71,6 +71,7 @@ const AdaptiveTabsList = (props: Props) => {
leftSlotProps, leftSlotProps,
stickyEnabled, stickyEnabled,
isLoading, isLoading,
variant,
} = props; } = props;
const scrollDirection = useScrollDirection(); const scrollDirection = useScrollDirection();
...@@ -80,11 +81,13 @@ const AdaptiveTabsList = (props: Props) => { ...@@ -80,11 +81,13 @@ const AdaptiveTabsList = (props: Props) => {
return [ ...tabs, menuButton ]; return [ ...tabs, menuButton ];
}, [ tabs ]); }, [ tabs ]);
const { tabsCut, tabsRefs, listRef, rightSlotRef, leftSlotRef } = useAdaptiveTabs(tabsList, isMobile); const { tabsCut, tabsRefs, listRef, rightSlotRef, leftSlotRef } = useAdaptiveTabs(tabsList, isLoading || isMobile);
const isSticky = useIsSticky(listRef, 5, stickyEnabled); const isSticky = useIsSticky(listRef, 5, stickyEnabled);
const activeTabIndex = tabsList.findIndex((tab) => getTabValue(tab) === activeTab) ?? 0; const activeTabIndex = tabsList.findIndex((tab) => getTabValue(tab) === activeTab) ?? 0;
useScrollToActiveTab({ activeTabIndex, listRef, tabsRefs, isMobile, isLoading }); useScrollToActiveTab({ activeTabIndex, listRef, tabsRefs, isMobile, isLoading });
const isReady = !isLoading && tabsCut !== undefined;
return ( return (
<TabsList <TabsList
ref={ listRef } ref={ listRef }
...@@ -92,10 +95,6 @@ const AdaptiveTabsList = (props: Props) => { ...@@ -92,10 +95,6 @@ const AdaptiveTabsList = (props: Props) => {
alignItems="center" alignItems="center"
whiteSpace="nowrap" whiteSpace="nowrap"
bgColor={{ _light: 'white', _dark: 'black' }} bgColor={{ _light: 'white', _dark: 'black' }}
// initially our cut is 0 and we don't want to show the list
// but we want to keep all items in the tabs row so it won't collapse
// that's why we only change opacity but not the position itself
opacity={ tabsCut ? 1 : 0 }
marginBottom={ 6 } marginBottom={ 6 }
mx={{ base: '-12px', lg: 'unset' }} mx={{ base: '-12px', lg: 'unset' }}
px={{ base: '12px', lg: 'unset' }} px={{ base: '12px', lg: 'unset' }}
...@@ -134,15 +133,11 @@ const AdaptiveTabsList = (props: Props) => { ...@@ -134,15 +133,11 @@ const AdaptiveTabsList = (props: Props) => {
</Box> </Box>
) )
} }
{ tabsList.slice(0, isLoading ? 5 : Infinity).map((tab, index) => { { tabsList.map((tab, index) => {
const value = getTabValue(tab); const value = getTabValue(tab);
const ref = tabsRefs[index]; const ref = tabsRefs[index];
if (tab.id === 'menu') { if (tab.id === 'menu') {
if (isLoading) {
return null;
}
return ( return (
<AdaptiveTabsMenu <AdaptiveTabsMenu
key="menu" key="menu"
...@@ -150,7 +145,7 @@ const AdaptiveTabsList = (props: Props) => { ...@@ -150,7 +145,7 @@ const AdaptiveTabsList = (props: Props) => {
tabs={ tabs } tabs={ tabs }
tabsCut={ tabsCut ?? 0 } tabsCut={ tabsCut ?? 0 }
isActive={ activeTabIndex > 0 && tabsCut !== undefined && tabsCut > 0 && activeTabIndex >= tabsCut } isActive={ activeTabIndex > 0 && tabsCut !== undefined && tabsCut > 0 && activeTabIndex >= tabsCut }
{ ...getMenuStyles(tabs.length, tabsCut) } { ...getMenuStyles(tabs.length, tabsCut, isLoading) }
/> />
); );
} }
...@@ -162,19 +157,30 @@ const AdaptiveTabsList = (props: Props) => { ...@@ -162,19 +157,30 @@ const AdaptiveTabsList = (props: Props) => {
ref={ ref } ref={ ref }
scrollSnapAlign="start" scrollSnapAlign="start"
flexShrink={ 0 } flexShrink={ 0 }
{ ...getItemStyles(index, tabsCut) } { ...getItemStyles(index, tabsCut, isLoading) }
> >
{ isLoading ? (
<Skeleton loading>
{ typeof tab.title === 'function' ? tab.title() : tab.title } { typeof tab.title === 'function' ? tab.title() : tab.title }
<TabsCounter count={ tab.count }/> <TabsCounter count={ tab.count }/>
</Skeleton> </TabsTrigger>
) : ( );
<> }) }
{ tabs.slice(0, isReady ? 0 : 5).map((tab, index) => {
const value = `${ getTabValue(tab) }-pre`;
return (
<TabsTrigger
key={ value }
value={ value }
flexShrink={ 0 }
bgColor={
activeTabIndex === index && (variant === 'solid' || variant === undefined) ?
{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' } :
undefined
}
>
<Skeleton loading>
{ typeof tab.title === 'function' ? tab.title() : tab.title } { typeof tab.title === 'function' ? tab.title() : tab.title }
<TabsCounter count={ tab.count }/> <TabsCounter count={ tab.count }/>
</> </Skeleton>
) }
</TabsTrigger> </TabsTrigger>
); );
}) } }) }
......
import { Flex, chakra, Box } from '@chakra-ui/react';
import React from 'react';
import type { TabItemRegular } from '../AdaptiveTabs/types';
import { Skeleton } from '../../chakra/skeleton';
import type { TabsProps } from '../../chakra/tabs';
import useActiveTabFromQuery from './useActiveTabFromQuery';
const SkeletonTabText = ({ size, title }: { size: TabsProps['size']; title: TabItemRegular['title'] }) => (
<Skeleton
borderRadius="base"
borderWidth={ size === 'sm' ? '2px' : 0 }
fontWeight={ 600 }
mx={ size === 'sm' ? 3 : 4 }
flexShrink={ 0 }
loading
>
{ typeof title === 'string' ? title : title() }
</Skeleton>
);
interface Props {
className?: string;
tabs: Array<TabItemRegular>;
size?: 'sm' | 'md';
}
const RoutedTabsSkeleton = ({ className, tabs, size = 'md' }: Props) => {
const activeTab = useActiveTabFromQuery(tabs);
if (tabs.length === 1) {
return null;
}
const tabIndex = activeTab ? tabs.findIndex((tab) => tab.id === activeTab.id) : 0;
return (
<Flex className={ className } my={ 8 } alignItems="center" overflow="hidden">
{ tabs.slice(0, tabIndex).map(({ title, id }) => (
<SkeletonTabText
key={ id.toString() }
title={ title }
size={ size }
/>
)) }
{ tabs.slice(tabIndex, tabIndex + 1).map(({ title, id }) => (
<Box
key={ id.toString() }
bgColor={{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' }}
py={ size === 'sm' ? 1 : 2 }
borderRadius="base"
flexShrink={ 0 }
>
<SkeletonTabText
key={ id.toString() }
title={ title }
size={ size }
/>
</Box>
)) }
{ tabs.slice(tabIndex + 1).map(({ title, id }) => (
<SkeletonTabText
key={ id.toString() }
title={ title }
size={ size }
/>
)) }
</Flex>
);
};
export default chakra(RoutedTabsSkeleton);
export { default as RoutedTabs } from './RoutedTabs'; export { default as RoutedTabs } from './RoutedTabs';
export { default as RoutedTabsSkeleton } from './RoutedTabsSkeleton';
export { default as useActiveTabFromQuery } from './useActiveTabFromQuery'; export { default as useActiveTabFromQuery } from './useActiveTabFromQuery';
...@@ -11,9 +11,10 @@ export interface TruncatedTextTooltipProps { ...@@ -11,9 +11,10 @@ export interface TruncatedTextTooltipProps {
children: React.ReactNode; children: React.ReactNode;
label: React.ReactNode; label: React.ReactNode;
placement?: Placement; placement?: Placement;
interactive?: boolean;
} }
export const TruncatedTextTooltip = React.memo(({ children, label, placement }: TruncatedTextTooltipProps) => { export const TruncatedTextTooltip = React.memo(({ children, label, placement, interactive }: TruncatedTextTooltipProps) => {
const childRef = React.useRef<HTMLElement>(null); const childRef = React.useRef<HTMLElement>(null);
const [ isTruncated, setTruncated ] = React.useState(false); const [ isTruncated, setTruncated ] = React.useState(false);
const { open, onToggle, onOpen, onClose } = useDisclosure(); const { open, onToggle, onOpen, onClose } = useDisclosure();
...@@ -83,6 +84,7 @@ export const TruncatedTextTooltip = React.memo(({ children, label, placement }: ...@@ -83,6 +84,7 @@ export const TruncatedTextTooltip = React.memo(({ children, label, placement }:
contentProps={{ maxW: { base: 'calc(100vw - 8px)', lg: '400px' } }} contentProps={{ maxW: { base: 'calc(100vw - 8px)', lg: '400px' } }}
positioning={{ placement }} positioning={{ placement }}
open={ open } open={ open }
interactive={ interactive }
> >
{ modifiedChildren } { modifiedChildren }
</Tooltip> </Tooltip>
......
...@@ -3,17 +3,16 @@ export const zIndex = { ...@@ -3,17 +3,16 @@ export const zIndex = {
auto: { value: 'auto' }, auto: { value: 'auto' },
base: { value: 0 }, base: { value: 0 },
docked: { value: 10 }, docked: { value: 10 },
dropdown: { value: 1000 },
sticky: { value: 1100 }, sticky: { value: 1100 },
sticky1: { value: 1101 }, sticky1: { value: 1101 },
sticky2: { value: 1102 }, sticky2: { value: 1102 },
popover: { value: 1150 },
banner: { value: 1200 }, banner: { value: 1200 },
overlay: { value: 1300 }, overlay: { value: 1300 },
modal: { value: 1400 }, modal: { value: 1400 },
popover: { value: 1500 }, modal2: { value: 14001 },
tooltip: { value: 1550 }, // otherwise tooltips will not be visible in modals tooltip: { value: 1550 }, // otherwise tooltips will not be visible in modals
tooltip2: { value: 1551 }, // for tooltips in tooltips tooltip2: { value: 1551 }, // for tooltips in tooltips
skipLink: { value: 1600 },
toast: { value: 1700 }, toast: { value: 1700 },
}; };
......
...@@ -16,7 +16,7 @@ export const recipe = defineRecipe({ ...@@ -16,7 +16,7 @@ export const recipe = defineRecipe({
bg: 'blue.600', bg: 'blue.600',
color: 'white', color: 'white',
_hover: { _hover: {
bg: 'blue.400', bg: 'link.primary.hover',
}, },
_loading: { _loading: {
opacity: 1, opacity: 1,
...@@ -27,7 +27,7 @@ export const recipe = defineRecipe({ ...@@ -27,7 +27,7 @@ export const recipe = defineRecipe({
}, },
}, },
_expanded: { _expanded: {
bg: 'blue.400', bg: 'link.primary.hover',
}, },
}, },
outline: { outline: {
...@@ -38,8 +38,8 @@ export const recipe = defineRecipe({ ...@@ -38,8 +38,8 @@ export const recipe = defineRecipe({
borderColor: 'button.outline.fg', borderColor: 'button.outline.fg',
_hover: { _hover: {
bg: 'transparent', bg: 'transparent',
color: 'blue.400', color: 'link.primary.hover',
borderColor: 'blue.400', borderColor: 'link.primary.hover',
}, },
_loading: { _loading: {
opacity: 1, opacity: 1,
...@@ -58,8 +58,8 @@ export const recipe = defineRecipe({ ...@@ -58,8 +58,8 @@ export const recipe = defineRecipe({
borderColor: 'button.dropdown.border', borderColor: 'button.dropdown.border',
_hover: { _hover: {
bg: 'transparent', bg: 'transparent',
color: 'blue.400', color: 'link.primary.hover',
borderColor: 'blue.400', borderColor: 'link.primary.hover',
}, },
_loading: { _loading: {
opacity: 1, opacity: 1,
...@@ -72,8 +72,8 @@ export const recipe = defineRecipe({ ...@@ -72,8 +72,8 @@ export const recipe = defineRecipe({
// When the dropdown is open, the button should be active // When the dropdown is open, the button should be active
_expanded: { _expanded: {
bg: 'transparent', bg: 'transparent',
color: 'blue.400', color: 'link.primary.hover',
borderColor: 'blue.400', borderColor: 'link.primary.hover',
}, },
// We have a special state for this button variant that serves as a popover trigger. // We have a special state for this button variant that serves as a popover trigger.
// When any items (filters) are selected in the popover, the button should change its background and text color. // When any items (filters) are selected in the popover, the button should change its background and text color.
...@@ -84,9 +84,12 @@ export const recipe = defineRecipe({ ...@@ -84,9 +84,12 @@ export const recipe = defineRecipe({
borderColor: 'transparent', borderColor: 'transparent',
_hover: { _hover: {
bg: 'button.dropdown.bg.selected', bg: 'button.dropdown.bg.selected',
color: 'button.dropdown.fg.selected', color: 'link.primary.hover',
borderColor: 'transparent', borderColor: 'transparent',
}, },
_expanded: {
color: 'link.primary.hover',
},
}, },
}, },
header: { header: {
...@@ -97,8 +100,8 @@ export const recipe = defineRecipe({ ...@@ -97,8 +100,8 @@ export const recipe = defineRecipe({
borderStyle: 'solid', borderStyle: 'solid',
_hover: { _hover: {
bg: 'transparent', bg: 'transparent',
color: 'blue.400', color: 'link.primary.hover',
borderColor: 'blue.400', borderColor: 'link.primary.hover',
}, },
_loading: { _loading: {
opacity: 1, opacity: 1,
...@@ -115,16 +118,22 @@ export const recipe = defineRecipe({ ...@@ -115,16 +118,22 @@ export const recipe = defineRecipe({
borderWidth: '0px', borderWidth: '0px',
_hover: { _hover: {
bg: 'button.header.bg.selected', bg: 'button.header.bg.selected',
color: 'button.header.fg.selected', color: 'link.primary.hover',
},
_expanded: {
color: 'link.primary.hover',
}, },
_highlighted: { _highlighted: {
bg: 'button.header.bg.highlighted', bg: 'button.header.bg.highlighted',
color: 'button.header.fg.highlighted', color: 'button.header.fg.highlighted',
borderColor: 'transparent', borderColor: 'transparent',
borderWidth: '0px', borderWidth: '0px',
_expanded: {
color: 'link.primary.hover',
},
_hover: { _hover: {
bg: 'button.header.bg.highlighted', bg: 'button.header.bg.highlighted',
color: 'button.header.fg.highlighted', color: 'link.primary.hover',
}, },
}, },
}, },
...@@ -149,7 +158,10 @@ export const recipe = defineRecipe({ ...@@ -149,7 +158,10 @@ export const recipe = defineRecipe({
color: 'button.hero.fg.selected', color: 'button.hero.fg.selected',
_hover: { _hover: {
bg: 'button.hero.bg.selected', bg: 'button.hero.bg.selected',
color: 'button.hero.fg.selected', color: 'link.primary.hover',
},
_expanded: {
color: 'link.primary.hover',
}, },
}, },
}, },
...@@ -230,7 +242,10 @@ export const recipe = defineRecipe({ ...@@ -230,7 +242,10 @@ export const recipe = defineRecipe({
color: 'button.icon_secondary.fg.selected', color: 'button.icon_secondary.fg.selected',
_hover: { _hover: {
bg: 'button.icon_secondary.bg.selected', bg: 'button.icon_secondary.bg.selected',
color: 'button.icon_secondary.fg.selected', color: 'link.primary.hover',
},
_expanded: {
color: 'link.primary.hover',
}, },
}, },
_expanded: { _expanded: {
......
...@@ -207,7 +207,7 @@ export const recipe = defineSlotRecipe({ ...@@ -207,7 +207,7 @@ export const recipe = defineSlotRecipe({
defaultVariants: { defaultVariants: {
size: 'md', size: 'md',
scrollBehavior: 'inside', scrollBehavior: 'inside',
placement: 'center', placement: { base: 'top', lg: 'center' },
motionPreset: 'scale', motionPreset: 'scale',
}, },
}); });
...@@ -9,7 +9,7 @@ export const recipe = defineSlotRecipe({ ...@@ -9,7 +9,7 @@ export const recipe = defineSlotRecipe({
boxShadow: 'popover', boxShadow: 'popover',
color: 'initial', color: 'initial',
maxHeight: 'var(--available-height)', maxHeight: 'var(--available-height)',
'--menu-z-index': 'zIndex.dropdown', '--menu-z-index': 'zIndex.popover',
zIndex: 'calc(var(--menu-z-index) + var(--layer-index, 0))', zIndex: 'calc(var(--menu-z-index) + var(--layer-index, 0))',
borderRadius: 'md', borderRadius: 'md',
overflow: 'hidden', overflow: 'hidden',
...@@ -25,7 +25,7 @@ export const recipe = defineSlotRecipe({ ...@@ -25,7 +25,7 @@ export const recipe = defineSlotRecipe({
}, },
item: { item: {
textDecoration: 'none', textDecoration: 'none',
color: 'initial', color: 'text.primary',
userSelect: 'none', userSelect: 'none',
borderRadius: 'none', borderRadius: 'none',
width: '100%', width: '100%',
......
...@@ -27,7 +27,7 @@ export type ScrollL2TxnBatch = { ...@@ -27,7 +27,7 @@ export type ScrollL2TxnBatch = {
confirmation_transaction: ScrollL2TxnBatchConfirmationTransaction; confirmation_transaction: ScrollL2TxnBatchConfirmationTransaction;
start_block_number: number; start_block_number: number;
end_block_number: number; end_block_number: number;
transactions_count: number; transactions_count: number | null;
data_availability: { data_availability: {
batch_data_container: 'in_blob4844' | 'in_calldata'; batch_data_container: 'in_blob4844' | 'in_calldata';
}; };
......
import { chakra } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react';
import { pickBy } from 'es-toolkit';
import React from 'react'; import React from 'react';
import type { AddressFromToFilter } from 'types/api/address'; import type { AddressFromToFilter } from 'types/api/address';
...@@ -26,11 +27,11 @@ const AddressAdvancedFilterLink = ({ isLoading, address, typeFilter, directionFi ...@@ -26,11 +27,11 @@ const AddressAdvancedFilterLink = ({ isLoading, address, typeFilter, directionFi
return null; return null;
} }
const queryParams = { const queryParams = pickBy({
to_address_hashes_to_include: !directionFilter || directionFilter === 'to' ? [ address ] : undefined, to_address_hashes_to_include: !directionFilter || directionFilter === 'to' ? [ address ] : undefined,
from_address_hashes_to_include: !directionFilter || directionFilter === 'from' ? [ address ] : undefined, from_address_hashes_to_include: !directionFilter || directionFilter === 'from' ? [ address ] : undefined,
transaction_types: typeFilter.length > 0 ? typeFilter : ADVANCED_FILTER_TYPES.filter((type) => type !== 'coin_transfer'), transaction_types: typeFilter.length > 0 ? typeFilter : ADVANCED_FILTER_TYPES.filter((type) => type !== 'coin_transfer'),
}; }, (value) => value !== undefined);
return ( return (
<Link <Link
......
...@@ -85,36 +85,6 @@ test.describe('socket', () => { ...@@ -85,36 +85,6 @@ test.describe('socket', () => {
// test cases which use socket cannot run in parallel since the socket server always run on the same port // test cases which use socket cannot run in parallel since the socket server always run on the same port
test.describe.configure({ mode: 'serial' }); test.describe.configure({ mode: 'serial' });
test('without overload', async({ render, mockApiResponse, page, createSocket }) => {
await mockApiResponse(
'address_txs',
{ items: [ txMock.base ], next_page_params: DEFAULT_PAGINATION },
{ pathParams: { hash: CURRENT_ADDRESS } },
);
await render(
<Box pt={{ base: '134px', lg: 6 }}>
<AddressTxs/>
</Box>,
{ hooksConfig },
{ withSocket: true },
);
const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, `addresses:${ CURRENT_ADDRESS.toLowerCase() }`);
const itemsCount = await page.locator('tbody tr').count();
expect(itemsCount).toBe(2);
socketServer.sendMessage(socket, channel, 'transaction', { transactions: [ txMock.base2, txMock.base4 ] });
const thirdRow = page.locator('tbody tr:nth-child(3)');
await thirdRow.waitFor();
const itemsCountNew = await page.locator('tbody tr').count();
expect(itemsCountNew).toBe(4);
});
test('with update', async({ render, mockApiResponse, page, createSocket }) => { test('with update', async({ render, mockApiResponse, page, createSocket }) => {
await mockApiResponse( await mockApiResponse(
'address_txs', 'address_txs',
...@@ -136,13 +106,13 @@ test.describe('socket', () => { ...@@ -136,13 +106,13 @@ test.describe('socket', () => {
const itemsCount = await page.locator('tbody tr').count(); const itemsCount = await page.locator('tbody tr').count();
expect(itemsCount).toBe(2); expect(itemsCount).toBe(2);
socketServer.sendMessage(socket, channel, 'transaction', { transactions: [ txMock.base, txMock.base2 ] }); socketServer.sendMessage(socket, channel, 'transaction', { transactions: [ txMock.base ] });
const thirdRow = page.locator('tbody tr:nth-child(3)'); const secondRow = page.locator('tbody tr:nth-child(2)');
await thirdRow.waitFor(); await secondRow.waitFor();
const itemsCountNew = await page.locator('tbody tr').count(); const itemsCountNew = await page.locator('tbody tr').count();
expect(itemsCountNew).toBe(3); expect(itemsCountNew).toBe(2);
}); });
test('with overload', async({ render, mockApiResponse, page, createSocket }) => { test('with overload', async({ render, mockApiResponse, page, createSocket }) => {
...@@ -154,7 +124,7 @@ test.describe('socket', () => { ...@@ -154,7 +124,7 @@ test.describe('socket', () => {
await render( await render(
<Box pt={{ base: '134px', lg: 6 }}> <Box pt={{ base: '134px', lg: 6 }}>
<AddressTxs overloadCount={ 2 }/> <AddressTxs/>
</Box>, </Box>,
{ hooksConfig }, { hooksConfig },
{ withSocket: true }, { withSocket: true },
...@@ -205,10 +175,10 @@ test.describe('socket', () => { ...@@ -205,10 +175,10 @@ test.describe('socket', () => {
const itemsCount = await page.locator('tbody tr').count(); const itemsCount = await page.locator('tbody tr').count();
expect(itemsCount).toBe(2); expect(itemsCount).toBe(2);
socketServer.sendMessage(socket, channel, 'transaction', { transactions: [ txMock.base2, txMock.base4 ] }); socketServer.sendMessage(socket, channel, 'transaction', { transactions: [ txMock.base2 ] });
const thirdRow = page.locator('tbody tr:nth-child(3)'); const secondRow = page.locator('tbody tr:nth-child(2)');
await thirdRow.waitFor(); await secondRow.waitFor();
const itemsCountNew = await page.locator('tbody tr').count(); const itemsCountNew = await page.locator('tbody tr').count();
expect(itemsCountNew).toBe(3); expect(itemsCountNew).toBe(3);
...@@ -229,7 +199,7 @@ test.describe('socket', () => { ...@@ -229,7 +199,7 @@ test.describe('socket', () => {
await render( await render(
<Box pt={{ base: '134px', lg: 6 }}> <Box pt={{ base: '134px', lg: 6 }}>
<AddressTxs overloadCount={ 2 }/> <AddressTxs/>
</Box>, </Box>,
{ hooksConfig: hooksConfigWithFilter }, { hooksConfig: hooksConfigWithFilter },
{ withSocket: true }, { withSocket: true },
......
import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { SocketMessage } from 'lib/socket/types'; import type { AddressFromToFilter } from 'types/api/address';
import type { AddressFromToFilter, AddressTransactionsResponse } from 'types/api/address';
import { AddressFromToFilterValues } from 'types/api/address'; import { AddressFromToFilterValues } from 'types/api/address';
import type { Transaction, TransactionsSortingField, TransactionsSortingValue, TransactionsSorting } from 'types/api/transaction'; import type { TransactionsSortingField, TransactionsSortingValue, TransactionsSorting } from 'types/api/transaction';
import { getResourceKey } from 'lib/api/useApiQuery';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery'; import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useIsMounted from 'lib/hooks/useIsMounted'; import useIsMounted from 'lib/hooks/useIsMounted';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { TX } from 'stubs/tx'; import { TX } from 'stubs/tx';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
...@@ -21,45 +16,23 @@ import Pagination from 'ui/shared/pagination/Pagination'; ...@@ -21,45 +16,23 @@ import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue'; import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery'; import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import { sortTxsFromSocket } from 'ui/txs/sortTxs';
import TxsWithAPISorting from 'ui/txs/TxsWithAPISorting'; import TxsWithAPISorting from 'ui/txs/TxsWithAPISorting';
import { SORT_OPTIONS } from 'ui/txs/useTxsSort'; import { SORT_OPTIONS } from 'ui/txs/useTxsSort';
import AddressCsvExportLink from './AddressCsvExportLink'; import AddressCsvExportLink from './AddressCsvExportLink';
import AddressTxsFilter from './AddressTxsFilter'; import AddressTxsFilter from './AddressTxsFilter';
const OVERLOAD_COUNT = 75;
const getFilterValue = (getFilterValueFromQuery<AddressFromToFilter>).bind(null, AddressFromToFilterValues); const getFilterValue = (getFilterValueFromQuery<AddressFromToFilter>).bind(null, AddressFromToFilterValues);
const matchFilter = (filterValue: AddressFromToFilter, transaction: Transaction, address?: string) => {
if (!filterValue) {
return true;
}
if (filterValue === 'from') {
return transaction.from.hash === address;
}
if (filterValue === 'to') {
return transaction.to?.hash === address;
}
};
type Props = { type Props = {
shouldRender?: boolean; shouldRender?: boolean;
isQueryEnabled?: boolean; isQueryEnabled?: boolean;
// for tests only
overloadCount?: number;
}; };
const AddressTxs = ({ overloadCount = OVERLOAD_COUNT, shouldRender = true, isQueryEnabled = true }: Props) => { const AddressTxs = ({ shouldRender = true, isQueryEnabled = true }: Props) => {
const router = useRouter(); const router = useRouter();
const queryClient = useQueryClient();
const isMounted = useIsMounted(); const isMounted = useIsMounted();
const [ socketAlert, setSocketAlert ] = React.useState('');
const [ newItemsCount, setNewItemsCount ] = React.useState(0);
const [ sort, setSort ] = React.useState<TransactionsSortingValue>(getSortValueFromQuery<TransactionsSortingValue>(router.query, SORT_OPTIONS) || 'default'); const [ sort, setSort ] = React.useState<TransactionsSortingValue>(getSortValueFromQuery<TransactionsSortingValue>(router.query, SORT_OPTIONS) || 'default');
const isMobile = useIsMobile(); const isMobile = useIsMobile();
...@@ -90,76 +63,6 @@ const AddressTxs = ({ overloadCount = OVERLOAD_COUNT, shouldRender = true, isQue ...@@ -90,76 +63,6 @@ const AddressTxs = ({ overloadCount = OVERLOAD_COUNT, shouldRender = true, isQue
addressTxsQuery.onFilterChange({ filter: newVal }); addressTxsQuery.onFilterChange({ filter: newVal });
}, [ addressTxsQuery ]); }, [ addressTxsQuery ]);
const handleNewSocketMessage: SocketMessage.AddressTxs['handler'] = React.useCallback((payload) => {
setSocketAlert('');
queryClient.setQueryData(
getResourceKey('address_txs', { pathParams: { hash: currentAddress }, queryParams: { filter: filterValue } }),
(prevData: AddressTransactionsResponse | undefined) => {
if (!prevData) {
return;
}
const newItems: Array<Transaction> = [];
let newCount = 0;
payload.transactions.forEach(tx => {
const currIndex = prevData.items.findIndex((item) => item.hash === tx.hash);
if (currIndex > -1) {
prevData.items[currIndex] = tx;
} else {
if (matchFilter(filterValue, tx, currentAddress)) {
if (newItems.length + prevData.items.length >= overloadCount) {
newCount++;
} else {
newItems.push(tx);
}
}
}
});
if (newCount > 0) {
setNewItemsCount(prev => prev + newCount);
}
return {
...prevData,
items: [
...newItems,
...prevData.items,
].sort(sortTxsFromSocket(sort)),
};
});
}, [ currentAddress, filterValue, overloadCount, queryClient, sort ]);
const handleSocketClose = React.useCallback(() => {
setSocketAlert('Connection is lost. Please refresh the page to load new transactions.');
}, []);
const handleSocketError = React.useCallback(() => {
setSocketAlert('An error has occurred while fetching new transactions. Please refresh the page.');
}, []);
const channel = useSocketChannel({
topic: `addresses:${ currentAddress?.toLowerCase() }`,
onSocketClose: handleSocketClose,
onSocketError: handleSocketError,
isDisabled: addressTxsQuery.pagination.page !== 1 || addressTxsQuery.isPlaceholderData,
});
useSocketMessage({
channel,
event: 'transaction',
handler: handleNewSocketMessage,
});
useSocketMessage({
channel,
event: 'pending_transaction',
handler: handleNewSocketMessage,
});
if (!isMounted || !shouldRender) { if (!isMounted || !shouldRender) {
return null; return null;
} }
...@@ -197,9 +100,7 @@ const AddressTxs = ({ overloadCount = OVERLOAD_COUNT, shouldRender = true, isQue ...@@ -197,9 +100,7 @@ const AddressTxs = ({ overloadCount = OVERLOAD_COUNT, shouldRender = true, isQue
query={ addressTxsQuery } query={ addressTxsQuery }
currentAddress={ typeof currentAddress === 'string' ? currentAddress : undefined } currentAddress={ typeof currentAddress === 'string' ? currentAddress : undefined }
enableTimeIncrement enableTimeIncrement
showSocketInfo={ addressTxsQuery.pagination.page === 1 } socketType="address_txs"
socketInfoAlert={ socketAlert }
socketInfoNum={ newItemsCount }
top={ ACTION_BAR_HEIGHT_DESKTOP } top={ ACTION_BAR_HEIGHT_DESKTOP }
sorting={ sort } sorting={ sort }
setSort={ setSort } setSort={ setSort }
......
...@@ -87,7 +87,6 @@ const AddressQrCode = ({ hash, className, isLoading }: Props) => { ...@@ -87,7 +87,6 @@ const AddressQrCode = ({ hash, className, isLoading }: Props) => {
<AddressEntity <AddressEntity
mb={ 3 } mb={ 3 }
fontWeight={ 500 } fontWeight={ 500 }
color="text"
address={{ hash }} address={{ hash }}
noLink noLink
/> />
......
...@@ -71,7 +71,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc ...@@ -71,7 +71,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
<TableColumnFilterWrapper <TableColumnFilterWrapper
columnName="And/Or" columnName="And/Or"
isLoading={ isLoading } isLoading={ isLoading }
selected={ false } selected
w="106px" w="106px"
value={ filters.address_relation === 'and' ? 'AND' : 'OR' } value={ filters.address_relation === 'and' ? 'AND' : 'OR' }
> >
......
...@@ -51,10 +51,9 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -51,10 +51,9 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
const { handleSubmit, watch, formState, setError, reset, getFieldState, getValues, clearErrors } = formApi; const { handleSubmit, watch, formState, setError, reset, getFieldState, getValues, clearErrors } = formApi;
const submitPromiseResolver = React.useRef<(value: unknown) => void>(); const submitPromiseResolver = React.useRef<(value: unknown) => void>();
const methodNameRef = React.useRef<string>(); const methodNameRef = React.useRef<string>();
const activityToken = React.useRef<string | undefined>();
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const { trackContract, trackContractConfirm } = useRewardsActivity(); const { trackContract } = useRewardsActivity();
const onFormSubmit: SubmitHandler<FormFields> = React.useCallback(async(data) => { const onFormSubmit: SubmitHandler<FormFields> = React.useCallback(async(data) => {
const body = prepareRequestBody(data); const body = prepareRequestBody(data);
...@@ -78,8 +77,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -78,8 +77,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
} }
try { try {
const activityResponse = await trackContract(data.address); await trackContract(data.address);
activityToken.current = activityResponse?.token;
await apiFetch('contract_verification_via', { await apiFetch('contract_verification_via', {
pathParams: { method: data.method[0], hash: data.address.toLowerCase() }, pathParams: { method: data.method[0], hash: data.address.toLowerCase() },
fetchParams: { fetchParams: {
...@@ -132,13 +130,8 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro ...@@ -132,13 +130,8 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
{ send_immediately: true }, { send_immediately: true },
); );
if (activityToken.current) {
await trackContractConfirm(activityToken.current);
activityToken.current = undefined;
}
window.location.assign(route({ pathname: '/address/[hash]', query: { hash: address, tab: 'contract' } })); window.location.assign(route({ pathname: '/address/[hash]', query: { hash: address, tab: 'contract' } }));
}, [ setError, address, getValues, trackContractConfirm ]); }, [ setError, address, getValues ]);
const handleSocketError = React.useCallback(() => { const handleSocketError = React.useCallback(() => {
if (!formState.isSubmitting) { if (!formState.isSubmitting) {
......
...@@ -14,7 +14,7 @@ import DepositsTableItem from './DepositsTableItem'; ...@@ -14,7 +14,7 @@ import DepositsTableItem from './DepositsTableItem';
const DepositsTable = ({ items, top, isLoading }: Props) => { const DepositsTable = ({ items, top, isLoading }: Props) => {
return ( return (
<TableRoot style={{ tableLayout: 'auto' }} minW="950px"> <TableRoot tableLayout="auto" minW="950px">
<TableHeaderSticky top={ top }> <TableHeaderSticky top={ top }>
<TableRow> <TableRow>
<TableColumnHeader>L1 block No</TableColumnHeader> <TableColumnHeader>L1 block No</TableColumnHeader>
...@@ -26,7 +26,7 @@ const DepositsTable = ({ items, top, isLoading }: Props) => { ...@@ -26,7 +26,7 @@ const DepositsTable = ({ items, top, isLoading }: Props) => {
</TableHeaderSticky> </TableHeaderSticky>
<TableBody> <TableBody>
{ items.map((item, index) => ( { items.map((item, index) => (
<DepositsTableItem key={ item.l2_transaction_hash + (isLoading ? index : '') } item={ item } isLoading={ isLoading }/> <DepositsTableItem key={ `${ item.l2_transaction_hash }-${ index }` } item={ item } isLoading={ isLoading }/>
)) } )) }
</TableBody> </TableBody>
</TableRoot> </TableRoot>
......
...@@ -6,6 +6,7 @@ import React from 'react'; ...@@ -6,6 +6,7 @@ import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { Button } from 'toolkit/chakra/button'; import { Button } from 'toolkit/chakra/button';
import { Heading } from 'toolkit/chakra/heading';
import { Link } from 'toolkit/chakra/link'; import { Link } from 'toolkit/chakra/link';
const easterEggBadgeFeature = config.features.easterEggBadge; const easterEggBadgeFeature = config.features.easterEggBadge;
...@@ -36,7 +37,7 @@ const CapybaraRunner = () => { ...@@ -36,7 +37,7 @@ const CapybaraRunner = () => {
return ( return (
<> <>
<Box as="h2" mt={ 12 } mb={ 2 } fontWeight={ 600 } fontSize="xl">Score 1000 to win a special prize!</Box> <Heading level="2" mt={ 12 } mb={ 2 }>Score 1000 to win a special prize!</Heading>
<Box mb={ 4 }>{ isMobile ? 'Tap below to start' : 'Press space to start' }</Box> <Box mb={ 4 }>{ isMobile ? 'Tap below to start' : 'Press space to start' }</Box>
<Script strategy="lazyOnload" src="/static/capibara/index.js"/> <Script strategy="lazyOnload" src="/static/capibara/index.js"/>
<Box width={{ base: '100%', lg: '600px' }} height="300px" p="50px 0"> <Box width={{ base: '100%', lg: '600px' }} height="300px" p="50px 0">
......
...@@ -6,10 +6,10 @@ import { route } from 'nextjs-routes'; ...@@ -6,10 +6,10 @@ import { route } from 'nextjs-routes';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight'; import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useNewTxsSocket from 'lib/hooks/useNewTxsSocket';
import { TX } from 'stubs/tx'; import { TX } from 'stubs/tx';
import { Link } from 'toolkit/chakra/link'; import { Link } from 'toolkit/chakra/link';
import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import useNewTxsSocket from 'ui/txs/socket/useTxsSocketTypeAll';
import LatestTxsItem from './LatestTxsItem'; import LatestTxsItem from './LatestTxsItem';
import LatestTxsItemMobile from './LatestTxsItemMobile'; import LatestTxsItemMobile from './LatestTxsItemMobile';
...@@ -23,7 +23,7 @@ const LatestTransactions = () => { ...@@ -23,7 +23,7 @@ const LatestTransactions = () => {
}, },
}); });
const { num, socketAlert } = useNewTxsSocket(); const { num, alertText } = useNewTxsSocket({ type: 'txs_home', isLoading: isPlaceholderData });
if (isError) { if (isError) {
return <Text mt={ 4 }>No data. Please reload the page.</Text>; return <Text mt={ 4 }>No data. Please reload the page.</Text>;
...@@ -33,7 +33,7 @@ const LatestTransactions = () => { ...@@ -33,7 +33,7 @@ const LatestTransactions = () => {
const txsUrl = route({ pathname: '/txs' }); const txsUrl = route({ pathname: '/txs' });
return ( return (
<> <>
<SocketNewItemsNotice borderBottomRadius={ 0 } url={ txsUrl } num={ num } alert={ socketAlert } isLoading={ isPlaceholderData }/> <SocketNewItemsNotice borderBottomRadius={ 0 } url={ txsUrl } num={ num } alert={ alertText } isLoading={ isPlaceholderData }/>
<Box mb={ 3 } display={{ base: 'block', lg: 'none' }}> <Box mb={ 3 } display={{ base: 'block', lg: 'none' }}>
{ data.slice(0, txsCount).map(((tx, index) => ( { data.slice(0, txsCount).map(((tx, index) => (
<LatestTxsItemMobile <LatestTxsItemMobile
......
...@@ -126,10 +126,10 @@ const ChainIndicators = () => { ...@@ -126,10 +126,10 @@ const ChainIndicators = () => {
alignItems="stretch" alignItems="stretch"
> >
<Flex flexGrow={ 1 } flexDir="column"> <Flex flexGrow={ 1 } flexDir="column">
<Flex alignItems="center"> <Skeleton loading={ isPlaceholderData } display="flex" alignItems="center" w="fit-content" columnGap={ 1 }>
<Text fontWeight={ 500 }>{ title }</Text> <Text fontWeight={ 500 }>{ title }</Text>
{ hint && <Hint label={ hint } ml={ 1 }/> } { hint && <Hint label={ hint }/> }
</Flex> </Skeleton>
<Flex mb={{ base: 0, lg: 2 }} mt={ 1 } alignItems="end"> <Flex mb={{ base: 0, lg: 2 }} mt={ 1 } alignItems="end">
{ valueTitle } { valueTitle }
{ valueDiff } { valueDiff }
......
...@@ -17,13 +17,13 @@ interface Props { ...@@ -17,13 +17,13 @@ interface Props {
const InternalTxsTable = ({ data, currentAddress, isLoading }: Props) => { const InternalTxsTable = ({ data, currentAddress, isLoading }: Props) => {
return ( return (
<AddressHighlightProvider> <AddressHighlightProvider>
<TableRoot> <TableRoot minW="900px">
<TableHeaderSticky top={ 68 }> <TableHeaderSticky top={ 68 }>
<TableRow> <TableRow>
<TableColumnHeader width="15%">Parent txn hash</TableColumnHeader> <TableColumnHeader width="180px">Parent txn hash</TableColumnHeader>
<TableColumnHeader width="15%">Type</TableColumnHeader> <TableColumnHeader width="15%">Type</TableColumnHeader>
<TableColumnHeader width="10%">Block</TableColumnHeader> <TableColumnHeader width="15%">Block</TableColumnHeader>
<TableColumnHeader width="40%">From/To</TableColumnHeader> <TableColumnHeader width="50%">From/To</TableColumnHeader>
<TableColumnHeader width="20%" isNumeric> <TableColumnHeader width="20%" isNumeric>
Value { currencyUnits.ether } Value { currencyUnits.ether }
</TableColumnHeader> </TableColumnHeader>
......
...@@ -6,13 +6,13 @@ import type { InternalTransaction } from 'types/api/internalTransaction'; ...@@ -6,13 +6,13 @@ import type { InternalTransaction } from 'types/api/internalTransaction';
import config from 'configs/app'; import config from 'configs/app';
import { Badge } from 'toolkit/chakra/badge'; import { Badge } from 'toolkit/chakra/badge';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { TableCell, TableRow } from 'toolkit/chakra/table'; import { TableCell, TableRow } from 'toolkit/chakra/table';
import AddressFromTo from 'ui/shared/address/AddressFromTo'; import AddressFromTo from 'ui/shared/address/AddressFromTo';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxStatus from 'ui/shared/statusTag/TxStatus'; import TxStatus from 'ui/shared/statusTag/TxStatus';
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
import TruncatedValue from 'ui/shared/TruncatedValue';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
type Props = InternalTransaction & { currentAddress?: string; isLoading?: boolean }; type Props = InternalTransaction & { currentAddress?: string; isLoading?: boolean };
...@@ -81,9 +81,13 @@ const InternalTxsTableItem = ({ ...@@ -81,9 +81,13 @@ const InternalTxsTableItem = ({
/> />
</TableCell> </TableCell>
<TableCell isNumeric verticalAlign="middle"> <TableCell isNumeric verticalAlign="middle">
<Skeleton loading={ isLoading } display="inline-block" minW={ 6 }> <TruncatedValue
{ BigNumber(value).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() } value={ BigNumber(value).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() }
</Skeleton> isLoading={ isLoading }
minW={ 6 }
maxW="100%"
verticalAlign="middle"
/>
</TableCell> </TableCell>
</TableRow> </TableRow>
); );
......
...@@ -12,6 +12,7 @@ import config from 'configs/app'; ...@@ -12,6 +12,7 @@ import config from 'configs/app';
import solidityScanIcon from 'icons/brands/solidity_scan.svg'; import solidityScanIcon from 'icons/brands/solidity_scan.svg';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import { Link } from 'toolkit/chakra/link'; import { Link } from 'toolkit/chakra/link';
import type { PopoverContentProps } from 'toolkit/chakra/popover';
import { PopoverBody, PopoverContent, PopoverRoot } from 'toolkit/chakra/popover'; import { PopoverBody, PopoverContent, PopoverRoot } from 'toolkit/chakra/popover';
import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import { apos } from 'toolkit/utils/htmlEntities'; import { apos } from 'toolkit/utils/htmlEntities';
...@@ -30,10 +31,20 @@ type Props = { ...@@ -30,10 +31,20 @@ type Props = {
popoverPlacement?: 'bottom-start' | 'bottom-end' | 'left'; popoverPlacement?: 'bottom-start' | 'bottom-end' | 'left';
buttonProps?: ButtonProps; buttonProps?: ButtonProps;
triggerWrapperProps?: BoxProps; triggerWrapperProps?: BoxProps;
popoverContentProps?: PopoverContentProps;
}; };
const AppSecurityReport = ({ const AppSecurityReport = ({
id, securityReport, showContractList, isLoading, onlyIcon, source, triggerWrapperProps, buttonProps, popoverPlacement = 'bottom-start', id,
securityReport,
showContractList,
isLoading,
onlyIcon,
source,
triggerWrapperProps,
buttonProps,
popoverPlacement = 'bottom-start',
popoverContentProps,
}: Props) => { }: Props) => {
const { open, onOpenChange } = useDisclosure(); const { open, onOpenChange } = useDisclosure();
...@@ -76,7 +87,7 @@ const AppSecurityReport = ({ ...@@ -76,7 +87,7 @@ const AppSecurityReport = ({
wrapperProps={ triggerWrapperProps } wrapperProps={ triggerWrapperProps }
{ ...buttonProps } { ...buttonProps }
/> />
<PopoverContent w={{ base: 'calc(100vw - 48px)', lg: '328px' }} mx={{ base: 3, lg: 0 }}> <PopoverContent w={{ base: 'calc(100vw - 48px)', lg: '328px' }} mx={{ base: 3, lg: 0 }} { ...popoverContentProps }>
<PopoverBody px="26px" py="20px" textStyle="sm"> <PopoverBody px="26px" py="20px" textStyle="sm">
<Text fontWeight="500" textStyle="xs" mb={ 2 } color="text.secondary">Smart contracts info</Text> <Text fontWeight="500" textStyle="xs" mb={ 2 } color="text.secondary">Smart contracts info</Text>
<Flex alignItems="center" justifyContent="space-between" py={ 1.5 }> <Flex alignItems="center" justifyContent="space-between" py={ 1.5 }>
......
...@@ -58,7 +58,6 @@ const ContractListModal = ({ onClose, onBack, type, contracts }: Props) => { ...@@ -58,7 +58,6 @@ const ContractListModal = ({ onClose, onBack, type, contracts }: Props) => {
open={ Boolean(type) } open={ Boolean(type) }
onOpenChange={ handleOpenChange } onOpenChange={ handleOpenChange }
size={{ lgDown: 'full', lg: 'md' }} size={{ lgDown: 'full', lg: 'md' }}
placement="center"
> >
<DialogContent> <DialogContent>
<DialogHeader display="flex" alignItems="center" mb={ 4 } onBackToClick={ onBack }> <DialogHeader display="flex" alignItems="center" mb={ 4 } onBackToClick={ onBack }>
......
...@@ -135,7 +135,6 @@ const MarketplaceAppModal = ({ ...@@ -135,7 +135,6 @@ const MarketplaceAppModal = ({
open={ Boolean(data.id) } open={ Boolean(data.id) }
onOpenChange={ handleOpenChange } onOpenChange={ handleOpenChange }
size={{ lgDown: 'full', lg: 'md' }} size={{ lgDown: 'full', lg: 'md' }}
placement="center"
> >
<DialogContent> <DialogContent>
<Box <Box
...@@ -197,6 +196,7 @@ const MarketplaceAppModal = ({ ...@@ -197,6 +196,7 @@ const MarketplaceAppModal = ({
fullView fullView
canRate={ canRate } canRate={ canRate }
source="App modal" source="App modal"
popoverContentProps={{ zIndex: 'modal' }}
/> />
</Box> </Box>
) } ) }
...@@ -265,6 +265,7 @@ const MarketplaceAppModal = ({ ...@@ -265,6 +265,7 @@ const MarketplaceAppModal = ({
showContractList={ showContractList } showContractList={ showContractList }
source="App modal" source="App modal"
popoverPlacement={ isMobile ? 'bottom-start' : 'left' } popoverPlacement={ isMobile ? 'bottom-start' : 'left' }
popoverContentProps={{ zIndex: 'modal' }}
/> />
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -27,7 +27,6 @@ const MarketplaceDisclaimerModal = ({ isOpen, onClose, appId }: Props) => { ...@@ -27,7 +27,6 @@ const MarketplaceDisclaimerModal = ({ isOpen, onClose, appId }: Props) => {
open={ isOpen } open={ isOpen }
onOpenChange={ onClose } onOpenChange={ onClose }
size={ isMobile ? 'full' : 'md' } size={ isMobile ? 'full' : 'md' }
placement="center"
> >
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
......
...@@ -5,6 +5,7 @@ import type { AppRating } from 'types/client/marketplace'; ...@@ -5,6 +5,7 @@ import type { AppRating } from 'types/client/marketplace';
import config from 'configs/app'; import config from 'configs/app';
import type { EventTypes, EventPayload } from 'lib/mixpanel/index'; import type { EventTypes, EventPayload } from 'lib/mixpanel/index';
import type { PopoverContentProps } from 'toolkit/chakra/popover';
import { PopoverBody, PopoverContent, PopoverRoot } from 'toolkit/chakra/popover'; import { PopoverBody, PopoverContent, PopoverRoot } from 'toolkit/chakra/popover';
import { Rating } from 'toolkit/chakra/rating'; import { Rating } from 'toolkit/chakra/rating';
import { Skeleton } from 'toolkit/chakra/skeleton'; import { Skeleton } from 'toolkit/chakra/skeleton';
...@@ -26,11 +27,13 @@ type Props = { ...@@ -26,11 +27,13 @@ type Props = {
fullView?: boolean; fullView?: boolean;
canRate: boolean | undefined; canRate: boolean | undefined;
source: EventPayload<EventTypes.APP_FEEDBACK>['Source']; source: EventPayload<EventTypes.APP_FEEDBACK>['Source'];
popoverContentProps?: PopoverContentProps;
}; };
const MarketplaceRating = ({ const MarketplaceRating = ({
appId, rating, userRating, rate, appId, rating, userRating, rate,
isSending, isLoading, fullView, canRate, source, isSending, isLoading, fullView, canRate, source,
popoverContentProps,
}: Props) => { }: Props) => {
if (!isEnabled) { if (!isEnabled) {
...@@ -60,7 +63,7 @@ const MarketplaceRating = ({ ...@@ -60,7 +63,7 @@ const MarketplaceRating = ({
canRate={ canRate } canRate={ canRate }
/> />
{ canRate ? ( { canRate ? (
<PopoverContent w="250px"> <PopoverContent w="250px" { ...popoverContentProps }>
<PopoverBody> <PopoverBody>
<Content <Content
appId={ appId } appId={ appId }
......
...@@ -137,7 +137,7 @@ const AdvancedFilter = () => { ...@@ -137,7 +137,7 @@ const AdvancedFilter = () => {
const content = ( const content = (
<AddressHighlightProvider> <AddressHighlightProvider>
<Box maxW="100%" overflowX="scroll" whiteSpace="nowrap"> <Box maxW="100%" display="grid" overflowX="scroll" whiteSpace="nowrap">
<TableRoot tableLayout="fixed" minWidth="950px" w="100%"> <TableRoot tableLayout="fixed" minWidth="950px" w="100%">
<TableHeaderSticky> <TableHeaderSticky>
<TableRow> <TableRow>
......
...@@ -12,7 +12,6 @@ import { BLOCK } from 'stubs/block'; ...@@ -12,7 +12,6 @@ import { BLOCK } from 'stubs/block';
import { TX } from 'stubs/tx'; import { TX } from 'stubs/tx';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import BlocksContent from 'ui/blocks/BlocksContent'; import BlocksContent from 'ui/blocks/BlocksContent';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
...@@ -86,7 +85,7 @@ const ArbitrumL2TxnBatch = () => { ...@@ -86,7 +85,7 @@ const ArbitrumL2TxnBatch = () => {
{ {
id: 'txs', id: 'txs',
title: 'Transactions', title: 'Transactions',
component: <TxsWithFrontendSorting query={ batchTxsQuery } showSocketInfo={ false } top={ hasPagination ? TABS_HEIGHT : 0 }/>, component: <TxsWithFrontendSorting query={ batchTxsQuery } top={ hasPagination ? TABS_HEIGHT : 0 }/>,
}, },
{ {
id: 'blocks', id: 'blocks',
...@@ -116,15 +115,13 @@ const ArbitrumL2TxnBatch = () => { ...@@ -116,15 +115,13 @@ const ArbitrumL2TxnBatch = () => {
backLink={ backLink } backLink={ backLink }
isLoading={ batchQuery.isPlaceholderData } isLoading={ batchQuery.isPlaceholderData }
/> />
{ batchQuery.isPlaceholderData ?
<RoutedTabsSkeleton tabs={ tabs }/> : (
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
isLoading={ batchQuery.isPlaceholderData }
listProps={ isMobile ? undefined : TAB_LIST_PROPS } listProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ hasPagination && pagination ? <Pagination { ...(pagination) }/> : null } rightSlot={ hasPagination && pagination ? <Pagination { ...(pagination) }/> : null }
stickyEnabled={ hasPagination } stickyEnabled={ hasPagination }
/> />
) }
</> </>
); );
}; };
......
...@@ -15,7 +15,6 @@ import getNetworkValidationActionText from 'lib/networks/getNetworkValidationAct ...@@ -15,7 +15,6 @@ import getNetworkValidationActionText from 'lib/networks/getNetworkValidationAct
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { Skeleton } from 'toolkit/chakra/skeleton'; import { Skeleton } from 'toolkit/chakra/skeleton';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import BlockCeloEpochTag from 'ui/block/BlockCeloEpochTag'; import BlockCeloEpochTag from 'ui/block/BlockCeloEpochTag';
import BlockDetails from 'ui/block/BlockDetails'; import BlockDetails from 'ui/block/BlockDetails';
import BlockEpochRewards from 'ui/block/BlockEpochRewards'; import BlockEpochRewards from 'ui/block/BlockEpochRewards';
...@@ -74,7 +73,7 @@ const BlockPageContent = () => { ...@@ -74,7 +73,7 @@ const BlockPageContent = () => {
component: ( component: (
<> <>
{ blockTxsQuery.isDegradedData && <ServiceDegradationWarning isLoading={ blockTxsQuery.isPlaceholderData } mb={ 6 }/> } { blockTxsQuery.isDegradedData && <ServiceDegradationWarning isLoading={ blockTxsQuery.isPlaceholderData } mb={ 6 }/> }
<TxsWithFrontendSorting query={ blockTxsQuery } showBlockInfo={ false } showSocketInfo={ false } top={ hasPagination ? TABS_HEIGHT : 0 }/> <TxsWithFrontendSorting query={ blockTxsQuery } showBlockInfo={ false } top={ hasPagination ? TABS_HEIGHT : 0 }/>
</> </>
), ),
}, },
...@@ -83,7 +82,7 @@ const BlockPageContent = () => { ...@@ -83,7 +82,7 @@ const BlockPageContent = () => {
id: 'blob_txs', id: 'blob_txs',
title: 'Blob txns', title: 'Blob txns',
component: ( component: (
<TxsWithFrontendSorting query={ blockBlobTxsQuery } showBlockInfo={ false } showSocketInfo={ false }/> <TxsWithFrontendSorting query={ blockBlobTxsQuery } showBlockInfo={ false }/>
), ),
} : null, } : null,
config.features.beaconChain.isEnabled && Boolean(blockQuery.data?.withdrawals_count) ? config.features.beaconChain.isEnabled && Boolean(blockQuery.data?.withdrawals_count) ?
...@@ -183,14 +182,13 @@ const BlockPageContent = () => { ...@@ -183,14 +182,13 @@ const BlockPageContent = () => {
secondRow={ titleSecondRow } secondRow={ titleSecondRow }
isLoading={ blockQuery.isPlaceholderData } isLoading={ blockQuery.isPlaceholderData }
/> />
{ blockQuery.isPlaceholderData ? <RoutedTabsSkeleton tabs={ tabs }/> : (
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
isLoading={ blockQuery.isPlaceholderData }
listProps={ isMobile ? undefined : TAB_LIST_PROPS } listProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ hasPagination ? <Pagination { ...(pagination as PaginationParams) }/> : null } rightSlot={ hasPagination ? <Pagination { ...(pagination as PaginationParams) }/> : null }
stickyEnabled={ hasPagination } stickyEnabled={ hasPagination }
/> />
) }
</> </>
); );
}; };
......
...@@ -31,10 +31,7 @@ const KettleTxs = () => { ...@@ -31,10 +31,7 @@ const KettleTxs = () => {
<> <>
<PageTitle title="Computor transactions" withTextAd/> <PageTitle title="Computor transactions" withTextAd/>
<AddressEntity address={{ hash }} mb={ 6 }/> <AddressEntity address={{ hash }} mb={ 6 }/>
<TxsWithFrontendSorting <TxsWithFrontendSorting query={ query }/>
query={ query }
showSocketInfo={ false }
/>
</> </>
); );
}; };
......
...@@ -170,7 +170,7 @@ const Marketplace = () => { ...@@ -170,7 +170,7 @@ const Marketplace = () => {
<IconSvg name="dots"/> <IconSvg name="dots"/>
</IconButton> </IconButton>
</MenuTrigger> </MenuTrigger>
<MenuContent> <MenuContent zIndex="banner">
{ links.map(({ label, href, icon }) => ( { links.map(({ label, href, icon }) => (
<MenuItem key={ label } value={ label } asChild> <MenuItem key={ label } value={ label } asChild>
<Link external href={ href } variant="menu" gap={ 0 }> <Link external href={ href } variant="menu" gap={ 0 }>
...@@ -205,6 +205,7 @@ const Marketplace = () => { ...@@ -205,6 +205,7 @@ const Marketplace = () => {
showShadow showShadow
display="flex" display="flex"
flexDirection="column" flexDirection="column"
mt={ 0 }
mx={{ base: -3, lg: -12 }} mx={{ base: -3, lg: -12 }}
px={{ base: 3, lg: 12 }} px={{ base: 3, lg: 12 }}
pt={{ base: 4, lg: 6 }} pt={{ base: 4, lg: 6 }}
......
...@@ -14,8 +14,6 @@ import { ENS_DOMAIN } from 'stubs/ENS'; ...@@ -14,8 +14,6 @@ import { ENS_DOMAIN } from 'stubs/ENS';
import { Link } from 'toolkit/chakra/link'; import { Link } from 'toolkit/chakra/link';
import { Tooltip } from 'toolkit/chakra/tooltip'; import { Tooltip } from 'toolkit/chakra/tooltip';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import useActiveTabFromQuery from 'toolkit/components/RoutedTabs/useActiveTabFromQuery';
import NameDomainDetails from 'ui/nameDomain/NameDomainDetails'; import NameDomainDetails from 'ui/nameDomain/NameDomainDetails';
import NameDomainHistory from 'ui/nameDomain/NameDomainHistory'; import NameDomainHistory from 'ui/nameDomain/NameDomainHistory';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
...@@ -40,8 +38,6 @@ const NameDomain = () => { ...@@ -40,8 +38,6 @@ const NameDomain = () => {
{ id: 'history', title: 'History', component: <NameDomainHistory domain={ infoQuery.data }/> }, { id: 'history', title: 'History', component: <NameDomainHistory domain={ infoQuery.data }/> },
]; ];
const activeTab = useActiveTabFromQuery(tabs);
throwOnResourceLoadError(infoQuery); throwOnResourceLoadError(infoQuery);
const isLoading = infoQuery.isPlaceholderData; const isLoading = infoQuery.isPlaceholderData;
...@@ -87,12 +83,7 @@ const NameDomain = () => { ...@@ -87,12 +83,7 @@ const NameDomain = () => {
<> <>
<TextAd mb={ 6 }/> <TextAd mb={ 6 }/>
<PageTitle title="Name details" secondRow={ titleSecondRow }/> <PageTitle title="Name details" secondRow={ titleSecondRow }/>
{ infoQuery.isPlaceholderData ? ( <RoutedTabs tabs={ tabs } isLoading={ infoQuery.isPlaceholderData }/>
<>
<RoutedTabsSkeleton tabs={ tabs } mt={ 6 }/>
{ activeTab?.component }
</>
) : <RoutedTabs tabs={ tabs }/> }
</> </>
); );
}; };
......
...@@ -12,7 +12,6 @@ import { BLOCK } from 'stubs/block'; ...@@ -12,7 +12,6 @@ import { BLOCK } from 'stubs/block';
import { TX } from 'stubs/tx'; import { TX } from 'stubs/tx';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import BlocksContent from 'ui/blocks/BlocksContent'; import BlocksContent from 'ui/blocks/BlocksContent';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
...@@ -84,7 +83,7 @@ const OptimisticL2TxnBatch = () => { ...@@ -84,7 +83,7 @@ const OptimisticL2TxnBatch = () => {
{ {
id: 'txs', id: 'txs',
title: 'Transactions', title: 'Transactions',
component: <TxsWithFrontendSorting query={ batchTxsQuery } showSocketInfo={ false } top={ hasPagination ? TABS_HEIGHT : 0 }/>, component: <TxsWithFrontendSorting query={ batchTxsQuery } top={ hasPagination ? TABS_HEIGHT : 0 }/>,
}, },
{ {
id: 'blocks', id: 'blocks',
...@@ -114,15 +113,13 @@ const OptimisticL2TxnBatch = () => { ...@@ -114,15 +113,13 @@ const OptimisticL2TxnBatch = () => {
backLink={ backLink } backLink={ backLink }
isLoading={ batchQuery.isPlaceholderData } isLoading={ batchQuery.isPlaceholderData }
/> />
{ batchQuery.isPlaceholderData ?
<RoutedTabsSkeleton tabs={ tabs }/> : (
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
isLoading={ batchQuery.isPlaceholderData }
listProps={ isMobile ? undefined : TAB_LIST_PROPS } listProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ hasPagination && pagination ? <Pagination { ...(pagination) }/> : null } rightSlot={ hasPagination && pagination ? <Pagination { ...(pagination) }/> : null }
stickyEnabled={ hasPagination } stickyEnabled={ hasPagination }
/> />
) }
</> </>
); );
}; };
......
...@@ -14,7 +14,6 @@ import { SCROLL_L2_TXN_BATCH } from 'stubs/scrollL2'; ...@@ -14,7 +14,6 @@ import { SCROLL_L2_TXN_BATCH } from 'stubs/scrollL2';
import { TX } from 'stubs/tx'; import { TX } from 'stubs/tx';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import BlocksContent from 'ui/blocks/BlocksContent'; import BlocksContent from 'ui/blocks/BlocksContent';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
...@@ -91,7 +90,7 @@ const ScrollL2TxnBatch = () => { ...@@ -91,7 +90,7 @@ const ScrollL2TxnBatch = () => {
{ {
id: 'txs', id: 'txs',
title: 'Transactions', title: 'Transactions',
component: <TxsWithFrontendSorting query={ batchTxsQuery } showSocketInfo={ false } top={ hasPagination ? TABS_HEIGHT : 0 }/>, component: <TxsWithFrontendSorting query={ batchTxsQuery } top={ hasPagination ? TABS_HEIGHT : 0 }/>,
}, },
{ {
id: 'blocks', id: 'blocks',
...@@ -120,15 +119,13 @@ const ScrollL2TxnBatch = () => { ...@@ -120,15 +119,13 @@ const ScrollL2TxnBatch = () => {
title={ `Txn batch #${ number }` } title={ `Txn batch #${ number }` }
backLink={ backLink } backLink={ backLink }
/> />
{ batchQuery.isPlaceholderData ?
<RoutedTabsSkeleton tabs={ tabs }/> : (
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
isLoading={ batchQuery.isPlaceholderData }
listProps={ isMobile ? undefined : TAB_LIST_PROPS } listProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ hasPagination && pagination ? <Pagination { ...(pagination) }/> : null } rightSlot={ hasPagination && pagination ? <Pagination { ...(pagination) }/> : null }
stickyEnabled={ hasPagination } stickyEnabled={ hasPagination }
/> />
) }
</> </>
); );
}; };
......
...@@ -42,7 +42,7 @@ const L2Deposits = () => { ...@@ -42,7 +42,7 @@ const L2Deposits = () => {
<Box hideFrom="lg"> <Box hideFrom="lg">
{ data.items.map(((item, index) => ( { data.items.map(((item, index) => (
<DepositsListItem <DepositsListItem
key={ item.l2_transaction_hash + (isPlaceholderData ? index : '') } key={ `${ item.l2_transaction_hash }-${ index }` }
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
item={ item } item={ item }
/> />
......
...@@ -14,7 +14,7 @@ import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; ...@@ -14,7 +14,7 @@ import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText';
import WithdrawalsListItem from 'ui/withdrawals/shibarium/WithdrawalsListItem'; import WithdrawalsListItem from 'ui/withdrawals/shibarium/WithdrawalsListItem';
import WithdrawalsTable from 'ui/withdrawals/shibarium/WithdrawalsTable'; import WithdrawalsTable from 'ui/withdrawals/shibarium/WithdrawalsTable';
const L2Withdrawals = () => { const ShibariumWithdrawals = () => {
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
resourceName: 'shibarium_withdrawals', resourceName: 'shibarium_withdrawals',
options: { options: {
...@@ -42,7 +42,7 @@ const L2Withdrawals = () => { ...@@ -42,7 +42,7 @@ const L2Withdrawals = () => {
<Box hideFrom="lg"> <Box hideFrom="lg">
{ data.items.map(((item, index) => ( { data.items.map(((item, index) => (
<WithdrawalsListItem <WithdrawalsListItem
key={ item.l2_transaction_hash + (isPlaceholderData ? index : '') } key={ `${ item.l2_transaction_hash }-${ index }` }
item={ item } item={ item }
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
/> />
...@@ -83,4 +83,4 @@ const L2Withdrawals = () => { ...@@ -83,4 +83,4 @@ const L2Withdrawals = () => {
); );
}; };
export default L2Withdrawals; export default ShibariumWithdrawals;
...@@ -10,8 +10,6 @@ import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; ...@@ -10,8 +10,6 @@ import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { publicClient } from 'lib/web3/client'; import { publicClient } from 'lib/web3/client';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import useActiveTabFromQuery from 'toolkit/components/RoutedTabs/useActiveTabFromQuery';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError'; import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import EntityTags from 'ui/shared/EntityTags/EntityTags'; import EntityTags from 'ui/shared/EntityTags/EntityTags';
...@@ -78,8 +76,6 @@ const TransactionPageContent = () => { ...@@ -78,8 +76,6 @@ const TransactionPageContent = () => {
].filter(Boolean); ].filter(Boolean);
})(); })();
const activeTab = useActiveTabFromQuery(tabs);
const txTags: Array<TEntityTag> = data?.transaction_tag ? const txTags: Array<TEntityTag> = data?.transaction_tag ?
[ { slug: data.transaction_tag, name: data.transaction_tag, tagType: 'private_tag' as const, ordinal: 1 } ] : []; [ { slug: data.transaction_tag, name: data.transaction_tag, tagType: 'private_tag' as const, ordinal: 1 } ] : [];
if (rollupFeature.isEnabled && rollupFeature.interopEnabled && data?.op_interop) { if (rollupFeature.isEnabled && rollupFeature.interopEnabled && data?.op_interop) {
...@@ -113,19 +109,6 @@ const TransactionPageContent = () => { ...@@ -113,19 +109,6 @@ const TransactionPageContent = () => {
const titleSecondRow = <TxSubHeading hash={ hash } hasTag={ Boolean(data?.transaction_tag) } txQuery={ txQuery }/>; const titleSecondRow = <TxSubHeading hash={ hash } hasTag={ Boolean(data?.transaction_tag) } txQuery={ txQuery }/>;
const content = (() => {
if (isPlaceholderData && !showDegradedView) {
return (
<>
<RoutedTabsSkeleton tabs={ tabs } mt={ 6 }/>
{ activeTab?.component }
</>
);
}
return <RoutedTabs tabs={ tabs }/>;
})();
if (isError && !showDegradedView) { if (isError && !showDegradedView) {
if (isCustomAppError(error)) { if (isCustomAppError(error)) {
throwOnResourceLoadError({ resource: 'tx', error, isError: true }); throwOnResourceLoadError({ resource: 'tx', error, isError: true });
...@@ -141,7 +124,7 @@ const TransactionPageContent = () => { ...@@ -141,7 +124,7 @@ const TransactionPageContent = () => {
contentAfter={ tags } contentAfter={ tags }
secondRow={ titleSecondRow } secondRow={ titleSecondRow }
/> />
{ content } <RoutedTabs tabs={ tabs } isLoading={ isPlaceholderData }/>
</> </>
); );
}; };
......
...@@ -9,7 +9,6 @@ import { route } from 'nextjs-routes'; ...@@ -9,7 +9,6 @@ import { route } from 'nextjs-routes';
import config from 'configs/app'; import config from 'configs/app';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useNewTxsSocket from 'lib/hooks/useNewTxsSocket';
import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText'; import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { TX } from 'stubs/tx'; import { TX } from 'stubs/tx';
...@@ -91,8 +90,6 @@ const Transactions = () => { ...@@ -91,8 +90,6 @@ const Transactions = () => {
}, },
}); });
const { num, socketAlert } = useNewTxsSocket();
const isAuth = useIsAuth(); const isAuth = useIsAuth();
const tabs: Array<TabItemRegular> = [ const tabs: Array<TabItemRegular> = [
...@@ -102,9 +99,7 @@ const Transactions = () => { ...@@ -102,9 +99,7 @@ const Transactions = () => {
component: component:
<TxsWithFrontendSorting <TxsWithFrontendSorting
query={ txsValidatedQuery } query={ txsValidatedQuery }
showSocketInfo={ txsValidatedQuery.pagination.page === 1 } socketType="txs_validated"
socketInfoNum={ num }
socketInfoAlert={ socketAlert }
top={ TABS_HEIGHT } top={ TABS_HEIGHT }
/> }, /> },
{ {
...@@ -114,9 +109,7 @@ const Transactions = () => { ...@@ -114,9 +109,7 @@ const Transactions = () => {
<TxsWithFrontendSorting <TxsWithFrontendSorting
query={ txsPendingQuery } query={ txsPendingQuery }
showBlockInfo={ false } showBlockInfo={ false }
showSocketInfo={ txsPendingQuery.pagination.page === 1 } socketType="txs_pending"
socketInfoNum={ num }
socketInfoAlert={ socketAlert }
top={ TABS_HEIGHT } top={ TABS_HEIGHT }
/> />
), ),
...@@ -127,9 +120,6 @@ const Transactions = () => { ...@@ -127,9 +120,6 @@ const Transactions = () => {
component: ( component: (
<TxsWithFrontendSorting <TxsWithFrontendSorting
query={ txsWithBlobsQuery } query={ txsWithBlobsQuery }
showSocketInfo={ txsWithBlobsQuery.pagination.page === 1 }
socketInfoNum={ num }
socketInfoAlert={ socketAlert }
top={ TABS_HEIGHT } top={ TABS_HEIGHT }
/> />
), ),
......
...@@ -13,8 +13,6 @@ import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; ...@@ -13,8 +13,6 @@ import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { USER_OP } from 'stubs/userOps'; import { USER_OP } from 'stubs/userOps';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import useActiveTabFromQuery from 'toolkit/components/RoutedTabs/useActiveTabFromQuery';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import TxLogs from 'ui/tx/TxLogs'; import TxLogs from 'ui/tx/TxLogs';
...@@ -82,8 +80,6 @@ const UserOp = () => { ...@@ -82,8 +80,6 @@ const UserOp = () => {
{ id: 'raw', title: 'Raw', component: <UserOpRaw rawData={ userOpQuery.data?.raw } isLoading={ userOpQuery.isPlaceholderData }/> }, { id: 'raw', title: 'Raw', component: <UserOpRaw rawData={ userOpQuery.data?.raw } isLoading={ userOpQuery.isPlaceholderData }/> },
]), [ userOpQuery, txQuery, filterTokenTransfersByLogIndex, filterLogsByLogIndex ]); ]), [ userOpQuery, txQuery, filterTokenTransfersByLogIndex, filterLogsByLogIndex ]);
const activeTab = useActiveTabFromQuery(tabs);
const backLink = React.useMemo(() => { const backLink = React.useMemo(() => {
const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/ops'); const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/ops');
...@@ -110,13 +106,7 @@ const UserOp = () => { ...@@ -110,13 +106,7 @@ const UserOp = () => {
backLink={ backLink } backLink={ backLink }
secondRow={ titleSecondRow } secondRow={ titleSecondRow }
/> />
{ userOpQuery.isPlaceholderData ? ( <RoutedTabs tabs={ tabs } isLoading={ userOpQuery.isPlaceholderData }/>
<>
<RoutedTabsSkeleton tabs={ tabs } mt={ 6 }/>
{ activeTab?.component }
</>
) :
<RoutedTabs tabs={ tabs }/> }
</> </>
); );
}; };
......
...@@ -12,7 +12,6 @@ import { TX_ZKEVM_L2 } from 'stubs/tx'; ...@@ -12,7 +12,6 @@ import { TX_ZKEVM_L2 } from 'stubs/tx';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import { ZKEVM_L2_TXN_BATCH } from 'stubs/zkEvmL2'; import { ZKEVM_L2_TXN_BATCH } from 'stubs/zkEvmL2';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
...@@ -48,7 +47,7 @@ const ZkEvmL2TxnBatch = () => { ...@@ -48,7 +47,7 @@ const ZkEvmL2TxnBatch = () => {
const tabs: Array<TabItemRegular> = React.useMemo(() => ([ const tabs: Array<TabItemRegular> = React.useMemo(() => ([
{ id: 'index', title: 'Details', component: <ZkEvmL2TxnBatchDetails query={ batchQuery }/> }, { id: 'index', title: 'Details', component: <ZkEvmL2TxnBatchDetails query={ batchQuery }/> },
{ id: 'txs', title: 'Transactions', component: <TxsWithFrontendSorting query={ batchTxsQuery } showSocketInfo={ false }/> }, { id: 'txs', title: 'Transactions', component: <TxsWithFrontendSorting query={ batchTxsQuery }/> },
].filter(Boolean)), [ batchQuery, batchTxsQuery ]); ].filter(Boolean)), [ batchQuery, batchTxsQuery ]);
const backLink = React.useMemo(() => { const backLink = React.useMemo(() => {
...@@ -71,11 +70,10 @@ const ZkEvmL2TxnBatch = () => { ...@@ -71,11 +70,10 @@ const ZkEvmL2TxnBatch = () => {
title={ `Txn batch #${ number }` } title={ `Txn batch #${ number }` }
backLink={ backLink } backLink={ backLink }
/> />
{ batchQuery.isPlaceholderData ? <RoutedTabsSkeleton tabs={ tabs }/> : (
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
isLoading={ batchQuery.isPlaceholderData }
/> />
) }
</> </>
); );
}; };
......
...@@ -13,7 +13,6 @@ import { TX } from 'stubs/tx'; ...@@ -13,7 +13,6 @@ import { TX } from 'stubs/tx';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import { ZKSYNC_L2_TXN_BATCH } from 'stubs/zkSyncL2'; import { ZKSYNC_L2_TXN_BATCH } from 'stubs/zkSyncL2';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
...@@ -68,7 +67,7 @@ const ZkSyncL2TxnBatch = () => { ...@@ -68,7 +67,7 @@ const ZkSyncL2TxnBatch = () => {
{ {
id: 'txs', id: 'txs',
title: 'Transactions', title: 'Transactions',
component: <TxsWithFrontendSorting query={ batchTxsQuery } showSocketInfo={ false } top={ hasPagination ? TABS_HEIGHT : 0 }/>, component: <TxsWithFrontendSorting query={ batchTxsQuery } top={ hasPagination ? TABS_HEIGHT : 0 }/>,
}, },
].filter(Boolean)), [ batchQuery, batchTxsQuery, hasPagination ]); ].filter(Boolean)), [ batchQuery, batchTxsQuery, hasPagination ]);
...@@ -92,15 +91,13 @@ const ZkSyncL2TxnBatch = () => { ...@@ -92,15 +91,13 @@ const ZkSyncL2TxnBatch = () => {
title={ `Txn batch #${ number }` } title={ `Txn batch #${ number }` }
backLink={ backLink } backLink={ backLink }
/> />
{ batchQuery.isPlaceholderData ?
<RoutedTabsSkeleton tabs={ tabs }/> : (
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
isLoading={ batchQuery.isPlaceholderData }
listProps={ isMobile ? undefined : TAB_LIST_PROPS } listProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ hasPagination ? <Pagination { ...(batchTxsQuery.pagination) }/> : null } rightSlot={ hasPagination ? <Pagination { ...(batchTxsQuery.pagination) }/> : null }
stickyEnabled={ hasPagination } stickyEnabled={ hasPagination }
/> />
) }
</> </>
); );
}; };
......
...@@ -14,6 +14,7 @@ import getErrorObj from 'lib/errors/getErrorObj'; ...@@ -14,6 +14,7 @@ import getErrorObj from 'lib/errors/getErrorObj';
import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; import getErrorObjPayload from 'lib/errors/getErrorObjPayload';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { Button } from 'toolkit/chakra/button'; import { Button } from 'toolkit/chakra/button';
import { Heading } from 'toolkit/chakra/heading';
import { FormFieldEmail } from 'toolkit/components/forms/fields/FormFieldEmail'; import { FormFieldEmail } from 'toolkit/components/forms/fields/FormFieldEmail';
import { FormFieldText } from 'toolkit/components/forms/fields/FormFieldText'; import { FormFieldText } from 'toolkit/components/forms/fields/FormFieldText';
import { FormFieldUrl } from 'toolkit/components/forms/fields/FormFieldUrl'; import { FormFieldUrl } from 'toolkit/components/forms/fields/FormFieldUrl';
...@@ -96,8 +97,10 @@ const PublicTagsSubmitForm = ({ config, userInfo, onSubmitResult }: Props) => { ...@@ -96,8 +97,10 @@ const PublicTagsSubmitForm = ({ config, userInfo, onSubmitResult }: Props) => {
rowGap={ 3 } rowGap={ 3 }
templateColumns={{ base: '1fr', lg: '1fr 1fr minmax(0, 200px)', xl: '1fr 1fr minmax(0, 250px)' }} templateColumns={{ base: '1fr', lg: '1fr 1fr minmax(0, 200px)', xl: '1fr 1fr minmax(0, 250px)' }}
> >
<GridItem colSpan={{ base: 1, lg: 3 }} as="h2" textStyle="h4"> <GridItem colSpan={{ base: 1, lg: 3 }}>
<Heading level="2">
Company info Company info
</Heading>
</GridItem> </GridItem>
<FormFieldText<FormFields> name="requesterName" required placeholder="Your name"/> <FormFieldText<FormFields> name="requesterName" required placeholder="Your name"/>
<FormFieldEmail<FormFields> name="requesterEmail" required/> <FormFieldEmail<FormFields> name="requesterEmail" required/>
...@@ -107,9 +110,11 @@ const PublicTagsSubmitForm = ({ config, userInfo, onSubmitResult }: Props) => { ...@@ -107,9 +110,11 @@ const PublicTagsSubmitForm = ({ config, userInfo, onSubmitResult }: Props) => {
<FormFieldUrl<FormFields> name="companyWebsite" placeholder="Company website"/> <FormFieldUrl<FormFields> name="companyWebsite" placeholder="Company website"/>
{ !isMobile && <div/> } { !isMobile && <div/> }
<GridItem colSpan={{ base: 1, lg: 3 }} as="h2" textStyle="h4" mt={{ base: 3, lg: 5 }}> <GridItem colSpan={{ base: 1, lg: 3 }} mt={{ base: 3, lg: 5 }}>
<Heading level="2" display="flex" alignItems="center" columnGap={ 1 }>
Public tags/labels Public tags/labels
<Hint label="Submit a public tag proposal for our moderation team to review" ml={ 1 } color="link.primary"/> <Hint label="Submit a public tag proposal for our moderation team to review"/>
</Heading>
</GridItem> </GridItem>
<PublicTagsSubmitFieldAddresses/> <PublicTagsSubmitFieldAddresses/>
<PublicTagsSubmitFieldTags tagTypes={ config?.tagTypes }/> <PublicTagsSubmitFieldTags tagTypes={ config?.tagTypes }/>
......
import { Box, Flex, Grid, GridItem } from '@chakra-ui/react'; import { Flex, Grid, GridItem } from '@chakra-ui/react';
import { pickBy } from 'es-toolkit'; import { pickBy } from 'es-toolkit';
import React from 'react'; import React from 'react';
...@@ -8,6 +8,7 @@ import { route } from 'nextjs-routes'; ...@@ -8,6 +8,7 @@ import { route } from 'nextjs-routes';
import { Alert } from 'toolkit/chakra/alert'; import { Alert } from 'toolkit/chakra/alert';
import { Button } from 'toolkit/chakra/button'; import { Button } from 'toolkit/chakra/button';
import { Heading } from 'toolkit/chakra/heading';
import { Link } from 'toolkit/chakra/link'; import { Link } from 'toolkit/chakra/link';
import { makePrettyLink } from 'toolkit/utils/url'; import { makePrettyLink } from 'toolkit/utils/url';
...@@ -43,7 +44,7 @@ const PublicTagsSubmitResult = ({ data }: Props) => { ...@@ -43,7 +44,7 @@ const PublicTagsSubmitResult = ({ data }: Props) => {
</Alert> </Alert>
) } ) }
<Box as="h2" textStyle="h4">Company info</Box> <Heading level="2">Company info</Heading>
<Grid rowGap={ 3 } columnGap={ 6 } gridTemplateColumns="170px 1fr" mt={ 6 }> <Grid rowGap={ 3 } columnGap={ 6 } gridTemplateColumns="170px 1fr" mt={ 6 }>
<GridItem>Your name</GridItem> <GridItem>Your name</GridItem>
<GridItem>{ groupedData.requesterName }</GridItem> <GridItem>{ groupedData.requesterName }</GridItem>
...@@ -65,7 +66,7 @@ const PublicTagsSubmitResult = ({ data }: Props) => { ...@@ -65,7 +66,7 @@ const PublicTagsSubmitResult = ({ data }: Props) => {
) } ) }
</Grid> </Grid>
<Box as="h2" textStyle="h4" mt={ 8 } mb={ 5 }>Public tags/labels</Box> <Heading level="2" mt={ 8 } mb={ 5 }>Public tags/labels</Heading>
{ hasErrors ? <PublicTagsSubmitResultWithErrors data={ groupedData }/> : <PublicTagsSubmitResultSuccess data={ groupedData }/> } { hasErrors ? <PublicTagsSubmitResultWithErrors data={ groupedData }/> : <PublicTagsSubmitResultSuccess data={ groupedData }/> }
<Flex flexDir={{ base: 'column', lg: 'row' }} columnGap={ 6 } mt={ 8 } rowGap={ 3 }> <Flex flexDir={{ base: 'column', lg: 'row' }} columnGap={ 6 } mt={ 8 } rowGap={ 3 }>
......
...@@ -290,7 +290,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr ...@@ -290,7 +290,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
<Grid templateColumns={ templateCols } alignItems="center" gap={ 2 }> <Grid templateColumns={ templateCols } alignItems="center" gap={ 2 }>
<Skeleton loading={ isLoading } overflow="hidden" display="flex" alignItems="center"> <Skeleton loading={ isLoading } overflow="hidden" display="flex" alignItems="center">
<Text whiteSpace="nowrap" overflow="hidden"> <Text whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ hash } isTooltipDisabled/> <HashStringShortenDynamic hash={ hash } noTooltip/>
</Text> </Text>
{ data.is_smart_contract_verified && <IconSvg name="status/success" boxSize="14px" color="green.500" ml={ 1 } flexShrink={ 0 }/> } { data.is_smart_contract_verified && <IconSvg name="status/success" boxSize="14px" color="green.500" ml={ 1 } flexShrink={ 0 }/> }
</Skeleton> </Skeleton>
......
...@@ -23,9 +23,8 @@ const ButtonItem = ({ className, label, onClick, icon, isDisabled }: Props) => { ...@@ -23,9 +23,8 @@ const ButtonItem = ({ className, label, onClick, icon, isDisabled }: Props) => {
disabled={ isDisabled } disabled={ isDisabled }
variant="icon_secondary" variant="icon_secondary"
boxSize={ 8 } boxSize={ 8 }
_icon={{ boxSize: 6 }}
> >
{ typeof icon === 'string' ? <IconSvg name={ icon }/> : icon } { typeof icon === 'string' ? <IconSvg name={ icon } boxSize={ 6 }/> : icon }
</IconButton> </IconButton>
</Tooltip> </Tooltip>
); );
......
...@@ -29,6 +29,7 @@ const ActionBar = ({ children, className, showShadow }: Props) => { ...@@ -29,6 +29,7 @@ const ActionBar = ({ children, className, showShadow }: Props) => {
className={ className } className={ className }
backgroundColor={{ _light: 'white', _dark: 'black' }} backgroundColor={{ _light: 'white', _dark: 'black' }}
pt={ 6 } pt={ 6 }
mt={ -6 }
pb={{ base: 6, lg: 3 }} pb={{ base: 6, lg: 3 }}
mx={{ base: -3, lg: 0 }} mx={{ base: -3, lg: 0 }}
px={{ base: 3, lg: 0 }} px={{ base: 3, lg: 0 }}
......
...@@ -10,10 +10,14 @@ export interface Props extends Omit<IconButtonProps, 'type' | 'loading'> { ...@@ -10,10 +10,14 @@ export interface Props extends Omit<IconButtonProps, 'type' | 'loading'> {
text: string; text: string;
type?: 'link' | 'text' | 'share'; type?: 'link' | 'text' | 'share';
isLoading?: boolean; isLoading?: boolean;
// Chakra v3 doesn't support tooltip inside tooltip - https://github.com/chakra-ui/chakra-ui/issues/9939#issuecomment-2817168121
// so we disable the copy tooltip manually when the button is inside a tooltip
noTooltip?: boolean;
tooltipInteractive?: boolean;
} }
const CopyToClipboard = (props: Props) => { const CopyToClipboard = (props: Props) => {
const { text, type = 'text', isLoading, onClick, boxSize = 5, ...rest } = props; const { text, type = 'text', isLoading, onClick, boxSize = 5, noTooltip, tooltipInteractive, ...rest } = props;
const { hasCopied, copy, disclosure } = useClipboard(text); const { hasCopied, copy, disclosure } = useClipboard(text);
...@@ -23,37 +27,18 @@ const CopyToClipboard = (props: Props) => { ...@@ -23,37 +27,18 @@ const CopyToClipboard = (props: Props) => {
onClick?.(event); onClick?.(event);
}, [ onClick, copy ]); }, [ onClick, copy ]);
const tooltipContent = (() => {
if (hasCopied) {
return 'Copied';
}
if (type === 'link') {
return 'Copy link to clipboard';
}
return 'Copy to clipboard';
})();
const iconName = (() => { const iconName = (() => {
switch (type) { switch (type) {
case 'link': case 'link':
return 'link'; return hasCopied ? 'check' : 'link';
case 'share': case 'share':
return 'share'; return hasCopied ? 'check' : 'share';
default: default:
return 'copy'; return hasCopied ? 'copy_check' : 'copy';
} }
})(); })();
return ( const button = (
<Tooltip
content={ tooltipContent }
contentProps={{ zIndex: 'tooltip2' }}
open={ disclosure.open }
onOpenChange={ disclosure.onOpenChange }
closeOnPointerDown={ false }
>
<IconButton <IconButton
aria-label="copy" aria-label="copy"
boxSize={ boxSize } boxSize={ boxSize }
...@@ -67,6 +52,34 @@ const CopyToClipboard = (props: Props) => { ...@@ -67,6 +52,34 @@ const CopyToClipboard = (props: Props) => {
> >
<IconSvg name={ iconName }/> <IconSvg name={ iconName }/>
</IconButton> </IconButton>
);
if (noTooltip) {
return button;
}
const tooltipContent = (() => {
if (hasCopied) {
return 'Copied';
}
if (type === 'link') {
return 'Copy link to clipboard';
}
return 'Copy to clipboard';
})();
return (
<Tooltip
content={ tooltipContent }
contentProps={{ zIndex: 'tooltip2' }}
open={ disclosure.open }
onOpenChange={ disclosure.onOpenChange }
closeOnPointerDown={ false }
interactive={ tooltipInteractive }
>
{ button }
</Tooltip> </Tooltip>
); );
}; };
......
...@@ -6,20 +6,27 @@ import { Tooltip } from 'toolkit/chakra/tooltip'; ...@@ -6,20 +6,27 @@ import { Tooltip } from 'toolkit/chakra/tooltip';
interface Props { interface Props {
hash: string; hash: string;
isTooltipDisabled?: boolean; noTooltip?: boolean;
tooltipInteractive?: boolean;
type?: 'long' | 'short'; type?: 'long' | 'short';
as?: React.ElementType; as?: React.ElementType;
} }
const HashStringShorten = ({ hash, isTooltipDisabled, as = 'span', type }: Props) => { const HashStringShorten = ({ hash, noTooltip, as = 'span', type, tooltipInteractive }: Props) => {
const charNumber = type === 'long' ? 16 : 8; const charNumber = type === 'long' ? 16 : 8;
if (hash.length <= charNumber) { if (hash.length <= charNumber) {
return <chakra.span as={ as }>{ hash }</chakra.span>; return <chakra.span as={ as }>{ hash }</chakra.span>;
} }
const content = <chakra.span as={ as }>{ shortenString(hash, charNumber) }</chakra.span>;
if (noTooltip) {
return content;
}
return ( return (
<Tooltip content={ hash } disabled={ isTooltipDisabled }> <Tooltip content={ hash } interactive={ tooltipInteractive }>
<chakra.span as={ as }>{ shortenString(hash, charNumber) }</chakra.span> { content }
</Tooltip> </Tooltip>
); );
}; };
......
...@@ -23,12 +23,13 @@ const HEAD_MIN_LENGTH = 4; ...@@ -23,12 +23,13 @@ const HEAD_MIN_LENGTH = 4;
interface Props { interface Props {
hash: string; hash: string;
fontWeight?: string | number; fontWeight?: string | number;
isTooltipDisabled?: boolean; noTooltip?: boolean;
tooltipInteractive?: boolean;
tailLength?: number; tailLength?: number;
as?: React.ElementType; as?: React.ElementType;
} }
const HashStringShortenDynamic = ({ hash, fontWeight = '400', isTooltipDisabled, tailLength = TAIL_LENGTH, as = 'span' }: Props) => { const HashStringShortenDynamic = ({ hash, fontWeight = '400', noTooltip, tailLength = TAIL_LENGTH, as = 'span', tooltipInteractive }: Props) => {
const elementRef = useRef<HTMLSpanElement>(null); const elementRef = useRef<HTMLSpanElement>(null);
const [ displayedString, setDisplayedString ] = React.useState(hash); const [ displayedString, setDisplayedString ] = React.useState(hash);
...@@ -93,12 +94,12 @@ const HashStringShortenDynamic = ({ hash, fontWeight = '400', isTooltipDisabled, ...@@ -93,12 +94,12 @@ const HashStringShortenDynamic = ({ hash, fontWeight = '400', isTooltipDisabled,
const content = <chakra.span ref={ elementRef } as={ as }>{ displayedString }</chakra.span>; const content = <chakra.span ref={ elementRef } as={ as }>{ displayedString }</chakra.span>;
const isTruncated = hash.length !== displayedString.length; const isTruncated = hash.length !== displayedString.length;
if (isTruncated) { if (isTruncated && !noTooltip) {
return ( return (
<Tooltip <Tooltip
content={ hash } content={ hash }
disabled={ isTooltipDisabled }
contentProps={{ maxW: { base: 'calc(100vw - 8px)', lg: '400px' } }} contentProps={{ maxW: { base: 'calc(100vw - 8px)', lg: '400px' } }}
interactive={ tooltipInteractive }
> >
{ content } { content }
</Tooltip> </Tooltip>
......
...@@ -10,11 +10,12 @@ interface Props { ...@@ -10,11 +10,12 @@ interface Props {
isLoading?: boolean; isLoading?: boolean;
value: string; value: string;
tooltipPlacement?: Placement; tooltipPlacement?: Placement;
tooltipInteractive?: boolean;
} }
const TruncatedValue = ({ className, isLoading, value, tooltipPlacement }: Props) => { const TruncatedValue = ({ className, isLoading, value, tooltipPlacement, tooltipInteractive }: Props) => {
return ( return (
<TruncatedTextTooltip label={ value } placement={ tooltipPlacement }> <TruncatedTextTooltip label={ value } placement={ tooltipPlacement } interactive={ tooltipInteractive }>
<Skeleton <Skeleton
className={ className } className={ className }
loading={ isLoading } loading={ isLoading }
......
...@@ -40,7 +40,7 @@ const init = () => { ...@@ -40,7 +40,7 @@ const init = () => {
'--w3m-font-family': `${ BODY_TYPEFACE }, sans-serif`, '--w3m-font-family': `${ BODY_TYPEFACE }, sans-serif`,
'--w3m-accent': colors.blue[600].value, '--w3m-accent': colors.blue[600].value,
'--w3m-border-radius-master': '2px', '--w3m-border-radius-master': '2px',
'--w3m-z-index': zIndex?.popover?.value, '--w3m-z-index': zIndex?.modal2?.value,
}, },
featuredWalletIds: [], featuredWalletIds: [],
allowUnsupportedChain: true, allowUnsupportedChain: true,
......
...@@ -36,7 +36,9 @@ const Link = chakra((props: LinkProps) => { ...@@ -36,7 +36,9 @@ const Link = chakra((props: LinkProps) => {
); );
}); });
type IconProps = Pick<EntityProps, 'address' | 'isSafeAddress'> & EntityBase.IconBaseProps; type IconProps = Pick<EntityProps, 'address' | 'isSafeAddress'> & EntityBase.IconBaseProps & {
tooltipInteractive?: boolean;
};
const Icon = (props: IconProps) => { const Icon = (props: IconProps) => {
if (props.noIcon) { if (props.noIcon) {
...@@ -70,7 +72,7 @@ const Icon = (props: IconProps) => { ...@@ -70,7 +72,7 @@ const Icon = (props: IconProps) => {
const label = (isVerified ? 'verified ' : '') + (isProxy ? 'proxy contract' : 'contract'); const label = (isVerified ? 'verified ' : '') + (isProxy ? 'proxy contract' : 'contract');
return ( return (
<Tooltip content={ label.slice(0, 1).toUpperCase() + label.slice(1) }> <Tooltip content={ label.slice(0, 1).toUpperCase() + label.slice(1) } interactive={ props.tooltipInteractive }>
<span> <span>
<EntityBase.Icon <EntityBase.Icon
{ ...props } { ...props }
...@@ -90,7 +92,7 @@ const Icon = (props: IconProps) => { ...@@ -90,7 +92,7 @@ const Icon = (props: IconProps) => {
})(); })();
return ( return (
<Tooltip content={ label } disabled={ !label }> <Tooltip content={ label } disabled={ !label } interactive={ props.tooltipInteractive }>
<Flex marginRight={ styles.marginRight } position="relative"> <Flex marginRight={ styles.marginRight } position="relative">
<AddressIdenticon <AddressIdenticon
size={ props.variant === 'heading' ? 30 : 20 } size={ props.variant === 'heading' ? 30 : 20 }
...@@ -128,7 +130,12 @@ const Content = chakra((props: ContentProps) => { ...@@ -128,7 +130,12 @@ const Content = chakra((props: ContentProps) => {
); );
return ( return (
<Tooltip content={ label } contentProps={{ maxW: { base: 'calc(100vw - 8px)', lg: '400px' } }}> <Tooltip
content={ label }
contentProps={{ maxW: { base: 'calc(100vw - 8px)', lg: '400px' } }}
triggerProps={{ minW: 0 }}
interactive={ props.tooltipInteractive }
>
<Skeleton loading={ props.isLoading } overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap" { ...styles }> <Skeleton loading={ props.isLoading } overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap" { ...styles }>
{ nameText } { nameText }
</Skeleton> </Skeleton>
...@@ -174,7 +181,10 @@ const AddressEntity = (props: EntityProps) => { ...@@ -174,7 +181,10 @@ const AddressEntity = (props: EntityProps) => {
const settingsContext = useSettingsContext(); const settingsContext = useSettingsContext();
const altHash = !props.noAltHash && settingsContext?.addressFormat === 'bech32' ? toBech32Address(props.address.hash) : undefined; const altHash = !props.noAltHash && settingsContext?.addressFormat === 'bech32' ? toBech32Address(props.address.hash) : undefined;
const content = <Content { ...partsProps.content } altHash={ altHash }/>; // inside highlight context all tooltips should be interactive
// because non-interactive ones will not pass 'onMouseLeave' event to the parent component
// see issue - https://github.com/chakra-ui/chakra-ui/issues/9939#issuecomment-2810567024
const content = <Content { ...partsProps.content } altHash={ altHash } tooltipInteractive={ Boolean(highlightContext) }/>;
return ( return (
<Container <Container
...@@ -187,9 +197,9 @@ const AddressEntity = (props: EntityProps) => { ...@@ -187,9 +197,9 @@ const AddressEntity = (props: EntityProps) => {
position="relative" position="relative"
zIndex={ 0 } zIndex={ 0 }
> >
<Icon { ...partsProps.icon }/> <Icon { ...partsProps.icon } tooltipInteractive={ Boolean(highlightContext) }/>
{ props.noLink ? content : <Link { ...partsProps.link }>{ content }</Link> } { props.noLink ? content : <Link { ...partsProps.link }>{ content }</Link> }
<Copy { ...partsProps.copy } altHash={ altHash }/> <Copy { ...partsProps.copy } altHash={ altHash } tooltipInteractive={ Boolean(highlightContext) }/>
</Container> </Container>
); );
}; };
......
...@@ -25,7 +25,14 @@ const AddressEntityContentProxy = (props: ContentProps) => { ...@@ -25,7 +25,14 @@ const AddressEntityContentProxy = (props: ContentProps) => {
Proxy contract Proxy contract
{ props.address.name ? ` (${ props.address.name })` : '' } { props.address.name ? ` (${ props.address.name })` : '' }
</Box> </Box>
<AddressEntity address={{ hash: props.address.hash, filecoin: props.address.filecoin }} noLink noIcon noHighlight justifyContent="center"/> <AddressEntity
address={{ hash: props.address.hash, filecoin: props.address.filecoin }}
noLink
noIcon
noHighlight
noTooltip
justifyContent="center"
/>
<Box fontWeight={ 600 } mt={ 2 }> <Box fontWeight={ 600 } mt={ 2 }>
Implementation{ implementations.length > 1 ? 's' : '' } Implementation{ implementations.length > 1 ? 's' : '' }
{ implementationName ? ` (${ implementationName })` : '' } { implementationName ? ` (${ implementationName })` : '' }
...@@ -38,10 +45,10 @@ const AddressEntityContentProxy = (props: ContentProps) => { ...@@ -38,10 +45,10 @@ const AddressEntityContentProxy = (props: ContentProps) => {
noLink noLink
noIcon noIcon
noHighlight noHighlight
noTooltip
minW={ `calc((100% - ${ colNum - 1 } * 12px) / ${ colNum })` } minW={ `calc((100% - ${ colNum - 1 } * 12px) / ${ colNum })` }
flex={ 1 } flex={ 1 }
justifyContent={ colNum === 1 ? 'center' : undefined } justifyContent={ colNum === 1 ? 'center' : undefined }
isTooltipDisabled
/> />
)) } )) }
</Flex> </Flex>
...@@ -49,13 +56,13 @@ const AddressEntityContentProxy = (props: ContentProps) => { ...@@ -49,13 +56,13 @@ const AddressEntityContentProxy = (props: ContentProps) => {
); );
return ( return (
<Tooltip content={ content } interactive contentProps={{ maxW: { base: 'calc(100vw - 8px)', lg: '410px' } }}> <Tooltip content={ content } interactive contentProps={{ maxW: { base: 'calc(100vw - 8px)', lg: '410px' } }} triggerProps={{ minW: 0 }}>
<Box display="inline-flex" w="100%"> <Box display="inline-flex" w="100%">
<EntityBase.Content <EntityBase.Content
{ ...props } { ...props }
truncation={ nameTag || implementationName || props.address.name ? 'tail' : props.truncation } truncation={ nameTag || implementationName || props.address.name ? 'tail' : props.truncation }
text={ nameTag || implementationName || props.address.name || props.altHash || props.address.hash } text={ nameTag || implementationName || props.address.name || props.altHash || props.address.hash }
isTooltipDisabled noTooltip
/> />
</Box> </Box>
</Tooltip> </Tooltip>
......
...@@ -23,7 +23,7 @@ export interface EntityBaseProps { ...@@ -23,7 +23,7 @@ export interface EntityBaseProps {
icon?: EntityIconProps; icon?: EntityIconProps;
isExternal?: boolean; isExternal?: boolean;
isLoading?: boolean; isLoading?: boolean;
isTooltipDisabled?: boolean; noTooltip?: boolean;
noCopy?: boolean; noCopy?: boolean;
noIcon?: boolean; noIcon?: boolean;
noLink?: boolean; noLink?: boolean;
...@@ -112,12 +112,23 @@ const Icon = ({ isLoading, noIcon, variant, name, color, borderRadius, marginRig ...@@ -112,12 +112,23 @@ const Icon = ({ isLoading, noIcon, variant, name, color, borderRadius, marginRig
); );
}; };
export interface ContentBaseProps extends Pick<EntityBaseProps, 'className' | 'isLoading' | 'truncation' | 'tailLength' | 'isTooltipDisabled' | 'variant'> { export interface ContentBaseProps extends Pick<EntityBaseProps, 'className' | 'isLoading' | 'truncation' | 'tailLength' | 'noTooltip' | 'variant'> {
asProp?: React.ElementType; asProp?: React.ElementType;
text: string; text: string;
tooltipInteractive?: boolean;
} }
const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dynamic', tailLength, isTooltipDisabled, variant }: ContentBaseProps) => { const Content = chakra(({
className,
isLoading,
asProp,
text,
truncation = 'dynamic',
tailLength,
variant,
noTooltip,
tooltipInteractive,
}: ContentBaseProps) => {
const styles = getContentProps(variant); const styles = getContentProps(variant);
if (truncation === 'tail') { if (truncation === 'tail') {
...@@ -126,6 +137,7 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna ...@@ -126,6 +137,7 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna
className={ className } className={ className }
isLoading={ isLoading } isLoading={ isLoading }
value={ text } value={ text }
tooltipInteractive={ tooltipInteractive }
{ ...styles } { ...styles }
/> />
); );
...@@ -139,7 +151,8 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna ...@@ -139,7 +151,8 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna
hash={ text } hash={ text }
as={ asProp } as={ asProp }
type="long" type="long"
isTooltipDisabled={ isTooltipDisabled } noTooltip={ noTooltip }
tooltipInteractive={ tooltipInteractive }
/> />
); );
case 'constant': case 'constant':
...@@ -147,7 +160,8 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna ...@@ -147,7 +160,8 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna
<HashStringShorten <HashStringShorten
hash={ text } hash={ text }
as={ asProp } as={ asProp }
isTooltipDisabled={ isTooltipDisabled } noTooltip={ noTooltip }
tooltipInteractive={ tooltipInteractive }
/> />
); );
case 'dynamic': case 'dynamic':
...@@ -156,7 +170,8 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna ...@@ -156,7 +170,8 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna
hash={ text } hash={ text }
as={ asProp } as={ asProp }
tailLength={ tailLength } tailLength={ tailLength }
isTooltipDisabled={ isTooltipDisabled } noTooltip={ noTooltip }
tooltipInteractive={ tooltipInteractive }
/> />
); );
case 'none': case 'none':
...@@ -178,7 +193,10 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna ...@@ -178,7 +193,10 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna
); );
}); });
export type CopyBaseProps = Pick<CopyToClipboardProps, 'isLoading' | 'text'> & Pick<EntityBaseProps, 'noCopy'>; export type CopyBaseProps =
Pick<CopyToClipboardProps, 'isLoading' | 'text' | 'tooltipInteractive'> &
Pick<EntityBaseProps, 'noCopy' | 'noTooltip'>
;
const Copy = ({ noCopy, ...props }: CopyBaseProps) => { const Copy = ({ noCopy, ...props }: CopyBaseProps) => {
if (noCopy) { if (noCopy) {
......
...@@ -22,6 +22,12 @@ const FilterButton = ({ isLoading, appliedFiltersNum, ...rest }: Props, ref: Rea ...@@ -22,6 +22,12 @@ const FilterButton = ({ isLoading, appliedFiltersNum, ...rest }: Props, ref: Rea
size={ 5 } size={ 5 }
bg={{ _light: 'blue.700', _dark: 'gray.50' }} bg={{ _light: 'blue.700', _dark: 'gray.50' }}
color={{ _light: 'white', _dark: 'black' }} color={{ _light: 'white', _dark: 'black' }}
_groupHover={{
bg: 'link.primary.hover',
}}
_groupExpanded={{
bg: 'link.primary.hover',
}}
> >
{ appliedFiltersNum } { appliedFiltersNum }
</Circle> </Circle>
......
...@@ -14,7 +14,7 @@ interface Props { ...@@ -14,7 +14,7 @@ interface Props {
const PopoverFilter = ({ appliedFiltersNum, children, contentProps, isLoading }: Props) => { const PopoverFilter = ({ appliedFiltersNum, children, contentProps, isLoading }: Props) => {
return ( return (
<PopoverRoot> <PopoverRoot>
<PopoverTrigger> <PopoverTrigger className="group">
<FilterButton <FilterButton
appliedFiltersNum={ appliedFiltersNum } appliedFiltersNum={ appliedFiltersNum }
isLoading={ isLoading } isLoading={ isLoading }
......
...@@ -21,7 +21,7 @@ const TableColumnFilterWrapper = ({ columnName, className, children, isLoading, ...@@ -21,7 +21,7 @@ const TableColumnFilterWrapper = ({ columnName, className, children, isLoading,
<Button <Button
display="inline-flex" display="inline-flex"
aria-label={ `filter by ${ columnName }` } aria-label={ `filter by ${ columnName }` }
variant="dropdown" variant="icon_secondary"
borderWidth="0" borderWidth="0"
h="20px" h="20px"
minW="auto" minW="auto"
......
...@@ -224,7 +224,7 @@ const TxInterpretation = ({ summary, isLoading, addressDataMap, className, isNov ...@@ -224,7 +224,7 @@ const TxInterpretation = ({ summary, isLoading, addressDataMap, className, isNov
<Tooltip content="Human readable transaction provided by Noves.fi"> <Tooltip content="Human readable transaction provided by Noves.fi">
<Badge ml={ 2 } verticalAlign="unset" transform="translateY(-2px)"> <Badge ml={ 2 } verticalAlign="unset" transform="translateY(-2px)">
by by
<Image src={ novesLogoUrl } alt="Noves logo" h="12px" ml={ 1.5 }/> <Image src={ novesLogoUrl } alt="Noves logo" h="12px" ml={ 1.5 } display="inline"/>
</Badge> </Badge>
</Tooltip> </Tooltip>
) } ) }
......
...@@ -3,7 +3,6 @@ import React from 'react'; ...@@ -3,7 +3,6 @@ import React from 'react';
import { TabsContent, TabsList, TabsRoot, TabsTrigger } from 'toolkit/chakra/tabs'; import { TabsContent, TabsList, TabsRoot, TabsTrigger } from 'toolkit/chakra/tabs';
import AdaptiveTabs from 'toolkit/components/AdaptiveTabs/AdaptiveTabs'; import AdaptiveTabs from 'toolkit/components/AdaptiveTabs/AdaptiveTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHeader } from './parts'; import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHeader } from './parts';
...@@ -74,13 +73,6 @@ const TabsShowcase = () => { ...@@ -74,13 +73,6 @@ const TabsShowcase = () => {
/> />
</Sample> </Sample>
</SamplesStack> </SamplesStack>
<SectionSubHeader>Tabs skeleton</SectionSubHeader>
<SamplesStack>
<Sample>
<RoutedTabsSkeleton tabs={ tabs }/>
</Sample>
</SamplesStack>
</Section> </Section>
</Container> </Container>
); );
......
...@@ -15,7 +15,7 @@ const TooltipShowcase = () => { ...@@ -15,7 +15,7 @@ const TooltipShowcase = () => {
<Tooltip content="Tooltip content"> <Tooltip content="Tooltip content">
<span>Default</span> <span>Default</span>
</Tooltip> </Tooltip>
<Tooltip content="Tooltip content"> <Tooltip content="Tooltip content" interactive>
<Utilization value={ 0.5 }/> <Utilization value={ 0.5 }/>
</Tooltip> </Tooltip>
</Sample> </Sample>
......
...@@ -35,6 +35,7 @@ test('base view', async({ render, page }) => { ...@@ -35,6 +35,7 @@ test('base view', async({ render, page }) => {
await expect(page).toHaveScreenshot(); await expect(page).toHaveScreenshot();
await page.getByRole('button', { name: 'Network menu' }).click(); await page.getByRole('button', { name: 'Network menu' }).click();
await page.mouse.move(0, 0);
await expect(page).toHaveScreenshot(); await expect(page).toHaveScreenshot();
}); });
...@@ -48,6 +49,7 @@ test.describe('dark mode', () => { ...@@ -48,6 +49,7 @@ test.describe('dark mode', () => {
await expect(page).toHaveScreenshot(); await expect(page).toHaveScreenshot();
await page.getByRole('button', { name: 'Network menu' }).click(); await page.getByRole('button', { name: 'Network menu' }).click();
await page.mouse.move(0, 0);
await expect(page).toHaveScreenshot(); await expect(page).toHaveScreenshot();
}); });
}); });
......
...@@ -58,6 +58,7 @@ const NetworkMenuContentMobile = ({ items, tabs }: Props) => { ...@@ -58,6 +58,7 @@ const NetworkMenuContentMobile = ({ items, tabs }: Props) => {
collection={ selectCollection } collection={ selectCollection }
placeholder="Select network type" placeholder="Select network type"
mb={ 3 } mb={ 3 }
contentProps={{ zIndex: 'modal' }}
/> />
) } ) }
<VStack as="ul" gap={ 2 } alignItems="stretch"> <VStack as="ul" gap={ 2 } alignItems="stretch">
......
...@@ -159,6 +159,7 @@ const SearchBar = ({ isHomepage }: Props) => { ...@@ -159,6 +159,7 @@ const SearchBar = ({ isHomepage }: Props) => {
w={ `${ menuWidth.current }px` } w={ `${ menuWidth.current }px` }
ref={ menuRef } ref={ menuRef }
overflow="hidden" overflow="hidden"
zIndex="modal"
> >
<PopoverBody <PopoverBody
p={ 0 } p={ 0 }
......
...@@ -139,7 +139,7 @@ const SearchBarInput = ( ...@@ -139,7 +139,7 @@ const SearchBarInput = (
position={{ base: isHomepage ? 'static' : 'absolute', lg: 'relative' }} position={{ base: isHomepage ? 'static' : 'absolute', lg: 'relative' }}
top={{ base: isHomepage ? 0 : 55, lg: 0 }} top={{ base: isHomepage ? 0 : 55, lg: 0 }}
left="0" left="0"
zIndex={{ base: isHomepage ? 'auto' : '0', lg: isSuggestOpen ? 'popover' : 'auto' }} zIndex={{ base: isHomepage ? 'auto' : '0', lg: isSuggestOpen ? 'modal' : 'auto' }}
paddingX={{ base: isHomepage ? 0 : 3, lg: 0 }} paddingX={{ base: isHomepage ? 0 : 3, lg: 0 }}
paddingTop={{ base: isHomepage ? 0 : 1, lg: 0 }} paddingTop={{ base: isHomepage ? 0 : 1, lg: 0 }}
paddingBottom={{ base: isHomepage ? 0 : 2, lg: 0 }} paddingBottom={{ base: isHomepage ? 0 : 2, lg: 0 }}
......
...@@ -80,7 +80,7 @@ const SearchBarSuggest = ({ onClick, onClear }: Props) => { ...@@ -80,7 +80,7 @@ const SearchBarSuggest = ({ onClick, onClear }: Props) => {
> >
{ kw.startsWith('0x') ? ( { kw.startsWith('0x') ? (
<Box overflow="hidden" whiteSpace="nowrap"> <Box overflow="hidden" whiteSpace="nowrap">
<HashStringShortenDynamic hash={ kw } isTooltipDisabled/> <HashStringShortenDynamic hash={ kw } noTooltip/>
</Box> </Box>
) : ) :
<Text overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis">{ kw }</Text> <Text overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis">{ kw }</Text>
......
...@@ -55,7 +55,7 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm, addressFormat }: ...@@ -55,7 +55,7 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm, addressFormat }:
const tagEl = data.type === 'metadata_tag' ? ( const tagEl = data.type === 'metadata_tag' ? (
<SearchResultEntityTag metadata={ data.metadata } searchTerm={ searchTerm } ml={{ base: 0, lg: 'auto' }}/> <SearchResultEntityTag metadata={ data.metadata } searchTerm={ searchTerm } ml={{ base: 0, lg: 'auto' }}/>
) : null; ) : null;
const addressEl = <HashStringShortenDynamic hash={ hash } isTooltipDisabled/>; const addressEl = <HashStringShortenDynamic hash={ hash } noTooltip/>;
if (isMobile) { if (isMobile) {
return ( return (
......
...@@ -12,7 +12,7 @@ const SearchBarSuggestBlob = ({ data }: ItemsProps<SearchResultBlob>) => { ...@@ -12,7 +12,7 @@ const SearchBarSuggestBlob = ({ data }: ItemsProps<SearchResultBlob>) => {
<Flex alignItems="center" minW={ 0 }> <Flex alignItems="center" minW={ 0 }>
<BlobEntity.Icon/> <BlobEntity.Icon/>
<chakra.mark overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }> <chakra.mark overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }>
<HashStringShortenDynamic hash={ data.blob_hash } isTooltipDisabled/> <HashStringShortenDynamic hash={ data.blob_hash } noTooltip/>
</chakra.mark> </chakra.mark>
</Flex> </Flex>
); );
......
...@@ -33,7 +33,7 @@ const SearchBarSuggestBlock = ({ data, isMobile, searchTerm }: ItemsProps<Search ...@@ -33,7 +33,7 @@ const SearchBarSuggestBlock = ({ data, isMobile, searchTerm }: ItemsProps<Search
as={ shouldHighlightHash ? 'mark' : 'span' } as={ shouldHighlightHash ? 'mark' : 'span' }
display="block" display="block"
> >
<HashStringShortenDynamic hash={ data.block_hash } isTooltipDisabled/> <HashStringShortenDynamic hash={ data.block_hash } noTooltip/>
</Text> </Text>
) : null; ) : null;
const date = !isFutureBlock ? dayjs(data.timestamp).format('llll') : undefined; const date = !isFutureBlock ? dayjs(data.timestamp).format('llll') : undefined;
......
...@@ -32,7 +32,7 @@ const SearchBarSuggestDomain = ({ data, isMobile, searchTerm, addressFormat }: I ...@@ -32,7 +32,7 @@ const SearchBarSuggestDomain = ({ data, isMobile, searchTerm, addressFormat }: I
whiteSpace="nowrap" whiteSpace="nowrap"
color="text.secondary" color="text.secondary"
> >
<HashStringShortenDynamic hash={ hash } isTooltipDisabled/> <HashStringShortenDynamic hash={ hash } noTooltip/>
</Text> </Text>
); );
......
...@@ -30,7 +30,7 @@ const SearchBarSuggestLabel = ({ data, isMobile, searchTerm, addressFormat }: It ...@@ -30,7 +30,7 @@ const SearchBarSuggestLabel = ({ data, isMobile, searchTerm, addressFormat }: It
whiteSpace="nowrap" whiteSpace="nowrap"
color="text.secondary" color="text.secondary"
> >
<HashStringShortenDynamic hash={ hash } isTooltipDisabled/> <HashStringShortenDynamic hash={ hash } noTooltip/>
</Text> </Text>
); );
......
...@@ -30,7 +30,7 @@ const SearchBarSuggestToken = ({ data, isMobile, searchTerm, addressFormat }: It ...@@ -30,7 +30,7 @@ const SearchBarSuggestToken = ({ data, isMobile, searchTerm, addressFormat }: It
const address = ( const address = (
<Text color="text.secondary" whiteSpace="nowrap" overflow="hidden"> <Text color="text.secondary" whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ hash } isTooltipDisabled/> <HashStringShortenDynamic hash={ hash } noTooltip/>
</Text> </Text>
); );
......
...@@ -12,7 +12,7 @@ const SearchBarSuggestTx = ({ data, isMobile }: ItemsProps<SearchResultTx>) => { ...@@ -12,7 +12,7 @@ const SearchBarSuggestTx = ({ data, isMobile }: ItemsProps<SearchResultTx>) => {
const icon = <TxEntity.Icon/>; const icon = <TxEntity.Icon/>;
const hash = ( const hash = (
<chakra.mark overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }> <chakra.mark overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }>
<HashStringShortenDynamic hash={ data.transaction_hash } isTooltipDisabled/> <HashStringShortenDynamic hash={ data.transaction_hash } noTooltip/>
</chakra.mark> </chakra.mark>
); );
const date = dayjs(data.timestamp).format('llll'); const date = dayjs(data.timestamp).format('llll');
......
...@@ -12,7 +12,7 @@ const SearchBarSuggestUserOp = ({ data, isMobile }: ItemsProps<SearchResultUserO ...@@ -12,7 +12,7 @@ const SearchBarSuggestUserOp = ({ data, isMobile }: ItemsProps<SearchResultUserO
const icon = <UserOpEntity.Icon/>; const icon = <UserOpEntity.Icon/>;
const hash = ( const hash = (
<chakra.mark overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }> <chakra.mark overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }>
<HashStringShortenDynamic hash={ data.user_operation_hash } isTooltipDisabled/> <HashStringShortenDynamic hash={ data.user_operation_hash } noTooltip/>
</chakra.mark> </chakra.mark>
); );
const date = dayjs(data.timestamp).format('llll'); const date = dayjs(data.timestamp).format('llll');
......
...@@ -42,8 +42,11 @@ const UserProfileButton = ({ profileQuery, size, variant, onClick, isPending, .. ...@@ -42,8 +42,11 @@ const UserProfileButton = ({ profileQuery, size, variant, onClick, isPending, ..
e.preventDefault(); e.preventDefault();
}, []); }, []);
const isButtonLoading = isPending || !isFetched || web3AccountWithDomain.isLoading;
const dataExists = !isButtonLoading && (Boolean(data) || Boolean(web3AccountWithDomain.address));
const content = (() => { const content = (() => {
if (web3AccountWithDomain.address) { if (web3AccountWithDomain.address && !isButtonLoading) {
return ( return (
<HStack gap={ 2 }> <HStack gap={ 2 }>
<UserIdenticon address={ web3AccountWithDomain.address } isAutoConnectDisabled={ isAutoConnectDisabled }/> <UserIdenticon address={ web3AccountWithDomain.address } isAutoConnectDisabled={ isAutoConnectDisabled }/>
...@@ -54,7 +57,7 @@ const UserProfileButton = ({ profileQuery, size, variant, onClick, isPending, .. ...@@ -54,7 +57,7 @@ const UserProfileButton = ({ profileQuery, size, variant, onClick, isPending, ..
); );
} }
if (!data) { if (!data || isButtonLoading) {
return 'Log in'; return 'Log in';
} }
...@@ -66,9 +69,6 @@ const UserProfileButton = ({ profileQuery, size, variant, onClick, isPending, .. ...@@ -66,9 +69,6 @@ const UserProfileButton = ({ profileQuery, size, variant, onClick, isPending, ..
); );
})(); })();
const isButtonLoading = isPending || !isFetched;
const dataExists = !isButtonLoading && (Boolean(data) || Boolean(web3AccountWithDomain.address));
return ( return (
<Tooltip <Tooltip
content={ <span>Sign in to My Account to add tags,<br/>create watchlists, access API keys and more</span> } content={ <span>Sign in to My Account to add tags,<br/>create watchlists, access API keys and more</span> }
......
...@@ -51,11 +51,11 @@ const UserProfileContentWallet = ({ onClose, className }: Props) => { ...@@ -51,11 +51,11 @@ const UserProfileContentWallet = ({ onClose, className }: Props) => {
<AddressEntity <AddressEntity
address={{ hash: web3AccountWithDomain.address, ens_domain_name: web3AccountWithDomain.domain }} address={{ hash: web3AccountWithDomain.address, ens_domain_name: web3AccountWithDomain.domain }}
isLoading={ web3AccountWithDomain.isLoading } isLoading={ web3AccountWithDomain.isLoading }
isTooltipDisabled
truncation="dynamic" truncation="dynamic"
fontSize="sm" fontSize="sm"
fontWeight={ 500 } fontWeight={ 500 }
noAltHash noAltHash
noTooltip
onClick={ handleAddressClick } onClick={ handleAddressClick }
/> />
{ web3Wallet.isReconnecting ? <Spinner size="sm" m="2px" flexShrink={ 0 }/> : ( { web3Wallet.isReconnecting ? <Spinner size="sm" m="2px" flexShrink={ 0 }/> : (
......
...@@ -35,7 +35,7 @@ const UserWalletMenuContent = ({ isAutoConnectDisabled, address, domain, isRecon ...@@ -35,7 +35,7 @@ const UserWalletMenuContent = ({ isAutoConnectDisabled, address, domain, isRecon
<Flex alignItems="center" columnGap={ 2 } justifyContent="space-between"> <Flex alignItems="center" columnGap={ 2 } justifyContent="space-between">
<AddressEntity <AddressEntity
address={{ hash: address, ens_domain_name: domain }} address={{ hash: address, ens_domain_name: domain }}
isTooltipDisabled noTooltip
truncation="dynamic" truncation="dynamic"
fontSize="sm" fontSize="sm"
fontWeight={ 700 } fontWeight={ 700 }
......
...@@ -14,6 +14,7 @@ import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; ...@@ -14,6 +14,7 @@ import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
import type { EntityProps as AddressEntityProps } from 'ui/shared/entities/address/AddressEntity'; import type { EntityProps as AddressEntityProps } from 'ui/shared/entities/address/AddressEntity';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import TruncatedValue from 'ui/shared/TruncatedValue';
type Props = { type Props = {
token: TokenInfo; token: TokenInfo;
...@@ -104,14 +105,18 @@ const TokensTableItem = ({ ...@@ -104,14 +105,18 @@ const TokensTableItem = ({
</Flex> </Flex>
</TableCell> </TableCell>
<TableCell isNumeric> <TableCell isNumeric>
<Skeleton loading={ isLoading } textStyle="sm" fontWeight={ 500 } display="inline-block"> <TruncatedValue
{ exchangeRate && `$${ Number(exchangeRate).toLocaleString(undefined, { minimumSignificantDigits: 4 }) }` } value={ exchangeRate ? `$${ Number(exchangeRate).toLocaleString(undefined, { minimumSignificantDigits: 4 }) }` : '' }
</Skeleton> isLoading={ isLoading }
maxW="100%"
/>
</TableCell> </TableCell>
<TableCell isNumeric maxWidth="300px" width="300px"> <TableCell isNumeric maxWidth="300px" width="300px">
<Skeleton loading={ isLoading } textStyle="sm" fontWeight={ 500 } display="inline-block"> <TruncatedValue
{ marketCap && `$${ BigNumber(marketCap).toFormat() }` } value={ marketCap ? `$${ BigNumber(marketCap).toFormat() }` : '' }
</Skeleton> isLoading={ isLoading }
maxW="100%"
/>
</TableCell> </TableCell>
<TableCell isNumeric> <TableCell isNumeric>
<Skeleton <Skeleton
......
...@@ -115,6 +115,8 @@ const ScrollL2TxnBatchDetails = ({ query }: Props) => { ...@@ -115,6 +115,8 @@ const ScrollL2TxnBatchDetails = ({ query }: Props) => {
} }
</DetailedInfo.ItemValue> </DetailedInfo.ItemValue>
{ typeof data.transactions_count === 'number' ? (
<>
<DetailedInfo.ItemLabel <DetailedInfo.ItemLabel
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
hint="Number of transactions in this batch" hint="Number of transactions in this batch"
...@@ -126,6 +128,8 @@ const ScrollL2TxnBatchDetails = ({ query }: Props) => { ...@@ -126,6 +128,8 @@ const ScrollL2TxnBatchDetails = ({ query }: Props) => {
{ data.transactions_count.toLocaleString() } transaction{ data.transactions_count === 1 ? '' : 's' } { data.transactions_count.toLocaleString() } transaction{ data.transactions_count === 1 ? '' : 's' }
</Link> </Link>
</DetailedInfo.ItemValue> </DetailedInfo.ItemValue>
</>
) : null }
<DetailedInfo.ItemLabel <DetailedInfo.ItemLabel
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
......
...@@ -104,6 +104,8 @@ const ScrollL2TxnBatchesListItem = ({ item, isLoading }: Props) => { ...@@ -104,6 +104,8 @@ const ScrollL2TxnBatchesListItem = ({ item, isLoading }: Props) => {
</Link> </Link>
</ListItemMobileGrid.Value> </ListItemMobileGrid.Value>
{ typeof item.transactions_count === 'number' ? (
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Txn count</ListItemMobileGrid.Label> <ListItemMobileGrid.Label isLoading={ isLoading }>Txn count</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value> <ListItemMobileGrid.Value>
<Link <Link
...@@ -115,6 +117,8 @@ const ScrollL2TxnBatchesListItem = ({ item, isLoading }: Props) => { ...@@ -115,6 +117,8 @@ const ScrollL2TxnBatchesListItem = ({ item, isLoading }: Props) => {
{ item.transactions_count.toLocaleString() } { item.transactions_count.toLocaleString() }
</Link> </Link>
</ListItemMobileGrid.Value> </ListItemMobileGrid.Value>
</>
) : null }
</ListItemMobileGrid.Container> </ListItemMobileGrid.Container>
); );
......
...@@ -87,12 +87,14 @@ const TxnBatchesTableItem = ({ item, isLoading }: Props) => { ...@@ -87,12 +87,14 @@ const TxnBatchesTableItem = ({ item, isLoading }: Props) => {
</Link> </Link>
</TableCell> </TableCell>
<TableCell verticalAlign="middle" isNumeric> <TableCell verticalAlign="middle" isNumeric>
{ typeof item.transactions_count === 'number' ? (
<Link <Link
href={ route({ pathname: '/batches/[number]', query: { number: item.number.toString(), tab: 'txs' } }) } href={ route({ pathname: '/batches/[number]', query: { number: item.number.toString(), tab: 'txs' } }) }
loading={ isLoading } loading={ isLoading }
> >
{ item.transactions_count.toLocaleString() } { item.transactions_count.toLocaleString() }
</Link> </Link>
) : 'N/A' }
</TableCell> </TableCell>
</TableRow> </TableRow>
); );
......
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TxsSocketType } from './socket/types';
import type { AddressFromToFilter } from 'types/api/address'; import type { AddressFromToFilter } from 'types/api/address';
import type { Transaction, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction'; import type { Transaction, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction';
...@@ -26,9 +27,7 @@ type Props = { ...@@ -26,9 +27,7 @@ type Props = {
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
query: QueryWithPagesResult<'txs_validated' | 'txs_pending'> | QueryWithPagesResult<'txs_watchlist'> | QueryWithPagesResult<'block_txs'> | QueryWithPagesResult<'zkevm_l2_txn_batch_txs'>; query: QueryWithPagesResult<'txs_validated' | 'txs_pending'> | QueryWithPagesResult<'txs_watchlist'> | QueryWithPagesResult<'block_txs'> | QueryWithPagesResult<'zkevm_l2_txn_batch_txs'>;
showBlockInfo?: boolean; showBlockInfo?: boolean;
showSocketInfo?: boolean; socketType?: TxsSocketType;
socketInfoAlert?: string;
socketInfoNum?: number;
currentAddress?: string; currentAddress?: string;
filter?: React.ReactNode; filter?: React.ReactNode;
filterValue?: AddressFromToFilter; filterValue?: AddressFromToFilter;
...@@ -46,9 +45,7 @@ const TxsContent = ({ ...@@ -46,9 +45,7 @@ const TxsContent = ({
filter, filter,
filterValue, filterValue,
showBlockInfo = true, showBlockInfo = true,
showSocketInfo = true, socketType,
socketInfoAlert,
socketInfoNum,
currentAddress, currentAddress,
enableTimeIncrement, enableTimeIncrement,
top, top,
...@@ -72,9 +69,7 @@ const TxsContent = ({ ...@@ -72,9 +69,7 @@ const TxsContent = ({
<Box hideFrom="lg"> <Box hideFrom="lg">
<TxsList <TxsList
showBlockInfo={ showBlockInfo } showBlockInfo={ showBlockInfo }
showSocketInfo={ showSocketInfo } socketType={ socketType }
socketInfoAlert={ socketInfoAlert }
socketInfoNum={ socketInfoNum }
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
enableTimeIncrement={ enableTimeIncrement } enableTimeIncrement={ enableTimeIncrement }
currentAddress={ currentAddress } currentAddress={ currentAddress }
...@@ -87,9 +82,7 @@ const TxsContent = ({ ...@@ -87,9 +82,7 @@ const TxsContent = ({
sort={ sort } sort={ sort }
onSortToggle={ onSortToggle } onSortToggle={ onSortToggle }
showBlockInfo={ showBlockInfo } showBlockInfo={ showBlockInfo }
showSocketInfo={ showSocketInfo } socketType={ socketType }
socketInfoAlert={ socketInfoAlert }
socketInfoNum={ socketInfoNum }
top={ top || (query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0) } top={ top || (query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0) }
currentAddress={ currentAddress } currentAddress={ currentAddress }
enableTimeIncrement={ enableTimeIncrement } enableTimeIncrement={ enableTimeIncrement }
......
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TxsSocketType } from './socket/types';
import type { Transaction } from 'types/api/transaction'; import type { Transaction } from 'types/api/transaction';
import useInitialList from 'lib/hooks/useInitialList'; import useInitialList from 'lib/hooks/useInitialList';
import useLazyRenderedList from 'lib/hooks/useLazyRenderedList'; import useLazyRenderedList from 'lib/hooks/useLazyRenderedList';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import TxsSocketNotice from './socket/TxsSocketNotice';
import TxsListItem from './TxsListItem'; import TxsListItem from './TxsListItem';
interface Props { interface Props {
showBlockInfo: boolean; showBlockInfo: boolean;
showSocketInfo?: boolean; socketType?: TxsSocketType;
socketInfoAlert?: string;
socketInfoNum?: number;
enableTimeIncrement?: boolean; enableTimeIncrement?: boolean;
currentAddress?: string; currentAddress?: string;
isLoading: boolean; isLoading: boolean;
...@@ -30,14 +29,7 @@ const TxsList = (props: Props) => { ...@@ -30,14 +29,7 @@ const TxsList = (props: Props) => {
return ( return (
<Box> <Box>
{ props.showSocketInfo && ( { props.socketType && <TxsSocketNotice type={ props.socketType } place="list" isLoading={ props.isLoading }/> }
<SocketNewItemsNotice.Mobile
url={ window.location.href }
num={ props.socketInfoNum }
alert={ props.socketInfoAlert }
isLoading={ props.isLoading }
/>
) }
{ props.items.slice(0, renderedItemsNum).map((tx, index) => ( { props.items.slice(0, renderedItemsNum).map((tx, index) => (
<TxsListItem <TxsListItem
key={ tx.hash + (props.isLoading ? index : '') } key={ tx.hash + (props.isLoading ? index : '') }
......
...@@ -15,7 +15,6 @@ test('base view +@dark-mode', async({ render }) => { ...@@ -15,7 +15,6 @@ test('base view +@dark-mode', async({ render }) => {
onSortToggle={ () => {} } onSortToggle={ () => {} }
top={ 0 } top={ 0 }
showBlockInfo showBlockInfo
showSocketInfo={ false }
/>, />,
); );
...@@ -36,7 +35,6 @@ test.describe('screen xl', () => { ...@@ -36,7 +35,6 @@ test.describe('screen xl', () => {
onSortToggle={ () => {} } onSortToggle={ () => {} }
top={ 0 } top={ 0 }
showBlockInfo showBlockInfo
showSocketInfo={ false }
/>, />,
); );
......
import React from 'react'; import React from 'react';
import type { TxsSocketType } from './socket/types';
import type { Transaction, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction'; import type { Transaction, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction';
import config from 'configs/app'; import config from 'configs/app';
...@@ -8,8 +9,8 @@ import useInitialList from 'lib/hooks/useInitialList'; ...@@ -8,8 +9,8 @@ import useInitialList from 'lib/hooks/useInitialList';
import useLazyRenderedList from 'lib/hooks/useLazyRenderedList'; import useLazyRenderedList from 'lib/hooks/useLazyRenderedList';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import { TableBody, TableColumnHeader, TableColumnHeaderSortable, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import { TableBody, TableColumnHeader, TableColumnHeaderSortable, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import TxsSocketNotice from './socket/TxsSocketNotice';
import TxsTableItem from './TxsTableItem'; import TxsTableItem from './TxsTableItem';
type Props = { type Props = {
...@@ -18,9 +19,7 @@ type Props = { ...@@ -18,9 +19,7 @@ type Props = {
onSortToggle: (field: TransactionsSortingField) => void; onSortToggle: (field: TransactionsSortingField) => void;
top: number; top: number;
showBlockInfo: boolean; showBlockInfo: boolean;
showSocketInfo: boolean; socketType?: TxsSocketType;
socketInfoAlert?: string;
socketInfoNum?: number;
currentAddress?: string; currentAddress?: string;
enableTimeIncrement?: boolean; enableTimeIncrement?: boolean;
isLoading?: boolean; isLoading?: boolean;
...@@ -32,9 +31,7 @@ const TxsTable = ({ ...@@ -32,9 +31,7 @@ const TxsTable = ({
onSortToggle, onSortToggle,
top, top,
showBlockInfo, showBlockInfo,
showSocketInfo, socketType,
socketInfoAlert,
socketInfoNum,
currentAddress, currentAddress,
enableTimeIncrement, enableTimeIncrement,
isLoading, isLoading,
...@@ -96,14 +93,7 @@ const TxsTable = ({ ...@@ -96,14 +93,7 @@ const TxsTable = ({
</TableRow> </TableRow>
</TableHeaderSticky> </TableHeaderSticky>
<TableBody> <TableBody>
{ showSocketInfo && ( { socketType && <TxsSocketNotice type={ socketType } place="table" isLoading={ isLoading }/> }
<SocketNewItemsNotice.Desktop
url={ window.location.href }
alert={ socketInfoAlert }
num={ socketInfoNum }
isLoading={ isLoading }
/>
) }
{ txs.slice(0, renderedItemsNum).map((item, index) => ( { txs.slice(0, renderedItemsNum).map((item, index) => (
<TxsTableItem <TxsTableItem
key={ item.hash + (isLoading ? index : '') } key={ item.hash + (isLoading ? index : '') }
......
...@@ -101,7 +101,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement, ...@@ -101,7 +101,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement,
</TableCell> </TableCell>
{ !config.UI.views.tx.hiddenFields?.value && ( { !config.UI.views.tx.hiddenFields?.value && (
<TableCell isNumeric> <TableCell isNumeric>
<CurrencyValue value={ tx.value } accuracy={ 8 } isLoading={ isLoading }/> <CurrencyValue value={ tx.value } accuracy={ 8 } isLoading={ isLoading } wordBreak="break-word"/>
</TableCell> </TableCell>
) } ) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && ( { !config.UI.views.tx.hiddenFields?.tx_fee && (
...@@ -112,6 +112,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement, ...@@ -112,6 +112,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement,
isLoading={ isLoading } isLoading={ isLoading }
withCurrency={ Boolean(tx.celo || tx.stability_fee) } withCurrency={ Boolean(tx.celo || tx.stability_fee) }
justifyContent="end" justifyContent="end"
wordBreak="break-word"
/> />
</TableCell> </TableCell>
) } ) }
......
...@@ -10,7 +10,7 @@ type Props = { ...@@ -10,7 +10,7 @@ type Props = {
const TxsWatchlist = ({ query }: Props) => { const TxsWatchlist = ({ query }: Props) => {
useRedirectForInvalidAuthToken(); useRedirectForInvalidAuthToken();
return <TxsWithFrontendSorting query={ query } showSocketInfo={ false } top={ 88 }/>; return <TxsWithFrontendSorting query={ query } top={ 88 }/>;
}; };
export default TxsWatchlist; export default TxsWatchlist;
import React from 'react'; import React from 'react';
import type { TxsSocketType } from './socket/types';
import type { AddressFromToFilter } from 'types/api/address'; import type { AddressFromToFilter } from 'types/api/address';
import type { TransactionsSortingValue } from 'types/api/transaction'; import type { TransactionsSortingValue } from 'types/api/transaction';
...@@ -12,9 +13,7 @@ type Props = { ...@@ -12,9 +13,7 @@ type Props = {
query: QueryWithPagesResult<'address_txs'>; query: QueryWithPagesResult<'address_txs'>;
showBlockInfo?: boolean; showBlockInfo?: boolean;
showSocketInfo?: boolean; socketType?: TxsSocketType;
socketInfoAlert?: string;
socketInfoNum?: number;
currentAddress?: string; currentAddress?: string;
filter?: React.ReactNode; filter?: React.ReactNode;
filterValue?: AddressFromToFilter; filterValue?: AddressFromToFilter;
...@@ -29,9 +28,7 @@ const TxsWithAPISorting = ({ ...@@ -29,9 +28,7 @@ const TxsWithAPISorting = ({
filterValue, filterValue,
query, query,
showBlockInfo = true, showBlockInfo = true,
showSocketInfo = true, socketType,
socketInfoAlert,
socketInfoNum,
currentAddress, currentAddress,
enableTimeIncrement, enableTimeIncrement,
top, top,
...@@ -49,9 +46,7 @@ const TxsWithAPISorting = ({ ...@@ -49,9 +46,7 @@ const TxsWithAPISorting = ({
filter={ filter } filter={ filter }
filterValue={ filterValue } filterValue={ filterValue }
showBlockInfo={ showBlockInfo } showBlockInfo={ showBlockInfo }
showSocketInfo={ showSocketInfo } socketType={ socketType }
socketInfoAlert={ socketInfoAlert }
socketInfoNum={ socketInfoNum }
currentAddress={ currentAddress } currentAddress={ currentAddress }
enableTimeIncrement={ enableTimeIncrement } enableTimeIncrement={ enableTimeIncrement }
top={ top } top={ top }
......
import React from 'react'; import React from 'react';
import type { TxsSocketType } from './socket/types';
import type { AddressFromToFilter } from 'types/api/address'; import type { AddressFromToFilter } from 'types/api/address';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
...@@ -11,9 +12,7 @@ type Props = { ...@@ -11,9 +12,7 @@ type Props = {
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
query: QueryWithPagesResult<'txs_validated' | 'txs_pending'> | QueryWithPagesResult<'txs_watchlist'> | QueryWithPagesResult<'block_txs'> | QueryWithPagesResult<'zkevm_l2_txn_batch_txs'>; query: QueryWithPagesResult<'txs_validated' | 'txs_pending'> | QueryWithPagesResult<'txs_watchlist'> | QueryWithPagesResult<'block_txs'> | QueryWithPagesResult<'zkevm_l2_txn_batch_txs'>;
showBlockInfo?: boolean; showBlockInfo?: boolean;
showSocketInfo?: boolean; socketType?: TxsSocketType;
socketInfoAlert?: string;
socketInfoNum?: number;
currentAddress?: string; currentAddress?: string;
filter?: React.ReactNode; filter?: React.ReactNode;
filterValue?: AddressFromToFilter; filterValue?: AddressFromToFilter;
...@@ -26,9 +25,7 @@ const TxsWithFrontendSorting = ({ ...@@ -26,9 +25,7 @@ const TxsWithFrontendSorting = ({
filterValue, filterValue,
query, query,
showBlockInfo = true, showBlockInfo = true,
showSocketInfo = true, socketType,
socketInfoAlert,
socketInfoNum,
currentAddress, currentAddress,
enableTimeIncrement, enableTimeIncrement,
top, top,
...@@ -40,9 +37,7 @@ const TxsWithFrontendSorting = ({ ...@@ -40,9 +37,7 @@ const TxsWithFrontendSorting = ({
filter={ filter } filter={ filter }
filterValue={ filterValue } filterValue={ filterValue }
showBlockInfo={ showBlockInfo } showBlockInfo={ showBlockInfo }
showSocketInfo={ showSocketInfo } socketType={ socketType }
socketInfoAlert={ socketInfoAlert }
socketInfoNum={ socketInfoNum }
currentAddress={ currentAddress } currentAddress={ currentAddress }
enableTimeIncrement={ enableTimeIncrement } enableTimeIncrement={ enableTimeIncrement }
top={ top } top={ top }
......
import React from 'react';
import type { TxsSocketNoticePlace, TxsSocketType } from './types';
import useIsMobile from 'lib/hooks/useIsMobile';
import TxsSocketNoticeTypeAddress from './TxsSocketNoticeTypeAddress';
import TxsSocketNoticeTypeAll from './TxsSocketNoticeTypeAll';
interface Props {
type: TxsSocketType;
place: TxsSocketNoticePlace;
isLoading?: boolean;
}
const TxsSocketNotice = ({ type, place, isLoading }: Props) => {
const isMobile = useIsMobile();
if ((isMobile && place === 'table') || (!isMobile && place === 'list')) {
return null;
}
switch (type) {
case 'txs_home':
case 'txs_validated':
case 'txs_pending': {
return <TxsSocketNoticeTypeAll type={ type } place={ place } isLoading={ isLoading }/>;
}
case 'address_txs': {
return <TxsSocketNoticeTypeAddress place={ place } isLoading={ isLoading }/>;
}
default:
return null;
}
};
export default React.memo(TxsSocketNotice);
import React from 'react';
import type { TxsSocketNoticePlace } from './types';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import useTxsSocketTypeAddress from './useTxsSocketTypeAddress';
interface Props {
place: TxsSocketNoticePlace;
isLoading?: boolean;
}
const TxsSocketNoticeTypeAddress = ({ place, isLoading }: Props) => {
const { num, alertText } = useTxsSocketTypeAddress({ isLoading });
if (num === undefined) {
return null;
}
if (place === 'table') {
return (
<SocketNewItemsNotice.Desktop
url={ window.location.href }
alert={ alertText }
num={ num }
isLoading={ isLoading }
/>
);
}
if (place === 'list') {
return (
<SocketNewItemsNotice.Mobile
url={ window.location.href }
num={ num }
alert={ alertText }
isLoading={ isLoading }
/>
);
}
};
export default React.memo(TxsSocketNoticeTypeAddress);
import React from 'react';
import type { TxsSocketNoticePlace, TxsSocketType } from './types';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import useNewTxsSocketTypeAll from './useTxsSocketTypeAll';
interface Props {
type: TxsSocketType;
place: TxsSocketNoticePlace;
isLoading?: boolean;
}
const TxsSocketNoticeTypeAll = ({ type, place, isLoading }: Props) => {
const { num, alertText } = useNewTxsSocketTypeAll({ type, isLoading });
if (num === undefined) {
return null;
}
if (place === 'table') {
return (
<SocketNewItemsNotice.Desktop
url={ window.location.href }
alert={ alertText }
num={ num }
isLoading={ isLoading }
/>
);
}
if (place === 'list') {
return (
<SocketNewItemsNotice.Mobile
url={ window.location.href }
num={ num }
alert={ alertText }
isLoading={ isLoading }
/>
);
}
};
export default React.memo(TxsSocketNoticeTypeAll);
export type TxsSocketType = 'txs_validated' | 'txs_pending' | 'txs_home' | 'address_txs';
export type TxsSocketNoticePlace = 'list' | 'table';
import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { AddressFromToFilter, AddressTransactionsResponse } from 'types/api/address';
import type { Transaction, TransactionsSortingValue } from 'types/api/transaction';
import config from 'configs/app';
import { getResourceKey } from 'lib/api/useApiQuery';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import { sortTxsFromSocket } from '../sortTxs';
import { SORT_OPTIONS } from '../useTxsSort';
const matchFilter = (filterValue: AddressFromToFilter, transaction: Transaction, address?: string) => {
if (!filterValue) {
return true;
}
if (filterValue === 'from') {
return transaction.from.hash === address;
}
if (filterValue === 'to') {
return transaction.to?.hash === address;
}
};
const OVERLOAD_COUNT = config.app.isPw ? 2 : 75;
interface Params {
isLoading?: boolean;
}
export default function useTxsSocketTypeAddress({ isLoading }: Params) {
const [ alertText, setAlertText ] = React.useState('');
const [ num, setNum ] = React.useState(0);
const router = useRouter();
const queryClient = useQueryClient();
const currentAddress = getQueryParamString(router.query.hash);
const filterValue = getQueryParamString(router.query.filter);
const page = getQueryParamString(router.query.page);
const sort = getSortValueFromQuery<TransactionsSortingValue>(router.query, SORT_OPTIONS) || 'default';
const handleNewSocketMessage: SocketMessage.AddressTxs['handler'] = React.useCallback((payload) => {
setAlertText('');
queryClient.setQueryData(
getResourceKey('address_txs', { pathParams: { hash: currentAddress }, queryParams: filterValue ? { filter: filterValue } : undefined }),
(prevData: AddressTransactionsResponse | undefined) => {
if (!prevData) {
return;
}
const newItems: Array<Transaction> = [];
let newCount = 0;
payload.transactions.forEach(tx => {
const currIndex = prevData.items.findIndex((item) => item.hash === tx.hash);
if (currIndex > -1) {
prevData.items[currIndex] = tx;
} else {
const isMatch = matchFilter(filterValue as AddressFromToFilter, tx, currentAddress);
if (isMatch) {
if (newItems.length + prevData.items.length >= OVERLOAD_COUNT) {
newCount++;
} else {
newItems.push(tx);
}
}
}
});
if (newCount > 0) {
setNum(prev => prev + newCount);
}
return {
...prevData,
items: [
...newItems,
...prevData.items,
].sort(sortTxsFromSocket(sort)),
};
});
}, [ currentAddress, filterValue, queryClient, sort ]);
const handleSocketClose = React.useCallback(() => {
setAlertText('Connection is lost. Please refresh the page to load new transactions.');
}, []);
const handleSocketError = React.useCallback(() => {
setAlertText('An error has occurred while fetching new transactions. Please refresh the page.');
}, []);
const isDisabled = Boolean((page && page !== '1') || isLoading);
const channel = useSocketChannel({
topic: `addresses:${ currentAddress?.toLowerCase() }`,
onSocketClose: handleSocketClose,
onSocketError: handleSocketError,
isDisabled,
});
useSocketMessage({
channel,
event: 'transaction',
handler: handleNewSocketMessage,
});
useSocketMessage({
channel,
event: 'pending_transaction',
handler: handleNewSocketMessage,
});
if (isDisabled) {
return { };
}
return { num, alertText };
}
import type { NextRouter } from 'next/router';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { TxsSocketType } from './types';
import useGradualIncrement from 'lib/hooks/useGradualIncrement'; import useGradualIncrement from 'lib/hooks/useGradualIncrement';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage'; import useSocketMessage from 'lib/socket/useSocketMessage';
function getSocketParams(router: NextRouter) { function getSocketParams(type: TxsSocketType, page: string) {
if ( switch (type) {
router.pathname === '/txs' && case 'txs_home': {
(router.query.tab === 'validated' || router.query.tab === undefined) &&
!router.query.block_number &&
!router.query.page
) {
return { topic: 'transactions:new_transaction' as const, event: 'transaction' as const }; return { topic: 'transactions:new_transaction' as const, event: 'transaction' as const };
} }
case 'txs_validated': {
if (router.pathname === '/') { return !page || page === '1' ? { topic: 'transactions:new_transaction' as const, event: 'transaction' as const } : {};
return { topic: 'transactions:new_transaction' as const, event: 'transaction' as const };
} }
case 'txs_pending': {
if ( return !page || page === '1' ? { topic: 'transactions:new_pending_transaction' as const, event: 'pending_transaction' as const } : {};
router.pathname === '/txs' &&
router.query.tab === 'pending' &&
!router.query.block_number &&
!router.query.page
) {
return { topic: 'transactions:new_pending_transaction' as const, event: 'pending_transaction' as const };
} }
default:
return {}; return {};
}
} }
function assertIsNewTxResponse(response: unknown): response is { transaction: number } { function assertIsNewTxResponse(response: unknown): response is { transaction: number } {
...@@ -40,12 +32,19 @@ function assertIsNewPendingTxResponse(response: unknown): response is { pending_ ...@@ -40,12 +32,19 @@ function assertIsNewPendingTxResponse(response: unknown): response is { pending_
return typeof response === 'object' && response !== null && 'pending_transaction' in response; return typeof response === 'object' && response !== null && 'pending_transaction' in response;
} }
export default function useNewTxsSocket() { interface Params {
type: TxsSocketType;
isLoading?: boolean;
}
export default function useNewTxsSocketTypeAll({ type, isLoading }: Params) {
const router = useRouter(); const router = useRouter();
const page = getQueryParamString(router.query.page);
const [ num, setNum ] = useGradualIncrement(0); const [ num, setNum ] = useGradualIncrement(0);
const [ socketAlert, setSocketAlert ] = React.useState(''); const [ alertText, setAlertText ] = React.useState('');
const { topic, event } = getSocketParams(router); const { topic, event } = getSocketParams(type, page);
const handleNewTxMessage = React.useCallback((response: { transaction: number } | { pending_transaction: number } | unknown) => { const handleNewTxMessage = React.useCallback((response: { transaction: number } | { pending_transaction: number } | unknown) => {
if (assertIsNewTxResponse(response)) { if (assertIsNewTxResponse(response)) {
...@@ -57,18 +56,18 @@ export default function useNewTxsSocket() { ...@@ -57,18 +56,18 @@ export default function useNewTxsSocket() {
}, [ setNum ]); }, [ setNum ]);
const handleSocketClose = React.useCallback(() => { const handleSocketClose = React.useCallback(() => {
setSocketAlert('Connection is lost. Please reload the page.'); setAlertText('Connection is lost. Please reload the page.');
}, []); }, []);
const handleSocketError = React.useCallback(() => { const handleSocketError = React.useCallback(() => {
setSocketAlert('An error has occurred while fetching new transactions. Please reload the page.'); setAlertText('An error has occurred while fetching new transactions. Please reload the page.');
}, []); }, []);
const channel = useSocketChannel({ const channel = useSocketChannel({
topic, topic,
onSocketClose: handleSocketClose, onSocketClose: handleSocketClose,
onSocketError: handleSocketError, onSocketError: handleSocketError,
isDisabled: !topic, isDisabled: !topic || Boolean(isLoading),
}); });
useSocketMessage({ useSocketMessage({
...@@ -78,8 +77,8 @@ export default function useNewTxsSocket() { ...@@ -78,8 +77,8 @@ export default function useNewTxsSocket() {
}); });
if (!topic && !event) { if (!topic && !event) {
return {}; return { };
} }
return { num, socketAlert }; return { num, alertText };
} }
...@@ -25,7 +25,7 @@ const VerifiedContractsTable = ({ data, sort, setSorting, isLoading }: Props) => ...@@ -25,7 +25,7 @@ const VerifiedContractsTable = ({ data, sort, setSorting, isLoading }: Props) =>
}, [ sort, setSorting ]); }, [ sort, setSorting ]);
return ( return (
<TableRoot> <TableRoot minW="950px">
<TableHeaderSticky top={ ACTION_BAR_HEIGHT_DESKTOP }> <TableHeaderSticky top={ ACTION_BAR_HEIGHT_DESKTOP }>
<TableRow> <TableRow>
<TableColumnHeader width="50%">Contract</TableColumnHeader> <TableColumnHeader width="50%">Contract</TableColumnHeader>
......
...@@ -64,7 +64,7 @@ const VerifiedContractsTableItem = ({ data, isLoading }: Props) => { ...@@ -64,7 +64,7 @@ const VerifiedContractsTableItem = ({ data, isLoading }: Props) => {
value={ balance } value={ balance }
isLoading={ isLoading } isLoading={ isLoading }
my={ 1 } my={ 1 }
w="100%" maxW="100%"
/> />
</TableCell> </TableCell>
<TableCell isNumeric> <TableCell isNumeric>
......
...@@ -45,11 +45,11 @@ const WatchListItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEmail } ...@@ -45,11 +45,11 @@ const WatchListItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEmail }
const showNotificationToast = useCallback((isOn: boolean) => { const showNotificationToast = useCallback((isOn: boolean) => {
toaster.success({ toaster.success({
title: 'Success', title: 'Success',
description: !isOn ? 'Email notification is ON' : 'Email notification is OFF', description: isOn ? 'Email notification is ON' : 'Email notification is OFF',
}); });
}, [ ]); }, [ ]);
const { mutate } = useMutation({ const { mutate } = useMutation<WatchlistAddress>({
mutationFn: () => { mutationFn: () => {
setSwitchDisabled(true); setSwitchDisabled(true);
const body = { ...item, notification_methods: { email: !notificationEnabled } }; const body = { ...item, notification_methods: { email: !notificationEnabled } };
...@@ -57,16 +57,16 @@ const WatchListItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEmail } ...@@ -57,16 +57,16 @@ const WatchListItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEmail }
return apiFetch('watchlist', { return apiFetch('watchlist', {
pathParams: { id: String(item.id) }, pathParams: { id: String(item.id) },
fetchParams: { method: 'PUT', body }, fetchParams: { method: 'PUT', body },
}); }) as Promise<WatchlistAddress>;
}, },
onError: () => { onError: () => {
showErrorToast(); showErrorToast();
setNotificationEnabled(prevState => !prevState); setNotificationEnabled(prevState => !prevState);
setSwitchDisabled(false); setSwitchDisabled(false);
}, },
onSuccess: () => { onSuccess: (data) => {
setSwitchDisabled(false); setSwitchDisabled(false);
showNotificationToast(notificationEnabled); showNotificationToast(data.notification_methods.email);
}, },
}); });
......
...@@ -44,11 +44,11 @@ const WatchlistTableItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEm ...@@ -44,11 +44,11 @@ const WatchlistTableItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEm
const showNotificationToast = useCallback((isOn: boolean) => { const showNotificationToast = useCallback((isOn: boolean) => {
toaster.success({ toaster.success({
title: 'Success', title: 'Success',
description: !isOn ? 'Email notification is ON' : 'Email notification is OFF', description: isOn ? 'Email notification is ON' : 'Email notification is OFF',
}); });
}, [ ]); }, [ ]);
const { mutate } = useMutation({ const { mutate } = useMutation<WatchlistAddress>({
mutationFn: () => { mutationFn: () => {
setSwitchDisabled(true); setSwitchDisabled(true);
const body = { ...item, notification_methods: { email: !notificationEnabled } }; const body = { ...item, notification_methods: { email: !notificationEnabled } };
...@@ -56,16 +56,16 @@ const WatchlistTableItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEm ...@@ -56,16 +56,16 @@ const WatchlistTableItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEm
return apiFetch('watchlist', { return apiFetch('watchlist', {
pathParams: { id: String(item.id) }, pathParams: { id: String(item.id) },
fetchParams: { method: 'PUT', body }, fetchParams: { method: 'PUT', body },
}); }) as Promise<WatchlistAddress>;
}, },
onError: () => { onError: () => {
showErrorToast(); showErrorToast();
setNotificationEnabled(prevState => !prevState); setNotificationEnabled(prevState => !prevState);
setSwitchDisabled(false); setSwitchDisabled(false);
}, },
onSuccess: () => { onSuccess: (data) => {
setSwitchDisabled(false); setSwitchDisabled(false);
showNotificationToast(!notificationEnabled); showNotificationToast(data.notification_methods.email);
}, },
}); });
......
...@@ -14,7 +14,7 @@ import WithdrawalsTableItem from './WithdrawalsTableItem'; ...@@ -14,7 +14,7 @@ import WithdrawalsTableItem from './WithdrawalsTableItem';
const WithdrawalsTable = ({ items, top, isLoading }: Props) => { const WithdrawalsTable = ({ items, top, isLoading }: Props) => {
return ( return (
<TableRoot style={{ tableLayout: 'auto' }} minW="950px"> <TableRoot tableLayout="auto" minW="950px">
<TableHeaderSticky top={ top }> <TableHeaderSticky top={ top }>
<TableRow> <TableRow>
<TableColumnHeader>L2 block No</TableColumnHeader> <TableColumnHeader>L2 block No</TableColumnHeader>
...@@ -26,7 +26,7 @@ const WithdrawalsTable = ({ items, top, isLoading }: Props) => { ...@@ -26,7 +26,7 @@ const WithdrawalsTable = ({ items, top, isLoading }: Props) => {
</TableHeaderSticky> </TableHeaderSticky>
<TableBody> <TableBody>
{ items.map((item, index) => ( { items.map((item, index) => (
<WithdrawalsTableItem key={ item.l2_transaction_hash + (isLoading ? index : '') } item={ item } isLoading={ isLoading }/> <WithdrawalsTableItem key={ `${ item.l2_transaction_hash }-${ index }` } item={ item } isLoading={ isLoading }/>
)) } )) }
</TableBody> </TableBody>
</TableRoot> </TableRoot>
......
...@@ -1536,10 +1536,10 @@ ...@@ -1536,10 +1536,10 @@
resolved "https://registry.yarnpkg.com/@blockscout/bens-types/-/bens-types-1.4.1.tgz#9182a79d9015b7fa2339edf0bfa3cd0c32045e66" resolved "https://registry.yarnpkg.com/@blockscout/bens-types/-/bens-types-1.4.1.tgz#9182a79d9015b7fa2339edf0bfa3cd0c32045e66"
integrity sha512-TlZ1HVdZ2Cswm/CcvNoxS+Ydiht/YGaLo//PJR/UmkmihlEFoY4HfVJvVcUnOQXi+Si7FwJ486DPii889nTJsQ== integrity sha512-TlZ1HVdZ2Cswm/CcvNoxS+Ydiht/YGaLo//PJR/UmkmihlEFoY4HfVJvVcUnOQXi+Si7FwJ486DPii889nTJsQ==
"@blockscout/points-types@1.3.0-alpha.1": "@blockscout/points-types@1.3.0-alpha.2":
version "1.3.0-alpha.1" version "1.3.0-alpha.2"
resolved "https://registry.yarnpkg.com/@blockscout/points-types/-/points-types-1.3.0-alpha.1.tgz#d1f255de6ccfa09b8a938ffe17f6aedd559273a3" resolved "https://registry.yarnpkg.com/@blockscout/points-types/-/points-types-1.3.0-alpha.2.tgz#0308dcb4eef0dadf96f43b144835470e9f78f64f"
integrity sha512-yZcxvPpS1JT79dZrzSeP4r3BM5cqSnsVnclCIpJMUO3qBRWEytVfDGXcqNacwqp3342Im8RB/YPLKAuJGc+CrA== integrity sha512-tXCA51q3y08caCm7UhGyj+xsP0pd6yBhjElDHxEzM5SRop3culMiacaBXd0OPBszHjA0YdYgXFymuJhofB22ig==
"@blockscout/stats-types@2.5.0-alpha": "@blockscout/stats-types@2.5.0-alpha":
version "2.5.0-alpha" version "2.5.0-alpha"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment