Commit 7252d107 authored by isstuev's avatar isstuev

profile menu change icon

parent 3c931880
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path d="M15 25c5.523 0 10-4.477 10-10S20.523 5 15 5 5 9.477 5 15s4.477 10 10 10Z" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linejoin="round"/>
<path d="M21.667 22.333a6.666 6.666 0 1 0-13.334 0" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linejoin="round"/>
<path d="M15 15.667a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linejoin="round"/>
</svg>
...@@ -15,7 +15,6 @@ import globeIcon from 'icons/globe-b.svg'; ...@@ -15,7 +15,6 @@ import globeIcon from 'icons/globe-b.svg';
import graphQLIcon from 'icons/graphQL.svg'; import graphQLIcon from 'icons/graphQL.svg';
import outputRootsIcon from 'icons/output_roots.svg'; import outputRootsIcon from 'icons/output_roots.svg';
import privateTagIcon from 'icons/privattags.svg'; import privateTagIcon from 'icons/privattags.svg';
import profileIcon from 'icons/profile.svg';
import publicTagIcon from 'icons/publictags.svg'; import publicTagIcon from 'icons/publictags.svg';
import apiDocsIcon from 'icons/restAPI.svg'; import apiDocsIcon from 'icons/restAPI.svg';
import rpcIcon from 'icons/RPC.svg'; import rpcIcon from 'icons/RPC.svg';
...@@ -27,6 +26,7 @@ import txnBatchIcon from 'icons/txn_batches.svg'; ...@@ -27,6 +26,7 @@ import txnBatchIcon from 'icons/txn_batches.svg';
import verifiedIcon from 'icons/verified.svg'; import verifiedIcon from 'icons/verified.svg';
import watchlistIcon from 'icons/watchlist.svg'; import watchlistIcon from 'icons/watchlist.svg';
import { rightLineArrow } from 'lib/html-entities'; import { rightLineArrow } from 'lib/html-entities';
import UserAvatar from 'ui/shared/UserAvatar';
interface ReturnType { interface ReturnType {
mainNavItems: Array<NavItem | NavGroupItem>; mainNavItems: Array<NavItem | NavGroupItem>;
...@@ -213,7 +213,7 @@ export default function useNavItems(): ReturnType { ...@@ -213,7 +213,7 @@ export default function useNavItems(): ReturnType {
const profileItem = { const profileItem = {
text: 'My profile', text: 'My profile',
nextRoute: { pathname: '/auth/profile' as const }, nextRoute: { pathname: '/auth/profile' as const },
icon: profileIcon, iconComponent: UserAvatar,
isActive: pathname === '/auth/profile', isActive: pathname === '/auth/profile',
}; };
......
import type { Route } from 'nextjs-routes'; import type { Route } from 'nextjs-routes';
import type React from 'react';
type NavIconOrComponent = {
icon?: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
} | {
iconComponent?: React.FC<{size?: number}>;
};
type NavItemCommon = { type NavItemCommon = {
text: string; text: string;
icon?: React.FunctionComponent<React.SVGAttributes<SVGElement>>; } & NavIconOrComponent;
}
export type NavItemInternal = NavItemCommon & { export type NavItemInternal = NavItemCommon & {
nextRoute: Route; nextRoute: Route;
......
...@@ -9,7 +9,7 @@ import PageTitle from 'ui/shared/Page/PageTitle'; ...@@ -9,7 +9,7 @@ import PageTitle from 'ui/shared/Page/PageTitle';
import UserAvatar from 'ui/shared/UserAvatar'; import UserAvatar from 'ui/shared/UserAvatar';
const MyProfile = () => { const MyProfile = () => {
const { data, isLoading, isError, error, isFetched } = useFetchProfileInfo(); const { data, isLoading, isError, error } = useFetchProfileInfo();
useRedirectForInvalidAuthToken(); useRedirectForInvalidAuthToken();
const content = (() => { const content = (() => {
...@@ -26,7 +26,7 @@ const MyProfile = () => { ...@@ -26,7 +26,7 @@ const MyProfile = () => {
return ( return (
<VStack maxW="412px" mt={ 8 } gap={ 5 } alignItems="stretch"> <VStack maxW="412px" mt={ 8 } gap={ 5 } alignItems="stretch">
<UserAvatar size={ 64 } data={ data } isFetched={ isFetched }/> <UserAvatar size={ 64 }/>
<FormControl variant="floating" id="name" isRequired size="lg"> <FormControl variant="floating" id="name" isRequired size="lg">
<Input <Input
required required
......
...@@ -2,10 +2,9 @@ import { useColorModeValue, useToken, SkeletonCircle, Image, Box } from '@chakra ...@@ -2,10 +2,9 @@ import { useColorModeValue, useToken, SkeletonCircle, Image, Box } from '@chakra
import React from 'react'; import React from 'react';
import Identicon from 'react-identicons'; import Identicon from 'react-identicons';
import type { UserInfo } from 'types/api/account';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
const IdenticonComponent = typeof Identicon === 'object' && 'default' in Identicon ? Identicon.default : Identicon; const IdenticonComponent = typeof Identicon === 'object' && 'default' in Identicon ? Identicon.default : Identicon;
...@@ -34,14 +33,13 @@ const FallbackImage = ({ size, id }: { size: number; id: string }) => { ...@@ -34,14 +33,13 @@ const FallbackImage = ({ size, id }: { size: number; id: string }) => {
interface Props { interface Props {
size: number; size: number;
data?: UserInfo;
isFetched: boolean;
} }
const UserAvatar = ({ size, data, isFetched }: Props) => { const UserAvatar = ({ size }: Props) => {
const appProps = useAppContext(); const appProps = useAppContext();
const hasAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN, appProps.cookies)); const hasAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN, appProps.cookies));
const [ isImageLoadError, setImageLoadError ] = React.useState(false); const [ isImageLoadError, setImageLoadError ] = React.useState(false);
const { data, isFetched } = useFetchProfileInfo();
const sizeString = `${ size }px`; const sizeString = `${ size }px`;
......
import { Link, Icon, Text, HStack, Tooltip, Box, useBreakpointValue, chakra, shouldForwardProp } from '@chakra-ui/react'; import { Link, Text, HStack, Tooltip, Box, useBreakpointValue, chakra, shouldForwardProp } from '@chakra-ui/react';
import NextLink from 'next/link'; import NextLink from 'next/link';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
...@@ -8,6 +8,7 @@ import type { NavItem } from 'types/client/navigation-items'; ...@@ -8,6 +8,7 @@ import type { NavItem } from 'types/client/navigation-items';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { isInternalItem } from 'lib/hooks/useNavItems'; import { isInternalItem } from 'lib/hooks/useNavItems';
import NavLinkIcon from './NavLinkIcon';
import useColors from './useColors'; import useColors from './useColors';
import useNavLinkStyleProps from './useNavLinkStyleProps'; import useNavLinkStyleProps from './useNavLinkStyleProps';
...@@ -50,7 +51,7 @@ const NavLink = ({ item, isCollapsed, px, className }: Props) => { ...@@ -50,7 +51,7 @@ const NavLink = ({ item, isCollapsed, px, className }: Props) => {
color={ isInternalLink && item.isActive ? colors.text.active : colors.text.hover } color={ isInternalLink && item.isActive ? colors.text.active : colors.text.hover }
> >
<HStack spacing={ 3 } overflow="hidden"> <HStack spacing={ 3 } overflow="hidden">
{ item.icon && <Icon as={ item.icon } boxSize="30px"/> } <NavLinkIcon item={ item }/>
<Text { ...styleProps.textProps }> <Text { ...styleProps.textProps }>
{ item.text } { item.text }
</Text> </Text>
......
...@@ -17,16 +17,18 @@ import type { NavGroupItem } from 'types/client/navigation-items'; ...@@ -17,16 +17,18 @@ import type { NavGroupItem } from 'types/client/navigation-items';
import chevronIcon from 'icons/arrows/east-mini.svg'; import chevronIcon from 'icons/arrows/east-mini.svg';
import NavLink from './NavLink'; import NavLink from './NavLink';
import NavLinkIcon from './NavLinkIcon';
import useNavLinkStyleProps from './useNavLinkStyleProps'; import useNavLinkStyleProps from './useNavLinkStyleProps';
type Props = NavGroupItem & { type Props = {
item: NavGroupItem;
isCollapsed?: boolean; isCollapsed?: boolean;
} }
const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Props) => { const NavLinkGroupDesktop = ({ item, isCollapsed }: Props) => {
const isExpanded = isCollapsed === false; const isExpanded = isCollapsed === false;
const styleProps = useNavLinkStyleProps({ isCollapsed, isExpanded, isActive }); const styleProps = useNavLinkStyleProps({ isCollapsed, isExpanded, isActive: item.isActive });
return ( return (
<Box as="li" listStyleType="none" w="100%"> <Box as="li" listStyleType="none" w="100%">
...@@ -41,15 +43,15 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr ...@@ -41,15 +43,15 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr
w={{ lg: isExpanded ? '180px' : '60px', xl: isCollapsed ? '60px' : '180px' }} w={{ lg: isExpanded ? '180px' : '60px', xl: isCollapsed ? '60px' : '180px' }}
pl={{ lg: isExpanded ? 3 : '15px', xl: isCollapsed ? '15px' : 3 }} pl={{ lg: isExpanded ? 3 : '15px', xl: isCollapsed ? '15px' : 3 }}
pr={{ lg: isExpanded ? 0 : '15px', xl: isCollapsed ? '15px' : 0 }} pr={{ lg: isExpanded ? 0 : '15px', xl: isCollapsed ? '15px' : 0 }}
aria-label={ `${ text } link group` } aria-label={ `${ item.text } link group` }
position="relative" position="relative"
> >
<HStack spacing={ 3 } overflow="hidden"> <HStack spacing={ 3 } overflow="hidden">
<Icon as={ icon } boxSize="30px"/> <NavLinkIcon item={ item }/>
<Text <Text
{ ...styleProps.textProps } { ...styleProps.textProps }
> >
{ text } { item.text }
</Text> </Text>
<Icon <Icon
as={ chevronIcon } as={ chevronIcon }
...@@ -68,10 +70,10 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr ...@@ -68,10 +70,10 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr
<PopoverContent width="252px" top={{ lg: isExpanded ? '-16px' : 0, xl: isCollapsed ? 0 : '-16px' }}> <PopoverContent width="252px" top={{ lg: isExpanded ? '-16px' : 0, xl: isCollapsed ? 0 : '-16px' }}>
<PopoverBody p={ 4 }> <PopoverBody p={ 4 }>
<Text variant="secondary" fontSize="sm" mb={ 2 } display={{ lg: isExpanded ? 'none' : 'block', xl: isCollapsed ? 'block' : 'none' }}> <Text variant="secondary" fontSize="sm" mb={ 2 } display={{ lg: isExpanded ? 'none' : 'block', xl: isCollapsed ? 'block' : 'none' }}>
{ text } { item.text }
</Text> </Text>
<VStack spacing={ 1 } alignItems="start"> <VStack spacing={ 1 } alignItems="start">
{ subItems.map((item, index) => Array.isArray(item) ? ( { item.subItems.map((subItem, index) => Array.isArray(subItem) ? (
<Box <Box
key={ index } key={ index }
w="100%" w="100%"
...@@ -83,10 +85,10 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr ...@@ -83,10 +85,10 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr
borderColor: 'divider', borderColor: 'divider',
}} }}
> >
{ item.map(subItem => <NavLink key={ subItem.text } item={ subItem } isCollapsed={ false }/>) } { subItem.map(subSubItem => <NavLink key={ subSubItem.text } item={ subSubItem } isCollapsed={ false }/>) }
</Box> </Box>
) : ) :
<NavLink key={ item.text } item={ item } isCollapsed={ false }/>, <NavLink key={ item.text } item={ subItem } isCollapsed={ false }/>,
) } ) }
</VStack> </VStack>
</PopoverBody> </PopoverBody>
......
...@@ -11,15 +11,16 @@ import type { NavGroupItem } from 'types/client/navigation-items'; ...@@ -11,15 +11,16 @@ import type { NavGroupItem } from 'types/client/navigation-items';
import chevronIcon from 'icons/arrows/east-mini.svg'; import chevronIcon from 'icons/arrows/east-mini.svg';
import NavLinkIcon from './NavLinkIcon';
import useNavLinkStyleProps from './useNavLinkStyleProps'; import useNavLinkStyleProps from './useNavLinkStyleProps';
type Props = NavGroupItem & { type Props = {
isCollapsed?: boolean; item: NavGroupItem;
onClick: () => void; onClick: () => void;
} }
const NavLinkGroup = ({ text, icon, isActive, onClick }: Props) => { const NavLinkGroup = ({ item, onClick }: Props) => {
const styleProps = useNavLinkStyleProps({ isActive }); const styleProps = useNavLinkStyleProps({ isActive: item.isActive });
return ( return (
<Box as="li" listStyleType="none" w="100%" onClick={ onClick }> <Box as="li" listStyleType="none" w="100%" onClick={ onClick }>
...@@ -27,15 +28,15 @@ const NavLinkGroup = ({ text, icon, isActive, onClick }: Props) => { ...@@ -27,15 +28,15 @@ const NavLinkGroup = ({ text, icon, isActive, onClick }: Props) => {
{ ...styleProps.itemProps } { ...styleProps.itemProps }
w="100%" w="100%"
px={ 3 } px={ 3 }
aria-label={ `${ text } link group` } aria-label={ `${ item.text } link group` }
> >
<Flex justifyContent="space-between" width="100%" alignItems="center" pr={ 1 }> <Flex justifyContent="space-between" width="100%" alignItems="center" pr={ 1 }>
<HStack spacing={ 3 } overflow="hidden"> <HStack spacing={ 3 } overflow="hidden">
<Icon as={ icon } boxSize="30px"/> <NavLinkIcon item={ item }/>
<Text <Text
{ ...styleProps.textProps } { ...styleProps.textProps }
> >
{ text } { item.text }
</Text> </Text>
</HStack> </HStack>
<Icon as={ chevronIcon } transform="rotate(180deg)" boxSize={ 6 }/> <Icon as={ chevronIcon } transform="rotate(180deg)" boxSize={ 6 }/>
......
import { Icon } from '@chakra-ui/react';
import React from 'react';
import type { NavItem, NavGroupItem } from 'types/client/navigation-items';
const NavLinkIcon = ({ item }: { item: NavItem | NavGroupItem}) => {
if ('icon' in item) {
return <Icon as={ item.icon } boxSize="30px"/>;
}
if ('iconComponent' in item && item.iconComponent) {
const IconComponent = item.iconComponent;
return <IconComponent size={ 30 }/>;
}
return null;
};
export default NavLinkIcon;
...@@ -82,7 +82,7 @@ const NavigationDesktop = () => { ...@@ -82,7 +82,7 @@ const NavigationDesktop = () => {
<VStack as="ul" spacing="1" alignItems="flex-start"> <VStack as="ul" spacing="1" alignItems="flex-start">
{ mainNavItems.map((item) => { { mainNavItems.map((item) => {
if (isGroupItem(item)) { if (isGroupItem(item)) {
return <NavLinkGroupDesktop key={ item.text } { ...item } isCollapsed={ isCollapsed }/>; return <NavLinkGroupDesktop key={ item.text } item={ item } isCollapsed={ isCollapsed }/>;
} else { } else {
return <NavLink key={ item.text } item={ item } isCollapsed={ isCollapsed }/>; return <NavLink key={ item.text } item={ item } isCollapsed={ isCollapsed }/>;
} }
......
...@@ -57,7 +57,7 @@ const NavigationMobile = () => { ...@@ -57,7 +57,7 @@ const NavigationMobile = () => {
> >
{ mainNavItems.map((item, index) => { { mainNavItems.map((item, index) => {
if (isGroupItem(item)) { if (isGroupItem(item)) {
return <NavLinkGroupMobile key={ item.text } { ...item } onClick={ onGroupItemOpen(index) }/>; return <NavLinkGroupMobile key={ item.text } item={ item } onClick={ onGroupItemOpen(index) }/>;
} else { } else {
return <NavLink key={ item.text } item={ item }/>; return <NavLink key={ item.text } item={ item }/>;
} }
......
...@@ -53,6 +53,6 @@ test.describe('auth', () => { ...@@ -53,6 +53,6 @@ test.describe('auth', () => {
); );
await component.getByAltText(/Profile picture/i).click(); await component.getByAltText(/Profile picture/i).click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 550 } }); await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 600 } });
}); });
}); });
...@@ -7,7 +7,7 @@ import UserAvatar from 'ui/shared/UserAvatar'; ...@@ -7,7 +7,7 @@ import UserAvatar from 'ui/shared/UserAvatar';
import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent'; import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent';
const ProfileMenuDesktop = () => { const ProfileMenuDesktop = () => {
const { data, isFetched } = useFetchProfileInfo(); const { data } = useFetchProfileInfo();
const loginUrl = useLoginUrl(); const loginUrl = useLoginUrl();
return ( return (
...@@ -21,7 +21,7 @@ const ProfileMenuDesktop = () => { ...@@ -21,7 +21,7 @@ const ProfileMenuDesktop = () => {
as={ data ? undefined : 'a' } as={ data ? undefined : 'a' }
href={ data ? undefined : loginUrl } href={ data ? undefined : loginUrl }
> >
<UserAvatar size={ 50 } data={ data } isFetched={ isFetched }/> <UserAvatar size={ 50 }/>
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
{ data && ( { data && (
......
...@@ -8,19 +8,26 @@ import buildApiUrl from 'playwright/utils/buildApiUrl'; ...@@ -8,19 +8,26 @@ import buildApiUrl from 'playwright/utils/buildApiUrl';
import ProfileMenuMobile from './ProfileMenuMobile'; import ProfileMenuMobile from './ProfileMenuMobile';
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('no auth', async({ mount, page }) => { test('no auth', async({ mount, page }) => {
const hooksConfig = {
router: {
asPath: '/',
pathname: '/',
},
};
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<ProfileMenuMobile/> <ProfileMenuMobile/>
</TestApp>, </TestApp>,
{ hooksConfig },
); );
await component.locator('.identicon').click(); await component.locator('.identicon').click();
await expect(page).toHaveScreenshot(); expect(page.url()).toBe('http://localhost:3100/auth/auth0?path=%2F');
}); });
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test.describe('auth', () => { test.describe('auth', () => {
const extendedTest = test.extend({ const extendedTest = test.extend({
context: ({ context }, use) => { context: ({ context }, use) => {
......
import { Box, Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, Button, Flex } from '@chakra-ui/react'; import { Box, Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, Button } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
...@@ -9,34 +9,36 @@ import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent'; ...@@ -9,34 +9,36 @@ import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent';
const ProfileMenuMobile = () => { const ProfileMenuMobile = () => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const { data, isFetched } = useFetchProfileInfo(); const { data } = useFetchProfileInfo();
const loginUrl = useLoginUrl(); const loginUrl = useLoginUrl();
return ( return (
<> <>
<Box padding={ 2 } onClick={ onOpen }> <Box padding={ 2 } onClick={ data ? onOpen : undefined }>
<UserAvatar size={ 24 } data={ data } isFetched={ isFetched }/> <Button
variant="unstyled"
height="auto"
as={ data ? undefined : 'a' }
href={ data ? undefined : loginUrl }
>
<UserAvatar size={ 24 }/>
</Button>
</Box> </Box>
<Drawer { data && (
isOpen={ isOpen } <Drawer
placement="right" isOpen={ isOpen }
onClose={ onClose } placement="right"
autoFocus={ false } onClose={ onClose }
> autoFocus={ false }
<DrawerOverlay/> >
<DrawerContent maxWidth="260px"> <DrawerOverlay/>
<DrawerBody p={ 6 }> <DrawerContent maxWidth="260px">
<Flex justifyContent="end" mb={ 6 }> <DrawerBody p={ 6 }>
<Box onClick={ onClose }> <ProfileMenuContent { ...data }/>
<UserAvatar size={ 24 } data={ data } isFetched={ isFetched }/> </DrawerBody>
</Box> </DrawerContent>
</Flex> </Drawer>
{ data ? <ProfileMenuContent { ...data }/> : ( ) }
<Button size="sm" width="full" variant="outline" as="a" href={ loginUrl }>Sign In</Button>
) }
</DrawerBody>
</DrawerContent>
</Drawer>
</> </>
); );
}; };
......
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