Commit d1f7e165 authored by Max Alekseenko's avatar Max Alekseenko Committed by GitHub

Merge pull request #1822 from blockscout/rework-marketplace-app-header

Rework marketplace app header and tooltips
parents 528ee6fa 7828a98d
import { useRouter } from 'next/router';
import React, { createContext, useContext, useEffect, useState, useMemo } from 'react';
type Props = {
children: React.ReactNode;
}
type TMarketplaceContext = {
isAutoConnectDisabled: boolean;
setIsAutoConnectDisabled: (isAutoConnectDisabled: boolean) => void;
}
const MarketplaceContext = createContext<TMarketplaceContext>({
isAutoConnectDisabled: false,
setIsAutoConnectDisabled: () => {},
});
export function MarketplaceContextProvider({ children }: Props) {
const router = useRouter();
const [ isAutoConnectDisabled, setIsAutoConnectDisabled ] = useState(false);
useEffect(() => {
const handleRouteChange = () => {
setIsAutoConnectDisabled(false);
};
router.events.on('routeChangeStart', handleRouteChange);
return () => {
router.events.off('routeChangeStart', handleRouteChange);
};
}, [ router.events ]);
const value = useMemo(() => ({
isAutoConnectDisabled,
setIsAutoConnectDisabled,
}), [ isAutoConnectDisabled, setIsAutoConnectDisabled ]);
return (
<MarketplaceContext.Provider value={ value }>
{ children }
</MarketplaceContext.Provider>
);
}
export function useMarketplaceContext() {
return useContext(MarketplaceContext);
}
......@@ -12,6 +12,7 @@ import config from 'configs/app';
import useQueryClientConfig from 'lib/api/useQueryClientConfig';
import { AppContextProvider } from 'lib/contexts/app';
import { ChakraProvider } from 'lib/contexts/chakra';
import { MarketplaceContextProvider } from 'lib/contexts/marketplace';
import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
import { growthBook } from 'lib/growthbook/init';
import useLoadFeatures from 'lib/growthbook/useLoadFeatures';
......@@ -67,7 +68,9 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
<GrowthBookProvider growthbook={ growthBook }>
<ScrollDirectionProvider>
<SocketProvider url={ `${ config.api.socket }${ config.api.basePath }/socket/v2` }>
<MarketplaceContextProvider>
{ getLayout(<Component { ...pageProps }/>) }
</MarketplaceContextProvider>
</SocketProvider>
</ScrollDirectionProvider>
</GrowthBookProvider>
......
......@@ -54,6 +54,7 @@ const AppSecurityReport = ({ id, securityReport, height, showContractList, isLoa
onClick={ handleButtonClick }
height={ height }
onlyIcon={ onlyIcon }
label="The security score is based on analysis of a DApp's smart contracts."
/>
</PopoverTrigger>
<PopoverContent w={{ base: '100vw', lg: '328px' }}>
......
import { Alert } from '@chakra-ui/react';
import React from 'react';
import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg';
type Props = {
internalWallet: boolean | undefined;
isWalletConnected: boolean;
}
const MarketplaceAppAlert = ({ internalWallet, isWalletConnected }: Props) => {
const message = React.useMemo(() => {
let icon: IconName = 'wallet';
let text = 'Connect your wallet to Blockscout for full-featured access';
let status: 'warning' | 'success' = 'warning';
if (isWalletConnected && internalWallet) {
icon = 'integration/full';
text = 'Your wallet is connected with Blockscout';
status = 'success';
} else if (!internalWallet) {
icon = 'integration/partial';
text = 'Connect your wallet in the app below';
}
return { icon, text, status };
}, [ isWalletConnected, internalWallet ]);
return (
<Alert
status={ message.status }
borderRadius="base"
px={ 3 }
py={{ base: 3, md: 1.5 }}
fontSize="sm"
lineHeight={ 5 }
>
<IconSvg
name={ message.icon }
color={ message.status === 'success' ? 'green.600' : 'current' }
boxSize={ 5 }
flexShrink={ 0 }
mr={ 2 }
/>
{ message.text }
</Alert>
);
};
export default MarketplaceAppAlert;
import { chakra, Flex, Tooltip, Skeleton, useBoolean, Box } from '@chakra-ui/react';
import { chakra, Flex, Tooltip, Skeleton, useBoolean } from '@chakra-ui/react';
import React from 'react';
import type { MarketplaceAppOverview, MarketplaceAppSecurityReport } from 'types/client/marketplace';
......@@ -6,26 +6,28 @@ import { ContractListTypes } from 'types/client/marketplace';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import { useAppContext } from 'lib/contexts/app';
import useFeatureValue from 'lib/growthbook/useFeatureValue';
import useIsMobile from 'lib/hooks/useIsMobile';
import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';
import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo';
import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop';
import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop';
import AppSecurityReport from './AppSecurityReport';
import ContractListModal from './ContractListModal';
import MarketplaceAppAlert from './MarketplaceAppAlert';
import MarketplaceAppInfo from './MarketplaceAppInfo';
type Props = {
data: MarketplaceAppOverview | undefined;
isLoading: boolean;
isWalletConnected: boolean;
securityReport?: MarketplaceAppSecurityReport;
}
const MarketplaceAppTopBar = ({ data, isLoading, isWalletConnected, securityReport }: Props) => {
const MarketplaceAppTopBar = ({ data, isLoading, securityReport }: Props) => {
const [ showContractList, setShowContractList ] = useBoolean(false);
const appProps = useAppContext();
const isMobile = useIsMobile();
......@@ -46,32 +48,14 @@ const MarketplaceAppTopBar = ({ data, isLoading, isWalletConnected, securityRepo
return (
<>
<Flex alignItems="center" flexWrap="wrap" mb={{ base: 6, md: 2 }} rowGap={ 3 } columnGap={ 2 }>
<Tooltip label="Back to dApps list" order={ 1 }>
<LinkInternal display="inline-flex" href={ goBackUrl } h="32px" isLoading={ isLoading }>
<Flex alignItems="center" flexWrap="wrap" mb={{ base: 3, md: 2 }} rowGap={ 3 } columnGap={ 2 }>
{ !isMobile && <NetworkLogo isCollapsed/> }
<Tooltip label="Back to dApps list">
<LinkInternal display="inline-flex" href={ goBackUrl } h="32px" isLoading={ isLoading } ml={ isMobile ? 0 : 4 }>
<IconSvg name="arrows/east" boxSize={ 6 } transform="rotate(180deg)" margin="auto" color="gray.400"/>
</LinkInternal>
</Tooltip>
<Skeleton width={{ base: '100%', md: 'auto' }} order={{ base: 5, md: 2 }} isLoaded={ !isLoading }>
<MarketplaceAppAlert internalWallet={ data?.internalWallet } isWalletConnected={ isWalletConnected }/>
</Skeleton>
<Skeleton order={{ base: 2, md: 3 }} isLoaded={ !isLoading }>
<MarketplaceAppInfo data={ data }/>
</Skeleton>
{ (isExperiment && (securityReport || isLoading)) && (
<Box order={{ base: 3, md: 4 }}>
<AppSecurityReport
id={ data?.id || '' }
securityReport={ securityReport }
showContractList={ setShowContractList.on }
isLoading={ isLoading }
onlyIcon={ isMobile }
source="App page"
/>
</Box>
) }
<LinkExternal
order={{ base: 4, md: 5 }}
href={ data?.url }
variant="subtle"
fontSize="sm"
......@@ -85,6 +69,25 @@ const MarketplaceAppTopBar = ({ data, isLoading, isWalletConnected, securityRepo
{ getHostname(data?.url) }
</chakra.span>
</LinkExternal>
<Skeleton isLoaded={ !isLoading }>
<MarketplaceAppInfo data={ data }/>
</Skeleton>
{ (isExperiment && (securityReport || isLoading)) && (
<AppSecurityReport
id={ data?.id || '' }
securityReport={ securityReport }
showContractList={ setShowContractList.on }
isLoading={ isLoading }
onlyIcon={ isMobile }
source="App page"
/>
) }
{ !isMobile && (
<Flex flex="1" justifyContent="flex-end">
{ config.features.account.isEnabled && <ProfileMenuDesktop boxSize="32px" fallbackIconSize={ 16 }/> }
{ config.features.blockchainInteraction.isEnabled && <WalletMenuDesktop size="sm"/> }
</Flex>
) }
</Flex>
{ showContractList && (
<ContractListModal
......
......@@ -11,6 +11,7 @@ import { route } from 'nextjs-routes';
import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch';
import { useMarketplaceContext } from 'lib/contexts/marketplace';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import useFetch from 'lib/hooks/useFetch';
import * as metadata from 'lib/metadata';
......@@ -129,6 +130,7 @@ const MarketplaceApp = () => {
enabled: feature.isEnabled,
});
const { data, isPending } = query;
const { setIsAutoConnectDisabled } = useMarketplaceContext();
useEffect(() => {
if (data) {
......@@ -136,8 +138,9 @@ const MarketplaceApp = () => {
{ pathname: '/apps/[id]', query: { id: data.id } },
{ app_name: data.title },
);
setIsAutoConnectDisabled(!data.internalWallet);
}
}, [ data ]);
}, [ data, setIsAutoConnectDisabled ]);
throwOnResourceLoadError(query);
......@@ -146,7 +149,6 @@ const MarketplaceApp = () => {
<MarketplaceAppTopBar
data={ data }
isLoading={ isPending || isSecurityReportsLoading }
isWalletConnected={ Boolean(address) }
securityReport={ securityReports?.[id] }
/>
<DappscoutIframeProvider
......
......@@ -8,9 +8,10 @@ import IconSvg from 'ui/shared/IconSvg';
interface Props {
size: number;
fallbackIconSize?: number;
}
const UserAvatar = ({ size }: Props) => {
const UserAvatar = ({ size, fallbackIconSize = 20 }: Props) => {
const appProps = useAppContext();
const hasAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN, appProps.cookies));
const [ isImageLoadError, setImageLoadError ] = React.useState(false);
......@@ -34,7 +35,7 @@ const UserAvatar = ({ size }: Props) => {
boxSize={ `${ size }px` }
borderRadius="full"
overflow="hidden"
fallback={ isImageLoadError || !data?.avatar ? <IconSvg name="profile" boxSize={ 5 }/> : undefined }
fallback={ isImageLoadError || !data?.avatar ? <IconSvg name="profile" boxSize={ `${ fallbackIconSize }px` }/> : undefined }
onError={ handleImageLoadError }
/>
);
......
......@@ -3,26 +3,28 @@ import React from 'react';
import type { Props } from './types';
import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary';
import HeaderDesktop from 'ui/snippets/header/HeaderDesktop';
import HeaderMobile from 'ui/snippets/header/HeaderMobile';
import * as Layout from './components';
const LayoutDefault = ({ children }: Props) => {
return (
<Layout.Container overflowY="hidden" height="100vh">
<Layout.Container
overflowY="hidden"
height={ [ '-webkit-fill-available', '100vh' ] }
display="flex"
flexDirection="column"
>
<Layout.TopRow/>
<HeaderMobile/>
<Layout.MainArea>
<HeaderMobile hideSearchBar/>
<Layout.MainArea minH="auto" flex={ 1 }>
<Layout.MainColumn
paddingTop={{ base: 16, lg: 6 }}
paddingTop={{ base: 0, lg: 0 }}
paddingBottom={ 0 }
paddingX={{ base: 4, lg: 6 }}
height={{ base: 'calc(100vh - 92px)', sm: 'auto' }} // 92px = Layout.TopRow + HeaderMobile
>
<HeaderDesktop isMarketplaceAppPage/>
<AppErrorBoundary>
<Layout.Content pt={{ base: 0, lg: 4 }} flexGrow={ 1 }>
<Layout.Content pt={{ base: 0, lg: 2 }} flexGrow={ 1 }>
{ children }
</Layout.Content>
</AppErrorBoundary>
......
......@@ -12,7 +12,7 @@ const LayoutHome = ({ children }: Props) => {
return (
<Layout.Container>
<Layout.TopRow/>
<HeaderMobile isHomePage/>
<HeaderMobile hideSearchBar/>
<Layout.MainArea>
<Layout.SideBar/>
<Layout.MainColumn
......
import { Flex } from '@chakra-ui/react';
import { Flex, chakra } from '@chakra-ui/react';
import React from 'react';
interface Props {
children: React.ReactNode;
className?: string;
}
const MainArea = ({ children }: Props) => {
const MainArea = ({ children, className }: Props) => {
return (
<Flex w="100%" minH="calc(100vh - 36px)" alignItems="stretch">
<Flex className={ className } w="100%" minH="calc(100vh - 36px)" alignItems="stretch">
{ children }
</Flex>
);
};
export default React.memo(MainArea);
export default React.memo(chakra(MainArea));
......@@ -13,16 +13,17 @@ interface Props {
height?: string;
onlyIcon?: boolean;
onClick?: () => void;
label?: string;
}
const SolidityscanReportButton = (
{ className, score, isLoading, height = '32px', onlyIcon, onClick }: Props,
{ className, score, isLoading, height = '32px', onlyIcon, onClick, label = 'Security score' }: Props,
ref: React.ForwardedRef<HTMLButtonElement>,
) => {
const { scoreColor } = useScoreLevelAndColor(score);
return (
<PopoverTriggerTooltip label="Security score" isLoading={ isLoading } className={ className }>
<PopoverTriggerTooltip label={ label } isLoading={ isLoading } className={ className }>
<Button
ref={ ref }
color={ scoreColor }
......
......@@ -16,11 +16,11 @@ const LOGO_IMAGE_PROPS = {
};
type Props = {
isHomePage?: boolean;
hideSearchBar?: boolean;
renderSearchBar?: () => React.ReactNode;
}
const HeaderMobile = ({ isHomePage, renderSearchBar }: Props) => {
const HeaderMobile = ({ hideSearchBar, renderSearchBar }: Props) => {
const bgColor = useColorModeValue('white', 'black');
const scrollDirection = useScrollDirection();
const { ref, inView } = useInView({ threshold: 1 });
......@@ -57,7 +57,7 @@ const HeaderMobile = ({ isHomePage, renderSearchBar }: Props) => {
{ config.features.blockchainInteraction.isEnabled && <WalletMenuMobile/> }
</Flex>
</Flex>
{ !isHomePage && searchBar }
{ !hideSearchBar && searchBar }
</Box>
);
};
......
import type { IconButtonProps } from '@chakra-ui/react';
import { Popover, PopoverContent, PopoverBody, PopoverTrigger, IconButton, Tooltip, Box } from '@chakra-ui/react';
import { Popover, PopoverContent, PopoverBody, PopoverTrigger, IconButton, Tooltip, Box, chakra } from '@chakra-ui/react';
import React from 'react';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
......@@ -12,9 +12,11 @@ import useMenuButtonColors from '../useMenuButtonColors';
type Props = {
isHomePage?: boolean;
className?: string;
fallbackIconSize?: number;
};
const ProfileMenuDesktop = ({ isHomePage }: Props) => {
const ProfileMenuDesktop = ({ isHomePage, className, fallbackIconSize }: Props) => {
const { data, error, isPending } = useFetchProfileInfo();
const loginUrl = useLoginUrl();
const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors();
......@@ -81,8 +83,9 @@ const ProfileMenuDesktop = ({ isHomePage }: Props) => {
<Box>
<PopoverTrigger>
<IconButton
className={ className }
aria-label="profile menu"
icon={ <UserAvatar size={ 20 }/> }
icon={ <UserAvatar size={ 20 } fallbackIconSize={ fallbackIconSize }/> }
variant={ variant }
colorScheme="blue"
boxSize="40px"
......@@ -104,4 +107,4 @@ const ProfileMenuDesktop = ({ isHomePage }: Props) => {
);
};
export default ProfileMenuDesktop;
export default chakra(ProfileMenuDesktop);
......@@ -2,8 +2,9 @@ import { useColorModeValue } from '@chakra-ui/react';
export default function useMenuColors() {
const themedBackground = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const themedBackgroundOrange = useColorModeValue('orange.100', 'orange.900');
const themedBorderColor = useColorModeValue('gray.300', 'gray.700');
const themedColor = useColorModeValue('blackAlpha.800', 'gray.400');
return { themedBackground, themedBorderColor, themedColor };
return { themedBackground, themedBackgroundOrange, themedBorderColor, themedColor };
}
import { Box, Flex, chakra } 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 useMenuButtonColors from '../useMenuButtonColors';
type Props = {
address: string;
isAutoConnectDisabled?: boolean;
className?: string;
};
const WalletIdenticon = ({ address, isAutoConnectDisabled, className }: Props) => {
const { themedBackgroundOrange } = useMenuButtonColors();
const isMobile = useIsMobile();
return (
<Box className={ className } position="relative">
<AddressIdenticon size={ 20 } hash={ address }/>
{ isAutoConnectDisabled && (
<Flex
alignItems="center"
justifyContent="center"
boxSize="14px"
position="absolute"
bottom={ isMobile ? '-3px' : '-1px' }
right={ isMobile ? '-4px' : '-5px' }
backgroundColor="rgba(16, 17, 18, 0.80)"
borderRadius="full"
border="1px solid"
borderColor={ themedBackgroundOrange }
>
<IconSvg
name="integration/partial"
color="white"
boxSize="8px"
/>
</Flex>
) }
</Box>
);
};
export default chakra(WalletIdenticon);
import { Box, Button, Text } from '@chakra-ui/react';
import { Box, Button, Text, Flex } from '@chakra-ui/react';
import React from 'react';
import * as mixpanel from 'lib/mixpanel/index';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import IconSvg from 'ui/shared/IconSvg';
import useMenuButtonColors from '../useMenuButtonColors';
type Props = {
address?: string;
disconnect?: () => void;
isAutoConnectDisabled?: boolean;
};
const WalletMenuContent = ({ address, disconnect }: Props) => {
const WalletMenuContent = ({ address, disconnect, isAutoConnectDisabled }: Props) => {
const { themedBackgroundOrange } = useMenuButtonColors();
const onAddressClick = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.WALLET_ACTION, { Action: 'Address click' });
}, []);
return (
<Box>
{ isAutoConnectDisabled && (
<Flex
borderRadius="base"
p={ 3 }
mb={ 3 }
alignItems="center"
backgroundColor={ themedBackgroundOrange }
>
<IconSvg
name="integration/partial"
color="text"
boxSize={ 5 }
flexShrink={ 0 }
mr={ 2 }
/>
<Text fontSize="xs" lineHeight="16px">
Connect your wallet in the app below
</Text>
</Flex>
) }
<Text
fontSize="sm"
fontWeight={ 600 }
......
import type { ButtonProps } from '@chakra-ui/react';
import { Popover, PopoverContent, PopoverBody, PopoverTrigger, Button, Box, useBoolean } from '@chakra-ui/react';
import { Popover, PopoverContent, PopoverBody, PopoverTrigger, Button, Box, useBoolean, chakra, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import { useMarketplaceContext } from 'lib/contexts/marketplace';
import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index';
import AddressIdenticon from 'ui/shared/entities/address/AddressIdenticon';
import HashStringShorten from 'ui/shared/HashStringShorten';
import useWallet from 'ui/snippets/walletMenu/useWallet';
import WalletMenuContent from 'ui/snippets/walletMenu/WalletMenuContent';
import useMenuButtonColors from '../useMenuButtonColors';
import WalletIdenticon from './WalletIdenticon';
import WalletTooltip from './WalletTooltip';
type Props = {
isHomePage?: boolean;
className?: string;
size?: 'sm' | 'md';
};
const WalletMenuDesktop = ({ isHomePage }: Props) => {
const WalletMenuDesktop = ({ isHomePage, className, size = 'md' }: Props) => {
const { isWalletConnected, address, connect, disconnect, isModalOpening, isModalOpen } = useWallet({ source: 'Header' });
const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors();
const { themedBackground, themedBackgroundOrange, themedBorderColor, themedColor } = useMenuButtonColors();
const [ isPopoverOpen, setIsPopoverOpen ] = useBoolean(false);
const isMobile = useIsMobile();
const { isAutoConnectDisabled } = useMarketplaceContext();
const variant = React.useMemo(() => {
if (isWalletConnected) {
......@@ -29,13 +33,16 @@ const WalletMenuDesktop = ({ isHomePage }: Props) => {
return isHomePage ? 'solid' : 'outline';
}, [ isWalletConnected, isHomePage ]);
const themedColorForOrangeBg = useColorModeValue('blackAlpha.800', 'whiteAlpha.800');
let buttonStyles: Partial<ButtonProps> = {};
if (isWalletConnected) {
const backgroundColor = isAutoConnectDisabled ? themedBackgroundOrange : themedBackground;
const color = isAutoConnectDisabled ? themedColorForOrangeBg : themedColor;
buttonStyles = {
bg: isHomePage ? 'blue.50' : themedBackground,
color: isHomePage ? 'blackAlpha.800' : themedColor,
bg: isHomePage ? 'blue.50' : backgroundColor,
color: isHomePage ? 'blackAlpha.800' : color,
_hover: {
color: isHomePage ? 'blackAlpha.800' : themedColor,
color: isHomePage ? 'blackAlpha.800' : color,
},
};
} else if (isHomePage) {
......@@ -51,7 +58,7 @@ const WalletMenuDesktop = ({ isHomePage }: Props) => {
const openPopover = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.WALLET_ACTION, { Action: 'Open' });
setIsPopoverOpen.on();
setIsPopoverOpen.toggle();
}, [ setIsPopoverOpen ]);
return (
......@@ -63,10 +70,15 @@ const WalletMenuDesktop = ({ isHomePage }: Props) => {
isOpen={ isPopoverOpen }
onClose={ setIsPopoverOpen.off }
>
<WalletTooltip isDisabled={ isWalletConnected || isMobile === undefined || isMobile }>
<Box ml={ 2 }>
<PopoverTrigger>
<WalletTooltip
isDisabled={ isMobile === undefined || isMobile }
isWalletConnected={ isWalletConnected }
isAutoConnectDisabled={ isAutoConnectDisabled }
>
<Button
className={ className }
variant={ variant }
colorScheme="blue"
flexShrink={ 0 }
......@@ -74,24 +86,23 @@ const WalletMenuDesktop = ({ isHomePage }: Props) => {
loadingText="Connect wallet"
onClick={ isWalletConnected ? openPopover : connect }
fontSize="sm"
size={ size }
{ ...buttonStyles }
>
{ isWalletConnected ? (
<>
<Box mr={ 2 }>
<AddressIdenticon size={ 20 } hash={ address }/>
</Box>
<WalletIdenticon address={ address } isAutoConnectDisabled={ isAutoConnectDisabled } mr={ 2 }/>
<HashStringShorten hash={ address } isTooltipDisabled/>
</>
) : 'Connect wallet' }
</Button>
</WalletTooltip>
</PopoverTrigger>
</Box>
</WalletTooltip>
{ isWalletConnected && (
<PopoverContent w="235px">
<PopoverBody padding="24px 16px 16px 16px">
<WalletMenuContent address={ address } disconnect={ disconnect }/>
<WalletMenuContent address={ address } disconnect={ disconnect } isAutoConnectDisabled={ isAutoConnectDisabled }/>
</PopoverBody>
</PopoverContent>
) }
......@@ -99,4 +110,4 @@ const WalletMenuDesktop = ({ isHomePage }: Props) => {
);
};
export default WalletMenuDesktop;
export default chakra(WalletMenuDesktop);
import { Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, IconButton } from '@chakra-ui/react';
import React from 'react';
import { useMarketplaceContext } from 'lib/contexts/marketplace';
import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index';
import AddressIdenticon from 'ui/shared/entities/address/AddressIdenticon';
import IconSvg from 'ui/shared/IconSvg';
import useWallet from 'ui/snippets/walletMenu/useWallet';
import WalletMenuContent from 'ui/snippets/walletMenu/WalletMenuContent';
import useMenuButtonColors from '../useMenuButtonColors';
import WalletIdenticon from './WalletIdenticon';
import WalletTooltip from './WalletTooltip';
const WalletMenuMobile = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
const { isWalletConnected, address, connect, disconnect, isModalOpening, isModalOpen } = useWallet({ source: 'Header' });
const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors();
const { themedBackground, themedBackgroundOrange, themedBorderColor, themedColor } = useMenuButtonColors();
const isMobile = useIsMobile();
const { isAutoConnectDisabled } = useMarketplaceContext();
const openPopover = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.WALLET_ACTION, { Action: 'Open' });
onOpen();
}, [ onOpen ]);
const themedBg = isAutoConnectDisabled ? themedBackgroundOrange : themedBackground;
return (
<>
<WalletTooltip isDisabled={ isWalletConnected || isMobile === undefined || !isMobile } isMobile>
<WalletTooltip
isDisabled={ isMobile === undefined || !isMobile }
isMobile
isWalletConnected={ isWalletConnected }
isAutoConnectDisabled={ isAutoConnectDisabled }
>
<IconButton
aria-label="wallet menu"
icon={ isWalletConnected ?
<AddressIdenticon size={ 20 } hash={ address }/> :
<WalletIdenticon address={ address } isAutoConnectDisabled={ isAutoConnectDisabled }/> :
<IconSvg name="wallet" boxSize={ 6 }/>
}
variant={ isWalletConnected ? 'subtle' : 'outline' }
colorScheme="gray"
boxSize="40px"
flexShrink={ 0 }
bg={ isWalletConnected ? themedBackground : undefined }
bg={ isWalletConnected ? themedBg : undefined }
color={ themedColor }
borderColor={ !isWalletConnected ? themedBorderColor : undefined }
onClick={ isWalletConnected ? openPopover : connect }
......@@ -52,7 +61,7 @@ const WalletMenuMobile = () => {
<DrawerOverlay/>
<DrawerContent maxWidth="260px">
<DrawerBody p={ 6 }>
<WalletMenuContent address={ address } disconnect={ disconnect }/>
<WalletMenuContent address={ address } disconnect={ disconnect } isAutoConnectDisabled={ isAutoConnectDisabled }/>
</DrawerBody>
</DrawerContent>
</Drawer>
......
import { Tooltip, useBoolean, useOutsideClick } from '@chakra-ui/react';
import { Tooltip, useBoolean, useOutsideClick, Box } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -9,38 +9,46 @@ type Props = {
children: React.ReactNode;
isDisabled?: boolean;
isMobile?: boolean;
isWalletConnected?: boolean;
isAutoConnectDisabled?: boolean;
};
const WalletTooltip = ({ children, isDisabled, isMobile }: Props) => {
const LOCAL_STORAGE_KEY = 'wallet-connect-tooltip-shown';
const WalletTooltip = ({ children, isDisabled, isMobile, isWalletConnected, isAutoConnectDisabled }: Props, ref: React.ForwardedRef<HTMLDivElement>) => {
const router = useRouter();
const [ isTooltipShown, setIsTooltipShown ] = useBoolean(false);
const ref = React.useRef(null);
useOutsideClick({ ref, handler: setIsTooltipShown.off });
const innerRef = React.useRef(null);
useOutsideClick({ ref: innerRef, handler: setIsTooltipShown.off });
const label = React.useMemo(() => {
if (isWalletConnected) {
if (isAutoConnectDisabled) {
return <span>Your wallet is not<br/>connected to this app.<br/>Connect your wallet<br/>in the app directly</span>;
}
return <span>Your wallet is connected<br/>with Blockscout</span>;
}
return <span>Connect your wallet<br/>to Blockscout for<br/>full-featured access</span>;
}, [ isWalletConnected, isAutoConnectDisabled ]);
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 wasShown = window.localStorage.getItem(localStorageKey);
const isMarketplacePage = [ '/apps', '/apps/[id]' ].includes(router.pathname);
const wasShown = window.localStorage.getItem(LOCAL_STORAGE_KEY);
const isMarketplacePage = router.pathname === '/apps';
const isTooltipShowAction = router.query.action === 'tooltip';
const isConnectWalletAction = router.query.action === 'connect';
const needToShow = (!wasShown && !isConnectWalletAction) || isTooltipShowAction;
const needToShow = (isAppPage && !isConnectWalletAction) || isTooltipShowAction || (!wasShown && isMarketplacePage);
let timer1: ReturnType<typeof setTimeout>;
let timer2: ReturnType<typeof setTimeout>;
if (!isDisabled && isMarketplacePage && needToShow) {
if (!isDisabled && needToShow) {
timer1 = setTimeout(() => {
setIsTooltipShown.on();
window.localStorage.setItem(localStorageKey, 'true');
timer2 = setTimeout(() => setIsTooltipShown.off(), 5 * SECOND);
if (!wasShown && isMarketplacePage) {
window.localStorage.setItem(LOCAL_STORAGE_KEY, 'true');
}
if (isTooltipShowAction) {
removeQueryParam(router, 'action');
}
......@@ -51,23 +59,25 @@ const WalletTooltip = ({ children, isDisabled, isMobile }: Props) => {
clearTimeout(timer1);
clearTimeout(timer2);
};
}, [ setIsTooltipShown, localStorageKey, isDisabled, router ]);
}, [ setIsTooltipShown, isDisabled, router, isAppPage ]);
return (
<Box ref={ ref }>
<Tooltip
label={ isTooltipShown ? label : defaultLabel }
label={ label }
textAlign="center"
padding={ 2 }
isDisabled={ isDisabled }
isDisabled={ isDisabled || (isWalletConnected && !isAppPage) }
openDelay={ 500 }
isOpen={ isTooltipShown || (isMobile ? false : undefined) }
onClose={ setIsTooltipShown.off }
display={ isMobile ? { base: 'flex', lg: 'none' } : { base: 'none', lg: 'flex' } }
ref={ ref }
ref={ innerRef }
>
{ children }
</Tooltip>
</Box>
);
};
export default WalletTooltip;
export default React.forwardRef(WalletTooltip);
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