Commit 40981a37 authored by tom's avatar tom

handle add private tag

parent d8ec4dad
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
import type { UserInfo } from 'types/api/account';
import { resourceKey } from 'lib/api/resources';
import useLoginUrl from 'lib/hooks/useLoginUrl';
export default function useRedirectIfNotAuth() {
const queryClient = useQueryClient();
const profileData = queryClient.getQueryData<UserInfo>([ resourceKey('user_info') ]);
const isAuth = Boolean(profileData);
const loginUrl = useLoginUrl();
return React.useCallback(() => {
if (!isAuth) {
window.location.assign(loginUrl);
return true;
}
return false;
}, [ isAuth, loginUrl ]);
}
...@@ -3,14 +3,11 @@ import { useQueryClient } from '@tanstack/react-query'; ...@@ -3,14 +3,11 @@ import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { UserInfo } from 'types/api/account';
import starFilledIcon from 'icons/star_filled.svg'; import starFilledIcon from 'icons/star_filled.svg';
import starOutlineIcon from 'icons/star_outline.svg'; import starOutlineIcon from 'icons/star_outline.svg';
import { resourceKey } from 'lib/api/resources';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import useLoginUrl from 'lib/hooks/useLoginUrl';
import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing'; import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing';
import useRedirectIfNotAuth from 'lib/hooks/useRedirectIfNotAuth';
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';
...@@ -25,20 +22,15 @@ const AddressFavoriteButton = ({ className, hash, isAdded }: Props) => { ...@@ -25,20 +22,15 @@ const AddressFavoriteButton = ({ className, hash, isAdded }: Props) => {
const deleteModalProps = useDisclosure(); const deleteModalProps = useDisclosure();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter(); const router = useRouter();
const redirectIfNotAuth = useRedirectIfNotAuth();
const profileData = queryClient.getQueryData<UserInfo>([ resourceKey('user_info') ]);
const isAuth = Boolean(profileData);
const loginUrl = useLoginUrl();
const watchListQuery = useApiQuery('watchlist', { queryOptions: { enabled: isAdded } }); const watchListQuery = useApiQuery('watchlist', { queryOptions: { enabled: isAdded } });
const handleClick = React.useCallback(() => { const handleClick = React.useCallback(() => {
if (!isAuth) { if (redirectIfNotAuth()) {
window.location.assign(loginUrl);
return; return;
} }
isAdded ? deleteModalProps.onOpen() : addModalProps.onOpen(); isAdded ? deleteModalProps.onOpen() : addModalProps.onOpen();
}, [ addModalProps, deleteModalProps, isAdded, isAuth, loginUrl ]); }, [ addModalProps, deleteModalProps, isAdded, redirectIfNotAuth ]);
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() } });
......
...@@ -3,7 +3,7 @@ import { ...@@ -3,7 +3,7 @@ import {
Button, Button,
useColorModeValue, useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form'; import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
...@@ -11,7 +11,6 @@ import { useForm, Controller } from 'react-hook-form'; ...@@ -11,7 +11,6 @@ import { useForm, Controller } from 'react-hook-form';
import type { AddressTag, AddressTagErrors } from 'types/api/account'; import type { AddressTag, AddressTagErrors } from 'types/api/account';
import type { ResourceErrorAccount } from 'lib/api/resources'; import type { ResourceErrorAccount } from 'lib/api/resources';
import { resourceKey } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
import getErrorMessage from 'lib/getErrorMessage'; import getErrorMessage from 'lib/getErrorMessage';
import { ADDRESS_REGEXP } from 'lib/validations/address'; import { ADDRESS_REGEXP } from 'lib/validations/address';
...@@ -21,8 +20,9 @@ import TagInput from 'ui/shared/TagInput'; ...@@ -21,8 +20,9 @@ import TagInput from 'ui/shared/TagInput';
const TAG_MAX_LENGTH = 35; const TAG_MAX_LENGTH = 35;
type Props = { type Props = {
data?: AddressTag; data?: Partial<AddressTag>;
onClose: () => void; onClose: () => void;
onSuccess: () => Promise<void>;
setAlertVisible: (isAlertVisible: boolean) => void; setAlertVisible: (isAlertVisible: boolean) => void;
} }
...@@ -31,7 +31,7 @@ type Inputs = { ...@@ -31,7 +31,7 @@ type Inputs = {
tag: string; tag: string;
} }
const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { const AddressForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisible }) => {
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const [ pending, setPending ] = useState(false); const [ pending, setPending ] = useState(false);
const { control, handleSubmit, formState: { errors, isDirty }, setError } = useForm<Inputs>({ const { control, handleSubmit, formState: { errors, isDirty }, setError } = useForm<Inputs>({
...@@ -44,8 +44,6 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -44,8 +44,6 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const formBackgroundColor = useColorModeValue('white', 'gray.900'); const formBackgroundColor = useColorModeValue('white', 'gray.900');
const queryClient = useQueryClient();
const { mutate } = useMutation((formData: Inputs) => { const { mutate } = useMutation((formData: Inputs) => {
const body = { const body = {
name: formData?.tag, name: formData?.tag,
...@@ -74,11 +72,10 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -74,11 +72,10 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
setAlertVisible(true); setAlertVisible(true);
} }
}, },
onSuccess: () => { onSuccess: async() => {
queryClient.refetchQueries([ resourceKey('private_tags_address') ]).then(() => { await onSuccess();
onClose(); onClose();
setPending(false); setPending(false);
});
}, },
}); });
......
...@@ -9,18 +9,19 @@ import AddressForm from './AddressForm'; ...@@ -9,18 +9,19 @@ import AddressForm from './AddressForm';
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
data?: AddressTag; onSuccess: () => Promise<void>;
data?: Partial<AddressTag>;
} }
const AddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => { const AddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data }) => {
const title = data ? 'Edit address tag' : 'New address tag'; const title = data?.id ? 'Edit address tag' : 'New address tag';
const text = !data ? 'Label any address with a private address tag (up to 35 chars) to customize your explorer experience.' : ''; const text = !data?.id ? 'Label any address with a private address tag (up to 35 chars) to customize your explorer experience.' : '';
const [ isAlertVisible, setAlertVisible ] = useState(false); const [ isAlertVisible, setAlertVisible ] = useState(false);
const renderForm = useCallback(() => { const renderForm = useCallback(() => {
return <AddressForm data={ data } onClose={ onClose } setAlertVisible={ setAlertVisible }/>; return <AddressForm data={ data } onClose={ onClose } onSuccess={ onSuccess } setAlertVisible={ setAlertVisible }/>;
}, [ data, onClose ]); }, [ data, onClose, onSuccess ]);
return ( return (
<FormModal<AddressTag> <FormModal<AddressTag>
isOpen={ isOpen } isOpen={ isOpen }
......
...@@ -16,7 +16,7 @@ import AddressTagTable from './AddressTagTable/AddressTagTable'; ...@@ -16,7 +16,7 @@ import AddressTagTable from './AddressTagTable/AddressTagTable';
import DeletePrivateTagModal from './DeletePrivateTagModal'; import DeletePrivateTagModal from './DeletePrivateTagModal';
const PrivateAddressTags = () => { const PrivateAddressTags = () => {
const { data: addressTagsData, isLoading, isError } = useApiQuery('private_tags_address', { queryOptions: { refetchOnMount: false } }); const { data: addressTagsData, isLoading, isError, refetch } = useApiQuery('private_tags_address', { queryOptions: { refetchOnMount: false } });
const addressModalProps = useDisclosure(); const addressModalProps = useDisclosure();
const deleteModalProps = useDisclosure(); const deleteModalProps = useDisclosure();
...@@ -30,6 +30,10 @@ const PrivateAddressTags = () => { ...@@ -30,6 +30,10 @@ const PrivateAddressTags = () => {
addressModalProps.onOpen(); addressModalProps.onOpen();
}, [ addressModalProps ]); }, [ addressModalProps ]);
const onAddOrEditSuccess = useCallback(async() => {
await refetch();
}, [ refetch ]);
const onAddressModalClose = useCallback(() => { const onAddressModalClose = useCallback(() => {
setAddressModalData(undefined); setAddressModalData(undefined);
addressModalProps.onClose(); addressModalProps.onClose();
...@@ -103,7 +107,7 @@ const PrivateAddressTags = () => { ...@@ -103,7 +107,7 @@ const PrivateAddressTags = () => {
Add address tag Add address tag
</Button> </Button>
</Box> </Box>
<AddressModal { ...addressModalProps } onClose={ onAddressModalClose } data={ addressModalData }/> <AddressModal { ...addressModalProps } onClose={ onAddressModalClose } data={ addressModalData } onSuccess={ onAddOrEditSuccess }/>
{ deleteModalData && ( { deleteModalData && (
<DeletePrivateTagModal <DeletePrivateTagModal
{ ...deleteModalProps } { ...deleteModalProps }
......
import { Button, Menu, MenuButton, MenuItem, MenuList, Icon, Flex } from '@chakra-ui/react'; import { Button, Menu, MenuButton, MenuItem, MenuList, Icon, Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import iconArrow from 'icons/arrows/east-mini.svg'; import iconArrow from 'icons/arrows/east-mini.svg';
import getQueryParamString from 'lib/router/getQueryParamString';
import PrivateTagMenuItem from './PrivateTagMenuItem';
const AddressActions = () => {
const router = useRouter();
const hash = getQueryParamString(router.query.hash);
const TokenDetailsActions = () => {
return ( return (
<Menu> <Menu>
<MenuButton <MenuButton
...@@ -24,12 +32,10 @@ const TokenDetailsActions = () => { ...@@ -24,12 +32,10 @@ const TokenDetailsActions = () => {
<MenuItem py={ 2 } px={ 4 }> <MenuItem py={ 2 } px={ 4 }>
Add public tag Add public tag
</MenuItem> </MenuItem>
<MenuItem py={ 2 } px={ 4 }> <PrivateTagMenuItem py={ 2 } px={ 4 } hash={ hash }/>
Add private tag
</MenuItem>
</MenuList> </MenuList>
</Menu> </Menu>
); );
}; };
export default React.memo(TokenDetailsActions); export default React.memo(AddressActions);
import { MenuItem, chakra, useDisclosure } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
import type { Address } from 'types/api/address';
import { getResourceKey } from 'lib/api/useApiQuery';
import useRedirectIfNotAuth from 'lib/hooks/useRedirectIfNotAuth';
import PrivateTagModal from 'ui/privateTags/AddressModal/AddressModal';
interface Props {
className?: string;
hash: string;
}
const PrivateTagMenuItem = ({ className, hash }: Props) => {
const modal = useDisclosure();
const queryClient = useQueryClient();
const redirectIfNotAuth = useRedirectIfNotAuth();
const queryKey = getResourceKey('address', { pathParams: { hash } });
const addressData = queryClient.getQueryData<Address>(queryKey);
const handleClick = React.useCallback(() => {
if (redirectIfNotAuth()) {
return;
}
modal.onOpen();
}, [ modal, redirectIfNotAuth ]);
const handleAddPrivateTag = React.useCallback(async() => {
await queryClient.refetchQueries({ queryKey });
modal.onClose();
}, [ queryClient, queryKey, modal ]);
const formData = React.useMemo(() => {
return {
address_hash: hash,
};
}, [ hash ]);
if (addressData?.private_tags?.length) {
return null;
}
return (
<>
<MenuItem className={ className }onClick={ handleClick }>
Add private tag
</MenuItem>
<PrivateTagModal isOpen={ modal.isOpen } onClose={ modal.onClose } onSuccess={ handleAddPrivateTag } data={ formData }/>
</>
);
};
export default React.memo(chakra(PrivateTagMenuItem));
...@@ -7,10 +7,9 @@ import type { TokenInfo } from 'types/api/token'; ...@@ -7,10 +7,9 @@ import type { TokenInfo } from 'types/api/token';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import AddressActionsMenu from 'ui/shared/AddressActions/Menu';
import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo'; import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo';
import TokenDetailsActions from './TokenDetails/TokenDetailsActions';
interface Props { interface Props {
tokenQuery: UseQueryResult<TokenInfo>; tokenQuery: UseQueryResult<TokenInfo>;
} }
...@@ -51,7 +50,7 @@ const TokenContractInfo = ({ tokenQuery }: Props) => { ...@@ -51,7 +50,7 @@ const TokenContractInfo = ({ tokenQuery }: Props) => {
<AddressHeadingInfo <AddressHeadingInfo
address={ address } address={ address }
token={ contractQuery.data?.token } token={ contractQuery.data?.token }
after={ appConfig.isAccountSupported ? <TokenDetailsActions/> : null } after={ appConfig.isAccountSupported ? <AddressActionsMenu/> : null }
/> />
); );
}; };
......
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