Commit ec917a04 authored by tom's avatar tom

user profile menu

parent 2c283704
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.3 3.7a1.7 1.7 0 0 1 3.4 0v.903a1 1 0 1 0 2 0V3.7a3.7 3.7 0 0 0-7.4 0v6.272a1 1 0 0 0 2 0V3.7Zm5.4 6.302a1 1 0 0 0-2 0V16.3a1.7 1.7 0 0 1-3.4 0v-.914a1 1 0 1 0-2 0v.914a3.7 3.7 0 1 0 7.4 0v-6.298ZM3.692 8.3C2.76 8.3 2 9.059 2 10c0 .94.76 1.7 1.693 1.7H10a1 1 0 1 1 0 2H3.693A3.696 3.696 0 0 1 0 10C0 7.96 1.65 6.3 3.693 6.3h.902a1 1 0 0 1 0 2h-.902ZM10 6.3a1 1 0 0 0 0 2h6.294C17.238 8.3 18 9.064 18 10c0 .937-.761 1.7-1.705 1.7h-.865a1 1 0 1 0 0 2h.865A3.702 3.702 0 0 0 20 10c0-2.046-1.66-3.7-3.705-3.7H10Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.605 21a2.26 2.26 0 0 1-1.614-.665l-6.314-6.318a2.266 2.266 0 0 1 0-3.23l8.45-8.457C10.888 1.57 12.265 1 13.31 1h5.412C19.956 1 21 2.045 21 3.28v5.416c0 .403-.085.856-.233 1.304H19.18c.208-.443.348-.944.348-1.352V3.233a.851.851 0 0 0-.854-.855h-5.365v.047c-.665 0-1.71.428-2.184.903l-8.451 8.456a.832.832 0 0 0 0 1.188l6.314 6.318c.332.332.902.332 1.187 0l1.818-1.82v2.09l-.773.775A2.26 2.26 0 0 1 9.605 21Z" fill="currentColor"/>
<path d="m7.991 20.335-.177.177.177-.177Zm-6.314-6.318.176-.177-.176.177Zm0-3.23.176.176-.176-.177Zm8.45-8.457-.176-.177.177.177ZM20.768 10v.25h.181l.057-.172-.238-.078Zm-1.587 0-.226-.106-.168.356h.394V10Zm-5.871-7.622v-.25h-.25v.25h.25Zm0 .047v.25h.25v-.25h-.25Zm-2.184.903-.177-.177.177.177Zm-8.451 8.456.176.177-.176-.177Zm0 1.188-.177.176.177-.176Zm6.314 6.318-.177.177.177-.177Zm1.187 0-.177-.177-.007.007-.006.007.19.163Zm1.818-1.82h.25v-.604l-.426.428.176.176Zm0 2.09.177.177.073-.073v-.103h-.25Zm-.773.775.176.177-.176-.177Zm-3.406.177a2.51 2.51 0 0 0 1.791.738v-.5a2.01 2.01 0 0 1-1.437-.592l-.354.354ZM1.5 14.193l6.314 6.319.354-.354-6.315-6.318-.353.353Zm0-3.583c-1 1-1 2.583 0 3.583l.353-.353a2.016 2.016 0 0 1 0-2.877L1.5 10.61Zm8.45-8.457L1.5 10.61l.353.353 8.451-8.456-.353-.354ZM13.31.75c-.564 0-1.202.153-1.794.4-.592.246-1.156.595-1.564 1.003l.353.354c.352-.352.856-.668 1.403-.896.548-.229 1.12-.361 1.602-.361v-.5Zm5.412 0H13.31v.5h5.412v-.5Zm2.529 2.53c0-1.373-1.156-2.53-2.529-2.53v.5c1.096 0 2.029.933 2.029 2.03h.5Zm0 5.416V3.28h-.5v5.416h.5Zm-.245 1.382c.154-.466.245-.946.245-1.382h-.5c0 .37-.078.797-.22 1.226l.475.156Zm-1.825.172h1.587v-.5H19.18v.5Zm.098-1.602c0 .36-.126.823-.324 1.246l.452.213c.218-.464.372-1.002.372-1.459h-.5Zm0-5.415v5.415h.5V3.233h-.5Zm-.604-.605c.336 0 .604.268.604.605h.5c0-.613-.491-1.105-1.104-1.105v.5Zm-5.365 0h5.365v-.5h-5.365v.5Zm.25-.203v-.047h-.5v.047h.5Zm-2.257 1.08c.205-.206.552-.416.939-.576.386-.159.78-.254 1.068-.254v-.5c-.377 0-.839.119-1.259.292-.42.173-.833.414-1.102.684l.354.354ZM2.85 11.96l8.452-8.456-.354-.354-8.451 8.456.353.354Zm0 .834a.582.582 0 0 1 0-.834l-.353-.354c-.43.43-.43 1.111 0 1.541l.353-.353Zm6.315 6.318L2.85 12.795l-.353.353 6.314 6.319.354-.354Zm.82.014c-.178.208-.577.23-.82-.014l-.354.354c.422.422 1.162.443 1.554-.015l-.38-.325Zm1.832-1.833-1.819 1.82.354.352 1.818-1.819-.353-.353Zm.426 2.267v-2.09h-.5v2.09h.5Zm-.847.95.774-.774-.353-.353-.774.774.353.354Zm-1.79.739a2.51 2.51 0 0 0 1.79-.738l-.353-.354a2.01 2.01 0 0 1-1.438.592v.5ZM20.988 20v-5c0-.55-.45-1-1-1h-5.996c-.55 0-1 .45-1 1v5c0 .55.45 1 1 1h5.996c.55 0 1-.45 1-1Zm-2.998-2.5c0 .55-.45 1-1 1s-1-.45-1-1 .45-1 1-1 1 .45 1 1Z" fill="currentColor"/>
<path d="M19.489 16v-2.5c0-1.4-1.1-2.5-2.499-2.5s-2.498 1.1-2.498 2.5V16" stroke="currentColor" stroke-opacity=".8" stroke-miterlimit="10"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.916 9.556c-.111-.111-.111-.223-.222-.334L16.356 5.89a1.077 1.077 0 0 0-1.558 0 1.073 1.073 0 0 0 0 1.556l1.446 1.444h-5.118c-.667 0-1.112.444-1.112 1.111s.445 1.111 1.112 1.111h5.118l-1.446 1.445a1.073 1.073 0 0 0 0 1.555c.223.222.556.334.779.334.223 0 .556-.112.779-.334l3.338-3.333c.111-.111.222-.222.222-.333a1.225 1.225 0 0 0 0-.89Z" fill="currentColor"/>
<path d="M13.908 16.778c-1.224.666-2.559 1-3.894 1-4.34 0-7.789-3.445-7.789-7.778s3.45-7.778 7.789-7.778c1.335 0 2.67.334 3.894 1 .556.334 1.224.111 1.558-.444.334-.556.111-1.222-.445-1.556C13.463.444 11.794 0 10.014 0A9.965 9.965 0 0 0 0 10c0 5.556 4.45 10 10.014 10a9.94 9.94 0 0 0 5.007-1.333c.556-.334.667-1 .445-1.556-.334-.444-1.002-.667-1.558-.333Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.4 11a8.4 8.4 0 1 1-16.8 0 8.4 8.4 0 0 1 16.8 0Zm1.6 0c0 5.523-4.477 10-10 10S1 16.523 1 11 5.477 1 11 1s10 4.477 10 10Zm-5.895-3.706A.916.916 0 1 1 16.4 8.589l-6.022 6.022a1.05 1.05 0 0 1-1.485 0l-3.2-3.199a.915.915 0 0 1 1.295-1.295l2.258 2.258a.55.55 0 0 0 .778 0l5.081-5.081Z" fill="currentColor"/>
<path d="m16.4 7.293-.141.142.141-.142Zm-1.295 0 .142.142-.142-.141ZM16.4 8.59l-.141-.142.141.142Zm-6.022 6.022.141.141-.141-.141Zm-4.684-3.199.141-.141-.141.141Zm0-1.295-.142-.141.142.141Zm1.294 0-.141.142.141-.142Zm2.258 2.258-.14.142.14-.142Zm.778 0-.141-.141.141.141ZM11 19.6a8.6 8.6 0 0 0 8.6-8.6h-.4a8.2 8.2 0 0 1-8.2 8.2v.4ZM2.4 11a8.6 8.6 0 0 0 8.6 8.6v-.4A8.2 8.2 0 0 1 2.8 11h-.4ZM11 2.4A8.6 8.6 0 0 0 2.4 11h.4A8.2 8.2 0 0 1 11 2.8v-.4Zm8.6 8.6A8.6 8.6 0 0 0 11 2.4v.4a8.2 8.2 0 0 1 8.2 8.2h.4ZM11 21.2c5.633 0 10.2-4.567 10.2-10.2h-.4c0 5.412-4.388 9.8-9.8 9.8v.4ZM.8 11c0 5.633 4.567 10.2 10.2 10.2v-.4c-5.412 0-9.8-4.388-9.8-9.8H.8ZM11 .8C5.367.8.8 5.367.8 11h.4c0-5.412 4.388-9.8 9.8-9.8V.8ZM21.2 11C21.2 5.367 16.633.8 11 .8v.4c5.412 0 9.8 4.388 9.8 9.8h.4Zm-4.659-3.848a1.116 1.116 0 0 0-1.577 0l.283.283a.716.716 0 0 1 1.012 0l.282-.283Zm0 1.578a1.116 1.116 0 0 0 0-1.578l-.282.283c.28.28.28.733 0 1.012l.283.283Zm-6.022 6.022 6.023-6.022-.283-.283-6.023 6.023.283.282Zm-1.767 0a1.25 1.25 0 0 0 1.767 0l-.283-.282a.85.85 0 0 1-1.202 0l-.282.282Zm-3.2-3.199 3.2 3.2.282-.283-3.199-3.2-.283.283Zm0-1.577a1.115 1.115 0 0 0 0 1.577l.283-.282a.715.715 0 0 1 0-1.012l-.283-.283Zm1.578 0a1.115 1.115 0 0 0-1.578 0l.283.283a.715.715 0 0 1 1.012 0l.283-.283Zm2.258 2.258L7.13 9.976l-.283.283 2.258 2.258.283-.283Zm.495 0a.35.35 0 0 1-.495 0l-.283.283a.75.75 0 0 0 1.06 0l-.282-.283Zm5.081-5.082-5.081 5.082.283.283 5.08-5.082-.282-.283Z" fill="currentColor"/>
</svg>
......@@ -297,6 +297,7 @@ export default function useNavItems(): ReturnType {
},
].filter(Boolean);
// TODO @tom2drum remove this
const profileItem = {
text: 'My profile',
nextRoute: { pathname: '/auth/profile' as const },
......
......@@ -3,6 +3,7 @@
export type IconName =
| "ABI_slim"
| "ABI"
| "API_slim"
| "API"
| "apps_list"
| "apps_slim"
......@@ -103,6 +104,7 @@
| "output_roots"
| "payment_link"
| "plus"
| "private_tags_slim"
| "privattags"
| "profile"
| "publictags_slim"
......@@ -119,6 +121,7 @@
| "score/score-ok"
| "search"
| "share"
| "sign_out"
| "social/canny"
| "social/coingecko"
| "social/coinmarketcap"
......@@ -163,6 +166,7 @@
| "validator"
| "verification-steps/finalized"
| "verification-steps/unfinalized"
| "verified_slim"
| "verified"
| "wallet"
| "wallets/coinbase"
......
import type { ButtonProps } from '@chakra-ui/react';
import { Button, Tooltip } from '@chakra-ui/react';
import { Button, Skeleton, Tooltip, Text, HStack } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { UserInfo } from 'types/api/account';
import UserAvatar from 'ui/shared/UserAvatar';
import { getUserHandle } from './utils';
interface Props {
profileQuery: UseQueryResult<UserInfo, unknown>;
size?: ButtonProps['size'];
......@@ -12,9 +16,26 @@ interface Props {
onClick: () => void;
}
const ProfileButton = ({ profileQuery, size, variant, onClick }: Props) => {
const ProfileButton = ({ profileQuery, size, variant, onClick }: Props, ref: React.ForwardedRef<HTMLDivElement>) => {
const { data, isPending } = profileQuery;
const content = (() => {
if (!data) {
return 'Connect';
}
if (data.email) {
return (
<HStack gap={ 2 }>
<UserAvatar size={ 20 }/>
<Text>{ getUserHandle(data.email) }</Text>
</HStack>
);
}
return 'Connected';
})();
return (
<Tooltip
label={ <span>Sign in to My Account to add tags,<br/>create watchlists, access API keys and more</span> }
......@@ -23,9 +44,18 @@ const ProfileButton = ({ profileQuery, size, variant, onClick }: Props) => {
isDisabled={ isPending || Boolean(data) }
openDelay={ 500 }
>
<Button size={ size } variant={ variant } onClick={ onClick }>Connect</Button>
<Skeleton isLoaded={ !isPending } borderRadius="base" ref={ ref }>
<Button
size={ size }
variant={ variant }
onClick={ onClick }
data-selected={ Boolean(data) }
>
{ content }
</Button>
</Skeleton>
</Tooltip>
);
};
export default React.memo(ProfileButton);
export default React.memo(React.forwardRef(ProfileButton));
import { useDisclosure, type ButtonProps } from '@chakra-ui/react';
import { PopoverBody, PopoverContent, PopoverTrigger, useDisclosure, type ButtonProps } from '@chakra-ui/react';
import React from 'react';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
import Popover from 'ui/shared/chakra/Popover';
import AuthModal from 'ui/snippets/auth/AuthModal';
import ProfileButton from './ProfileButton';
import ProfileMenuContent from './ProfileMenuContent';
interface Props {
buttonSize?: ButtonProps['size'];
......@@ -14,15 +16,28 @@ interface Props {
const ProfileDesktop = ({ buttonSize, isHomePage }: Props) => {
const profileQuery = useFetchProfileInfo();
const authModal = useDisclosure();
const profileMenu = useDisclosure();
return (
<>
<ProfileButton
profileQuery={ profileQuery }
size={ buttonSize }
variant={ isHomePage ? 'hero' : 'header' }
onClick={ authModal.onOpen }
/>
<Popover openDelay={ 300 } placement="bottom-end" isLazy isOpen={ profileMenu.isOpen } onClose={ profileMenu.onClose }>
<PopoverTrigger>
<ProfileButton
profileQuery={ profileQuery }
size={ buttonSize }
variant={ isHomePage ? 'hero' : 'header' }
onClick={ profileQuery.data ? profileMenu.onOpen : authModal.onOpen }
/>
</PopoverTrigger>
{ profileQuery.data && (
<PopoverContent maxW="400px" minW="220px" w="min-content">
<PopoverBody>
<ProfileMenuContent data={ profileQuery.data } onNavLinkClick={ profileMenu.onClose }/>
</PopoverBody>
</PopoverContent>
) }
</Popover>
{ authModal.isOpen && <AuthModal onClose={ authModal.onClose } initialScreen={{ type: 'select_method' }}/> }
</>
);
......
import { Box, Divider, Flex, Text, VStack } from '@chakra-ui/react';
import React from 'react';
import type { NavLink } from './types';
import type { UserInfo } from 'types/api/account';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import ProfileMenuNavLink from './ProfileMenuNavLink';
import { getUserHandle } from './utils';
const navLinks: Array<NavLink> = [
{
text: 'Watch list',
href: route({ pathname: '/account/watchlist' }),
icon: 'star_outline' as const,
},
{
text: 'Private tags',
href: route({ pathname: '/account/tag-address' }),
icon: 'private_tags_slim' as const,
},
{
text: 'API keys',
href: route({ pathname: '/account/api-key' }),
icon: 'API_slim' as const,
},
{
text: 'Custom ABI',
href: route({ pathname: '/account/custom-abi' }),
icon: 'ABI_slim' as const,
},
config.features.addressVerification.isEnabled && {
text: 'Verified addrs',
href: route({ pathname: '/account/verified-addresses' }),
icon: 'verified_slim' as const,
},
].filter(Boolean);
interface Props {
data?: UserInfo;
onNavLinkClick?: () => void;
}
const ProfileMenuContent = ({ data, onNavLinkClick }: Props) => {
return (
<Box>
<Flex alignItems="center" justifyContent="space-between">
<ProfileMenuNavLink
text="Profile"
href={ route({ pathname: '/auth/profile' }) }
icon="profile"
onClick={ onNavLinkClick }
/>
{ data?.email && <Text variant="secondary" fontSize="sm">{ getUserHandle(data.email) }</Text> }
</Flex>
<Divider/>
<VStack as="ul" spacing="0" alignItems="flex-start" overflow="hidden">
{ navLinks.map((item) => (
<ProfileMenuNavLink
key={ item.text }
{ ...item }
onClick={ onNavLinkClick }
/>
)) }
</VStack>
<Divider my={ 1 }/>
<ProfileMenuNavLink
text="Sign out"
icon="sign_out"
onClick={ onNavLinkClick }
/>
</Box>
);
};
export default React.memo(ProfileMenuContent);
import { Box } from '@chakra-ui/react';
import React from 'react';
import type { NavLink } from './types';
import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal';
type Props = NavLink
const ProfileMenuNavLink = ({ href, icon, text, onClick }: Props) => {
return (
<LinkInternal
href={ href }
display="flex"
alignItems="center"
columnGap={ 3 }
py="14px"
color="initial"
_hover={{ textDecoration: 'none', color: 'link_hovered' }}
onClick={ onClick }
>
<IconSvg name={ icon } boxSize={ 5 } flexShrink={ 0 }/>
<Box fontSize="14px" fontWeight="500" lineHeight={ 5 }>{ text }</Box>
</LinkInternal>
);
};
export default React.memo(ProfileMenuNavLink);
import type { IconName } from 'public/icons/name';
export interface NavLink {
text: string;
href?: string;
onClick?: () => void;
icon: IconName;
}
export function getUserHandle(email: string) {
return email.split('@')[0];
}
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