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