mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
Merge branch 'main' of github.com:blockscout/frontend into tom2drum/issue-1414
This commit is contained in:
@@ -29,5 +29,5 @@ jobs:
|
||||
cleanup_docker_image:
|
||||
uses: blockscout/blockscout-ci-cd/.github/workflows/cleanup_docker.yaml@master
|
||||
with:
|
||||
dockerImage: prerelease-$GITHUB_REF_NAME_SLUG
|
||||
dockerImage: review-$GITHUB_REF_NAME_SLUG
|
||||
secrets: inherit
|
||||
|
||||
Vendored
+1
@@ -318,6 +318,7 @@
|
||||
"main.L2",
|
||||
"poa_core",
|
||||
"eth_goerli",
|
||||
"sepolia",
|
||||
"eth",
|
||||
"rootstock",
|
||||
"polygon",
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Set of ENVs for Sepolia testnet network explorer
|
||||
# https://eth-sepolia.blockscout.com/
|
||||
|
||||
# app configuration
|
||||
NEXT_PUBLIC_APP_PROTOCOL=http
|
||||
NEXT_PUBLIC_APP_HOST=localhost
|
||||
NEXT_PUBLIC_APP_PORT=3000
|
||||
|
||||
# blockchain parameters
|
||||
NEXT_PUBLIC_NETWORK_NAME=Sepolia
|
||||
NEXT_PUBLIC_NETWORK_SHORT_NAME=Sepolia
|
||||
NEXT_PUBLIC_NETWORK_ID=11155111
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
|
||||
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
|
||||
NEXT_PUBLIC_NETWORK_RPC_URL=https://eth-sepolia.public.blastapi.io
|
||||
NEXT_PUBLIC_IS_TESTNET=true
|
||||
|
||||
# api configuration
|
||||
NEXT_PUBLIC_API_HOST=eth-sepolia.blockscout.com
|
||||
NEXT_PUBLIC_API_BASE_PATH=/
|
||||
|
||||
# ui config
|
||||
## homepage
|
||||
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
|
||||
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='rgba(51, 53, 67, 1)'
|
||||
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='rgba(165, 252, 122, 1)'
|
||||
## sidebar
|
||||
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/eth-sepolia.json
|
||||
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg
|
||||
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg
|
||||
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png
|
||||
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png
|
||||
NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://sepolia.drpc.org?ref=559183','text':'Public RPC'}]
|
||||
## footer
|
||||
NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/sepolia.json
|
||||
##views
|
||||
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'LooksRare','collection_url':'https://sepolia.looksrare.org/collections/{hash}','instance_url':'https://sepolia.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]
|
||||
## misc
|
||||
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://sepolia.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}},{'title':'Tenderly','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/sepolia'}}]
|
||||
|
||||
# app features
|
||||
NEXT_PUBLIC_APP_ENV=development
|
||||
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xbf69c7abc4fee283b59a9633dadfdaedde5c5ee0fba3e80a08b5b8a3acbd4363
|
||||
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
|
||||
NEXT_PUBLIC_AUTH_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_LOGOUT_URL=https://blockscout-goerli.us.auth0.com/v2/logout
|
||||
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
|
||||
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
|
||||
NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s-dev.blockscout.com
|
||||
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
|
||||
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
|
||||
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
|
||||
NEXT_PUBLIC_WEB3_WALLETS=['token_pocket','metamask']
|
||||
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
|
||||
NEXT_PUBLIC_HAS_BEACON_CHAIN=true
|
||||
|
||||
#meta
|
||||
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/sepolia-testnet.png
|
||||
+12
-1
@@ -4,15 +4,26 @@ import React from 'react';
|
||||
|
||||
import PageNextJs from 'nextjs/PageNextJs';
|
||||
|
||||
import config from 'configs/app';
|
||||
import LinkExternal from 'ui/shared/LinkExternal';
|
||||
import PageTitle from 'ui/shared/Page/PageTitle';
|
||||
|
||||
const feature = config.features.marketplace;
|
||||
|
||||
const Marketplace = dynamic(() => import('ui/pages/Marketplace'), { ssr: false });
|
||||
|
||||
const Page: NextPage = () => {
|
||||
return (
|
||||
<PageNextJs pathname="/apps">
|
||||
<>
|
||||
<PageTitle title="DAppscout"/>
|
||||
<PageTitle
|
||||
title="DAppscout"
|
||||
contentAfter={ feature.isEnabled && (
|
||||
<LinkExternal href={ feature.submitFormUrl } variant="subtle" fontSize="sm" lineHeight={ 5 } ml="auto">
|
||||
Submit app
|
||||
</LinkExternal>
|
||||
) }
|
||||
/>
|
||||
<Marketplace/>
|
||||
</>
|
||||
</PageNextJs>
|
||||
|
||||
@@ -78,6 +78,31 @@ test('verified with changed byte code socket', async({ mount, page, createSocket
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('verified via lookup in eth_bytecode_db', async({ mount, page, createSocket }) => {
|
||||
await page.route(CONTRACT_API_URL, (route) => route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify(contractMock.nonVerified),
|
||||
}));
|
||||
await page.route('https://cdn.jsdelivr.net/npm/[email protected]/**', (route) => route.abort());
|
||||
|
||||
await mount(
|
||||
<TestApp withSocket>
|
||||
<ContractCode addressHash={ addressHash }/>
|
||||
</TestApp>,
|
||||
{ hooksConfig },
|
||||
);
|
||||
|
||||
const socket = await createSocket();
|
||||
const channel = await socketServer.joinChannel(socket, 'addresses:' + addressHash.toLowerCase());
|
||||
|
||||
await page.waitForResponse(CONTRACT_API_URL);
|
||||
socketServer.sendMessage(socket, channel, 'smart_contract_was_verified', {});
|
||||
|
||||
const request = await page.waitForRequest(CONTRACT_API_URL);
|
||||
|
||||
expect(request).toBeTruthy();
|
||||
});
|
||||
|
||||
test('verified with multiple sources', async({ mount, page }) => {
|
||||
await page.route(CONTRACT_API_URL, (route) => route.fulfill({
|
||||
status: 200,
|
||||
|
||||
@@ -38,7 +38,6 @@ const ContractCode = ({ addressHash, noSocket }: Props) => {
|
||||
const [ isChangedBytecodeSocket, setIsChangedBytecodeSocket ] = React.useState<boolean>();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const refetchQueries = queryClient.refetchQueries;
|
||||
const addressInfo = queryClient.getQueryData<AddressInfo>(getResourceKey('address', { pathParams: { hash: addressHash } }));
|
||||
|
||||
const { data, isPlaceholderData, isError } = useApiQuery('contract', {
|
||||
@@ -55,13 +54,13 @@ const ContractCode = ({ addressHash, noSocket }: Props) => {
|
||||
}, [ ]);
|
||||
|
||||
const handleContractWasVerifiedMessage: SocketMessage.SmartContractWasVerified['handler'] = React.useCallback(() => {
|
||||
refetchQueries({
|
||||
queryClient.refetchQueries({
|
||||
queryKey: getResourceKey('address', { pathParams: { hash: addressHash } }),
|
||||
});
|
||||
refetchQueries({
|
||||
queryClient.refetchQueries({
|
||||
queryKey: getResourceKey('contract', { pathParams: { hash: addressHash } }),
|
||||
});
|
||||
}, [ addressHash, refetchQueries ]);
|
||||
}, [ addressHash, queryClient ]);
|
||||
|
||||
const enableQuery = React.useCallback(() => setIsQueryEnabled(true), []);
|
||||
|
||||
|
||||
@@ -22,7 +22,13 @@ const ERC20TokensTableItem = ({
|
||||
} = getCurrencyValue({ value: value, exchangeRate: token.exchange_rate, decimals: token.decimals, accuracy: 8, accuracyUsd: 2 });
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
<Tr
|
||||
sx={{
|
||||
'&:hover [aria-label="Add token to wallet"]': {
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Td verticalAlign="middle">
|
||||
<TokenEntity
|
||||
token={ token }
|
||||
@@ -39,7 +45,7 @@ const ERC20TokensTableItem = ({
|
||||
isLoading={ isLoading }
|
||||
noIcon
|
||||
/>
|
||||
<AddressAddToWallet token={ token } ml={ 4 } isLoading={ isLoading }/>
|
||||
<AddressAddToWallet token={ token } ml={ 4 } isLoading={ isLoading } opacity="0"/>
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td isNumeric verticalAlign="middle">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Link, Skeleton } from '@chakra-ui/react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
@@ -7,7 +7,6 @@ import MarketplaceCategoriesMenu from 'ui/marketplace/MarketplaceCategoriesMenu'
|
||||
import MarketplaceDisclaimerModal from 'ui/marketplace/MarketplaceDisclaimerModal';
|
||||
import MarketplaceList from 'ui/marketplace/MarketplaceList';
|
||||
import FilterInput from 'ui/shared/filters/FilterInput';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
import useMarketplace from '../marketplace/useMarketplace';
|
||||
const feature = config.features.marketplace;
|
||||
@@ -91,29 +90,6 @@ const Marketplace = () => {
|
||||
appId={ selectedApp.id }
|
||||
/>
|
||||
) }
|
||||
|
||||
<Skeleton
|
||||
isLoaded={ !isPlaceholderData }
|
||||
marginTop={{ base: 8, sm: 16 }}
|
||||
display="inline-block"
|
||||
>
|
||||
<Link
|
||||
fontWeight="bold"
|
||||
display="inline-flex"
|
||||
alignItems="baseline"
|
||||
href={ feature.submitFormUrl }
|
||||
isExternal
|
||||
>
|
||||
<IconSvg
|
||||
name="plus"
|
||||
w={ 3 }
|
||||
h={ 3 }
|
||||
mr={ 2 }
|
||||
/>
|
||||
|
||||
Submit an app
|
||||
</Link>
|
||||
</Skeleton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -107,7 +107,7 @@ const AddressAddToWallet = ({ className, token, isLoading, variant = 'icon', ico
|
||||
|
||||
return (
|
||||
<Tooltip label={ `Add token to ${ WALLETS_INFO[wallet].name }` }>
|
||||
<Box className={ className } display="inline-flex" cursor="pointer" onClick={ handleClick } flexShrink={ 0 }>
|
||||
<Box className={ className } display="inline-flex" cursor="pointer" onClick={ handleClick } flexShrink={ 0 } aria-label="Add token to wallet">
|
||||
<IconSvg name={ WALLETS_INFO[wallet].icon } boxSize={ iconSize }/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
|
||||
@@ -12,10 +12,11 @@ import * as Layout from './components';
|
||||
const LayoutDefault = ({ children }: Props) => {
|
||||
return (
|
||||
<Layout.Container>
|
||||
<Layout.TopRow/>
|
||||
<HeaderMobile/>
|
||||
<Layout.MainArea>
|
||||
<Layout.MainColumn
|
||||
paddingTop={{ base: '138px', lg: 6 }}
|
||||
paddingTop={{ base: 16, lg: 6 }}
|
||||
paddingX={{ base: 4, lg: 6 }}
|
||||
>
|
||||
<HeaderAlert/>
|
||||
|
||||
@@ -76,7 +76,7 @@ const ProfileMenuDesktop = ({ isHomePage }: Props) => {
|
||||
textAlign="center"
|
||||
padding={ 2 }
|
||||
isDisabled={ hasMenu }
|
||||
openDelay={ 300 }
|
||||
openDelay={ 500 }
|
||||
>
|
||||
<Box>
|
||||
<PopoverTrigger>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Box, Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure, PopoverFooter } from '@chakra-ui/react';
|
||||
import { Box, Portal, Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure, PopoverFooter, useOutsideClick } from '@chakra-ui/react';
|
||||
import _debounce from 'lodash/debounce';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { FormEvent, FocusEvent } from 'react';
|
||||
import type { FormEvent } from 'react';
|
||||
import React from 'react';
|
||||
import { Element } from 'react-scroll';
|
||||
|
||||
@@ -59,13 +59,15 @@ const SearchBar = ({ isHomepage }: Props) => {
|
||||
inputRef.current?.querySelector('input')?.blur();
|
||||
}, [ onClose ]);
|
||||
|
||||
const handleBlur = React.useCallback((event: FocusEvent<HTMLFormElement>) => {
|
||||
const isFocusInMenu = menuRef.current?.contains(event.relatedTarget);
|
||||
const isFocusInInput = inputRef.current?.contains(event.relatedTarget);
|
||||
if (!isFocusInMenu && !isFocusInInput) {
|
||||
onClose();
|
||||
const handleOutsideClick = React.useCallback((event: Event) => {
|
||||
const isFocusInInput = inputRef.current?.contains(event.target as Node);
|
||||
|
||||
if (!isFocusInInput) {
|
||||
handelHide();
|
||||
}
|
||||
}, [ onClose ]);
|
||||
}, [ handelHide ]);
|
||||
|
||||
useOutsideClick({ ref: menuRef, handler: handleOutsideClick });
|
||||
|
||||
const handleClear = React.useCallback(() => {
|
||||
handleSearchTermChange('');
|
||||
@@ -118,53 +120,54 @@ const SearchBar = ({ isHomepage }: Props) => {
|
||||
onChange={ handleSearchTermChange }
|
||||
onSubmit={ handleSubmit }
|
||||
onFocus={ handleFocus }
|
||||
onBlur={ handleBlur }
|
||||
onHide={ handelHide }
|
||||
onClear={ handleClear }
|
||||
isHomepage={ isHomepage }
|
||||
value={ searchTerm }
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
w={ `${ menuWidth.current }px` }
|
||||
ref={ menuRef }
|
||||
>
|
||||
<PopoverBody
|
||||
p={ 0 }
|
||||
color="chakra-body-text"
|
||||
<Portal>
|
||||
<PopoverContent
|
||||
w={ `${ menuWidth.current }px` }
|
||||
ref={ menuRef }
|
||||
>
|
||||
<Box
|
||||
maxH="50vh"
|
||||
overflowY="auto"
|
||||
id={ SCROLL_CONTAINER_ID }
|
||||
ref={ scrollRef }
|
||||
as={ Element }
|
||||
px={ 4 }
|
||||
<PopoverBody
|
||||
p={ 0 }
|
||||
color="chakra-body-text"
|
||||
>
|
||||
{ searchTerm.trim().length === 0 && recentSearchKeywords.length > 0 && (
|
||||
<SearchBarRecentKeywords onClick={ handleSearchTermChange } onClear={ onClose }/>
|
||||
) }
|
||||
{ searchTerm.trim().length > 0 && (
|
||||
<SearchBarSuggest
|
||||
query={ query }
|
||||
searchTerm={ debouncedSearchTerm }
|
||||
onItemClick={ handleItemClick }
|
||||
containerId={ SCROLL_CONTAINER_ID }
|
||||
/>
|
||||
) }
|
||||
</Box>
|
||||
</PopoverBody>
|
||||
{ searchTerm.trim().length > 0 && query.data && query.data.length >= 50 && (
|
||||
<PopoverFooter>
|
||||
<LinkInternal
|
||||
href={ route({ pathname: '/search-results', query: { q: searchTerm } }) }
|
||||
fontSize="sm"
|
||||
<Box
|
||||
maxH="50vh"
|
||||
overflowY="auto"
|
||||
id={ SCROLL_CONTAINER_ID }
|
||||
ref={ scrollRef }
|
||||
as={ Element }
|
||||
px={ 4 }
|
||||
>
|
||||
{ searchTerm.trim().length === 0 && recentSearchKeywords.length > 0 && (
|
||||
<SearchBarRecentKeywords onClick={ handleSearchTermChange } onClear={ onClose }/>
|
||||
) }
|
||||
{ searchTerm.trim().length > 0 && (
|
||||
<SearchBarSuggest
|
||||
query={ query }
|
||||
searchTerm={ debouncedSearchTerm }
|
||||
onItemClick={ handleItemClick }
|
||||
containerId={ SCROLL_CONTAINER_ID }
|
||||
/>
|
||||
) }
|
||||
</Box>
|
||||
</PopoverBody>
|
||||
{ searchTerm.trim().length > 0 && query.data && query.data.length >= 50 && (
|
||||
<PopoverFooter>
|
||||
<LinkInternal
|
||||
href={ route({ pathname: '/search-results', query: { q: searchTerm } }) }
|
||||
fontSize="sm"
|
||||
>
|
||||
View all results
|
||||
</LinkInternal>
|
||||
</PopoverFooter>
|
||||
) }
|
||||
</PopoverContent>
|
||||
</LinkInternal>
|
||||
</PopoverFooter>
|
||||
) }
|
||||
</PopoverContent>
|
||||
</Portal>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,25 +20,33 @@ interface Props {
|
||||
}
|
||||
|
||||
const SearchBarInput = ({ onChange, onSubmit, isHomepage, onFocus, onBlur, onHide, onClear, value }: Props, ref: React.ForwardedRef<HTMLFormElement>) => {
|
||||
const innerRef = React.useRef<HTMLFormElement>(null);
|
||||
React.useImperativeHandle(ref, () => innerRef.current as HTMLFormElement, []);
|
||||
const [ isSticky, setIsSticky ] = React.useState(false);
|
||||
const scrollDirection = useScrollDirection();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const handleScroll = React.useCallback(() => {
|
||||
const TOP_BAR_HEIGHT = 36;
|
||||
if (window.pageYOffset >= TOP_BAR_HEIGHT) {
|
||||
setIsSticky(true);
|
||||
} else {
|
||||
setIsSticky(false);
|
||||
if (!isHomepage) {
|
||||
if (window.scrollY >= TOP_BAR_HEIGHT) {
|
||||
setIsSticky(true);
|
||||
} else {
|
||||
setIsSticky(false);
|
||||
}
|
||||
}
|
||||
}, [ ]);
|
||||
const clientRect = isMobile && innerRef?.current?.getBoundingClientRect();
|
||||
if (clientRect && clientRect.y < TOP_BAR_HEIGHT) {
|
||||
onHide?.();
|
||||
}
|
||||
}, [ isMobile, onHide, isHomepage ]);
|
||||
|
||||
const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(event.target.value);
|
||||
}, [ onChange ]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isMobile || isHomepage) {
|
||||
if (!isMobile) {
|
||||
return;
|
||||
}
|
||||
const throttledHandleScroll = throttle(handleScroll, 300);
|
||||
@@ -48,22 +56,14 @@ const SearchBarInput = ({ onChange, onSubmit, isHomepage, onFocus, onBlur, onHid
|
||||
return () => {
|
||||
window.removeEventListener('scroll', throttledHandleScroll);
|
||||
};
|
||||
// replicate componentDidMount
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [ isMobile ]);
|
||||
}, [ isMobile, handleScroll ]);
|
||||
|
||||
const bgColor = useColorModeValue('white', 'black');
|
||||
const transformMobile = scrollDirection !== 'down' ? 'translateY(0)' : 'translateY(-100%)';
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isMobile && scrollDirection === 'down') {
|
||||
onHide?.();
|
||||
}
|
||||
}, [ scrollDirection, onHide, isMobile ]);
|
||||
|
||||
return (
|
||||
<chakra.form
|
||||
ref={ ref }
|
||||
ref={ innerRef }
|
||||
noValidate
|
||||
onSubmit={ onSubmit }
|
||||
onBlur={ onBlur }
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { ButtonProps } from '@chakra-ui/react';
|
||||
import { Popover, PopoverContent, PopoverBody, PopoverTrigger, Button, Box, useBoolean } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import AddressIdenticon from 'ui/shared/entities/address/AddressIdenticon';
|
||||
import HashStringShorten from 'ui/shared/HashStringShorten';
|
||||
import useWallet from 'ui/snippets/walletMenu/useWallet';
|
||||
@@ -18,6 +19,7 @@ const WalletMenuDesktop = ({ isHomePage }: Props) => {
|
||||
const { isWalletConnected, address, connect, disconnect, isModalOpening, isModalOpen } = useWallet();
|
||||
const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors();
|
||||
const [ isPopoverOpen, setIsPopoverOpen ] = useBoolean(false);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const variant = React.useMemo(() => {
|
||||
if (isWalletConnected) {
|
||||
@@ -55,7 +57,7 @@ const WalletMenuDesktop = ({ isHomePage }: Props) => {
|
||||
isOpen={ isPopoverOpen }
|
||||
onClose={ setIsPopoverOpen.off }
|
||||
>
|
||||
<WalletTooltip isDisabled={ isWalletConnected }>
|
||||
<WalletTooltip isDisabled={ isWalletConnected || isMobile === undefined || isMobile }>
|
||||
<Box ml={ 2 }>
|
||||
<PopoverTrigger>
|
||||
<Button
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, IconButton } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import AddressIdenticon from 'ui/shared/entities/address/AddressIdenticon';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import useWallet from 'ui/snippets/walletMenu/useWallet';
|
||||
@@ -13,10 +14,11 @@ const WalletMenuMobile = () => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { isWalletConnected, address, connect, disconnect, isModalOpening, isModalOpen } = useWallet();
|
||||
const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WalletTooltip isDisabled={ isWalletConnected } isMobile>
|
||||
<WalletTooltip isDisabled={ isWalletConnected || isMobile === undefined || !isMobile } isMobile>
|
||||
<IconButton
|
||||
aria-label="wallet menu"
|
||||
icon={ isWalletConnected ?
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Tooltip, useBoolean } from '@chakra-ui/react';
|
||||
import { Tooltip, useBoolean, useOutsideClick } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
@@ -8,27 +9,41 @@ type Props = {
|
||||
};
|
||||
|
||||
const WalletTooltip = ({ children, isDisabled, isMobile }: Props) => {
|
||||
const router = useRouter();
|
||||
const [ isTooltipShown, setIsTooltipShown ] = useBoolean(false);
|
||||
const ref = React.useRef(null);
|
||||
useOutsideClick({ ref, handler: setIsTooltipShown.off });
|
||||
|
||||
const { defaultLabel, label, localStorageKey } = React.useMemo(() => {
|
||||
const isAppPage = router.pathname === '/apps/[id]';
|
||||
const defaultLabel = <span>Your wallet is used to interact with<br/>apps and contracts in the explorer</span>;
|
||||
const label = isAppPage ?
|
||||
<span>Connect once to use your wallet with<br/>all apps in the DAppscout marketplace!</span> :
|
||||
defaultLabel;
|
||||
const localStorageKey = `${ isAppPage ? 'dapp-' : '' }wallet-connect-tooltip-shown`;
|
||||
return { defaultLabel, label, localStorageKey };
|
||||
}, [ router.pathname ]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const key = `wallet-connect-tooltip-shown-${ isMobile ? 'mobile' : 'desktop' }`;
|
||||
const wasShown = window.localStorage.getItem(key);
|
||||
if (!wasShown) {
|
||||
const wasShown = window.localStorage.getItem(localStorageKey);
|
||||
if (!isDisabled && !wasShown) {
|
||||
setIsTooltipShown.on();
|
||||
window.localStorage.setItem(key, 'true');
|
||||
window.localStorage.setItem(localStorageKey, 'true');
|
||||
setTimeout(() => setIsTooltipShown.off(), 3000);
|
||||
}
|
||||
}, [ setIsTooltipShown, isMobile ]);
|
||||
}, [ setIsTooltipShown, localStorageKey, isDisabled ]);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
label={ <span>Your wallet is used to interact with<br/>apps and contracts in the explorer</span> }
|
||||
label={ isTooltipShown ? label : defaultLabel }
|
||||
textAlign="center"
|
||||
padding={ 2 }
|
||||
isDisabled={ isDisabled }
|
||||
openDelay={ 300 }
|
||||
openDelay={ 500 }
|
||||
isOpen={ isTooltipShown || (isMobile ? false : undefined) }
|
||||
onClose={ setIsTooltipShown.off }
|
||||
display={ isMobile ? { base: 'flex', lg: 'none' } : { base: 'none', lg: 'flex' } }
|
||||
ref={ ref }
|
||||
>
|
||||
{ children }
|
||||
</Tooltip>
|
||||
|
||||
@@ -51,7 +51,13 @@ const TokensTableItem = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
<Tr
|
||||
sx={{
|
||||
'&:hover [aria-label="Add token to wallet"]': {
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Td>
|
||||
<Flex alignItems="flex-start">
|
||||
<Skeleton
|
||||
@@ -81,7 +87,12 @@ const TokensTableItem = ({
|
||||
fontSize="sm"
|
||||
fontWeight={ 500 }
|
||||
/>
|
||||
<AddressAddToWallet token={ token } isLoading={ isLoading } iconSize={ 5 }/>
|
||||
<AddressAddToWallet
|
||||
token={ token }
|
||||
isLoading={ isLoading }
|
||||
iconSize={ 5 }
|
||||
opacity={ 0 }
|
||||
/>
|
||||
</Flex>
|
||||
<Flex columnGap={ 1 }>
|
||||
<Tag isLoading={ isLoading }>{ type }</Tag>
|
||||
|
||||
Reference in New Issue
Block a user