Commit 349f2e75 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #117 from blockscout/mobile-view-pages

mobil view for pages content
parents be1f9f3e e33d887d
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 9a7 7 0 1 1 7 7h-2v2h-2v2H9v2H2v-5.414l6.148-6.148A7.025 7.025 0 0 1 8 9Zm3 5h4a5 5 0 1 0-4.786-3.547l.174.573L4 17.414V20h3v-2h2v-2h2v-2Zm4-3a2 2 0 1 1 0-4 2 2 0 0 1 0 4Z" fill="currentColor"/>
</svg>
\ No newline at end of file
<svg width="30" height="30" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M23.433 12.64a1.212 1.212 0 0 1-.857.354H6.212a1.212 1.212 0 0 1-1.12-.745 1.212 1.212 0 0 1 .266-1.321L8.994 7.29a1.212 1.212 0 0 1 1.71 1.71L9.14 10.57h13.436a1.212 1.212 0 0 1 .857 2.068ZM7.424 17.236h16.363a1.213 1.213 0 0 1 1.121.745 1.212 1.212 0 0 1-.266 1.321l-3.636 3.637a1.213 1.213 0 1 1-1.71-1.71l1.564-1.569H7.424a1.212 1.212 0 0 1 0-2.424Z"/></svg>
\ No newline at end of file
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.433 12.64a1.212 1.212 0 0 1-.857.354H6.212a1.212 1.212 0 0 1-1.12-.745 1.212 1.212 0 0 1 .266-1.321L8.994 7.29a1.212 1.212 0 0 1 1.71 1.71L9.14 10.57h13.436a1.212 1.212 0 0 1 .857 2.068ZM7.424 17.236h16.363a1.213 1.213 0 0 1 1.121.745 1.212 1.212 0 0 1-.266 1.321l-3.636 3.637a1.213 1.213 0 1 1-1.71-1.71l1.564-1.569H7.424a1.212 1.212 0 0 1 0-2.424Z" fill="currentColor"/>
</svg>
\ No newline at end of file
import { alertAnatomy as parts } from '@chakra-ui/anatomy';
import type { StyleFunctionProps } from '@chakra-ui/styled-system';
import { createMultiStyleConfigHelpers } from '@chakra-ui/styled-system';
import { getColor, mode, transparentize } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys);
function getBg(props: StyleFunctionProps): string {
const { theme, colorScheme: c } = props;
const lightBg = getColor(theme, `${ c }.100`, c);
const darkBg = transparentize(`${ c }.200`, 0.16)(theme);
return mode(lightBg, darkBg)(props);
}
const baseStyle = definePartsStyle({
container: {
borderRadius: 'md',
......@@ -12,8 +22,21 @@ const baseStyle = definePartsStyle({
},
});
const variantSubtle = definePartsStyle((props) => {
return {
container: {
bgColor: getBg(props),
},
};
});
const variants = {
subtle: variantSubtle,
};
const Alert = defineMultiStyleConfig({
baseStyle,
variants,
});
export default Alert;
......@@ -125,6 +125,9 @@ const variantFloating = definePartsStyle((props) => {
margin: 0,
transformOrigin: 'top left',
transitionProperty: 'font-size, line-height, padding, top, background-color',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
},
'input:not(:placeholder-shown) + label, textarea:not(:placeholder-shown) + label': {
...activeLabelStyles,
......
......@@ -35,6 +35,7 @@ const baseStyleHeader = defineStyle((props) => ({
const baseStyleBody = defineStyle({
padding: 0,
marginBottom: 8,
flex: 'initial',
});
const baseStyleFooter = defineStyle({
......@@ -70,16 +71,33 @@ const baseStyle = definePartsStyle((props) => ({
const sizes = {
md: definePartsStyle({
dialogContainer: {
height: '100%',
},
dialog: {
maxW: '760px',
},
}),
full: definePartsStyle({
dialogContainer: {
height: '100%',
},
dialog: {
maxW: '100vw',
minH: '100vh',
my: '0',
borderRadius: '0',
padding: '80px 16px 32px 16px',
height: '100%',
overflowY: 'scroll',
},
closeButton: {
top: 4,
right: 6,
width: 6,
height: 6,
},
header: {
mb: 6,
},
}),
};
......
......@@ -4,6 +4,12 @@ import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system';
import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles';
const sizes = {
md: defineStyle({
fontSize: 'md',
lineHeight: '20px',
h: '160px',
borderRadius: 'base',
}),
lg: defineStyle({
fontSize: 'md',
lineHeight: '20px',
......
import React, { useCallback } from 'react';
import type { ApiKey } from 'types/api/account';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import ApiKeySnippet from 'ui/shared/ApiKeySnippet';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
interface Props {
item: ApiKey;
onEditClick: (item: ApiKey) => void;
onDeleteClick: (item: ApiKey) => void;
}
const ApiKeyListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
const onItemDeleteClick = useCallback(() => {
return onDeleteClick(item);
}, [ item, onDeleteClick ]);
return (
<AccountListItemMobile>
<ApiKeySnippet apiKey={ item.api_key } name={ item.name }/>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</AccountListItemMobile>
);
};
export default ApiKeyListItem;
import {
Tr,
Td,
HStack,
Text,
} from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { ApiKey } from 'types/api/account';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DeleteButton from 'ui/shared/DeleteButton';
import EditButton from 'ui/shared/EditButton';
import ApiKeySnippet from 'ui/shared/ApiKeySnippet';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
interface Props {
item: ApiKey;
......@@ -31,17 +28,10 @@ const ApiKeyTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return (
<Tr alignItems="top" key={ item.api_key }>
<Td>
<HStack>
<Text fontSize="md" fontWeight={ 600 }>{ item.api_key }</Text>
<CopyToClipboard text={ item.api_key }/>
</HStack>
<Text fontSize="sm" marginTop={ 0.5 } variant="secondary">{ item.name }</Text>
<ApiKeySnippet apiKey={ item.api_key } name={ item.name }/>
</Td>
<Td>
<HStack spacing={ 6 }>
<EditButton onClick={ onItemEditClick }/>
<DeleteButton onClick={ onItemDeleteClick }/>
</HStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</Td>
</Tr>
);
......
......@@ -28,7 +28,7 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const renderText = useCallback(() => {
return (
<Text display="flex">API key for<Text fontWeight="600" whiteSpace="pre">{ ` "${ data.name || 'name' }" ` }</Text>will be deleted</Text>
<Text> API key for <Text fontWeight="600" as="span">{ ` "${ data.name || 'name' }" ` }</Text> will be deleted </Text>
);
}, [ data.name ]);
......
......@@ -28,7 +28,7 @@ const Header = () => {
width="100%"
alignItems="center"
justifyContent="space-between"
zIndex={ 10 }
zIndex="sticky"
>
<Burger/>
<NetworkLogo/>
......
......@@ -56,6 +56,7 @@ const SearchBarMobile = ({ onChange, onSubmit }: Props) => {
position="fixed"
top="56px"
left="0"
zIndex="docked"
bgColor={ bgColor }
transform={ isVisible ? 'translateY(0)' : 'translateY(-100%)' }
transitionProperty="transform"
......
......@@ -128,6 +128,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
<Textarea
{ ...field }
size="lg"
minH="300px"
isInvalid={ Boolean(errors.abi) }
/>
<FormLabel>{ getPlaceholderWithError(`Custom ABI [{...}] (JSON format)`, errors.abi?.message) }</FormLabel>
......
import React, { useCallback } from 'react';
import type { CustomAbi } from 'types/api/account';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import AddressSnippet from 'ui/shared/AddressSnippet';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
interface Props {
item: CustomAbi;
onEditClick: (item: CustomAbi) => void;
onDeleteClick: (item: CustomAbi) => void;
}
const CustomAbiListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
const onItemDeleteClick = useCallback(() => {
return onDeleteClick(item);
}, [ item, onDeleteClick ]);
return (
<AccountListItemMobile>
<AddressSnippet address={ item.contract_address_hash } subtitle={ item.name }/>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</AccountListItemMobile>
);
};
export default React.memo(CustomAbiListItem);
import {
Tr,
Td,
HStack,
Text,
} from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { CustomAbi } from 'types/api/account';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DeleteButton from 'ui/shared/DeleteButton';
import EditButton from 'ui/shared/EditButton';
import AddressSnippet from 'ui/shared/AddressSnippet';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
interface Props {
item: CustomAbi;
......@@ -31,17 +28,10 @@ const CustomAbiTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return (
<Tr alignItems="top" key={ item.id }>
<Td>
<HStack>
<Text fontSize="md" fontWeight={ 600 }>{ item.contract_address_hash }</Text>
<CopyToClipboard text={ item.contract_address_hash }/>
</HStack>
<Text fontSize="sm" marginTop={ 0.5 } variant="secondary">{ item.name }</Text>
<AddressSnippet address={ item.contract_address_hash } subtitle={ item.name }/>
</Td>
<Td>
<HStack spacing={ 6 }>
<EditButton onClick={ onItemEditClick }/>
<DeleteButton onClick={ onItemDeleteClick }/>
</HStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</Td>
</Tr>
);
......
......@@ -28,7 +28,7 @@ const DeleteCustomAbiModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const renderText = useCallback(() => {
return (
<Text display="flex">Custom ABI for<Text fontWeight="600" whiteSpace="pre">{ ` "${ data.name || 'name' }" ` }</Text>will be deleted</Text>
<Text>Custom ABI for<Text fontWeight="600" as="span">{ ` "${ data.name || 'name' }" ` }</Text>will be deleted</Text>
);
}, [ data.name ]);
......
import { Box, Button, HStack, Link, Text, Skeleton, useDisclosure } from '@chakra-ui/react';
import { Box, Button, Stack, Link, Text, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { ApiKey, ApiKeys } from 'types/api/account';
import fetch from 'lib/client/fetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import { space } from 'lib/html-entities';
import ApiKeyModal from 'ui/apiKey/ApiKeyModal/ApiKeyModal';
import ApiKeyListItem from 'ui/apiKey/ApiKeyTable/ApiKeyListItem';
import ApiKeyTable from 'ui/apiKey/ApiKeyTable/ApiKeyTable';
import DeleteApiKeyModal from 'ui/apiKey/DeleteApiKeyModal';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import AccountPageHeader from 'ui/shared/AccountPageHeader';
import Page from 'ui/shared/Page/Page';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable';
import DataFetchAlert from '../shared/DataFetchAlert';
......@@ -20,6 +24,7 @@ const DATA_LIMIT = 3;
const ApiKeysPage: React.FC = () => {
const apiKeyModalProps = useDisclosure();
const deleteModalProps = useDisclosure();
const isMobile = useIsMobile();
const [ apiKeyModalData, setApiKeyModalData ] = useState<ApiKey>();
const [ deleteModalData, setDeleteModalData ] = useState<ApiKey>();
......@@ -47,40 +52,59 @@ const ApiKeysPage: React.FC = () => {
}, [ deleteModalProps ]);
const description = (
<Text marginBottom={ 12 }>
<AccountPageDescription>
Create API keys to use for your RPC and EthRPC API requests. For more information, see { space }
<Link href="#">“How to use a Blockscout API key”</Link>.
</Text>
</AccountPageDescription>
);
const content = (() => {
if (isLoading && !data) {
return (
const loader = isMobile ? <SkeletonAccountMobile/> : (
<>
{ description }
<SkeletonTable columns={ [ '100%', '108px' ] }/>
<Skeleton height="48px" width="156px" marginTop={ 8 }/>
</>
);
return (
<>
{ description }
{ loader }
</>
);
}
if (isError) {
return <DataFetchAlert/>;
}
const canAdd = data.length < DATA_LIMIT;
return (
<>
{ description }
{ Boolean(data.length) && (
const list = isMobile ? (
<Box>
{ data.map((item) => (
<ApiKeyListItem
item={ item }
key={ item.api_key }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
)) }
</Box>
) : (
<ApiKeyTable
data={ data }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
limit={ DATA_LIMIT }
/>
) }
<HStack marginTop={ 8 } spacing={ 5 }>
);
const canAdd = data.length < DATA_LIMIT;
return (
<>
{ description }
{ Boolean(data.length) && list }
<Stack marginTop={ 8 } spacing={ 5 } direction={{ base: 'column', lg: 'row' }}>
<Button
variant="primary"
size="lg"
......@@ -94,7 +118,7 @@ const ApiKeysPage: React.FC = () => {
{ `You have added the maximum number of API keys (${ DATA_LIMIT }). Contact us to request additional keys.` }
</Text>
) }
</HStack>
</Stack>
<ApiKeyModal { ...apiKeyModalProps } onClose={ onApiKeyModalClose } data={ apiKeyModalData }/>
{ deleteModalData && <DeleteApiKeyModal { ...deleteModalProps } onClose={ onDeleteModalClose } data={ deleteModalData }/> }
</>
......
import { Box, Button, HStack, Text, Skeleton, useDisclosure } from '@chakra-ui/react';
import { Box, Button, HStack, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { CustomAbi, CustomAbis } from 'types/api/account';
import fetch from 'lib/client/fetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal';
import CustomAbiListItem from 'ui/customAbi/CustomAbiTable/CustomAbiListItem';
import CustomAbiTable from 'ui/customAbi/CustomAbiTable/CustomAbiTable';
import DeleteCustomAbiModal from 'ui/customAbi/DeleteCustomAbiModal';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import AccountPageHeader from 'ui/shared/AccountPageHeader';
import Page from 'ui/shared/Page/Page';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable';
import DataFetchAlert from '../shared/DataFetchAlert';
......@@ -17,6 +21,7 @@ import DataFetchAlert from '../shared/DataFetchAlert';
const CustomAbiPage: React.FC = () => {
const customAbiModalProps = useDisclosure();
const deleteModalProps = useDisclosure();
const isMobile = useIsMobile();
const [ customAbiModalData, setCustomAbiModalData ] = useState<CustomAbi>();
const [ deleteModalData, setDeleteModalData ] = useState<CustomAbi>();
......@@ -44,36 +49,55 @@ const CustomAbiPage: React.FC = () => {
}, [ deleteModalProps ]);
const description = (
<Text marginBottom={ 12 }>
<AccountPageDescription>
Add custom ABIs for any contract and access when logged into your account. Helpful for debugging, functional testing and contract interaction.
</Text>
</AccountPageDescription>
);
const content = (() => {
if (isLoading && !data) {
return (
const loader = isMobile ? <SkeletonAccountMobile/> : (
<>
{ description }
<SkeletonTable columns={ [ '100%', '108px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
</>
);
return (
<>
{ description }
{ loader }
</>
);
}
if (isError) {
return <DataFetchAlert/>;
}
return (
<>
{ description }
{ data.length > 0 && (
const list = isMobile ? (
<Box>
{ data.map((item) => (
<CustomAbiListItem
item={ item }
key={ item.id }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
)) }
</Box>
) : (
<CustomAbiTable
data={ data }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
) }
);
return (
<>
{ description }
{ data.length > 0 && list }
<HStack marginTop={ 8 } spacing={ 5 }>
<Button
variant="primary"
......
......@@ -38,7 +38,7 @@ const PrivateTags = ({ tab }: Props) => {
<Box h="100%">
<AccountPageHeader text="Private tags"/>
<Tabs variant="soft-rounded" colorScheme="blue" isLazy onChange={ onChangeTab } defaultIndex={ TABS.indexOf(tab) }>
<TabList marginBottom={ 8 }>
<TabList marginBottom={{ base: 6, lg: 8 }}>
<Tab>Address</Tab>
<Tab>Transaction</Tab>
</TabList>
......
import { Box } from '@chakra-ui/react';
import { ArrowBackIcon } from '@chakra-ui/icons';
import { Box, Link, Text } from '@chakra-ui/react';
import React, { useCallback, useState } from 'react';
import { animateScroll } from 'react-scroll';
import type { PublicTag } from 'types/api/account';
import useIsMobile from 'lib/hooks/useIsMobile';
import useToast from 'lib/hooks/useToast';
import PublicTagsData from 'ui/publicTags/PublicTagsData';
import PublicTagsForm from 'ui/publicTags/PublicTagsForm/PublicTagsForm';
......@@ -24,6 +26,7 @@ const PublicTagsComponent: React.FC = () => {
const [ formData, setFormData ] = useState<PublicTag>();
const toast = useToast();
const isMobile = useIsMobile();
const showToast = useCallback((action: TToastAction) => {
toast({
......@@ -59,6 +62,7 @@ const PublicTagsComponent: React.FC = () => {
}, [ showToast ]);
const onTagDelete = useCallback(() => showToast('removed'), [ showToast ]);
const onGoBack = useCallback(() => setScreen('data'), [ ]);
let content;
let header;
......@@ -74,6 +78,12 @@ const PublicTagsComponent: React.FC = () => {
return (
<Page>
<Box h="100%">
{ isMobile && screen === 'form' && (
<Link display="inline-flex" alignItems="center" mb={ 6 } onClick={ onGoBack }>
<ArrowBackIcon w={ 6 } h={ 6 }/>
<Text variant="inherit" fontSize="sm" ml={ 2 }>Public tags</Text>
</Link>
) }
<AccountPageHeader text={ header }/>
{ content }
</Box>
......
import { Box, Button, Text, Skeleton, useDisclosure } from '@chakra-ui/react';
import { Box, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { TWatchlist, TWatchlistItem } from 'types/client/account';
import fetch from 'lib/client/fetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import AccountPageHeader from 'ui/shared/AccountPageHeader';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page/Page';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable';
import AddressModal from 'ui/watchlist/AddressModal/AddressModal';
import DeleteAddressModal from 'ui/watchlist/DeleteAddressModal';
import WatchListItem from 'ui/watchlist/WatchlistTable/WatchListItem';
import WatchlistTable from 'ui/watchlist/WatchlistTable/WatchlistTable';
const WatchList: React.FC = () => {
......@@ -19,6 +23,7 @@ const WatchList: React.FC = () => {
const addressModalProps = useDisclosure();
const deleteModalProps = useDisclosure();
const isMobile = useIsMobile();
const [ addressModalData, setAddressModalData ] = useState<TWatchlistItem>();
const [ deleteModalData, setDeleteModalData ] = useState<TWatchlistItem>();
......@@ -44,32 +49,52 @@ const WatchList: React.FC = () => {
}, [ deleteModalProps ]);
const description = (
<Text marginBottom={ 12 }>
<AccountPageDescription>
An email notification can be sent to you when an address on your watch list sends or receives any transactions.
</Text>
</AccountPageDescription>
);
let content;
if (isLoading && !data) {
content = (
const loader = isMobile ? <SkeletonAccountMobile showFooterSlot/> : (
<>
{ description }
<SkeletonTable columns={ [ '70%', '30%', '160px', '108px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
</>
);
content = (
<>
{ description }
{ loader }
</>
);
} else if (isError) {
content = <DataFetchAlert/>;
} else {
content = (
<>
{ Boolean(data?.length) && (
const list = isMobile ? (
<Box>
{ data.map((item) => (
<WatchListItem
item={ item }
key={ item.address_hash }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
)) }
</Box>
) : (
<WatchlistTable
data={ data }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
) }
);
content = (
<>
{ description }
{ Boolean(data?.length) && list }
<Box marginTop={ 8 }>
<Button
variant="primary"
......
import { Tag, Flex, HStack, Text } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { AddressTag } from 'types/api/account';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import AddressSnippet from 'ui/shared/AddressSnippet';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
interface Props {
item: AddressTag;
onEditClick: (data: AddressTag) => void;
onDeleteClick: (data: AddressTag) => void;
}
const AddressTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
const onItemDeleteClick = useCallback(() => {
return onDeleteClick(item);
}, [ item, onDeleteClick ]);
return (
<AccountListItemMobile>
<Flex alignItems="flex-start" flexDirection="column" maxW="100%">
<AddressSnippet address={ item.address_hash }/>
<HStack spacing={ 3 } mt={ 4 }>
<Text fontSize="sm" fontWeight={ 500 }>Private tag</Text>
<Tag variant="gray" lineHeight="24px">
{ item.name }
</Tag>
</HStack>
</Flex>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</AccountListItemMobile>
);
};
export default React.memo(AddressTagListItem);
......@@ -2,16 +2,13 @@ import {
Tag,
Tr,
Td,
HStack,
} from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { AddressTag } from 'types/api/account';
import AddressIcon from 'ui/shared/AddressIcon';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
import DeleteButton from 'ui/shared/DeleteButton';
import EditButton from 'ui/shared/EditButton';
import AddressSnippet from 'ui/shared/AddressSnippet';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
interface Props {
......@@ -32,10 +29,7 @@ const AddressTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return (
<Tr alignItems="top" key={ item.id }>
<Td>
<HStack spacing={ 4 }>
<AddressIcon address={ item.address_hash }/>
<AddressLinkWithTooltip address={ item.address_hash }/>
</HStack>
<AddressSnippet address={ item.address_hash }/>
</Td>
<Td>
<TruncatedTextTooltip label={ item.name }>
......@@ -45,10 +39,7 @@ const AddressTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
</TruncatedTextTooltip>
</Td>
<Td>
<HStack spacing={ 6 }>
<EditButton onClick={ onItemEditClick }/>
<DeleteButton onClick={ onItemDeleteClick }/>
</HStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</Td>
</Tr>
);
......
......@@ -37,7 +37,7 @@ const DeletePrivateTagModal: React.FC<Props> = ({ isOpen, onClose, data, type })
const renderText = useCallback(() => {
return (
<Text display="flex">Tag<Text fontWeight="600" whiteSpace="pre">{ ` "${ tag || 'tag' }" ` }</Text>will be deleted</Text>
<Text>Tag<Text fontWeight="600" as="span">{ ` "${ tag || 'tag' }" ` }</Text>will be deleted</Text>
);
}, [ tag ]);
......
import { Box, Button, Text, Skeleton, useDisclosure } from '@chakra-ui/react';
import { Box, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { AddressTags, AddressTag } from 'types/api/account';
import fetch from 'lib/client/fetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable';
import AddressModal from './AddressModal/AddressModal';
import AddressTagListItem from './AddressTagTable/AddressTagListItem';
import AddressTagTable from './AddressTagTable/AddressTagTable';
import DeletePrivateTagModal from './DeletePrivateTagModal';
......@@ -18,6 +22,7 @@ const PrivateAddressTags = () => {
const addressModalProps = useDisclosure();
const deleteModalProps = useDisclosure();
const isMobile = useIsMobile();
const [ addressModalData, setAddressModalData ] = useState<AddressTag>();
const [ deleteModalData, setDeleteModalData ] = useState<AddressTag>();
......@@ -43,36 +48,55 @@ const PrivateAddressTags = () => {
}, [ deleteModalProps ]);
const description = (
<Text marginBottom={ 12 }>
Use private transaction tags to label any transactions of interest.
<AccountPageDescription>
Use private address tags to track any addresses of interest.
Private tags are saved in your account and are only visible when you are logged in.
</Text>
</AccountPageDescription>
);
if (isLoading && !addressTagsData) {
return (
const loader = isMobile ? <SkeletonAccountMobile/> : (
<>
{ description }
<SkeletonTable columns={ [ '60%', '40%', '108px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
</>
);
return (
<>
{ description }
{ loader }
</>
);
}
if (isError) {
return <DataFetchAlert/>;
}
return (
<>
{ description }
{ Boolean(addressTagsData?.length) && (
const list = isMobile ? (
<Box>
{ addressTagsData.map((item: AddressTag) => (
<AddressTagListItem
item={ item }
key={ item.id }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
)) }
</Box>
) : (
<AddressTagTable
data={ addressTagsData }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
) }
);
return (
<>
{ description }
{ Boolean(addressTagsData?.length) && list }
<Box marginTop={ 8 }>
<Button
variant="primary"
......
import { Box, Button, Skeleton, Text, useDisclosure } from '@chakra-ui/react';
import { Box, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { TransactionTags, TransactionTag } from 'types/api/account';
import fetch from 'lib/client/fetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable';
import DeletePrivateTagModal from './DeletePrivateTagModal';
import TransactionModal from './TransactionModal/TransactionModal';
import TransactionTagListItem from './TransactionTagTable/TransactionTagListItem';
import TransactionTagTable from './TransactionTagTable/TransactionTagTable';
const PrivateTransactionTags = () => {
......@@ -18,6 +22,7 @@ const PrivateTransactionTags = () => {
const transactionModalProps = useDisclosure();
const deleteModalProps = useDisclosure();
const isMobile = useIsMobile();
const [ transactionModalData, setTransactionModalData ] = useState<TransactionTag>();
const [ deleteModalData, setDeleteModalData ] = useState<TransactionTag>();
......@@ -43,36 +48,55 @@ const PrivateTransactionTags = () => {
}, [ deleteModalProps ]);
const description = (
<Text marginBottom={ 12 }>
<AccountPageDescription>
Use private transaction tags to label any transactions of interest.
Private tags are saved in your account and are only visible when you are logged in.
</Text>
</AccountPageDescription>
);
if (isLoading && !transactionTagsData) {
return (
const loader = isMobile ? <SkeletonAccountMobile/> : (
<>
{ description }
<SkeletonTable columns={ [ '75%', '25%', '108px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
</>
);
return (
<>
{ description }
{ loader }
</>
);
}
if (isError) {
return <DataFetchAlert/>;
}
return (
<>
{ description }
{ Boolean(transactionTagsData.length) && (
const list = isMobile ? (
<Box>
{ transactionTagsData.map((item) => (
<TransactionTagListItem
item={ item }
key={ item.id }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
)) }
</Box>
) : (
<TransactionTagTable
data={ transactionTagsData }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
) }
);
return (
<>
{ description }
{ Boolean(transactionTagsData.length) && list }
<Box marginTop={ 8 }>
<Button
variant="primary"
......
import { Tag, HStack, Text, Flex } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { TransactionTag } from 'types/api/account';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import TransactionSnippet from 'ui/shared/TransactionSnippet';
interface Props {
item: TransactionTag;
onEditClick: (data: TransactionTag) => void;
onDeleteClick: (data: TransactionTag) => void;
}
const TransactionTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
const onItemDeleteClick = useCallback(() => {
return onDeleteClick(item);
}, [ item, onDeleteClick ]);
return (
<AccountListItemMobile>
<Flex alignItems="flex-start" flexDirection="column" maxW="100%">
<TransactionSnippet hash={ item.transaction_hash }/>
<HStack spacing={ 3 } mt={ 4 }>
<Text fontSize="sm" fontWeight={ 500 }>Private tag</Text>
<Tag variant="gray" lineHeight="24px">
{ item.name }
</Tag>
</HStack>
</Flex>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</AccountListItemMobile>
);
};
export default React.memo(TransactionTagListItem);
......@@ -2,16 +2,14 @@ import {
Tag,
Tr,
Td,
HStack,
Tooltip,
} from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { TransactionTag } from 'types/api/account';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
import DeleteButton from 'ui/shared/DeleteButton';
import EditButton from 'ui/shared/EditButton';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import TransactionSnippet from 'ui/shared/TransactionSnippet';
interface Props {
item: TransactionTag;
......@@ -19,7 +17,7 @@ interface Props {
onDeleteClick: (data: TransactionTag) => void;
}
const AddressTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const TransactionTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
......@@ -31,7 +29,7 @@ const AddressTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return (
<Tr alignItems="top" key={ item.id }>
<Td>
<AddressLinkWithTooltip address={ item.transaction_hash } type="transaction"/>
<TransactionSnippet hash={ item.transaction_hash }/>
</Td>
<Td>
<Tooltip label={ item.name }>
......@@ -41,13 +39,10 @@ const AddressTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
</Tooltip>
</Td>
<Td>
<HStack spacing={ 6 }>
<EditButton onClick={ onItemEditClick }/>
<DeleteButton onClick={ onItemDeleteClick }/>
</HStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</Td>
</Tr>
);
};
export default AddressTagTableItem;
export default TransactionTagTableItem;
import { Flex, Text, FormControl, FormLabel, Textarea } from '@chakra-ui/react';
import { Box, Text, FormControl, FormLabel, Textarea, useColorModeValue } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { ChangeEvent } from 'react';
......@@ -22,6 +22,7 @@ const DeletePublicTagModal: React.FC<Props> = ({ isOpen, onClose, data, onDelete
const tags = data.tags.split(';');
const queryClient = useQueryClient();
const formBackgroundColor = useColorModeValue('white', 'gray.900');
const deleteApiKey = useCallback(() => {
const body = JSON.stringify({ remove_reason: reason });
......@@ -44,9 +45,9 @@ const DeletePublicTagModal: React.FC<Props> = ({ isOpen, onClose, data, onDelete
if (tags.length === 1) {
text = (
<>
<Text display="flex">Public tag</Text>
<Text fontWeight="600" whiteSpace="pre">{ ` "${ tags[0] }" ` }</Text>
<Text>will be removed.</Text>
<Text display="inline" as="span">Public tag</Text>
<Text fontWeight="600" whiteSpace="pre" as="span">{ ` "${ tags[0] }" ` }</Text>
<Text as="span">will be removed.</Text>
</>
);
}
......@@ -54,29 +55,29 @@ const DeletePublicTagModal: React.FC<Props> = ({ isOpen, onClose, data, onDelete
const tagsText: Array<JSX.Element | string> = [];
tags.forEach((tag, index) => {
if (index < tags.length - 2) {
tagsText.push(<Text fontWeight="600" whiteSpace="pre">{ ` "${ tag }"` }</Text>);
tagsText.push(<Text fontWeight="600" whiteSpace="pre" as="span">{ ` "${ tag }"` }</Text>);
tagsText.push(',');
}
if (index === tags.length - 2) {
tagsText.push(<Text fontWeight="600" whiteSpace="pre">{ ` "${ tag }" ` }</Text>);
tagsText.push(<Text fontWeight="600" whiteSpace="pre" as="span">{ ` "${ tag }" ` }</Text>);
tagsText.push('and');
}
if (index === tags.length - 1) {
tagsText.push(<Text fontWeight="600" whiteSpace="pre">{ ` "${ tag }" ` }</Text>);
tagsText.push(<Text fontWeight="600" whiteSpace="pre" as="span">{ ` "${ tag }" ` }</Text>);
}
});
text = (
<>
<Text>Public tags</Text>{ tagsText }<Text>will be removed.</Text>
<Text as="span">Public tags</Text>{ tagsText }<Text as="span">will be removed.</Text>
</>
);
}
return (
<>
<Flex marginBottom={ 12 }>
<Box marginBottom={ 12 }>
{ text }
</Flex>
<FormControl variant="floating" id="tag-delete">
</Box>
<FormControl variant="floating" id="tag-delete" backgroundColor={ formBackgroundColor }>
<Textarea
size="lg"
value={ reason }
......@@ -86,7 +87,7 @@ const DeletePublicTagModal: React.FC<Props> = ({ isOpen, onClose, data, onDelete
</FormControl>
</>
);
}, [ tags, reason, onFieldChange ]);
}, [ tags, reason, onFieldChange, formBackgroundColor ]);
return (
<DeleteModal
......
import { Tag, VStack, Text, HStack } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { PublicTag } from 'types/api/account';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import AddressSnippet from 'ui/shared/AddressSnippet';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
interface Props {
item: PublicTag;
onEditClick: (data: PublicTag) => void;
onDeleteClick: (data: PublicTag) => void;
}
const PublicTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
const onItemDeleteClick = useCallback(() => {
return onDeleteClick(item);
}, [ item, onDeleteClick ]);
return (
<AccountListItemMobile>
<VStack spacing={ 3 } alignItems="flex-start" maxW="100%">
<VStack spacing={ 4 } alignItems="unset" maxW="100%">
{ item.addresses.map((address) => <AddressSnippet key={ address } address={ address }/>) }
</VStack>
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Public tags</Text>
<HStack spacing={ 2 } alignItems="baseline">
{ item.tags.split(';').map((tag) => {
return (
<TruncatedTextTooltip label={ tag } key={ tag }>
<Tag variant="gray" lineHeight="24px">
{ tag }
</Tag>
</TruncatedTextTooltip>
);
}) }
</HStack>
</HStack>
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Status</Text>
<Text fontSize="sm" variant="secondary">Submitted</Text>
</HStack>
</VStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</AccountListItemMobile>
);
};
export default React.memo(PublicTagListItem);
import {
Box,
Tag,
Tr,
Td,
HStack,
VStack,
Text,
} from '@chakra-ui/react';
......@@ -11,10 +9,8 @@ import React, { useCallback } from 'react';
import type { PublicTag } from 'types/api/account';
import AddressIcon from 'ui/shared/AddressIcon';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
import DeleteButton from 'ui/shared/DeleteButton';
import EditButton from 'ui/shared/EditButton';
import AddressSnippet from 'ui/shared/AddressSnippet';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
interface Props {
......@@ -36,18 +32,7 @@ const PublicTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
<Tr alignItems="top" key={ item.id }>
<Td>
<VStack spacing={ 4 } alignItems="unset">
{ item.addresses.map((address) => {
return (
<HStack spacing={ 4 } key={ address } overflow="hidden" alignItems="start">
<AddressIcon address={ address }/>
<Box overflow="hidden">
<AddressLinkWithTooltip address={ address }/>
{ /* will be added later */ }
{ /* <Text fontSize="sm" variant="secondary" mt={ 0.5 }>Address Name</Text> */ }
</Box>
</HStack>
);
}) }
{ item.addresses.map((address) => <AddressSnippet key={ address } address={ address }/>) }
</VStack>
</Td>
<Td>
......@@ -69,10 +54,7 @@ const PublicTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
</VStack>
</Td>
<Td>
<HStack spacing={ 6 }>
<EditButton onClick={ onItemEditClick }/>
<DeleteButton onClick={ onItemDeleteClick }/>
</HStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</Td>
</Tr>
);
......
import { Box, Text, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
import { Box, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { PublicTags, PublicTag } from 'types/api/account';
import fetch from 'lib/client/fetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import PublicTagListItem from 'ui/publicTags/PublicTagTable/PublicTagListItem';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable';
import DeletePublicTagModal from './DeletePublicTagModal';
......@@ -19,6 +23,7 @@ type Props = {
const PublicTagsData = ({ changeToFormScreen, onTagDelete }: Props) => {
const deleteModalProps = useDisclosure();
const [ deleteModalData, setDeleteModalData ] = useState<PublicTag>();
const isMobile = useIsMobile();
const { data, isLoading, isError } = useQuery<unknown, unknown, PublicTags>([ 'public-tags' ], async() => await fetch('/api/account/public-tags'));
......@@ -41,32 +46,53 @@ const PublicTagsData = ({ changeToFormScreen, onTagDelete }: Props) => {
}, [ deleteModalProps ]);
const description = (
<Text marginBottom={ 12 }>
<AccountPageDescription>
You can request a public category tag which is displayed to all Blockscout users.
Public tags may be added to contract or external addresses, and any associated transactions will inherit that tag.
Clicking a tag opens a page with related information and helps provide context and data organization.
Requests are sent to a moderator for review and approval. This process can take several days.
</Text>
</AccountPageDescription>
);
if (isLoading) {
return (
const loader = isMobile ? <SkeletonAccountMobile/> : (
<>
{ description }
<SkeletonTable columns={ [ '50%', '25%', '25%', '108px' ] }/>
<Skeleton height="48px" width="270px" marginTop={ 8 }/>
</>
);
return (
<>
{ description }
{ loader }
</>
);
}
if (isError) {
return <DataFetchAlert/>;
}
const list = isMobile ? (
<Box>
{ data.map((item) => (
<PublicTagListItem
item={ item }
key={ item.id }
onDeleteClick={ onItemDeleteClick }
onEditClick={ onItemEditClick }
/>
)) }
</Box>
) : (
<PublicTagTable data={ data } onEditClick={ onItemEditClick } onDeleteClick={ onItemDeleteClick }/>
);
return (
<>
{ description }
{ data.length > 0 && <PublicTagTable data={ data } onEditClick={ onItemEditClick } onDeleteClick={ onItemDeleteClick }/> }
{ data.length > 0 && list }
<Box marginTop={ 8 }>
<Button
variant="primary"
......
import { IconButton, Icon } from '@chakra-ui/react';
import { IconButton, Icon, Flex } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { ControllerRenderProps, Control, FieldError } from 'react-hook-form';
import { Controller } from 'react-hook-form';
......@@ -17,24 +17,25 @@ interface Props {
error?: FieldError;
onAddFieldClick: (e: React.SyntheticEvent) => void;
onRemoveFieldClick: (index: number) => (e: React.SyntheticEvent) => void;
size?: string;
}
const MAX_INPUTS_NUM = 10;
export default function PublicTagFormAction({ control, index, fieldsLength, error, onAddFieldClick, onRemoveFieldClick }: Props) {
export default function PublicTagFormAction({ control, index, fieldsLength, error, onAddFieldClick, onRemoveFieldClick, size }: Props) {
const renderAddressInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, `addresses.${ number }.address`>}) => {
return (
<AddressInput<Inputs, `addresses.${ number }.address`>
field={ field }
error={ error }
size="lg"
size={ size }
placeholder="Smart contract / Address (0x...)"
/>
);
}, [ error ]);
}, [ error, size ]);
return (
<>
<Flex flexDir="column" rowGap={ 5 } alignItems="flex-end">
<Controller
name={ `addresses.${ index }.address` }
control={ control }
......@@ -44,31 +45,34 @@ export default function PublicTagFormAction({ control, index, fieldsLength, erro
required: index === 0,
}}
/>
{ index === fieldsLength - 1 && fieldsLength < MAX_INPUTS_NUM && (
<Flex
columnGap={ 5 }
position={{ base: 'static', lg: 'absolute' }}
left={{ base: 'auto', lg: 'calc(100% + 20px)' }}
h="100%"
alignItems="center"
>
{ fieldsLength > 1 && (
<IconButton
aria-label="add"
aria-label="delete"
variant="iconBorder"
w="30px"
h="30px"
onClick={ onAddFieldClick }
icon={ <Icon as={ PlusIcon } w="20px" h="20px"/> }
position="absolute"
right={ index === 0 ? '-50px' : '-100px' }
top="25px"
onClick={ onRemoveFieldClick(index) }
icon={ <Icon as={ MinusIcon } w="20px" h="20px"/> }
/>
) }
{ fieldsLength > 1 && (
{ index === fieldsLength - 1 && fieldsLength < MAX_INPUTS_NUM && (
<IconButton
aria-label="delete"
aria-label="add"
variant="iconBorder"
w="30px"
h="30px"
onClick={ onRemoveFieldClick(index) }
icon={ <Icon as={ MinusIcon } w="20px" h="20px"/> }
position="absolute"
right="-50px"
top="25px"
onClick={ onAddFieldClick }
icon={ <Icon as={ PlusIcon } w="20px" h="20px"/> }
/>
) }</>
) }
</Flex>
</Flex>
);
}
......@@ -12,23 +12,24 @@ const TEXT_INPUT_MAX_LENGTH = 255;
interface Props {
control: Control<Inputs>;
error?: FieldError;
size?: string;
}
export default function PublicTagFormComment({ control, error }: Props) {
export default function PublicTagFormComment({ control, error, size }: Props) {
const renderComment = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'comment'>}) => {
return (
<FormControl variant="floating" id={ field.name } size="lg" isRequired>
<FormControl variant="floating" id={ field.name } size={ size } isRequired>
<Textarea
{ ...field }
isInvalid={ Boolean(error) }
size="lg"
size={ size }
/>
<FormLabel>
{ getPlaceholderWithError('Specify the reason for adding tags and color preference(s)', error?.message) }
</FormLabel>
</FormControl>
);
}, [ error ]);
}, [ error, size ]);
return (
<Controller
......
......@@ -16,6 +16,7 @@ import type { PublicTags, PublicTag, PublicTagNew, PublicTagErrors } from 'types
import type { ErrorType } from 'lib/client/fetch';
import fetch from 'lib/client/fetch';
import getErrorMessage from 'lib/getErrorMessage';
import useIsMobile from 'lib/hooks/useIsMobile';
import { EMAIL_REGEXP } from 'lib/validations/email';
import FormSubmitAlert from 'ui/shared/FormSubmitAlert';
......@@ -52,10 +53,12 @@ const placeholders = {
comment: 'Specify the reason for adding tags and color preference(s).',
} as Record<Path<Inputs>, string>;
const ADDRESS_INPUT_BUTTONS_WIDTH = 170;
const ADDRESS_INPUT_BUTTONS_WIDTH = 100;
const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
const queryClient = useQueryClient();
const isMobile = useIsMobile();
const inputSize = isMobile ? 'md' : 'lg';
const { control, handleSubmit, formState: { errors, isValid }, setError } = useForm<Inputs>({
defaultValues: {
......@@ -149,10 +152,10 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
}, [ changeToDataScreen ]);
return (
<Box width={ `calc(100% - ${ ADDRESS_INPUT_BUTTONS_WIDTH }px)` } maxWidth="844px">
<Box width={{ base: 'auto', lg: `calc(100% - ${ ADDRESS_INPUT_BUTTONS_WIDTH }px)` }} maxWidth="844px">
{ isAlertVisible && <Box mb={ 4 }><FormSubmitAlert/></Box> }
<Text size="sm" variant="secondary" paddingBottom={ 5 }>Company info</Text>
<Grid templateColumns="1fr 1fr" rowGap={ 4 } columnGap={ 5 }>
<Grid templateColumns={{ base: '1fr', lg: '1fr 1fr' }} rowGap={ 4 } columnGap={ 5 }>
<GridItem>
<PublicTagsFormInput<Inputs>
fieldName="fullName"
......@@ -160,6 +163,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
label={ placeholders.fullName }
error={ errors.fullName }
required
size={ inputSize }
/>
</GridItem>
<GridItem>
......@@ -168,6 +172,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
control={ control }
label={ placeholders.companyName }
error={ errors.companyName }
size={ inputSize }
/>
</GridItem>
<GridItem>
......@@ -178,6 +183,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
pattern={ EMAIL_REGEXP }
error={ errors.email }
required
size={ inputSize }
/>
</GridItem>
<GridItem>
......@@ -186,10 +192,11 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
control={ control }
label={ placeholders.companyUrl }
error={ errors?.companyUrl }
size={ inputSize }
/>
</GridItem>
</Grid>
<Box marginTop={ 4 } marginBottom={ 8 }>
<Box marginTop={{ base: 5, lg: 8 }} marginBottom={{ base: 5, lg: 8 }}>
<PublicTagFormAction control={ control }/>
</Box>
<Text size="sm" variant="secondary" marginBottom={ 5 }>Public tags (2 tags maximum, please use &quot;;&quot; as a divider)</Text>
......@@ -199,7 +206,9 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
control={ control }
label={ placeholders.tags }
error={ errors.tags }
required/>
required
size={ inputSize }
/>
</Box>
{ fields.map((field, index) => {
return (
......@@ -211,12 +220,13 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
fieldsLength={ fields.length }
onAddFieldClick={ onAddFieldClick }
onRemoveFieldClick={ onRemoveFieldClick }
size={ inputSize }
/>
</Box>
);
}) }
<Box marginBottom={ 8 }>
<PublicTagFormComment control={ control } error={ errors.comment }/>
<PublicTagFormComment control={ control } error={ errors.comment } size={ inputSize }/>
</Box>
<HStack spacing={ 6 }>
<Button
......
......@@ -14,6 +14,7 @@ interface Props<TInputs extends FieldValues> {
control: Control<TInputs, object>;
pattern?: RegExp;
error?: FieldError;
size?: string;
}
export default function PublicTagsFormInput<Inputs extends FieldValues>({
......@@ -23,13 +24,14 @@ export default function PublicTagsFormInput<Inputs extends FieldValues>({
fieldName,
pattern,
error,
size,
}: Props<Inputs>) {
const renderInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, typeof fieldName>}) => {
return (
<FormControl variant="floating" id={ field.name } isRequired={ required } size="lg">
<FormControl variant="floating" id={ field.name } isRequired={ required } size={ size }>
<Input
{ ...field }
size="lg"
size={ size }
required={ required }
isInvalid={ Boolean(error) }
maxLength={ TEXT_INPUT_MAX_LENGTH }
......@@ -37,7 +39,7 @@ export default function PublicTagsFormInput<Inputs extends FieldValues>({
<FormLabel>{ getPlaceholderWithError(label, error?.message) }</FormLabel>
</FormControl>
);
}, [ label, required, error ]);
}, [ label, required, error, size ]);
return (
<Controller
name={ fieldName }
......
import { VStack, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
interface Props {
children: React.ReactNode;
}
const AccountListItemMobile = ({ children }: Props) => {
return (
<VStack
gap={ 4 }
alignItems="flex-start"
paddingY={ 6 }
borderColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
borderTopWidth="1px"
_last={{
borderBottomWidth: '1px',
}}
>
{ children }
</VStack>
);
};
export default AccountListItemMobile;
import { Text } from '@chakra-ui/react';
import React from 'react';
const AccountPageDescription = ({ children }: {children: React.ReactNode}) => {
return (
<Text marginBottom={{ base: 6, lg: 12 }}>
{ children }
</Text>
);
};
export default AccountPageDescription;
import { Heading } from '@chakra-ui/react';
import React from 'react';
const PageHeader = ({ text }: {text: string}) => {
const AccountPageHeader = ({ text }: {text: string}) => {
return (
<Heading as="h1" size="lg" marginBottom={ 8 }>{ text }</Heading>
<Heading as="h1" size="lg" marginBottom={{ base: 6, lg: 8 }}>{ text }</Heading>
);
};
export default PageHeader;
export default AccountPageHeader;
......@@ -4,7 +4,7 @@ import Jazzicon, { jsNumberForAddress } from 'react-jazzicon';
const AddressIcon = ({ address }: {address: string}) => {
return (
<Box width="24px">
<Box width="24px" display="inline-flex">
<Jazzicon diameter={ 24 } seed={ jsNumberForAddress(address) }/>
</Box>
);
......
......@@ -22,13 +22,14 @@ const AddressLinkWithTooltip = ({ address, type = 'address' }: Props) => {
url = basePath + '/address/' + address + '/tokens#address-tabs';
}
return (
<HStack spacing={ 2 } alignContent="center" overflow="hidden">
<HStack spacing={ 2 } alignContent="center" overflow="hidden" maxW="100%">
<Link
href={ url }
target="_blank"
overflow="hidden"
fontWeight={ FONT_WEIGHT }
lineHeight="24px"
whiteSpace="nowrap"
>
<AddressWithDots address={ address } fontWeight={ FONT_WEIGHT }/>
</Link>
......
import { Box, HStack, Text } from '@chakra-ui/react';
import React from 'react';
import AddressIcon from 'ui/shared/AddressIcon';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
interface Props {
address: string;
subtitle?: string;
}
const AddressSnippet = ({ address, subtitle }: Props) => {
return (
<HStack spacing={ 4 } key={ address } overflow="hidden" alignItems="start" maxW="100%">
<AddressIcon address={ address }/>
<Box overflow="hidden">
<AddressLinkWithTooltip address={ address }/>
{ subtitle && <Text fontSize="sm" variant="secondary" mt={ 0.5 }>{ subtitle }</Text> }
</Box>
</HStack>
);
};
export default React.memo(AddressSnippet);
import { Box, HStack, Icon, Flex, Text, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import keyIcon from 'icons/key.svg';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
interface Props {
apiKey: string;
name: string;
}
const ApiKeySnippet = ({ apiKey, name }: Props) => {
return (
<HStack spacing={ 2 } alignItems="start">
<Icon as={ keyIcon } boxSize={ 6 } color={ useColorModeValue('gray.500', 'gray.400') }/>
<Box>
<Flex alignItems={{ base: 'flex-start', lg: 'center' }}>
<Text fontSize="md" lineHeight={ 6 } fontWeight={ 600 } mr={ 1 }>{ apiKey }</Text>
<CopyToClipboard text={ apiKey }/>
</Flex>
{ name && <Text fontSize="sm" variant="secondary" mt={ 1 }>{ name }</Text> }
</Box>
</HStack>
);
};
export default React.memo(ApiKeySnippet);
import { Tooltip, IconButton, Icon } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import DeleteIcon from 'icons/delete.svg';
type Props = {
onClick: () => void;
}
const DeleteButton = ({ onClick }: Props) => {
const onFocusCapture = useCallback((e: React.SyntheticEvent) => e.stopPropagation(), []);
return (
<Tooltip label="Delete">
<IconButton
aria-label="delete"
variant="icon"
w="30px"
h="30px"
onClick={ onClick }
icon={ <Icon as={ DeleteIcon } w="20px" h="20px"/> }
onFocusCapture={ onFocusCapture }
/>
</Tooltip>
);
};
export default DeleteButton;
......@@ -54,7 +54,7 @@ const DeleteModal: React.FC<Props> = ({
}, [ setAlertVisible, mutation ]);
return (
<Modal isOpen={ isOpen } onClose={ onModalClose } size="md">
<Modal isOpen={ isOpen } onClose={ onModalClose } size={{ base: 'full', lg: 'md' }}>
<ModalOverlay/>
<ModalContent>
<ModalHeader fontWeight="500" textStyle="h3">{ title }</ModalHeader>
......
import { Tooltip, IconButton, Icon } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import EditIcon from 'icons/edit.svg';
type Props = {
onClick: () => void;
}
const EditButton = ({ onClick }: Props) => {
const onFocusCapture = useCallback((e: React.SyntheticEvent) => e.stopPropagation(), []);
return (
<Tooltip label="Edit">
<IconButton
aria-label="edit"
variant="icon"
w="30px"
h="30px"
onClick={ onClick }
icon={ <Icon as={ EditIcon } w="20px" h="20px"/> }
onFocusCapture={ onFocusCapture }
/>
</Tooltip>
);
};
export default EditButton;
......@@ -39,14 +39,14 @@ export default function FormModal<TData>({
}, [ onClose, setAlertVisible ]);
return (
<Modal isOpen={ isOpen } onClose={ onModalClose } size="md" >
<Modal isOpen={ isOpen } onClose={ onModalClose } size={{ base: 'full', lg: 'md' }}>
<ModalOverlay/>
<ModalContent>
<ModalHeader fontWeight="500" textStyle="h3">{ title }</ModalHeader>
<ModalCloseButton/>
<ModalBody mb={ 0 }>
{ (isAlertVisible || text) && (
<Box marginBottom={ 12 }>
<Box marginBottom={{ base: 6, lg: 12 }}>
{ text && (
<Text lineHeight="30px" mb={ 3 }>
{ text }
......
import { Box, Flex, Skeleton, SkeletonCircle, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
interface Props {
showFooterSlot?: boolean;
}
const SkeletonAccountMobile = ({ showFooterSlot }: Props) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<Box>
{ Array.from(Array(2)).map((item, index) => (
<Flex
key={ index }
rowGap={ 3 }
flexDirection="column"
paddingY={ 6 }
borderTopWidth="1px"
borderColor={ borderColor }
_first={{
borderTopWidth: '0',
pt: '0',
}}
>
<Flex columnGap={ 2 } w="100%" alignItems="center">
<SkeletonCircle size="6" flexShrink="0"/>
<Skeleton h={ 4 } w="100%"/>
</Flex>
<Skeleton h={ 4 } w="164px"/>
<Skeleton h={ 4 } w="164px"/>
<Flex columnGap={ 3 } mt={ 7 }>
{ showFooterSlot && (
<Flex alignItems="center" columnGap={ 2 }>
<Skeleton h={ 4 } w="164px"/>
<SkeletonCircle size="6" flexShrink="0"/>
</Flex>
) }
<SkeletonCircle size="6" flexShrink="0" ml="auto"/>
<SkeletonCircle size="6" flexShrink="0"/>
</Flex>
</Flex>
)) }
</Box>
);
};
export default SkeletonAccountMobile;
import { Tooltip, IconButton, Icon, HStack } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import DeleteIcon from 'icons/delete.svg';
import EditIcon from 'icons/edit.svg';
type Props = {
onEditClick: () => void;
onDeleteClick: () => void;
}
const TableItemActionButtons = ({ onEditClick, onDeleteClick }: Props) => {
// prevent set focus on button when closing modal
const onFocusCapture = useCallback((e: React.SyntheticEvent) => e.stopPropagation(), []);
return (
<HStack spacing={ 6 } alignSelf="flex-end">
<Tooltip label="Edit">
<IconButton
aria-label="edit"
variant="icon"
w="30px"
h="30px"
onClick={ onEditClick }
icon={ <Icon as={ EditIcon } w="20px" h="20px"/> }
onFocusCapture={ onFocusCapture }
/>
</Tooltip>
<Tooltip label="Delete">
<IconButton
aria-label="delete"
variant="icon"
w="30px"
h="30px"
onClick={ onDeleteClick }
icon={ <Icon as={ DeleteIcon } w="20px" h="20px"/> }
onFocusCapture={ onFocusCapture }
/>
</Tooltip>
</HStack>
);
};
export default React.memo(TableItemActionButtons);
import { Box, HStack, Icon, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import transactionIcon from 'icons/transactions.svg';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
interface Props {
hash: string;
}
const TransactionSnippet = ({ hash }: Props) => {
return (
<HStack spacing={ 2 } overflow="hidden" alignItems="start" maxW="100%">
<Icon as={ transactionIcon } boxSize={ 6 } color={ useColorModeValue('gray.500', 'gray.400') }/>
<Box overflow="hidden">
<AddressLinkWithTooltip address={ hash } type="transaction"/>
</Box>
</HStack>
);
};
export default React.memo(TransactionSnippet);
......@@ -150,7 +150,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
return (
<>
<Box marginBottom={ 5 } marginTop={ 5 }>
<Box marginBottom={ 5 }>
<Controller
name="address"
control={ control }
......@@ -178,7 +178,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
<Box marginBottom={ 8 }>
<AddressFormNotifications control={ control }/>
</Box>
<Text variant="secondary" fontSize="sm" marginBottom={ 5 }>Notification methods</Text>
<Text variant="secondary" fontSize="sm" marginBottom={{ base: '10px', lg: 5 }}>Notification methods</Text>
<Controller
name={ 'notification' as Checkboxes }
control={ control }
......
......@@ -20,13 +20,21 @@ export default function AddressFormNotifications<Inputs extends FieldValues, Che
), []);
return (
<Grid templateColumns="repeat(3, max-content)" gap="20px 24px">
<Grid templateColumns={{ base: 'repeat(2, max-content)', lg: 'repeat(3, max-content)' }} gap={{ base: '10px 24px', lg: '20px 24px' }}>
{ NOTIFICATIONS.map((notification: string, index: number) => {
const incomingFieldName = `notification_settings.${ notification }.incoming` as Checkboxes;
const outgoingFieldName = `notification_settings.${ notification }.outcoming` as Checkboxes;
return (
<React.Fragment key={ notification }>
<GridItem>{ NOTIFICATIONS_NAMES[index] }</GridItem>
<GridItem
gridColumnStart={{ base: 1, lg: 1 }}
gridColumnEnd={{ base: 3, lg: 1 }}
_notFirst={{
mt: { base: 3, lg: 0 },
}}
>
{ NOTIFICATIONS_NAMES[index] }
</GridItem>
<GridItem>
<Controller
name={ incomingFieldName }
......
......@@ -5,6 +5,7 @@ import React, { useCallback } from 'react';
import type { TWatchlistItem, TWatchlist } from 'types/client/account';
import fetch from 'lib/client/fetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import DeleteModal from 'ui/shared/DeleteModal';
type Props = {
......@@ -15,6 +16,7 @@ type Props = {
const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const queryClient = useQueryClient();
const isMobile = useIsMobile();
const mutationFn = useCallback(() => {
return fetch(`/api/account1/watchlist/${ data?.id }`, { method: 'DELETE' });
......@@ -29,10 +31,11 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const address = data?.address_hash;
const renderModalContent = useCallback(() => {
const addressString = isMobile ? [ address.slice(0, 4), address.slice(-4) ].join('...') : address;
return (
<Text display="flex">Address <Text fontWeight="600" whiteSpace="pre"> { address || 'address' } </Text> will be deleted</Text>
<Text>Address <Text fontWeight="600" as="span"> { addressString || 'address' }</Text> will be deleted</Text>
);
}, [ address ]);
}, [ address, isMobile ]);
return (
<DeleteModal
......
......@@ -6,8 +6,7 @@ import type { TWatchlistItem } from 'types/client/account';
import TokensIcon from 'icons/tokens.svg';
// import WalletIcon from 'icons/wallet.svg';
import { nbsp } from 'lib/html-entities';
import AddressIcon from 'ui/shared/AddressIcon';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
import AddressSnippet from 'ui/shared/AddressSnippet';
// now this component works only for xDAI
// for other networks later we will use config or smth
......@@ -18,19 +17,18 @@ const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => {
const nativeBalance = ((item.address_balance || 0) / 10 ** DECIMALS).toFixed(1);
const nativeBalanceUSD = item.exchange_rate ? `$${ Number(nativeBalance) * item.exchange_rate } USD` : 'N/A';
const infoItemsPaddingLeft = { base: 0, lg: 10 };
return (
<HStack spacing={ 3 } align="top">
<AddressIcon address={ item.address_hash }/>
<VStack spacing={ 2 } align="stretch" overflow="hidden" fontWeight={ 500 } color="gray.700">
<AddressLinkWithTooltip address={ item.address_hash }/>
<HStack spacing={ 0 } fontSize="sm" h={ 6 }>
<AddressSnippet address={ item.address_hash }/>
<HStack spacing={ 0 } fontSize="sm" h={ 6 } pl={ infoItemsPaddingLeft }>
<Image src="/xdai.png" alt="chain-logo" marginRight="10px" w="16px" h="16px"/>
<Text color={ mainTextColor }>{ `xDAI balance:${ nbsp }` + nativeBalance }</Text>
<Text variant="secondary">{ `${ nbsp }(${ nativeBalanceUSD })` }</Text>
</HStack>
{ item.tokens_count && (
<HStack spacing={ 0 } fontSize="sm" h={ 6 }>
<HStack spacing={ 0 } fontSize="sm" h={ 6 } pl={ infoItemsPaddingLeft }>
<Icon as={ TokensIcon } marginRight="10px" w="17px" h="16px"/>
<Text color={ mainTextColor }>{ `Tokens:${ nbsp }` + item.tokens_count }</Text>
{ /* api does not provide token prices */ }
......@@ -40,14 +38,13 @@ const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => {
) }
{ /* api does not provide token prices */ }
{ /* { item.address_balance && (
<HStack spacing={ 0 } fontSize="sm" h={ 6 }>
<HStack spacing={ 0 } fontSize="sm" h={ 6 } pl={ infoItemsPaddingLeft }>
<Icon as={ WalletIcon } marginRight="10px" w="16px" h="16px"/>
<Text color={ mainTextColor }>{ `Net worth:${ nbsp }` }</Text>
<Link href="#">{ `$${ item.totalUSD } USD` }</Link>
</HStack>
) } */ }
</VStack>
</HStack>
);
};
......
import { Tag, Box, Switch, Text, HStack, Flex } from '@chakra-ui/react';
import { useMutation } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { TWatchlistItem } from 'types/client/account';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import WatchListAddressItem from './WatchListAddressItem';
interface Props {
item: TWatchlistItem;
onEditClick: (data: TWatchlistItem) => void;
onDeleteClick: (data: TWatchlistItem) => void;
}
const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const [ notificationEnabled, setNotificationEnabled ] = useState(item.notification_methods.email);
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
const onItemDeleteClick = useCallback(() => {
return onDeleteClick(item);
}, [ item, onDeleteClick ]);
const { mutate } = useMutation(() => {
const data = { ...item, notification_methods: { email: !notificationEnabled } };
return fetch(`/api/account/watchlist/${ item.id }`, { method: 'PUT', body: JSON.stringify(data) });
}, {
onError: () => {
// eslint-disable-next-line no-console
console.log('error');
},
onSuccess: () => {
setNotificationEnabled(prevState => !prevState);
},
});
const onSwitch = useCallback(() => {
return mutate();
}, [ mutate ]);
return (
<AccountListItemMobile>
<Box maxW="100%">
<WatchListAddressItem item={ item }/>
<HStack spacing={ 3 } mt={ 6 }>
<Text fontSize="sm" fontWeight={ 500 }>Private tag</Text>
<Tag variant="gray" lineHeight="24px">
{ item.name }
</Tag>
</HStack>
</Box>
<Flex alignItems="center" justifyContent="space-between" mt={ 6 } w="100%">
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Email notification</Text>
<Switch colorScheme="blue" size="md" isChecked={ notificationEnabled } onChange={ onSwitch }/>
</HStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</Flex>
</AccountListItemMobile>
);
};
export default WatchListItem;
......@@ -3,15 +3,13 @@ import {
Tr,
Td,
Switch,
HStack,
} from '@chakra-ui/react';
import { useMutation } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { TWatchlistItem } from 'types/client/account';
import DeleteButton from 'ui/shared/DeleteButton';
import EditButton from 'ui/shared/EditButton';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
import WatchListAddressItem from './WatchListAddressItem';
......@@ -61,10 +59,7 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
</Td>
<Td><Switch colorScheme="blue" size="md" isChecked={ notificationEnabled } onChange={ onSwitch }/></Td>
<Td>
<HStack spacing={ 6 }>
<EditButton onClick={ onItemEditClick }/>
<DeleteButton onClick={ onItemDeleteClick }/>
</HStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</Td>
</Tr>
);
......
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