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(() => {
setOpen((prev) => !prev); if (timeoutRef.current) {
}, [ ]); window.clearTimeout(timeoutRef.current);
}
timeoutRef.current = window.setTimeout(() => {
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) }
>
{ typeof tab.title === 'function' ? tab.title() : tab.title }
<TabsCounter count={ tab.count }/>
</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
}
> >
{ isLoading ? ( <Skeleton loading>
<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>
</Skeleton>
) : (
<>
{ typeof tab.title === 'function' ? tab.title() : tab.title }
<TabsCounter count={ tab.count }/>
</>
) }
</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 ? <RoutedTabs
<RoutedTabsSkeleton tabs={ tabs }/> : ( tabs={ tabs }
<RoutedTabs isLoading={ batchQuery.isPlaceholderData }
tabs={ tabs } 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 ? <RoutedTabs
<RoutedTabsSkeleton tabs={ tabs }/> : ( tabs={ tabs }
<RoutedTabs isLoading={ batchQuery.isPlaceholderData }
tabs={ tabs } 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 ? <RoutedTabs
<RoutedTabsSkeleton tabs={ tabs }/> : ( tabs={ tabs }
<RoutedTabs isLoading={ batchQuery.isPlaceholderData }
tabs={ tabs } 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 ? <RoutedTabs
<RoutedTabsSkeleton tabs={ tabs }/> : ( tabs={ tabs }
<RoutedTabs isLoading={ batchQuery.isPlaceholderData }
tabs={ tabs } 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 }}>
Company info <Heading level="2">
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 }}>
Public tags/labels <Heading level="2" display="flex" alignItems="center" columnGap={ 1 }>
<Hint label="Submit a public tag proposal for our moderation team to review" ml={ 1 } color="link.primary"/> Public tags/labels
<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,6 +27,37 @@ const CopyToClipboard = (props: Props) => { ...@@ -23,6 +27,37 @@ const CopyToClipboard = (props: Props) => {
onClick?.(event); onClick?.(event);
}, [ onClick, copy ]); }, [ onClick, copy ]);
const iconName = (() => {
switch (type) {
case 'link':
return hasCopied ? 'check' : 'link';
case 'share':
return hasCopied ? 'check' : 'share';
default:
return hasCopied ? 'copy_check' : 'copy';
}
})();
const button = (
<IconButton
aria-label="copy"
boxSize={ boxSize }
onClick={ handleClick }
ml={ 2 }
borderRadius="sm"
loadingSkeleton={ isLoading }
variant="icon_secondary"
size="2xs"
{ ...rest }
>
<IconSvg name={ iconName }/>
</IconButton>
);
if (noTooltip) {
return button;
}
const tooltipContent = (() => { const tooltipContent = (() => {
if (hasCopied) { if (hasCopied) {
return 'Copied'; return 'Copied';
...@@ -35,17 +70,6 @@ const CopyToClipboard = (props: Props) => { ...@@ -35,17 +70,6 @@ const CopyToClipboard = (props: Props) => {
return 'Copy to clipboard'; return 'Copy to clipboard';
})(); })();
const iconName = (() => {
switch (type) {
case 'link':
return 'link';
case 'share':
return 'share';
default:
return 'copy';
}
})();
return ( return (
<Tooltip <Tooltip
content={ tooltipContent } content={ tooltipContent }
...@@ -53,20 +77,9 @@ const CopyToClipboard = (props: Props) => { ...@@ -53,20 +77,9 @@ const CopyToClipboard = (props: Props) => {
open={ disclosure.open } open={ disclosure.open }
onOpenChange={ disclosure.onOpenChange } onOpenChange={ disclosure.onOpenChange }
closeOnPointerDown={ false } closeOnPointerDown={ false }
interactive={ tooltipInteractive }
> >
<IconButton { button }
aria-label="copy"
boxSize={ boxSize }
onClick={ handleClick }
ml={ 2 }
borderRadius="sm"
loadingSkeleton={ isLoading }
variant="icon_secondary"
size="2xs"
{ ...rest }
>
<IconSvg name={ iconName }/>
</IconButton>
</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 }}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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