Commit 9837acc2 authored by tom's avatar tom

add auth guard to address action items

parent b98271b6
import React from 'react';
// TODO @tom2drum remove this hook
export default function useIsAccountActionAllowed() {
return React.useCallback(() => {
return true;
}, []);
}
......@@ -5,10 +5,10 @@ import React from 'react';
import config from 'configs/app';
import { getResourceKey } from 'lib/api/useApiQuery';
import useIsAccountActionAllowed from 'lib/hooks/useIsAccountActionAllowed';
import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing';
import * as mixpanel from 'lib/mixpanel/index';
import IconSvg from 'ui/shared/IconSvg';
import AuthGuard from 'ui/snippets/auth/AuthGuard';
import WatchlistAddModal from 'ui/watchlist/AddressModal/AddressModal';
import DeleteAddressModal from 'ui/watchlist/DeleteAddressModal';
......@@ -23,16 +23,12 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
const deleteModalProps = useDisclosure();
const queryClient = useQueryClient();
const router = useRouter();
const isAccountActionAllowed = useIsAccountActionAllowed();
const onFocusCapture = usePreventFocusAfterModalClosing();
const handleClick = React.useCallback(() => {
if (!isAccountActionAllowed()) {
return;
}
const handleAddToFavorite = React.useCallback(() => {
watchListId ? deleteModalProps.onOpen() : addModalProps.onOpen();
!watchListId && mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Add to watchlist' });
}, [ isAccountActionAllowed, watchListId, deleteModalProps, addModalProps ]);
}, [ watchListId, deleteModalProps, addModalProps ]);
const handleAddOrDeleteSuccess = React.useCallback(async() => {
const queryKey = getResourceKey('address', { pathParams: { hash: router.query.hash?.toString() } });
......@@ -50,7 +46,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
const formData = React.useMemo(() => {
if (typeof watchListId !== 'number') {
return;
return { address_hash: hash };
}
return {
......@@ -65,21 +61,25 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
return (
<>
<Tooltip label={ `${ watchListId ? 'Remove address from Watch list' : 'Add address to Watch list' }` }>
<IconButton
isActive={ Boolean(watchListId) }
className={ className }
aria-label="edit"
variant="outline"
size="sm"
pl="6px"
pr="6px"
flexShrink={ 0 }
onClick={ handleClick }
icon={ <IconSvg name={ watchListId ? 'star_filled' : 'star_outline' } boxSize={ 5 }/> }
onFocusCapture={ onFocusCapture }
/>
</Tooltip>
<AuthGuard onAuthSuccess={ handleAddToFavorite }>
{ ({ onClick }) => (
<Tooltip label={ `${ watchListId ? 'Remove address from Watch list' : 'Add address to Watch list' }` }>
<IconButton
isActive={ Boolean(watchListId) }
className={ className }
aria-label="edit"
variant="outline"
size="sm"
pl="6px"
pr="6px"
flexShrink={ 0 }
onClick={ onClick }
icon={ <IconSvg name={ watchListId ? 'star_filled' : 'star_outline' } boxSize={ 5 }/> }
onFocusCapture={ onFocusCapture }
/>
</Tooltip>
) }
</AuthGuard>
<WatchlistAddModal
{ ...addModalProps }
isAdd
......@@ -87,7 +87,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
onSuccess={ handleAddOrDeleteSuccess }
data={ formData }
/>
{ formData && (
{ formData.id && (
<DeleteAddressModal
{ ...deleteModalProps }
onClose={ handleDeleteModalClose }
......
......@@ -5,7 +5,6 @@ import React from 'react';
import type { ItemProps } from './types';
import config from 'configs/app';
import useIsAccountActionAllowed from 'lib/hooks/useIsAccountActionAllowed';
import * as mixpanel from 'lib/mixpanel/index';
import getQueryParamString from 'lib/router/getQueryParamString';
import Menu from 'ui/shared/chakra/Menu';
......@@ -30,7 +29,6 @@ const AccountActionsMenu = ({ isLoading, className, showUpdateMetadataItem }: Pr
const isTokenPage = router.pathname === '/token/[hash]';
const isTokenInstancePage = router.pathname === '/token/[hash]/instance/[id]';
const isTxPage = router.pathname === '/tx/[hash]';
const isAccountActionAllowed = useIsAccountActionAllowed();
const profileQuery = useProfileQuery();
......@@ -74,7 +72,7 @@ const AccountActionsMenu = ({ isLoading, className, showUpdateMetadataItem }: Pr
if (items.length === 1) {
return (
<Box className={ className }>
{ items[0].render({ type: 'button', hash, onBeforeClick: isAccountActionAllowed }) }
{ items[0].render({ type: 'button', hash }) }
</Box>
);
}
......@@ -95,7 +93,7 @@ const AccountActionsMenu = ({ isLoading, className, showUpdateMetadataItem }: Pr
<MenuList minWidth="180px" zIndex="popover">
{ items.map(({ render }, index) => (
<React.Fragment key={ index }>
{ render({ type: 'menu_item', hash, onBeforeClick: isAccountActionAllowed }) }
{ render({ type: 'menu_item', hash }) }
</React.Fragment>
)) }
</MenuList>
......
......@@ -12,6 +12,7 @@ import getPageType from 'lib/mixpanel/getPageType';
import AddressModal from 'ui/privateTags/AddressModal/AddressModal';
import TransactionModal from 'ui/privateTags/TransactionModal/TransactionModal';
import IconSvg from 'ui/shared/IconSvg';
import AuthGuard from 'ui/snippets/auth/AuthGuard';
import ButtonItem from '../parts/ButtonItem';
import MenuItem from '../parts/MenuItem';
......@@ -20,7 +21,7 @@ interface Props extends ItemProps {
entityType?: 'address' | 'tx';
}
const PrivateTagMenuItem = ({ className, hash, onBeforeClick, entityType = 'address', type }: Props) => {
const PrivateTagMenuItem = ({ className, hash, entityType = 'address', type }: Props) => {
const modal = useDisclosure();
const queryClient = useQueryClient();
const router = useRouter();
......@@ -28,14 +29,6 @@ const PrivateTagMenuItem = ({ className, hash, onBeforeClick, entityType = 'addr
const queryKey = getResourceKey(entityType === 'tx' ? 'tx' : 'address', { pathParams: { hash } });
const queryData = queryClient.getQueryData<Address | Transaction>(queryKey);
const handleClick = React.useCallback(() => {
if (!onBeforeClick()) {
return;
}
modal.onOpen();
}, [ modal, onBeforeClick ]);
const handleAddPrivateTag = React.useCallback(async() => {
await queryClient.refetchQueries({ queryKey });
modal.onClose();
......@@ -62,14 +55,24 @@ const PrivateTagMenuItem = ({ className, hash, onBeforeClick, entityType = 'addr
const element = (() => {
switch (type) {
case 'button': {
return <ButtonItem label="Add private tag" icon="privattags" onClick={ handleClick } className={ className }/>;
return (
<AuthGuard onAuthSuccess={ modal.onOpen }>
{ ({ onClick }) => (
<ButtonItem label="Add private tag" icon="privattags" onClick={ onClick } className={ className }/>
) }
</AuthGuard>
);
}
case 'menu_item': {
return (
<MenuItem className={ className } onClick={ handleClick }>
<IconSvg name="privattags" boxSize={ 6 } mr={ 2 }/>
<span>Add private tag</span>
</MenuItem>
<AuthGuard onAuthSuccess={ modal.onOpen }>
{ ({ onClick }) => (
<MenuItem className={ className } onClick={ onClick }>
<IconSvg name="privattags" boxSize={ 6 } mr={ 2 }/>
<span>Add private tag</span>
</MenuItem>
) }
</AuthGuard>
);
}
}
......
......@@ -8,34 +8,26 @@ import IconSvg from 'ui/shared/IconSvg';
import ButtonItem from '../parts/ButtonItem';
import MenuItem from '../parts/MenuItem';
const PublicTagMenuItem = ({ className, hash, onBeforeClick, type }: ItemProps) => {
const PublicTagMenuItem = ({ className, hash, type }: ItemProps) => {
const router = useRouter();
const handleClick = React.useCallback(() => {
if (!onBeforeClick()) {
return;
}
router.push({ pathname: '/public-tags/submit', query: { addresses: [ hash ] } });
}, [ hash, onBeforeClick, router ]);
}, [ hash, router ]);
const element = (() => {
switch (type) {
case 'button': {
return <ButtonItem label="Add public tag" icon="publictags" onClick={ handleClick } className={ className }/>;
}
case 'menu_item': {
return (
<MenuItem className={ className } onClick={ handleClick }>
<IconSvg name="publictags" boxSize={ 6 } mr={ 2 }/>
<span>Add public tag</span>
</MenuItem>
);
}
switch (type) {
case 'button': {
return <ButtonItem label="Add public tag" icon="publictags" onClick={ handleClick } className={ className }/>;
}
})();
return element;
case 'menu_item': {
return (
<MenuItem className={ className } onClick={ handleClick }>
<IconSvg name="publictags" boxSize={ 6 } mr={ 2 }/>
<span>Add public tag</span>
</MenuItem>
);
}
}
};
export default React.memo(PublicTagMenuItem);
......@@ -9,12 +9,13 @@ import useApiQuery from 'lib/api/useApiQuery';
import { PAGE_TYPE_DICT } from 'lib/mixpanel/getPageType';
import AddressVerificationModal from 'ui/addressVerification/AddressVerificationModal';
import IconSvg from 'ui/shared/IconSvg';
import AuthGuard from 'ui/snippets/auth/AuthGuard';
import useIsAuth from 'ui/snippets/auth/useIsAuth';
import ButtonItem from '../parts/ButtonItem';
import MenuItem from '../parts/MenuItem';
const TokenInfoMenuItem = ({ className, hash, onBeforeClick, type }: ItemProps) => {
const TokenInfoMenuItem = ({ className, hash, type }: ItemProps) => {
const router = useRouter();
const modal = useDisclosure();
const isAuth = useIsAuth();
......@@ -38,14 +39,6 @@ const TokenInfoMenuItem = ({ className, hash, onBeforeClick, type }: ItemProps)
},
});
const handleAddAddressClick = React.useCallback(() => {
if (!onBeforeClick({ pathname: '/account/verified-addresses' })) {
return;
}
modal.onOpen();
}, [ modal, onBeforeClick ]);
const handleAddApplicationClick = React.useCallback(async() => {
router.push({ pathname: '/account/verified-addresses', query: { address: hash } });
}, [ hash, router ]);
......@@ -72,18 +65,28 @@ const TokenInfoMenuItem = ({ className, hash, onBeforeClick, type }: ItemProps)
return hasApplication || tokenInfoQuery.data?.tokenAddress ? 'Update token info' : 'Add token info';
})();
const onClick = isVerifiedAddress ? handleAddApplicationClick : handleAddAddressClick;
const onAuthSuccess = isVerifiedAddress ? handleAddApplicationClick : modal.onOpen;
switch (type) {
case 'button': {
return <ButtonItem label={ label } icon={ icon } onClick={ onClick } className={ className }/>;
return (
<AuthGuard onAuthSuccess={ onAuthSuccess }>
{ ({ onClick }) => (
<ButtonItem label={ label } icon={ icon } onClick={ onClick } className={ className }/>
) }
</AuthGuard>
);
}
case 'menu_item': {
return (
<MenuItem className={ className } onClick={ onClick }>
{ icon }
<chakra.span ml={ 2 }>{ label }</chakra.span>
</MenuItem>
<AuthGuard onAuthSuccess={ onAuthSuccess }>
{ ({ onClick }) => (
<MenuItem className={ className } onClick={ onClick }>
{ icon }
<chakra.span ml={ 2 }>{ label }</chakra.span>
</MenuItem>
) }
</AuthGuard>
);
}
}
......
export type ItemType = 'button' | 'menu_item';
import type { Route } from 'nextjs-routes';
export interface ItemProps {
className?: string;
type: ItemType;
hash: string;
onBeforeClick: (route?: Route) => boolean;
}
import { useDisclosure } from '@chakra-ui/react';
import React from 'react';
import AuthModal from './AuthModal';
import useIsAuth from './useIsAuth';
interface InjectedProps {
onClick: () => void;
}
interface Props {
children: (props: InjectedProps) => React.ReactNode;
onAuthSuccess: () => void;
}
const AuthGuard = ({ children, onAuthSuccess }: Props) => {
const authModal = useDisclosure();
const isAuth = useIsAuth();
const handleClick = React.useCallback(() => {
isAuth ? onAuthSuccess() : authModal.onOpen();
}, [ authModal, isAuth, onAuthSuccess ]);
const handleModalClose = React.useCallback((isSuccess?: boolean) => {
if (isSuccess) {
onAuthSuccess();
}
authModal.onClose();
}, [ authModal, onAuthSuccess ]);
return (
<>
{ children({ onClick: handleClick }) }
{ authModal.isOpen && <AuthModal onClose={ handleModalClose } initialScreen={{ type: 'select_method' }}/> }
</>
);
};
export default React.memo(AuthGuard);
......@@ -15,11 +15,12 @@ import useProfileQuery from './useProfileQuery';
interface Props {
initialScreen: Screen;
onClose: () => void;
onClose: (isSuccess?: boolean) => void;
}
const AuthModal = ({ initialScreen, onClose }: Props) => {
const [ steps, setSteps ] = React.useState<Array<Screen>>([ initialScreen ]);
const [ isSuccess, setIsSuccess ] = React.useState(false);
const profileQuery = useProfileQuery();
const onNextStep = React.useCallback((screen: Screen) => {
......@@ -35,6 +36,7 @@ const AuthModal = ({ initialScreen, onClose }: Props) => {
}, [ initialScreen, onClose ]);
const onAuthSuccess = React.useCallback(async(screen: ScreenSuccess) => {
setIsSuccess(true);
const { data } = await profileQuery.refetch();
if (data) {
onNextStep({ ...screen, profile: data });
......@@ -42,6 +44,10 @@ const AuthModal = ({ initialScreen, onClose }: Props) => {
// TODO @tom2drum handle error case
}, [ onNextStep, profileQuery ]);
const onModalClose = React.useCallback(() => {
onClose(isSuccess);
}, [ isSuccess, onClose ]);
const header = (() => {
const currentStep = steps[steps.length - 1];
switch (currentStep.type) {
......@@ -92,7 +98,7 @@ const AuthModal = ({ initialScreen, onClose }: Props) => {
})();
return (
<Modal isOpen onClose={ onClose } size={{ base: 'full', lg: 'sm' }}>
<Modal isOpen onClose={ onModalClose } size={{ base: 'full', lg: 'sm' }}>
<ModalOverlay/>
<ModalContent p={ 6 } maxW={{ lg: '400px' }}>
<ModalHeader fontWeight="500" textStyle="h3" mb={ 2 } display="flex" alignItems="center" columnGap={ 2 }>
......
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