Commit 5b91364b authored by tom's avatar tom

remove old components

parent 4b18a656
...@@ -6,6 +6,7 @@ import config from 'configs/app'; ...@@ -6,6 +6,7 @@ import config from 'configs/app';
const feature = config.features.account; const feature = config.features.account;
// TODO @tom2drum remove this hook
export default function useLoginUrl() { export default function useLoginUrl() {
const router = useRouter(); const router = useRouter();
return feature.isEnabled ? return feature.isEnabled ?
......
...@@ -7,6 +7,7 @@ import type { ResourceError } from 'lib/api/resources'; ...@@ -7,6 +7,7 @@ import type { ResourceError } from 'lib/api/resources';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useLoginUrl from 'lib/hooks/useLoginUrl'; import useLoginUrl from 'lib/hooks/useLoginUrl';
// TODO @tom2drum remove or revise this hook
export default function useRedirectForInvalidAuthToken() { export default function useRedirectForInvalidAuthToken() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
......
...@@ -6,11 +6,11 @@ export enum EventTypes { ...@@ -6,11 +6,11 @@ export enum EventTypes {
SEARCH_QUERY = 'Search query', SEARCH_QUERY = 'Search query',
LOCAL_SEARCH = 'Local search', LOCAL_SEARCH = 'Local search',
ADD_TO_WALLET = 'Add to wallet', ADD_TO_WALLET = 'Add to wallet',
ACCOUNT_ACCESS = 'Account access', ACCOUNT_ACCESS = 'Account access', // deprecated
PRIVATE_TAG = 'Private tag', PRIVATE_TAG = 'Private tag',
VERIFY_ADDRESS = 'Verify address', VERIFY_ADDRESS = 'Verify address',
VERIFY_TOKEN = 'Verify token', VERIFY_TOKEN = 'Verify token',
WALLET_CONNECT = 'Wallet connect', WALLET_CONNECT = 'Wallet connect', // 🦆
WALLET_ACTION = 'Wallet action', WALLET_ACTION = 'Wallet action',
CONTRACT_INTERACTION = 'Contract interaction', CONTRACT_INTERACTION = 'Contract interaction',
CONTRACT_VERIFICATION = 'Contract verification', CONTRACT_VERIFICATION = 'Contract verification',
......
...@@ -8,11 +8,11 @@ interface Params { ...@@ -8,11 +8,11 @@ interface Params {
source: mixpanel.EventPayload<mixpanel.EventTypes.WALLET_CONNECT>['Source']; source: mixpanel.EventPayload<mixpanel.EventTypes.WALLET_CONNECT>['Source'];
} }
export default function useWallet({ source }: Params) { export default function useWeb3Wallet({ source }: Params) {
const { open } = useWeb3Modal(); const { open: openModal } = useWeb3Modal();
const { open: isOpen } = useWeb3ModalState(); const { open: isOpen } = useWeb3ModalState();
const { disconnect } = useDisconnect(); const { disconnect } = useDisconnect();
const [ isModalOpening, setIsModalOpening ] = React.useState(false); const [ isOpening, setIsOpening ] = React.useState(false);
const [ isClientLoaded, setIsClientLoaded ] = React.useState(false); const [ isClientLoaded, setIsClientLoaded ] = React.useState(false);
const isConnectionStarted = React.useRef(false); const isConnectionStarted = React.useRef(false);
...@@ -21,12 +21,12 @@ export default function useWallet({ source }: Params) { ...@@ -21,12 +21,12 @@ export default function useWallet({ source }: Params) {
}, []); }, []);
const handleConnect = React.useCallback(async() => { const handleConnect = React.useCallback(async() => {
setIsModalOpening(true); setIsOpening(true);
await open(); await openModal();
setIsModalOpening(false); setIsOpening(false);
mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Started' }); mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Started' });
isConnectionStarted.current = true; isConnectionStarted.current = true;
}, [ open, source ]); }, [ openModal, source ]);
const handleAccountConnected = React.useCallback(({ isReconnected }: { isReconnected: boolean }) => { const handleAccountConnected = React.useCallback(({ isReconnected }: { isReconnected: boolean }) => {
if (!isReconnected && isConnectionStarted.current) { if (!isReconnected && isConnectionStarted.current) {
...@@ -46,15 +46,14 @@ export default function useWallet({ source }: Params) { ...@@ -46,15 +46,14 @@ export default function useWallet({ source }: Params) {
const { address, isDisconnected } = useAccount(); const { address, isDisconnected } = useAccount();
const isWalletConnected = isClientLoaded && !isDisconnected && address !== undefined; const isConnected = isClientLoaded && !isDisconnected && address !== undefined;
return { return {
openModal: open,
isWalletConnected,
address: address || '',
connect: handleConnect, connect: handleConnect,
disconnect: handleDisconnect, disconnect: handleDisconnect,
isModalOpening, isOpen: isOpening || isOpen,
isModalOpen: isOpen, isConnected,
address,
openModal,
}; };
} }
export const base = { import type { UserInfo } from 'types/api/account';
export const base: UserInfo = {
avatar: 'https://avatars.githubusercontent.com/u/22130104', avatar: 'https://avatars.githubusercontent.com/u/22130104',
email: 'tom@ohhhh.me', email: 'tom@ohhhh.me',
name: 'tom goriunov', name: 'tom goriunov',
nickname: 'tom2drum', nickname: 'tom2drum',
address_hash: null,
}; };
export const withoutEmail = { export const withoutEmail: UserInfo = {
avatar: 'https://avatars.githubusercontent.com/u/22130104', avatar: 'https://avatars.githubusercontent.com/u/22130104',
email: null, email: null,
name: 'tom goriunov', name: 'tom goriunov',
nickname: 'tom2drum', nickname: 'tom2drum',
address_hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859',
}; };
...@@ -3,28 +3,28 @@ import React from 'react'; ...@@ -3,28 +3,28 @@ 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 useWeb3Wallet from 'lib/web3/useWallet';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import useWallet from 'ui/snippets/walletMenu/useWallet';
interface Props { interface Props {
isLoading?: boolean; isLoading?: boolean;
} }
const ContractConnectWallet = ({ isLoading }: Props) => { const ContractConnectWallet = ({ isLoading }: Props) => {
const { isModalOpening, isModalOpen, connect, disconnect, address, isWalletConnected } = useWallet({ source: 'Smart contracts' }); const web3Wallet = useWeb3Wallet({ source: 'Smart contracts' });
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const content = (() => { const content = (() => {
if (!isWalletConnected) { if (!web3Wallet.isConnected) {
return ( return (
<> <>
<span>Disconnected</span> <span>Disconnected</span>
<Button <Button
ml={ 3 } ml={ 3 }
onClick={ connect } onClick={ web3Wallet.connect }
size="sm" size="sm"
variant="outline" variant="outline"
isLoading={ isModalOpening || isModalOpen } isLoading={ web3Wallet.isOpen }
loadingText="Connect wallet" loadingText="Connect wallet"
> >
Connect wallet Connect wallet
...@@ -38,20 +38,20 @@ const ContractConnectWallet = ({ isLoading }: Props) => { ...@@ -38,20 +38,20 @@ const ContractConnectWallet = ({ isLoading }: Props) => {
<Flex alignItems="center"> <Flex alignItems="center">
<span>Connected to </span> <span>Connected to </span>
<AddressEntity <AddressEntity
address={{ hash: address }} address={{ hash: web3Wallet.address || '' }}
truncation={ isMobile ? 'constant' : 'dynamic' } truncation={ isMobile ? 'constant' : 'dynamic' }
fontWeight={ 600 } fontWeight={ 600 }
ml={ 2 } ml={ 2 }
/> />
</Flex> </Flex>
<Button onClick={ disconnect } size="sm" variant="outline">Disconnect</Button> <Button onClick={ web3Wallet.disconnect } size="sm" variant="outline">Disconnect</Button>
</Flex> </Flex>
); );
})(); })();
return ( return (
<Skeleton isLoaded={ !isLoading } mb={ 6 }> <Skeleton isLoaded={ !isLoading } mb={ 6 }>
<Alert status={ address ? 'success' : 'warning' }> <Alert status={ web3Wallet.address ? 'success' : 'warning' }>
{ content } { content }
</Alert> </Alert>
</Skeleton> </Skeleton>
......
...@@ -12,7 +12,7 @@ const authTest = test.extend<{ context: BrowserContext }>({ ...@@ -12,7 +12,7 @@ const authTest = test.extend<{ context: BrowserContext }>({
context: contextWithAuth, context: contextWithAuth,
}); });
authTest('customization +@dark-mode', async({ render, page, mockEnvs, mockApiResponse, mockAssetResponse }) => { authTest('customization +@dark-mode', async({ render, page, mockEnvs, mockApiResponse }) => {
const IMAGE_URL = 'https://localhost:3000/my-image.png'; const IMAGE_URL = 'https://localhost:3000/my-image.png';
await mockEnvs([ await mockEnvs([
...@@ -28,7 +28,6 @@ authTest('customization +@dark-mode', async({ render, page, mockEnvs, mockApiRes ...@@ -28,7 +28,6 @@ authTest('customization +@dark-mode', async({ render, page, mockEnvs, mockApiRes
}); });
await mockApiResponse('user_info', profileMock.base); await mockApiResponse('user_info', profileMock.base);
await mockAssetResponse(profileMock.base.avatar, './playwright/mocks/image_s.jpg');
const component = await render(<HeroBanner/>); const component = await render(<HeroBanner/>);
......
...@@ -3,11 +3,11 @@ import { useEffect, useRef } from 'react'; ...@@ -3,11 +3,11 @@ import { useEffect, useRef } from 'react';
import removeQueryParam from 'lib/router/removeQueryParam'; import removeQueryParam from 'lib/router/removeQueryParam';
import updateQueryParam from 'lib/router/updateQueryParam'; import updateQueryParam from 'lib/router/updateQueryParam';
import useWallet from 'ui/snippets/walletMenu/useWallet'; import useWeb3Wallet from 'lib/web3/useWallet';
export default function useAutoConnectWallet() { export default function useAutoConnectWallet() {
const router = useRouter(); const router = useRouter();
const { isWalletConnected, isModalOpen, connect } = useWallet({ source: 'Swap button' }); const web3Wallet = useWeb3Wallet({ source: 'Swap button' });
const isConnectionStarted = useRef(false); const isConnectionStarted = useRef(false);
useEffect(() => { useEffect(() => {
...@@ -17,11 +17,11 @@ export default function useAutoConnectWallet() { ...@@ -17,11 +17,11 @@ export default function useAutoConnectWallet() {
let timer: ReturnType<typeof setTimeout>; let timer: ReturnType<typeof setTimeout>;
if (!isWalletConnected && !isModalOpen) { if (!web3Wallet.isConnected && !web3Wallet.isOpen) {
if (!isConnectionStarted.current) { if (!isConnectionStarted.current) {
timer = setTimeout(() => { timer = setTimeout(() => {
if (!isWalletConnected) { if (!web3Wallet.isConnected) {
connect(); web3Wallet.connect();
isConnectionStarted.current = true; isConnectionStarted.current = true;
} }
}, 500); }, 500);
...@@ -29,11 +29,11 @@ export default function useAutoConnectWallet() { ...@@ -29,11 +29,11 @@ export default function useAutoConnectWallet() {
isConnectionStarted.current = false; isConnectionStarted.current = false;
updateQueryParam(router, 'action', 'tooltip'); updateQueryParam(router, 'action', 'tooltip');
} }
} else if (isWalletConnected) { } else if (web3Wallet.isConnected) {
isConnectionStarted.current = false; isConnectionStarted.current = false;
removeQueryParam(router, 'action'); removeQueryParam(router, 'action');
} }
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [ isWalletConnected, isModalOpen, connect, router ]); }, [ router, web3Wallet ]);
} }
...@@ -12,7 +12,7 @@ const testWithAuth = test.extend<{ context: BrowserContext }>({ ...@@ -12,7 +12,7 @@ const testWithAuth = test.extend<{ context: BrowserContext }>({
context: contextWithAuth, context: contextWithAuth,
}); });
testWithAuth('base view +@dark-mode', async({ render, mockApiResponse, mockAssetResponse, mockEnvs, page }) => { testWithAuth('base view +@dark-mode', async({ render, mockApiResponse, mockEnvs, page }) => {
const hooksConfig = { const hooksConfig = {
router: { router: {
route: '/blocks', route: '/blocks',
...@@ -21,7 +21,6 @@ testWithAuth('base view +@dark-mode', async({ render, mockApiResponse, mockAsset ...@@ -21,7 +21,6 @@ testWithAuth('base view +@dark-mode', async({ render, mockApiResponse, mockAsset
}; };
await mockApiResponse('user_info', profileMock.base); await mockApiResponse('user_info', profileMock.base);
await mockAssetResponse(profileMock.base.avatar, './playwright/mocks/image_s.jpg');
await mockEnvs([ await mockEnvs([
...ENVS_MAP.userOps, ...ENVS_MAP.userOps,
...ENVS_MAP.nameService, ...ENVS_MAP.nameService,
......
...@@ -2,10 +2,10 @@ import { Button, Divider, Flex, IconButton } from '@chakra-ui/react'; ...@@ -2,10 +2,10 @@ import { Button, Divider, Flex, IconButton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import delay from 'lib/delay'; import delay from 'lib/delay';
import useWeb3Wallet from 'lib/web3/useWallet';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import useWallet from '../walletMenu/useWallet';
import useWeb3AccountWithDomain from './useWeb3AccountWithDomain'; import useWeb3AccountWithDomain from './useWeb3AccountWithDomain';
interface Props { interface Props {
...@@ -13,23 +13,23 @@ interface Props { ...@@ -13,23 +13,23 @@ interface Props {
} }
const ProfileMenuWallet = ({ onClose }: Props) => { const ProfileMenuWallet = ({ onClose }: Props) => {
const wallet = useWallet({ source: 'Header' }); const web3Wallet = useWeb3Wallet({ source: 'Header' });
const web3AccountWithDomain = useWeb3AccountWithDomain(true); const web3AccountWithDomain = useWeb3AccountWithDomain(true);
const handleConnectWalletClick = React.useCallback(async() => { const handleConnectWalletClick = React.useCallback(async() => {
wallet.openModal(); web3Wallet.openModal();
await delay(300); await delay(300);
onClose?.(); onClose?.();
}, [ wallet, onClose ]); }, [ web3Wallet, onClose ]);
const handleOpenWalletClick = React.useCallback(async() => { const handleOpenWalletClick = React.useCallback(async() => {
wallet.openModal(); web3Wallet.openModal();
await delay(300); await delay(300);
onClose?.(); onClose?.();
}, [ wallet, onClose ]); }, [ web3Wallet, onClose ]);
if (wallet.isWalletConnected && web3AccountWithDomain.address) { if (web3Wallet.isConnected && web3AccountWithDomain.address) {
return ( return (
<> <>
<Divider/> <Divider/>
...@@ -49,7 +49,7 @@ const ProfileMenuWallet = ({ onClose }: Props) => { ...@@ -49,7 +49,7 @@ const ProfileMenuWallet = ({ onClose }: Props) => {
color="icon_info" color="icon_info"
boxSize={ 5 } boxSize={ 5 }
onClick={ handleOpenWalletClick } onClick={ handleOpenWalletClick }
isLoading={ wallet.isModalOpening } isLoading={ web3Wallet.isOpen }
flexShrink={ 0 } flexShrink={ 0 }
/> />
</Flex> </Flex>
...@@ -58,13 +58,11 @@ const ProfileMenuWallet = ({ onClose }: Props) => { ...@@ -58,13 +58,11 @@ const ProfileMenuWallet = ({ onClose }: Props) => {
); );
} }
const isLoading = wallet.isModalOpening || wallet.isModalOpen;
return ( return (
<Button <Button
size="sm" size="sm"
onClick={ handleConnectWalletClick } onClick={ handleConnectWalletClick }
isLoading={ isLoading } isLoading={ web3Wallet.isOpen }
loadingText="Connect Wallet" loadingText="Connect Wallet"
w="100%" w="100%"
my={ 2 } my={ 2 }
......
import { Box, Button, VStack, chakra } from '@chakra-ui/react';
import React from 'react';
import type { UserInfo } from 'types/api/account';
import config from 'configs/app';
import useNavItems from 'lib/hooks/useNavItems';
import * as mixpanel from 'lib/mixpanel/index';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
import NavLink from 'ui/snippets/navigation/vertical/NavLink';
const feature = config.features.account;
type Props = {
data?: UserInfo;
onNavLinkClick?: () => void;
};
const ProfileMenuContent = ({ data, onNavLinkClick }: Props) => {
const { accountNavItems, profileItem } = useNavItems();
const handleSingOutClick = React.useCallback(() => {
mixpanel.logEvent(
mixpanel.EventTypes.ACCOUNT_ACCESS,
{ Action: 'Logged out' },
{ send_immediately: true },
);
}, []);
if (!feature.isEnabled) {
return null;
}
const userName = data?.email || data?.nickname || data?.name;
return (
<Box>
{ userName && (
<Box
fontSize="sm"
fontWeight={ 500 }
mb={ 1 }
>
<span>Signed in as </span>
<chakra.span color="text_secondary">{ userName }</chakra.span>
</Box>
) }
<NavLink item={ profileItem } disableActiveState={ true } px="0px" isCollapsed={ false } onClick={ onNavLinkClick }/>
<Box as="nav" mt={ 2 } pt={ 2 } borderTopColor="divider" borderTopWidth="1px" { ...getDefaultTransitionProps() }>
<VStack as="ul" spacing="0" alignItems="flex-start" overflow="hidden">
{ accountNavItems.map((item) => (
<NavLink
key={ item.text }
item={ item }
disableActiveState={ true }
isCollapsed={ false }
px="0px"
onClick={ onNavLinkClick }
/>
)) }
</VStack>
</Box>
<Box mt={ 2 } pt={ 3 } borderTopColor="divider" borderTopWidth="1px" { ...getDefaultTransitionProps() }>
<Button size="sm" width="full" variant="outline" as="a" href={ feature.logoutUrl } onClick={ handleSingOutClick }>
Sign Out
</Button>
</Box>
</Box>
);
};
export default ProfileMenuContent;
import type { BrowserContext } from '@playwright/test';
import React from 'react';
import config from 'configs/app';
import * as profileMock from 'mocks/user/profile';
import { contextWithAuth } from 'playwright/fixtures/auth';
import { test, expect } from 'playwright/lib';
import ProfileMenuDesktop from './ProfileMenuDesktop';
test('no auth', async({ render, page }) => {
const hooksConfig = {
router: {
asPath: '/',
pathname: '/',
},
};
const component = await render(<ProfileMenuDesktop/>, { hooksConfig });
await component.locator('a').click();
expect(page.url()).toBe(`${ config.app.baseUrl }/auth/auth0?path=%2F`);
});
const authTest = test.extend<{ context: BrowserContext }>({
context: contextWithAuth,
});
authTest('auth +@dark-mode', async({ render, page, mockApiResponse, mockAssetResponse }) => {
await mockApiResponse('user_info', profileMock.base);
await mockAssetResponse(profileMock.base.avatar, './playwright/mocks/image_s.jpg');
const component = await render(<ProfileMenuDesktop/>);
await component.getByAltText(/Profile picture/i).click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 600 } });
});
import type { IconButtonProps } from '@chakra-ui/react';
import { PopoverContent, PopoverBody, PopoverTrigger, IconButton, Tooltip, Box, chakra } from '@chakra-ui/react';
import React from 'react';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
import useLoginUrl from 'lib/hooks/useLoginUrl';
import * as mixpanel from 'lib/mixpanel/index';
import Popover from 'ui/shared/chakra/Popover';
import UserAvatar from 'ui/shared/UserAvatar';
import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent';
type Props = {
isHomePage?: boolean;
className?: string;
fallbackIconSize?: number;
buttonBoxSize?: string;
};
const ProfileMenuDesktop = ({ isHomePage, className, fallbackIconSize, buttonBoxSize }: Props) => {
const { data, error, isPending } = useFetchProfileInfo();
const loginUrl = useLoginUrl();
const [ hasMenu, setHasMenu ] = React.useState(false);
React.useEffect(() => {
if (!isPending) {
setHasMenu(Boolean(data));
}
}, [ data, error?.status, isPending ]);
const handleSignInClick = React.useCallback(() => {
mixpanel.logEvent(
mixpanel.EventTypes.ACCOUNT_ACCESS,
{ Action: 'Auth0 init' },
{ send_immediately: true },
);
}, []);
const iconButtonProps: Partial<IconButtonProps> = (() => {
if (hasMenu || !loginUrl) {
return {};
}
return {
as: 'a',
href: loginUrl,
onClick: handleSignInClick,
};
})();
return (
<Popover openDelay={ 300 } placement="bottom-end" gutter={ 10 } isLazy>
<Tooltip
label={ <span>Sign in to My Account to add tags,<br/>create watchlists, access API keys and more</span> }
textAlign="center"
padding={ 2 }
isDisabled={ hasMenu }
openDelay={ 500 }
>
<Box>
<PopoverTrigger>
<IconButton
className={ className }
aria-label="profile menu"
icon={ <UserAvatar size={ 20 } fallbackIconSize={ fallbackIconSize }/> }
variant={ isHomePage ? 'hero' : 'header' }
data-selected={ hasMenu }
boxSize={ buttonBoxSize ?? '40px' }
flexShrink={ 0 }
{ ...iconButtonProps }
/>
</PopoverTrigger>
</Box>
</Tooltip>
{ hasMenu && (
<PopoverContent maxW="400px" minW="220px" w="min-content">
<PopoverBody padding="24px 16px 16px 16px">
<ProfileMenuContent data={ data }/>
</PopoverBody>
</PopoverContent>
) }
</Popover>
);
};
export default chakra(ProfileMenuDesktop);
import type { BrowserContext } from '@playwright/test';
import React from 'react';
import config from 'configs/app';
import * as profileMock from 'mocks/user/profile';
import { contextWithAuth } from 'playwright/fixtures/auth';
import { test, expect, devices } from 'playwright/lib';
import ProfileMenuMobile from './ProfileMenuMobile';
test('no auth', async({ render, page }) => {
const hooksConfig = {
router: {
asPath: '/',
pathname: '/',
},
};
const component = await render(<ProfileMenuMobile/>, { hooksConfig });
await component.locator('a').click();
expect(page.url()).toBe(`${ config.app.baseUrl }/auth/auth0?path=%2F`);
});
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
const authTest = test.extend<{ context: BrowserContext }>({
context: contextWithAuth,
});
authTest.describe('auth', () => {
authTest('base view', async({ render, page, mockApiResponse, mockAssetResponse }) => {
await mockApiResponse('user_info', profileMock.base);
await mockAssetResponse(profileMock.base.avatar, './playwright/mocks/image_s.jpg');
const component = await render(<ProfileMenuMobile/>);
await component.getByAltText(/Profile picture/i).click();
await expect(page).toHaveScreenshot();
});
});
import { Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, IconButton } from '@chakra-ui/react';
import type { IconButtonProps } from '@chakra-ui/react';
import React from 'react';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
import useLoginUrl from 'lib/hooks/useLoginUrl';
import * as mixpanel from 'lib/mixpanel/index';
import UserAvatar from 'ui/shared/UserAvatar';
import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent';
const ProfileMenuMobile = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
const { data, error, isPending } = useFetchProfileInfo();
const loginUrl = useLoginUrl();
const [ hasMenu, setHasMenu ] = React.useState(false);
const handleSignInClick = React.useCallback(() => {
mixpanel.logEvent(
mixpanel.EventTypes.ACCOUNT_ACCESS,
{ Action: 'Auth0 init' },
{ send_immediately: true },
);
}, []);
React.useEffect(() => {
if (!isPending) {
setHasMenu(Boolean(data));
}
}, [ data, error?.status, isPending ]);
const iconButtonProps: Partial<IconButtonProps> = (() => {
if (hasMenu || !loginUrl) {
return {};
}
return {
as: 'a',
href: loginUrl,
onClick: handleSignInClick,
};
})();
return (
<>
<IconButton
aria-label="profile menu"
icon={ <UserAvatar size={ 20 }/> }
variant="header"
data-selected={ hasMenu }
boxSize="40px"
flexShrink={ 0 }
onClick={ hasMenu ? onOpen : undefined }
{ ...iconButtonProps }
/>
{ hasMenu && (
<Drawer
isOpen={ isOpen }
placement="right"
onClose={ onClose }
autoFocus={ false }
>
<DrawerOverlay/>
<DrawerContent maxWidth="300px">
<DrawerBody p={ 6 }>
<ProfileMenuContent data={ data } onNavLinkClick={ onClose }/>
</DrawerBody>
</DrawerContent>
</Drawer>
) }
</>
);
};
export default ProfileMenuMobile;
...@@ -2,9 +2,9 @@ import { PopoverBody, PopoverContent, PopoverTrigger, useDisclosure, type Button ...@@ -2,9 +2,9 @@ import { PopoverBody, PopoverContent, PopoverTrigger, useDisclosure, type Button
import React from 'react'; import React from 'react';
import { useMarketplaceContext } from 'lib/contexts/marketplace'; import { useMarketplaceContext } from 'lib/contexts/marketplace';
import useWeb3Wallet from 'lib/web3/useWallet';
import Popover from 'ui/shared/chakra/Popover'; import Popover from 'ui/shared/chakra/Popover';
import useWeb3AccountWithDomain from 'ui/snippets/profile/useWeb3AccountWithDomain'; import useWeb3AccountWithDomain from 'ui/snippets/profile/useWeb3AccountWithDomain';
import useWallet from 'ui/snippets/walletMenu/useWallet';
import WalletButton from './WalletButton'; import WalletButton from './WalletButton';
import WalletMenuContent from './WalletMenuContent'; import WalletMenuContent from './WalletMenuContent';
...@@ -17,23 +17,23 @@ interface Props { ...@@ -17,23 +17,23 @@ interface Props {
const WalletDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { const WalletDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => {
const walletMenu = useDisclosure(); const walletMenu = useDisclosure();
const walletUtils = useWallet({ source: 'Header' }); const web3Wallet = useWeb3Wallet({ source: 'Header' });
const web3AccountWithDomain = useWeb3AccountWithDomain(walletUtils.isWalletConnected); const web3AccountWithDomain = useWeb3AccountWithDomain(web3Wallet.isConnected);
const { isAutoConnectDisabled } = useMarketplaceContext(); const { isAutoConnectDisabled } = useMarketplaceContext();
const isPending = const isPending =
(walletUtils.isWalletConnected && web3AccountWithDomain.isLoading) || (web3Wallet.isConnected && web3AccountWithDomain.isLoading) ||
(!walletUtils.isWalletConnected && (walletUtils.isModalOpening || walletUtils.isModalOpen)); (!web3Wallet.isConnected && web3Wallet.isOpen);
const handleOpenWalletClick = React.useCallback(() => { const handleOpenWalletClick = React.useCallback(() => {
walletUtils.openModal(); web3Wallet.openModal();
walletMenu.onClose(); walletMenu.onClose();
}, [ walletUtils, walletMenu ]); }, [ web3Wallet, walletMenu ]);
const handleDisconnectClick = React.useCallback(() => { const handleDisconnectClick = React.useCallback(() => {
walletUtils.disconnect(); web3Wallet.disconnect();
walletMenu.onClose(); walletMenu.onClose();
}, [ walletUtils, walletMenu ]); }, [ web3Wallet, walletMenu ]);
return ( return (
<Popover openDelay={ 300 } placement="bottom-end" isLazy isOpen={ walletMenu.isOpen } onClose={ walletMenu.onClose }> <Popover openDelay={ 300 } placement="bottom-end" isLazy isOpen={ walletMenu.isOpen } onClose={ walletMenu.onClose }>
...@@ -41,7 +41,7 @@ const WalletDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { ...@@ -41,7 +41,7 @@ const WalletDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => {
<WalletButton <WalletButton
size={ buttonSize } size={ buttonSize }
variant={ buttonVariant } variant={ buttonVariant }
onClick={ walletUtils.isWalletConnected ? walletMenu.onOpen : walletUtils.openModal } onClick={ web3Wallet.isConnected ? walletMenu.onOpen : web3Wallet.openModal }
address={ web3AccountWithDomain.address } address={ web3AccountWithDomain.address }
domain={ web3AccountWithDomain.domain } domain={ web3AccountWithDomain.domain }
isPending={ isPending } isPending={ isPending }
......
...@@ -2,8 +2,8 @@ import { Drawer, DrawerBody, DrawerContent, DrawerOverlay, useDisclosure } from ...@@ -2,8 +2,8 @@ import { Drawer, DrawerBody, DrawerContent, DrawerOverlay, useDisclosure } from
import React from 'react'; import React from 'react';
import { useMarketplaceContext } from 'lib/contexts/marketplace'; import { useMarketplaceContext } from 'lib/contexts/marketplace';
import useWeb3Wallet from 'lib/web3/useWallet';
import useWeb3AccountWithDomain from 'ui/snippets/profile/useWeb3AccountWithDomain'; import useWeb3AccountWithDomain from 'ui/snippets/profile/useWeb3AccountWithDomain';
import useWallet from 'ui/snippets/walletMenu/useWallet';
import WalletButton from './WalletButton'; import WalletButton from './WalletButton';
import WalletMenuContent from './WalletMenuContent'; import WalletMenuContent from './WalletMenuContent';
...@@ -11,29 +11,29 @@ import WalletMenuContent from './WalletMenuContent'; ...@@ -11,29 +11,29 @@ import WalletMenuContent from './WalletMenuContent';
const WalletMobile = () => { const WalletMobile = () => {
const walletMenu = useDisclosure(); const walletMenu = useDisclosure();
const walletUtils = useWallet({ source: 'Header' }); const web3Wallet = useWeb3Wallet({ source: 'Header' });
const web3AccountWithDomain = useWeb3AccountWithDomain(walletUtils.isWalletConnected); const web3AccountWithDomain = useWeb3AccountWithDomain(web3Wallet.isConnected);
const { isAutoConnectDisabled } = useMarketplaceContext(); const { isAutoConnectDisabled } = useMarketplaceContext();
const isPending = const isPending =
(walletUtils.isWalletConnected && web3AccountWithDomain.isLoading) || (web3Wallet.isConnected && web3AccountWithDomain.isLoading) ||
(!walletUtils.isWalletConnected && (walletUtils.isModalOpening || walletUtils.isModalOpen)); (!web3Wallet.isConnected && web3Wallet.isOpen);
const handleOpenWalletClick = React.useCallback(() => { const handleOpenWalletClick = React.useCallback(() => {
walletUtils.openModal(); web3Wallet.openModal();
walletMenu.onClose(); walletMenu.onClose();
}, [ walletUtils, walletMenu ]); }, [ web3Wallet, walletMenu ]);
const handleDisconnectClick = React.useCallback(() => { const handleDisconnectClick = React.useCallback(() => {
walletUtils.disconnect(); web3Wallet.disconnect();
walletMenu.onClose(); walletMenu.onClose();
}, [ walletUtils, walletMenu ]); }, [ web3Wallet, walletMenu ]);
return ( return (
<> <>
<WalletButton <WalletButton
variant="header" variant="header"
onClick={ walletUtils.isWalletConnected ? walletMenu.onOpen : walletUtils.openModal } onClick={ web3Wallet.isConnected ? walletMenu.onOpen : web3Wallet.openModal }
address={ web3AccountWithDomain.address } address={ web3AccountWithDomain.address }
domain={ web3AccountWithDomain.domain } domain={ web3AccountWithDomain.domain }
isPending={ isPending } isPending={ isPending }
......
import { Box, Flex, chakra, useColorModeValue } 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';
type Props = {
address: string;
isAutoConnectDisabled?: boolean;
className?: string;
};
const WalletIdenticon = ({ address, isAutoConnectDisabled, className }: Props) => {
const isMobile = useIsMobile();
const borderColor = useColorModeValue('orange.100', 'orange.900');
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={ borderColor }
>
<IconSvg
name="integration/partial"
color="white"
boxSize="8px"
/>
</Flex>
) }
</Box>
);
};
export default chakra(WalletIdenticon);
import { Box, Button, Text, Flex, IconButton, useColorModeValue } 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';
type Props = {
address?: string;
ensDomainName?: string | null;
disconnect?: () => void;
isAutoConnectDisabled?: boolean;
openWeb3Modal: () => void;
closeWalletMenu: () => void;
};
const WalletMenuContent = ({ address, ensDomainName, disconnect, isAutoConnectDisabled, openWeb3Modal, closeWalletMenu }: Props) => {
const bgColor = useColorModeValue('orange.100', 'orange.900');
const [ isModalOpening, setIsModalOpening ] = React.useState(false);
const onAddressClick = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.WALLET_ACTION, { Action: 'Address click' });
}, []);
const handleOpenWeb3Modal = React.useCallback(async() => {
setIsModalOpening(true);
await openWeb3Modal();
setTimeout(closeWalletMenu, 300);
}, [ openWeb3Modal, closeWalletMenu ]);
return (
<Box>
{ isAutoConnectDisabled && (
<Flex
borderRadius="base"
p={ 3 }
mb={ 3 }
alignItems="center"
backgroundColor={ bgColor }
>
<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 }
mb={ 1 }
{ ...getDefaultTransitionProps() }
>
My wallet
</Text>
<Text
fontSize="sm"
mb={ 5 }
fontWeight={ 400 }
color="text_secondary"
{ ...getDefaultTransitionProps() }
>
Your wallet is used to interact with apps and contracts in the explorer.
</Text>
{ address && (
<Flex alignItems="center" mb={ 6 }>
<AddressEntity
address={{ hash: address, ens_domain_name: ensDomainName }}
noTooltip
truncation="dynamic"
fontSize="sm"
fontWeight={ 700 }
color="text"
onClick={ onAddressClick }
flex={ 1 }
/>
<IconButton
aria-label="open wallet"
icon={ <IconSvg name="gear_slim" boxSize={ 5 }/> }
variant="simple"
h="20px"
w="20px"
ml={ 1 }
onClick={ handleOpenWeb3Modal }
isLoading={ isModalOpening }
/>
</Flex>
) }
<Button size="sm" width="full" variant="outline" onClick={ disconnect }>
Disconnect
</Button>
</Box>
);
};
export default WalletMenuContent;
import React from 'react';
import type * as bens from '@blockscout/bens-types';
import config from 'configs/app';
import * as addressMock from 'mocks/address/address';
import * as domainMock from 'mocks/ens/domain';
import { test, expect } from 'playwright/lib';
import { WalletMenuDesktop } from './WalletMenuDesktop';
const props = {
isWalletConnected: false,
address: '',
connect: () => {},
disconnect: () => {},
isModalOpening: false,
isModalOpen: false,
openModal: () => {},
};
test.use({ viewport: { width: 1440, height: 750 } }); // xl
test('wallet is not connected +@dark-mode', async({ page, render }) => {
await render(<WalletMenuDesktop { ...props }/>);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 50 } });
});
test('wallet is not connected (home page) +@dark-mode', async({ page, render }) => {
await render(<WalletMenuDesktop { ...props } isHomePage/>);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 50 } });
});
test('wallet is loading', async({ page, render }) => {
await render(<WalletMenuDesktop { ...props } isModalOpen/>);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 50 } });
});
test('wallet connected +@dark-mode', async({ page, render, mockApiResponse }) => {
await mockApiResponse(
'address_domain',
{ domain: undefined, resolved_domains_count: 0 } as bens.GetAddressResponse,
{ pathParams: { address: addressMock.hash, chainId: config.chain.id } },
);
const component = await render(<WalletMenuDesktop { ...props } isWalletConnected address={ addressMock.hash }/>);
await component.locator('button').click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 300 } });
});
test('wallet connected (home page) +@dark-mode', async({ page, render }) => {
const component = await render(<WalletMenuDesktop { ...props } isHomePage isWalletConnected address={ addressMock.hash }/>);
await component.locator('button').click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 300 } });
});
test('wallet with ENS connected', async({ page, render, mockApiResponse }) => {
await mockApiResponse(
'address_domain',
{ domain: domainMock.ensDomainB, resolved_domains_count: 1 } as bens.GetAddressResponse,
{ pathParams: { address: addressMock.hash, chainId: config.chain.id } },
);
const component = await render(<WalletMenuDesktop { ...props } isWalletConnected address={ addressMock.hash }/>);
await component.locator('button').click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 300 } });
});
import { PopoverContent, PopoverBody, PopoverTrigger, Button, Box, useBoolean, chakra } from '@chakra-ui/react';
import React from 'react';
import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
import { useMarketplaceContext } from 'lib/contexts/marketplace';
import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index';
import Popover from 'ui/shared/chakra/Popover';
import HashStringShorten from 'ui/shared/HashStringShorten';
import IconSvg from 'ui/shared/IconSvg';
import useWallet from 'ui/snippets/walletMenu/useWallet';
import WalletMenuContent from 'ui/snippets/walletMenu/WalletMenuContent';
import WalletIdenticon from './WalletIdenticon';
import WalletTooltip from './WalletTooltip';
type Props = {
isHomePage?: boolean;
className?: string;
size?: 'sm' | 'md';
};
type ComponentProps = Props & {
isWalletConnected: boolean;
address: string;
connect: () => void;
disconnect: () => void;
isModalOpening: boolean;
isModalOpen: boolean;
openModal: () => void;
};
export const WalletMenuDesktop = ({
isHomePage, className, size = 'md', isWalletConnected, address, connect,
disconnect, isModalOpening, isModalOpen, openModal,
}: ComponentProps) => {
const [ isPopoverOpen, setIsPopoverOpen ] = useBoolean(false);
const isMobile = useIsMobile();
const { isAutoConnectDisabled } = useMarketplaceContext();
const addressDomainQuery = useApiQuery('address_domain', {
pathParams: {
chainId: config.chain.id,
address,
},
queryOptions: {
enabled: config.features.nameService.isEnabled,
},
});
const openPopover = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.WALLET_ACTION, { Action: 'Open' });
setIsPopoverOpen.toggle();
}, [ setIsPopoverOpen ]);
return (
<Popover
openDelay={ 300 }
placement="bottom-end"
isLazy
isOpen={ isPopoverOpen }
onClose={ setIsPopoverOpen.off }
>
<Box ml={ 2 }>
<PopoverTrigger>
<WalletTooltip
isDisabled={ isMobile === undefined || isMobile || isModalOpening || isModalOpen }
isWalletConnected={ isWalletConnected }
isAutoConnectDisabled={ isAutoConnectDisabled }
>
<Button
className={ className }
variant={ isHomePage ? 'hero' : 'header' }
data-selected={ isWalletConnected }
data-warning={ isAutoConnectDisabled }
flexShrink={ 0 }
isLoading={
((isModalOpening || isModalOpen) && !isWalletConnected) ||
(addressDomainQuery.isLoading && isWalletConnected)
}
loadingText="Connect wallet"
onClick={ isWalletConnected ? openPopover : connect }
fontSize="sm"
size={ size }
px={{ lg: isHomePage ? 2 : 4, xl: 4 }}
>
{ isWalletConnected ? (
<>
<WalletIdenticon address={ address } isAutoConnectDisabled={ isAutoConnectDisabled } mr={ 2 }/>
{ addressDomainQuery.data?.domain?.name ? (
<chakra.span>{ addressDomainQuery.data.domain?.name }</chakra.span>
) : (
<HashStringShorten hash={ address } isTooltipDisabled/>
) }
</>
) : (
<>
<IconSvg display={{ base: isHomePage ? 'inline' : 'none', xl: 'none' }} name="wallet" boxSize={ 6 } p={ 0.5 }/>
<chakra.span display={{ base: isHomePage ? 'none' : 'inline', xl: 'inline' }}>Connect wallet</chakra.span>
</>
) }
</Button>
</WalletTooltip>
</PopoverTrigger>
</Box>
{ isWalletConnected && (
<PopoverContent w="235px">
<PopoverBody padding="24px 16px 16px 16px">
<WalletMenuContent
address={ address }
ensDomainName={ addressDomainQuery.data?.domain?.name }
disconnect={ disconnect }
isAutoConnectDisabled={ isAutoConnectDisabled }
openWeb3Modal={ openModal }
closeWalletMenu={ setIsPopoverOpen.off }
/>
</PopoverBody>
</PopoverContent>
) }
</Popover>
);
};
// separated the useWallet hook from the main component because it's hard to mock it in tests
const WalletMenuDesktopWrapper = ({ isHomePage, className, size = 'md' }: Props) => {
const {
isWalletConnected, address, connect, disconnect,
isModalOpening, isModalOpen, openModal,
} = useWallet({ source: 'Header' });
return (
<WalletMenuDesktop
isHomePage={ isHomePage }
className={ className }
size={ size }
isWalletConnected={ isWalletConnected }
address={ address }
connect={ connect }
disconnect={ disconnect }
isModalOpening={ isModalOpening }
isModalOpen={ isModalOpen }
openModal={ openModal }
/>
);
};
export default chakra(WalletMenuDesktopWrapper);
import React from 'react';
import type * as bens from '@blockscout/bens-types';
import config from 'configs/app';
import * as addressMock from 'mocks/address/address';
import * as domainMock from 'mocks/ens/domain';
import { test, expect, devices } from 'playwright/lib';
import { WalletMenuMobile } from './WalletMenuMobile';
const props = {
isWalletConnected: false,
address: '',
connect: () => {},
disconnect: () => {},
isModalOpening: false,
isModalOpen: false,
openModal: () => {},
};
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('wallet is not connected +@dark-mode', async({ page, render }) => {
await render(<WalletMenuMobile { ...props }/>);
await expect(page).toHaveScreenshot();
});
test('wallet is loading', async({ page, render }) => {
await render(<WalletMenuMobile { ...props } isModalOpen/>);
await expect(page).toHaveScreenshot();
});
test('wallet connected +@dark-mode', async({ page, render, mockApiResponse }) => {
await mockApiResponse(
'address_domain',
{ domain: undefined, resolved_domains_count: 0 } as bens.GetAddressResponse,
{ pathParams: { address: addressMock.hash, chainId: config.chain.id } },
);
const component = await render(<WalletMenuMobile { ...props } isWalletConnected address={ addressMock.hash }/>);
await component.locator('button').click();
await expect(page).toHaveScreenshot();
});
test('wallet with ENS connected', async({ page, render, mockApiResponse }) => {
await mockApiResponse(
'address_domain',
{ domain: domainMock.ensDomainB, resolved_domains_count: 1 } as bens.GetAddressResponse,
{ pathParams: { address: addressMock.hash, chainId: config.chain.id } },
);
const component = await render(<WalletMenuMobile { ...props } isWalletConnected address={ addressMock.hash }/>);
await component.locator('button').click();
await expect(page).toHaveScreenshot();
});
import { Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, IconButton } from '@chakra-ui/react';
import React from 'react';
import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
import { useMarketplaceContext } from 'lib/contexts/marketplace';
import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index';
import IconSvg from 'ui/shared/IconSvg';
import useWallet from 'ui/snippets/walletMenu/useWallet';
import WalletMenuContent from 'ui/snippets/walletMenu/WalletMenuContent';
import WalletIdenticon from './WalletIdenticon';
import WalletTooltip from './WalletTooltip';
type ComponentProps = {
isWalletConnected: boolean;
address: string;
connect: () => void;
disconnect: () => void;
isModalOpening: boolean;
isModalOpen: boolean;
openModal: () => void;
};
export const WalletMenuMobile = (
{ isWalletConnected, address, connect, disconnect, isModalOpening, isModalOpen, openModal }: ComponentProps,
) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const isMobile = useIsMobile();
const { isAutoConnectDisabled } = useMarketplaceContext();
const addressDomainQuery = useApiQuery('address_domain', {
pathParams: {
chainId: config.chain.id,
address,
},
queryOptions: {
enabled: config.features.nameService.isEnabled,
},
});
const openPopover = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.WALLET_ACTION, { Action: 'Open' });
onOpen();
}, [ onOpen ]);
return (
<>
<WalletTooltip
isDisabled={ isMobile === undefined || !isMobile }
isMobile
isWalletConnected={ isWalletConnected }
isAutoConnectDisabled={ isAutoConnectDisabled }
>
<IconButton
aria-label="wallet menu"
icon={ isWalletConnected ?
<WalletIdenticon address={ address } isAutoConnectDisabled={ isAutoConnectDisabled }/> :
<IconSvg name="wallet" boxSize={ 6 } p={ 0.5 }/>
}
variant="header"
data-selected={ isWalletConnected }
data-warning={ isAutoConnectDisabled }
boxSize="40px"
flexShrink={ 0 }
onClick={ isWalletConnected ? openPopover : connect }
isLoading={
((isModalOpening || isModalOpen) && !isWalletConnected) ||
(addressDomainQuery.isLoading && isWalletConnected)
}
/>
</WalletTooltip>
{ isWalletConnected && (
<Drawer
isOpen={ isOpen }
placement="right"
onClose={ onClose }
autoFocus={ false }
>
<DrawerOverlay/>
<DrawerContent maxWidth="260px">
<DrawerBody p={ 6 }>
<WalletMenuContent
address={ address }
ensDomainName={ addressDomainQuery.data?.domain?.name }
disconnect={ disconnect }
isAutoConnectDisabled={ isAutoConnectDisabled }
openWeb3Modal={ openModal }
closeWalletMenu={ onClose }
/>
</DrawerBody>
</DrawerContent>
</Drawer>
) }
</>
);
};
const WalletMenuMobileWrapper = () => {
const {
isWalletConnected, address, connect, disconnect,
isModalOpening, isModalOpen, openModal,
} = useWallet({ source: 'Header' });
return (
<WalletMenuMobile
isWalletConnected={ isWalletConnected }
address={ address }
connect={ connect }
disconnect={ disconnect }
isModalOpening={ isModalOpening }
isModalOpen={ isModalOpen }
openModal={ openModal }
/>
);
};
export default WalletMenuMobileWrapper;
import { Tooltip, useBoolean, useOutsideClick, Box } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import { SECOND } from 'lib/consts';
import removeQueryParam from 'lib/router/removeQueryParam';
type Props = {
children: React.ReactNode;
isDisabled?: boolean;
isMobile?: boolean;
isWalletConnected?: boolean;
isAutoConnectDisabled?: boolean;
};
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 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 null;
}
return <span>Connect your wallet<br/>to Blockscout for<br/>full-featured access</span>;
}, [ isWalletConnected, isAutoConnectDisabled ]);
const isAppPage = router.pathname === '/apps/[id]';
React.useEffect(() => {
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 = (isAppPage && !isConnectWalletAction) || isTooltipShowAction || (!wasShown && isMarketplacePage);
let timer1: ReturnType<typeof setTimeout>;
let timer2: ReturnType<typeof setTimeout>;
if (!isDisabled && needToShow) {
timer1 = setTimeout(() => {
setIsTooltipShown.on();
timer2 = setTimeout(() => setIsTooltipShown.off(), 3 * SECOND);
if (!wasShown && isMarketplacePage) {
window.localStorage.setItem(LOCAL_STORAGE_KEY, 'true');
}
if (isTooltipShowAction) {
removeQueryParam(router, 'action');
}
}, isTooltipShowAction ? 0 : SECOND);
}
return () => {
clearTimeout(timer1);
clearTimeout(timer2);
};
}, [ setIsTooltipShown, isDisabled, router, isAppPage ]);
return (
<Box ref={ ref }>
<Tooltip
label={ label }
textAlign="center"
padding={ 2 }
isDisabled={ isDisabled || !label || (isWalletConnected && !isAppPage) }
openDelay={ 500 }
isOpen={ isTooltipShown || (isMobile ? false : undefined) }
onClose={ setIsTooltipShown.off }
display={ isMobile ? { base: 'flex', lg: 'none' } : { base: 'none', lg: 'flex' } }
ref={ innerRef }
>
{ children }
</Tooltip>
</Box>
);
};
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