Commit e52386b8 authored by tom's avatar tom

blobs, pools and block countdown pages

parent 460883d9
......@@ -371,6 +371,7 @@
"eth_sepolia",
"filecoin",
"mekong",
"neon_devnet",
"optimism",
"optimism_celestia",
"optimism_sepolia",
......
......@@ -5,12 +5,12 @@ import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
// const Blob = dynamic(() => import('ui/pages/Blob'), { ssr: false });
const Blob = dynamic(() => import('ui/pages/Blob'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/blobs/[hash]" query={ props.query }>
{ /* <Blob/> */ }
<Blob/>
</PageNextJs>
);
};
......
......@@ -5,12 +5,12 @@ import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
// const BlockCountdown = dynamic(() => import('ui/pages/BlockCountdown'), { ssr: false });
const BlockCountdown = dynamic(() => import('ui/pages/BlockCountdown'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/block/countdown/[height]" query={ props.query }>
{ /* <BlockCountdown/> */ }
<BlockCountdown/>
</PageNextJs>
);
};
......
......@@ -5,12 +5,12 @@ import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
// const BlockCountdownIndex = dynamic(() => import('ui/pages/BlockCountdownIndex'), { ssr: false });
const BlockCountdownIndex = dynamic(() => import('ui/pages/BlockCountdownIndex'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/block/countdown" query={ props.query }>
{ /* <BlockCountdownIndex/> */ }
<BlockCountdownIndex/>
</PageNextJs>
);
};
......
......@@ -5,12 +5,12 @@ import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
// const Pool = dynamic(() => import('ui/pages/Pool'), { ssr: false });
const Pool = dynamic(() => import('ui/pages/Pool'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/pools/[hash]" query={ props.query }>
{ /* <Pool/> */ }
<Pool/>
</PageNextJs>
);
};
......
......@@ -4,12 +4,12 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
// const Pools = dynamic(() => import('ui/pages/Pools'), { ssr: false });
const Pools = dynamic(() => import('ui/pages/Pools'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/pools">
{ /* <Pools/> */ }
<Pools/>
</PageNextJs>
);
};
......
......@@ -5,12 +5,12 @@ import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
// const KettleTxs = dynamic(() => import('ui/pages/KettleTxs'), { ssr: false });
const KettleTxs = dynamic(() => import('ui/pages/KettleTxs'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/txs/kettle/[hash]" query={ props.query }>
{ /* <KettleTxs/> */ }
<KettleTxs/>
</PageNextJs>
);
};
......
......@@ -2,6 +2,7 @@ import { useCopyToClipboard } from '@uidotdev/usehooks';
import React from 'react';
import { SECOND } from 'lib/consts';
import useIsMobile from 'lib/hooks/useIsMobile';
import { useDisclosure } from './useDisclosure';
......@@ -11,12 +12,15 @@ export default function useClipboard(text: string, timeout = SECOND) {
const disclosureTimeoutRef = React.useRef<number | null>(null);
const [ hasCopied, setHasCopied ] = React.useState(false);
const isMobile = useIsMobile();
const [ , copyToClipboard ] = useCopyToClipboard();
const { open, onOpenChange } = useDisclosure();
const copy = React.useCallback(() => {
copyToClipboard(text);
setHasCopied(true);
// there is no hover on mobile, so we need to open the disclosure manually after click
isMobile && onOpenChange({ open: true });
disclosureTimeoutRef.current = window.setTimeout(() => {
onOpenChange({ open: false });
......@@ -26,7 +30,7 @@ export default function useClipboard(text: string, timeout = SECOND) {
flagTimeoutRef.current = window.setTimeout(() => {
setHasCopied(false);
}, timeout + 200);
}, [ text, copyToClipboard, timeout, onOpenChange ]);
}, [ text, copyToClipboard, timeout, onOpenChange, isMobile ]);
React.useEffect(() => {
return () => {
......
......@@ -66,8 +66,9 @@ export const recipe = defineSlotRecipe({
},
popover: {
content: {
maxW: 'none',
bg: 'popover.bg',
color: 'popover.fg',
color: 'text.primary',
p: '4',
boxShadow: 'popover',
boxShadowColor: 'popover.shadow',
......
import { Flex, GridItem, Button } from '@chakra-ui/react';
import { createListCollection, Flex, GridItem } from '@chakra-ui/react';
import React from 'react';
import * as blobUtils from 'lib/blob';
......@@ -8,10 +8,11 @@ import downloadBlob from 'lib/downloadBlob';
import hexToBase64 from 'lib/hexToBase64';
import hexToBytes from 'lib/hexToBytes';
import hexToUtf8 from 'lib/hexToUtf8';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Button } from 'toolkit/chakra/button';
import { SelectContent, SelectItem, SelectRoot, SelectControl, SelectValueText } from 'toolkit/chakra/select';
import { Skeleton } from 'toolkit/chakra/skeleton';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import RawDataSnippet from 'ui/shared/RawDataSnippet';
import Select from 'ui/shared/select/Select';
import BlobDataImage from './BlobDataImage';
......@@ -41,7 +42,12 @@ const BlobData = ({ data, isLoading, hash }: Props) => {
}, [ data, isLoading ]);
const isImage = guessedType?.mime?.startsWith('image/');
const formats = isImage ? FORMATS : FORMATS.filter((format) => format.value !== 'Image');
const collection = React.useMemo(() => {
const formats = isImage ? FORMATS : FORMATS.filter((format) => format.value !== 'Image');
return createListCollection({
items: formats,
});
}, [ isImage ]);
React.useEffect(() => {
if (isImage) {
......@@ -49,6 +55,10 @@ const BlobData = ({ data, isLoading, hash }: Props) => {
}
}, [ isImage ]);
const handleFormatChange = React.useCallback(({ value }: { value: Array<string> }) => {
setFormat(value[0] as Format);
}, []);
const handleDownloadButtonClick = React.useCallback(() => {
const fileBlob = (() => {
switch (format) {
......@@ -102,20 +112,27 @@ const BlobData = ({ data, isLoading, hash }: Props) => {
return (
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 3, lg: 2 }}>
<Flex alignItems="center" mb={ 3 }>
<Skeleton fontWeight={{ base: 700, lg: 500 }} isLoaded={ !isLoading }>
<Skeleton fontWeight={{ base: 700, lg: 500 }} loading={ isLoading }>
Blob data
</Skeleton>
<Skeleton ml={ 5 } isLoaded={ !isLoading }>
<Select
options={ formats }
name="format"
defaultValue={ format }
onChange={ setFormat }
isLoading={ isLoading }
w="95px"
/>
</Skeleton>
<Skeleton ml="auto" mr={ 3 } isLoaded={ !isLoading }>
<SelectRoot
collection={ collection }
variant="outline"
value={ [ format ] }
onValueChange={ handleFormatChange }
>
<SelectControl loading={ isLoading } ml={ 5 } w="100px">
<SelectValueText placeholder="Select framework"/>
</SelectControl>
<SelectContent>
{ collection.items.map((item) => (
<SelectItem item={ item } key={ item.value }>
{ item.label }
</SelectItem>
)) }
</SelectContent>
</SelectRoot>
<Skeleton ml="auto" mr={ 3 } loading={ isLoading }>
<Button
variant="outline"
size="sm"
......
import { Alert, Grid, GridItem } from '@chakra-ui/react';
import { Grid, GridItem } from '@chakra-ui/react';
import React from 'react';
import type { Blob } from 'types/api/blobs';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Alert } from 'toolkit/chakra/alert';
import { Skeleton } from 'toolkit/chakra/skeleton';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo';
import DetailedInfoSponsoredItem from 'ui/shared/DetailedInfo/DetailedInfoSponsoredItem';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
......@@ -26,7 +26,7 @@ const BlobInfo = ({ data, isLoading }: Props) => {
>
{ !data.blob_data && (
<GridItem colSpan={{ base: undefined, lg: 2 }} mb={ 3 }>
<Skeleton isLoaded={ !isLoading }>
<Skeleton loading={ isLoading }>
<Alert status="warning">This blob is not yet indexed</Alert>
</Skeleton>
</GridItem>
......@@ -41,7 +41,7 @@ const BlobInfo = ({ data, isLoading }: Props) => {
Proof
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all" lineHeight={ 6 } my="-2px">
<Skeleton loading={ isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all">
{ data.kzg_proof }
<CopyToClipboard text={ data.kzg_proof } isLoading={ isLoading }/>
</Skeleton>
......@@ -58,7 +58,7 @@ const BlobInfo = ({ data, isLoading }: Props) => {
Commitment
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all" lineHeight={ 6 } my="-2px">
<Skeleton loading={ isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all">
{ data.kzg_commitment }
<CopyToClipboard text={ data.kzg_commitment } isLoading={ isLoading }/>
</Skeleton>
......@@ -75,7 +75,7 @@ const BlobInfo = ({ data, isLoading }: Props) => {
Size, bytes
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all">
<Skeleton loading={ isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all">
{ (data.blob_data.replace('0x', '').length / 2).toLocaleString() }
</Skeleton>
</DetailedInfo.ItemValue>
......
import { HStack, StackDivider, useColorModeValue } from '@chakra-ui/react';
import { HStack, StackSeparator } from '@chakra-ui/react';
import React from 'react';
import { SECOND } from 'lib/consts';
......@@ -15,8 +15,6 @@ const BlockCountdownTimer = ({ value: initialValue, onFinish }: Props) => {
const [ value, setValue ] = React.useState(initialValue);
const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100');
React.useEffect(() => {
const intervalId = window.setInterval(() => {
setValue((prev) => {
......@@ -38,11 +36,11 @@ const BlockCountdownTimer = ({ value: initialValue, onFinish }: Props) => {
return (
<HStack
bgColor={ bgColor }
bgColor={{ _light: 'gray.50', _dark: 'whiteAlpha.100' }}
mt={{ base: 6, lg: 8 }}
p={{ base: 3, lg: 4 }}
borderRadius="base"
divider={ <StackDivider borderColor="border.divider"/> }
separator={ <StackSeparator borderColor="border.divider"/> }
>
<BlockCountdownTimerItem label="Days" value={ periods.days }/>
<BlockCountdownTimerItem label="Hours" value={ periods.hours }/>
......
/* eslint-disable @next/next/no-img-element */
import { Box, Text, Button, Flex } from '@chakra-ui/react';
import { Box, Text, Flex } from '@chakra-ui/react';
import Script from 'next/script';
import React from 'react';
import config from 'configs/app';
import useIsMobile from 'lib/hooks/useIsMobile';
import { Button } from 'toolkit/chakra/button';
import { Link } from 'toolkit/chakra/link';
const easterEggBadgeFeature = config.features.easterEggBadge;
const CapybaraRunner = () => {
......@@ -50,7 +52,13 @@ const CapybaraRunner = () => {
<Flex flexDirection="column" alignItems="center" justifyContent="center" gap={ 4 } mt={ 10 }>
<Text fontSize="2xl" fontWeight="bold">You unlocked a hidden badge!</Text>
<Text fontSize="lg" textAlign="center">Congratulations! You’re eligible to claim an epic hidden badge!</Text>
<Button as="a" href={ easterEggBadgeFeature.badgeClaimLink } target="_blank">Claim</Button>
<Link
href={ easterEggBadgeFeature.badgeClaimLink }
target="_blank"
asChild
>
<Button>Claim</Button>
</Link>
</Flex>
) }
</>
......
import { Box, Center, Flex, Heading, Image, useColorModeValue, Grid, Button } from '@chakra-ui/react';
import { Box, Center, Flex, Grid } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -9,12 +9,15 @@ import dayjs from 'lib/date/dayjs';
import downloadBlob from 'lib/downloadBlob';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import getQueryParamString from 'lib/router/getQueryParamString';
import { Button } from 'toolkit/chakra/button';
import { Heading } from 'toolkit/chakra/heading';
import { Image } from 'toolkit/chakra/image';
import { Link } from 'toolkit/chakra/link';
import BlockCountdownTimer from 'ui/blockCountdown/BlockCountdownTimer';
import createGoogleCalendarLink from 'ui/blockCountdown/createGoogleCalendarLink';
import createIcsFileBlob from 'ui/blockCountdown/createIcsFileBlob';
import ContentLoader from 'ui/shared/ContentLoader';
import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/links/LinkExternal';
import StatsWidget from 'ui/shared/stats/StatsWidget';
import TruncatedValue from 'ui/shared/TruncatedValue';
......@@ -27,8 +30,6 @@ type Props = {
const BlockCountdown = ({ hideCapybaraRunner }: Props) => {
const router = useRouter();
const height = getQueryParamString(router.query.height);
const iconColor = useColorModeValue('gray.300', 'gray.600');
const buttonBgColor = useColorModeValue('gray.100', 'gray.700');
const { data, isPending, isError, error } = useApiQuery('block_countdown', {
queryParams: {
......@@ -70,9 +71,7 @@ const BlockCountdown = ({ hideCapybaraRunner }: Props) => {
<Flex columnGap={ 8 } alignItems="flex-start" justifyContent={{ base: 'space-between', lg: undefined }} w="100%">
<Box maxW={{ base: 'calc(100% - 65px - 32px)', lg: 'calc(100% - 125px - 32px)' }}>
<Heading
fontSize={{ base: '18px', lg: '32px' }}
lineHeight={{ base: '24px', lg: '40px' }}
h={{ base: '24px', lg: '40px' }}
level="1"
>
<TruncatedValue value={ `Block #${ height }` } w="100%"/>
</Heading>
......@@ -81,32 +80,40 @@ const BlockCountdown = ({ hideCapybaraRunner }: Props) => {
<Box>{ dayjs().add(Number(data.result.EstimateTimeInSec), 's').format('llll') }</Box>
</Box>
<Flex columnGap={ 2 } mt={ 3 }>
<LinkExternal
variant="subtle"
fontSize="sm"
lineHeight="20px"
<Link
external
variant="underlaid"
textStyle="sm"
px={ 2 }
display="inline-flex"
href={ createGoogleCalendarLink({ blockHeight: height, timeFromNow: Number(data.result.EstimateTimeInSec) }) }
>
<Image src="/static/google_calendar.svg" alt="Google calendar logo" boxSize={ 5 } mr={ 2 }/>
<span>Google</span>
</LinkExternal>
</Link>
<Button
variant="subtle"
fontWeight={ 400 }
variant="plain"
px={ 2 }
size="sm"
bgColor={ buttonBgColor }
fontWeight="normal"
color="link.primary"
_hover={{ color: 'link.primary.hover' }}
bgColor="link.underlaid.bg"
display="inline-flex"
onClick={ handleAddToAppleCalClick }
>
<Image src="/static/apple_calendar.svg" alt="Apple calendar logo" boxSize={ 5 } mr={ 2 }/>
<Image src="/static/apple_calendar.svg" alt="Apple calendar logo" boxSize={ 5 }/>
<span>Apple</span>
</Button>
</Flex>
</Box>
<IconSvg name="block_slim" w={{ base: '65px', lg: '125px' }} h={{ base: '75px', lg: '140px' }} color={ iconColor } flexShrink={ 0 }/>
<IconSvg
name="block_slim"
w={{ base: '65px', lg: '125px' }}
h={{ base: '75px', lg: '140px' }}
color={{ _light: 'gray.300', _dark: 'gray.600' }}
flexShrink={ 0 }
/>
</Flex>
{ data.result.EstimateTimeInSec && (
<BlockCountdownTimer
......
import { chakra, Box, Center, Heading, useColorModeValue } from '@chakra-ui/react';
import { chakra, Box, Center } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import { Heading } from 'toolkit/chakra/heading';
import FilterInput from 'ui/shared/filters/FilterInput';
import IconSvg from 'ui/shared/IconSvg';
const BlockCountdownIndex = () => {
const router = useRouter();
const iconColor = useColorModeValue('gray.300', 'gray.600');
const handleFormSubmit = React.useCallback((event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
......@@ -21,11 +21,14 @@ const BlockCountdownIndex = () => {
return (
<Center h="100%" justifyContent={{ base: 'flex-start', lg: 'center' }} flexDir="column" textAlign="center" pt={{ base: 8, lg: 0 }}>
<IconSvg name="block_countdown" color={ iconColor } w={{ base: '160px', lg: '240px' }} h={{ base: '123px', lg: '184px' }}/>
<IconSvg
name="block_countdown"
color={{ _light: 'gray.300', _dark: 'gray.600' }}
w={{ base: '160px', lg: '240px' }}
h={{ base: '123px', lg: '184px' }}
/>
<Heading
fontSize={{ base: '18px', lg: '32px' }}
lineHeight={{ base: '24px', lg: '40px' }}
h={{ base: '24px', lg: '40px' }}
level="1"
mt={{ base: 3, lg: 6 }}
>
Block countdown
......@@ -41,7 +44,7 @@ const BlockCountdownIndex = () => {
>
<FilterInput
placeholder="Search by block number"
size="xs"
size="sm"
type="number"
name="search_term"
/>
......
import { Tag, Box, Flex, Image } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -11,14 +11,16 @@ import { getPoolTitle } from 'lib/pools/getPoolTitle';
import getQueryParamString from 'lib/router/getQueryParamString';
import * as addressStubs from 'stubs/address';
import { POOL } from 'stubs/pools';
import { Image } from 'toolkit/chakra/image';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { Tag } from 'toolkit/chakra/tag';
import PoolInfo from 'ui/pool/PoolInfo';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import Skeleton from 'ui/shared/chakra/Skeleton';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import * as PoolEntity from 'ui/shared/entities/pool/PoolEntity';
import InfoButton from 'ui/shared/InfoButton';
import LinkExternal from 'ui/shared/links/LinkExternal';
import PageTitle from 'ui/shared/Page/PageTitle';
import VerifyWith from 'ui/shared/VerifyWith';
......@@ -71,10 +73,10 @@ const Pool = () => {
return externalLinks
.map((link) => {
return (
<LinkExternal h="34px" key={ link.url } href={ link.url } alignItems="center" display="inline-flex" minW="120px">
<Link external h="34px" key={ link.url } href={ link.url } alignItems="center" display="inline-flex" minW="120px">
<Image boxSize={ 5 } mr={ 2 } src={ link.image } alt={ `${ link.title } icon` }/>
{ link.title }
</LinkExternal>
</Link>
);
});
}, [ externalLinks ]);
......@@ -125,7 +127,7 @@ const Pool = () => {
size="lg"
/>
) : null }
contentAfter={ <Skeleton isLoaded={ !isPlaceholderData }><Tag>Pool</Tag></Skeleton> }
contentAfter={ <Skeleton loading={ isPlaceholderData }><Tag>Pool</Tag></Skeleton> }
secondRow={ titleSecondRow }
isLoading={ isPlaceholderData }
withTextAd
......
import { Show, Hide, Flex } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -39,7 +39,7 @@ const Pools = () => {
const content = (
<>
<Show below="lg" ssr={ false }>
<Box hideFrom="lg">
{ poolsQuery.data?.items.map((item, index) => (
<PoolsListItem
key={ item.contract_address + (poolsQuery.isPlaceholderData ? index : '') }
......@@ -47,22 +47,22 @@ const Pools = () => {
item={ item }
/>
)) }
</Show>
<Hide below="lg" ssr={ false }>
</Box>
<Box hideBelow="lg">
<PoolsTable
items={ poolsQuery.data?.items ?? [] }
top={ poolsQuery.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
isLoading={ poolsQuery.isPlaceholderData }
page={ poolsQuery.pagination.page }
/>
</Hide>
</Box>
</>
);
const filter = (
<FilterInput
w={{ base: '100%', lg: '360px' }}
size="xs"
size="sm"
onChange={ handleSearchTermChange }
placeholder="Pair, token symbol or token address"
initialValue={ searchTerm }
......@@ -78,9 +78,9 @@ const Pools = () => {
mt={ -6 }
display={{ base: poolsQuery.pagination.isVisible ? 'flex' : 'none', lg: 'flex' }}
>
<Hide below="lg">
<Box hideBelow="lg">
{ filter }
</Hide>
</Box>
<Pagination { ...poolsQuery.pagination } ml="auto"/>
</ActionBar>
</>
......@@ -94,15 +94,16 @@ const Pools = () => {
/>
<DataListDisplay
isError={ poolsQuery.isError }
items={ poolsQuery.data?.items }
itemsNum={ poolsQuery.data?.items.length }
emptyText="There are no pools."
content={ content }
actionBar={ actionBar }
filterProps={{
emptyFilteredText: `Couldn${ apos }t find pools that matches your filter query.`,
hasActiveFilters: Boolean(debouncedSearchTerm),
}}
/>
>
{ content }
</DataListDisplay>
</>
);
};
......
import { Flex, Box, Tooltip, useClipboard, useColorModeValue } from '@chakra-ui/react';
import type { HTMLChakraProps } from '@chakra-ui/react';
import { Flex, Box } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
......@@ -6,6 +7,8 @@ import type { StaticRoute } from 'nextjs-routes';
import { route } from 'nextjs-routes';
import useFetch from 'lib/hooks/useFetch';
import { Tooltip } from 'toolkit/chakra/tooltip';
import useClipboard from 'toolkit/hooks/useClipboard';
import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import EmptySearchResult from 'ui/shared/EmptySearchResult';
......@@ -21,8 +24,8 @@ interface IconInfo {
fileSize: number;
}
const Item = ({ name, fileSize, bgColor }: IconInfo & { bgColor: string }) => {
const { hasCopied, onCopy } = useClipboard(name, 1000);
const Item = ({ name, fileSize, bgColor }: IconInfo & HTMLChakraProps<'div'>) => {
const { hasCopied, copy } = useClipboard(name, 1000);
const [ copied, setCopied ] = React.useState(false);
React.useEffect(() => {
......@@ -41,11 +44,11 @@ const Item = ({ name, fileSize, bgColor }: IconInfo & { bgColor: string }) => {
wordBreak="break-word"
maxW="100px"
textAlign="center"
onClick={ onCopy }
onClick={ copy }
cursor="pointer"
>
<IconSvg name={ name as IconName } boxSize="100px" bgColor={ bgColor } borderRadius="base"/>
<Tooltip label={ copied ? 'Copied' : 'Copy to clipboard' } isOpen={ copied }>
<Tooltip content={ copied ? 'Copied' : 'Copy to clipboard' } open={ copied }>
<Box fontWeight={ 500 } mt={ 2 }>{ name }</Box>
</Tooltip>
<Box color="text_secondary">{ formatFileSize(fileSize) }</Box>
......@@ -55,7 +58,6 @@ const Item = ({ name, fileSize, bgColor }: IconInfo & { bgColor: string }) => {
const Sprite = () => {
const [ searchTerm, setSearchTerm ] = React.useState('');
const bgColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.100');
const fetch = useFetch();
const { data, isFetching, isError } = useQuery({
......@@ -83,7 +85,7 @@ const Sprite = () => {
return (
<Flex flexWrap="wrap" fontSize="sm" columnGap={ 5 } rowGap={ 5 } justifyContent="flex-start">
{ items.map((item) => <Item key={ item.name } { ...item } bgColor={ bgColor }/>) }
{ items.map((item) => <Item key={ item.name } { ...item } bgColor={{ _light: 'blackAlpha.100', _dark: 'whiteAlpha.100' }}/>) }
</Flex>
);
})();
......@@ -99,7 +101,7 @@ const Sprite = () => {
}, { num: 0, fileSize: 0 });
}, [ data ]);
const searchInput = <FilterInput placeholder="Search by name..." onChange={ setSearchTerm } isLoading={ isFetching } minW={{ base: '100%', lg: '300px' }}/>;
const searchInput = <FilterInput placeholder="Search by name..." onChange={ setSearchTerm } loading={ isFetching } minW={{ base: '100%', lg: '300px' }}/>;
const totalEl = total ? <Box ml="auto">Items: { total.num } / Size: { formatFileSize(total.fileSize) }</Box> : null;
const contentAfter = (
......
......@@ -3,7 +3,7 @@ import React from 'react';
import type { Pool } from 'types/api/pools';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo';
import DetailedInfoSponsoredItem from 'ui/shared/DetailedInfo/DetailedInfoSponsoredItem';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
......@@ -64,33 +64,33 @@ const PoolInfo = ({ data, isPlaceholderData }: Props) => {
hint="Fully Diluted Valuation: theoretical market cap if all tokens were in circulation"
>
Base token FDV
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton loading={ isPlaceholderData }>
${ Number(data.base_token_fully_diluted_valuation_usd).toLocaleString(undefined, { maximumFractionDigits: 2, notation: 'compact' }) }
</Skeleton>
</DetailsInfoItem.Value>
</DetailedInfo.ItemValue>
<DetailsInfoItem.Label
<DetailedInfo.ItemLabel
isLoading={ isPlaceholderData }
hint="Current market capitalization of the base token"
>
Base token market cap
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton loading={ isPlaceholderData }>
${ Number(data.base_token_market_cap_usd).toLocaleString(undefined, { maximumFractionDigits: 2, notation: 'compact' }) }
</Skeleton>
</DetailsInfoItem.Value>
</DetailedInfo.ItemValue>
<DetailsInfoItem.Label
<DetailedInfo.ItemLabel
isLoading={ isPlaceholderData }
hint="Fully Diluted Valuation: theoretical market cap if all tokens were in circulation"
>
Quote token FDV
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton isLoaded={ !isPlaceholderData }>
<Skeleton loading={ isPlaceholderData }>
${ Number(data.quote_token_fully_diluted_valuation_usd).toLocaleString(undefined, { maximumFractionDigits: 2, notation: 'compact' }) }
</Skeleton>
</DetailedInfo.ItemValue>
......@@ -102,7 +102,7 @@ const PoolInfo = ({ data, isPlaceholderData }: Props) => {
Quote token market cap
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton isLoaded={ !isPlaceholderData }>
<Skeleton loading={ isPlaceholderData }>
${ Number(data.quote_token_market_cap_usd).toLocaleString(undefined, { maximumFractionDigits: 2, notation: 'compact' }) }
</Skeleton>
</DetailedInfo.ItemValue>
......@@ -114,7 +114,7 @@ const PoolInfo = ({ data, isPlaceholderData }: Props) => {
Liquidity
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton isLoaded={ !isPlaceholderData }>
<Skeleton loading={ isPlaceholderData }>
${ Number(data.liquidity).toLocaleString(undefined, { maximumFractionDigits: 2, notation: 'compact' }) }
</Skeleton>
</DetailedInfo.ItemValue>
......@@ -126,7 +126,7 @@ const PoolInfo = ({ data, isPlaceholderData }: Props) => {
DEX
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton isLoaded={ !isPlaceholderData }>
<Skeleton loading={ isPlaceholderData }>
{ data.dex.name }
</Skeleton>
</DetailedInfo.ItemValue>
......
import { Image } from '@chakra-ui/react';
import React from 'react';
import type { Pool } from 'types/api/pools';
import getPoolLinks from 'lib/pools/getPoolLinks';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Image } from 'toolkit/chakra/image';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import PoolEntity from 'ui/shared/entities/pool/PoolEntity';
import LinkExternal from 'ui/shared/links/LinkExternal';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
type Props = {
......@@ -32,19 +32,19 @@ const UserOpsListItem = ({ item, isLoading }: Props) => {
<ListItemMobileGrid.Label isLoading={ isLoading }>Liquidity</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading }>
<Skeleton loading={ isLoading }>
${ Number(item.liquidity).toLocaleString(undefined, { maximumFractionDigits: 2, notation: 'compact' }) }
</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>View in</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading }>
<Skeleton loading={ isLoading }>
{ externalLinks.map((link) => (
<LinkExternal href={ link.url } key={ link.url } display="inline-flex">
<Link external href={ link.url } key={ link.url } display="inline-flex">
<Image src={ link.image } alt={ link.title } boxSize={ 5 } mr={ 2 }/>
{ link.title }
</LinkExternal>
</Link>
)) }
</Skeleton>
</ListItemMobileGrid.Value>
......
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
import React from 'react';
import type { Pool } from 'types/api/pools';
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import { default as Thead } from 'ui/shared/TheadSticky';
import PoolsTableItem from './PoolsTableItem';
......@@ -17,21 +16,21 @@ type Props = {
const PoolsTable = ({ items, page, isLoading, top }: Props) => {
return (
<Table minWidth="900px">
<Thead top={ top ?? ACTION_BAR_HEIGHT_DESKTOP }>
<Tr>
<Th width="70%">Pool</Th>
<Th width="30%">DEX </Th>
<Th width="130px" isNumeric>Liquidity</Th>
<Th width="75px" isNumeric>View in</Th>
</Tr>
</Thead>
<Tbody>
<TableRoot minWidth="900px">
<TableHeaderSticky top={ top ?? ACTION_BAR_HEIGHT_DESKTOP }>
<TableRow>
<TableColumnHeader width="70%">Pool</TableColumnHeader>
<TableColumnHeader width="30%">DEX </TableColumnHeader>
<TableColumnHeader width="130px" isNumeric>Liquidity</TableColumnHeader>
<TableColumnHeader width="75px" isNumeric>View in</TableColumnHeader>
</TableRow>
</TableHeaderSticky>
<TableBody>
{ items.map((item, index) => (
<PoolsTableItem key={ item.contract_address + (isLoading ? index : '') } item={ item } index={ index } page={ page } isLoading={ isLoading }/>
)) }
</Tbody>
</Table>
</TableBody>
</TableRoot>
);
};
......
import { Flex, Box, Td, Tr, Text, Image, Tooltip } from '@chakra-ui/react';
import { Flex, Box, Text } from '@chakra-ui/react';
import React from 'react';
import type { Pool } from 'types/api/pools';
import getItemIndex from 'lib/getItemIndex';
import getPoolLinks from 'lib/pools/getPoolLinks';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Image } from 'toolkit/chakra/image';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { TableCell, TableRow } from 'toolkit/chakra/table';
import { Tooltip } from 'toolkit/chakra/tooltip';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import PoolEntity from 'ui/shared/entities/pool/PoolEntity';
import LinkExternal from 'ui/shared/links/LinkExternal';
type Props = {
item: Pool;
......@@ -26,10 +29,10 @@ const PoolsTableItem = ({
const externalLinks = getPoolLinks(item);
return (
<Tr>
<Td>
<TableRow>
<TableCell>
<Flex gap={ 2 } alignItems="start">
<Skeleton isLoaded={ !isLoading }>
<Skeleton loading={ isLoading }>
<Text px={ 2 }>{ getItemIndex(index, page) }</Text>
</Skeleton>
<Box overflow="hidden">
......@@ -42,29 +45,29 @@ const PoolsTableItem = ({
/>
</Box>
</Flex>
</Td>
<Td>
<Skeleton isLoaded={ !isLoading }>{ item.dex.name }</Skeleton>
</Td>
<Td isNumeric>
<Skeleton isLoaded={ !isLoading }>
</TableCell>
<TableCell>
<Skeleton loading={ isLoading }>{ item.dex.name }</Skeleton>
</TableCell>
<TableCell isNumeric>
<Skeleton loading={ isLoading }>
${ Number(item.liquidity).toLocaleString(undefined, { maximumFractionDigits: 2, notation: 'compact' }) }
</Skeleton>
</Td>
<Td isNumeric>
<Skeleton isLoaded={ !isLoading } display="flex" gap={ 2 } justifyContent="center">
</TableCell>
<TableCell isNumeric>
<Skeleton loading={ isLoading } display="flex" gap={ 2 } justifyContent="center">
{ externalLinks.map((link) => (
<Tooltip label={ link.title } key={ link.url }>
<Tooltip content={ link.title } key={ link.url }>
<Box display="inline-block">
<LinkExternal href={ link.url } display="inline-flex">
<Link external href={ link.url } display="inline-flex">
<Image src={ link.image } alt={ link.title } boxSize={ 5 }/>
</LinkExternal>
</Link>
</Box>
</Tooltip>
)) }
</Skeleton>
</Td>
</Tr>
</TableCell>
</TableRow>
);
};
......
......@@ -63,13 +63,12 @@ export interface EntityProps extends EntityBase.EntityBaseProps {
const BlobEntity = (props: EntityProps) => {
const partsProps = distributeEntityProps(props);
const content = <Content { ...partsProps.content }/>;
return (
<Container { ...partsProps.container }>
<Icon { ...partsProps.icon }/>
<Link { ...partsProps.link }>
<Content { ...partsProps.content }/>
</Link>
{ props.noLink ? content : <Link { ...partsProps.link }>{ content }</Link> }
<Copy { ...partsProps.copy }/>
</Container>
);
......
......@@ -107,13 +107,12 @@ export interface EntityProps extends EntityBase.EntityBaseProps {
const PoolEntity = (props: EntityProps) => {
const partsProps = distributeEntityProps(props);
const content = <Content { ...partsProps.content }/>;
return (
<Container w="100%" { ...partsProps.container }>
<Icon { ...partsProps.icon }/>
<Link { ...partsProps.link }>
<Content { ...partsProps.content }/>
</Link>
{ props.noLink ? content : <Link { ...partsProps.link }>{ content }</Link> }
</Container>
);
};
......
import {
PopoverTrigger,
PopoverBody,
PopoverContent,
Flex,
Link,
Image,
} from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import React from 'react';
import config from 'configs/app';
import Popover from 'ui/shared/chakra/Popover';
import { Image } from 'toolkit/chakra/image';
import { Link } from 'toolkit/chakra/link';
import { Tooltip } from 'toolkit/chakra/tooltip';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
const externalTxFeature = config.features.externalTxs;
......@@ -23,38 +18,43 @@ const TxExternalTxs: React.FC<Props> = ({ data }) => {
return null;
}
const content = (
<Box textStyle="sm">
<Flex alignItems="center" gap={ 2 } fontSize="md" mb={ 3 }>
<Image src={ externalTxFeature.chainLogoUrl } alt={ externalTxFeature.chainName } width={ 5 } height={ 5 }/>
{ externalTxFeature.chainName } transaction{ data.length > 1 ? 's' : '' }
</Flex>
<Flex flexDirection="column" gap={ 2 } w="100%" maxHeight="460px" overflowY="auto">
{ data.map((txHash) => (
<TxEntity
key={ txHash }
hash={ txHash }
href={ externalTxFeature.explorerUrlTemplate.replace('{hash}', txHash) }
isExternal
/>
)) }
</Flex>
</Box>
);
return (
<Popover placement="bottom-end" openDelay={ 300 } isLazy trigger="hover">
<PopoverTrigger>
<Link
_hover={{ textDecoration: 'none', color: 'link_hovered' }}
display="inline-flex"
alignItems="center"
gap={ 2 }
>
<Image src={ externalTxFeature.chainLogoUrl } alt={ externalTxFeature.chainName } width={ 5 } height={ 5 }/>
{ data.length } { externalTxFeature.chainName } txn{ data.length > 1 ? 's' : '' }
</Link>
</PopoverTrigger>
<PopoverContent border="1px solid" borderColor="divider" w={{ base: '300px', lg: '460px' }}>
<PopoverBody fontWeight={ 400 } fontSize="sm">
<Flex alignItems="center" gap={ 2 } fontSize="md" mb={ 3 }>
<Image src={ externalTxFeature.chainLogoUrl } alt={ externalTxFeature.chainName } width={ 5 } height={ 5 }/>
{ externalTxFeature.chainName } transaction{ data.length > 1 ? 's' : '' }
</Flex>
<Flex flexDirection="column" gap={ 2 } w="100%" maxHeight="460px" overflowY="auto">
{ data.map((txHash) => (
<TxEntity
key={ txHash }
hash={ txHash }
href={ externalTxFeature.explorerUrlTemplate.replace('{hash}', txHash) }
isExternal
/>
)) }
</Flex>
</PopoverBody>
</PopoverContent>
</Popover>
<Tooltip
content={ content }
variant="popover"
positioning={{ placement: 'bottom-end' }}
openDelay={ 300 }
contentProps={{ w: { base: '300px', lg: '460px' } }}
>
<Link
_hover={{ textDecoration: 'none', color: 'link.primary.hover' }}
display="inline-flex"
alignItems="center"
gap={ 2 }
>
<Image src={ externalTxFeature.chainLogoUrl } alt={ externalTxFeature.chainName } boxSize={ 5 }/>
{ data.length } { externalTxFeature.chainName } txn{ data.length > 1 ? 's' : '' }
</Link>
</Tooltip>
);
};
......
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