Commit 230f34d0 authored by tom's avatar tom

example for address private tags

parent 2d8cd224
......@@ -199,7 +199,7 @@ module.exports = {
groups: [
'module',
'/types/',
[ '/^configs/', '/^data/', '/^deploy/', '/^icons/', '/^lib/', '/^mocks/', '/^pages/', '/^playwright/', '/^theme/', '/^ui/' ],
[ '/^configs/', '/^data/', '/^deploy/', '/^icons/', '/^lib/', '/^mocks/', '/^pages/', '/^playwright/', '/^stubs/', '/^theme/', '/^ui/' ],
[ 'parent', 'sibling', 'index' ],
],
alphabetize: { order: 'asc', ignoreCase: true },
......
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 20">
<path d="M15.645 19.375h-10A1.875 1.875 0 0 1 3.77 17.5V5.625a.625.625 0 0 1 1.25 0V17.5a.625.625 0 0 0 .625.625h10a.625.625 0 0 0 .625-.625V5.625a.625.625 0 0 1 1.25 0V17.5a1.875 1.875 0 0 1-1.875 1.875Zm2.5-15h-15a.625.625 0 0 1 0-1.25h15a.625.625 0 1 1 0 1.25Z"/>
<path d="M13.145 4.375a.625.625 0 0 1-.625-.625V1.875H8.77V3.75a.625.625 0 0 1-1.25 0v-2.5a.625.625 0 0 1 .625-.625h5a.625.625 0 0 1 .625.625v2.5a.625.625 0 0 1-.625.625Zm-2.5 11.875a.625.625 0 0 1-.625-.625v-8.75a.625.625 0 0 1 1.25 0v8.75a.625.625 0 0 1-.625.625ZM13.77 15a.625.625 0 0 1-.625-.625v-6.25a.625.625 0 0 1 1.25 0v6.25a.625.625 0 0 1-.625.625Zm-6.25 0a.625.625 0 0 1-.625-.625v-6.25a.625.625 0 0 1 1.25 0v6.25A.625.625 0 0 1 7.52 15Z"/>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 19.375H5A1.875 1.875 0 0 1 3.125 17.5V5.625a.625.625 0 0 1 1.25 0V17.5a.625.625 0 0 0 .625.625h10a.624.624 0 0 0 .625-.625V5.625a.625.625 0 1 1 1.25 0V17.5A1.875 1.875 0 0 1 15 19.375Zm2.5-15h-15a.625.625 0 0 1 0-1.25h15a.625.625 0 1 1 0 1.25Z" fill="currentColor"/>
<path d="M12.5 4.375a.625.625 0 0 1-.625-.625V1.875h-3.75V3.75a.625.625 0 0 1-1.25 0v-2.5A.625.625 0 0 1 7.5.625h5a.625.625 0 0 1 .625.625v2.5a.625.625 0 0 1-.625.625ZM10 16.25a.625.625 0 0 1-.625-.625v-8.75a.625.625 0 0 1 1.25 0v8.75a.624.624 0 0 1-.625.625ZM13.125 15a.624.624 0 0 1-.625-.625v-6.25a.625.625 0 1 1 1.25 0v6.25a.624.624 0 0 1-.625.625Zm-6.25 0a.625.625 0 0 1-.625-.625v-6.25a.625.625 0 0 1 1.25 0v6.25a.625.625 0 0 1-.625.625Z" fill="currentColor"/>
</svg>
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M19.433 3.6 16.399.569A1.926 1.926 0 0 0 15.03 0c-.518 0-1.005.202-1.37.568L.961 13.264a.779.779 0 0 0-.221.446l-.734 5.406a.78.78 0 0 0 .877.877l5.406-.734a.78.78 0 0 0 .446-.221L19.433 6.342c.366-.366.567-.853.567-1.37 0-.518-.201-1.005-.567-1.371ZM5.82 17.75l-4.131.561.56-4.131 8.997-8.997 3.571 3.57L5.82 17.75ZM18.33 5.24l-2.41 2.412-3.57-3.57 2.411-2.413a.379.379 0 0 1 .538 0l3.033 3.033a.379.379 0 0 1 0 .538Z"/>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.432 3.6 16.4.569A1.925 1.925 0 0 0 15.03 0c-.518 0-1.005.202-1.371.568L.962 13.264a.779.779 0 0 0-.221.446l-.734 5.406a.779.779 0 0 0 .877.877l5.406-.734a.778.778 0 0 0 .446-.221L19.432 6.342c.366-.366.568-.853.568-1.37 0-.518-.202-1.005-.568-1.371ZM5.82 17.75l-4.132.561.561-4.131 8.997-8.997 3.57 3.57L5.82 17.75ZM18.33 5.24l-2.41 2.412-3.571-3.57L14.76 1.67a.379.379 0 0 1 .537 0l3.034 3.032a.378.378 0 0 1 0 .538Z" fill="currentColor"/>
</svg>
import { ADDRESS_PARAMS, ADDRESS_HASH } from './address';
export const PRIVATE_TAG_ADDRESS = {
address: ADDRESS_PARAMS,
address_hash: ADDRESS_HASH,
id: '4',
name: 'placeholder',
};
export const ADDRESS_HASH = '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a';
export const ADDRESS_PARAMS = {
hash: ADDRESS_HASH,
implementation_name: null,
is_contract: false,
is_verified: null,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
};
......@@ -26,7 +26,7 @@ const baseStyle = defineStyle((props) => {
return {
opacity: 1,
borderRadius: 'base',
borderRadius: 'md',
borderColor: start,
background: `linear-gradient(90deg, ${ start } 8%, ${ end } 18%, ${ start } 33%)`,
backgroundSize: '200% 100%',
......
import { Tag, Flex, HStack, Text } from '@chakra-ui/react';
import { Tag, Flex, HStack, Text, Skeleton } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { AddressTag } from 'types/api/account';
......@@ -11,9 +11,10 @@ interface Props {
item: AddressTag;
onEditClick: (data: AddressTag) => void;
onDeleteClick: (data: AddressTag) => void;
isLoading?: boolean;
}
const AddressTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const AddressTagListItem = ({ item, onEditClick, onDeleteClick, isLoading }: Props) => {
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
......@@ -25,15 +26,17 @@ const AddressTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return (
<ListItemMobile>
<Flex alignItems="flex-start" flexDirection="column" maxW="100%">
<AddressSnippet address={ item.address }/>
<AddressSnippet address={ item.address } isLoading={ isLoading }/>
<HStack spacing={ 3 } mt={ 4 }>
<Text fontSize="sm" fontWeight={ 500 }>Private tag</Text>
<Tag>
{ item.name }
</Tag>
<Skeleton isLoaded={ !isLoading } display="inline-block" borderRadius="sm">
<Tag>
{ item.name }
</Tag>
</Skeleton>
</HStack>
</Flex>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick } isLoading={ isLoading }/>
</ListItemMobile>
);
};
......
......@@ -12,28 +12,30 @@ import type { AddressTags, AddressTag } from 'types/api/account';
import AddressTagTableItem from './AddressTagTableItem';
interface Props {
data: AddressTags;
data?: AddressTags;
onEditClick: (data: AddressTag) => void;
onDeleteClick: (data: AddressTag) => void;
isLoading: boolean;
}
const AddressTagTable = ({ data, onDeleteClick, onEditClick }: Props) => {
const AddressTagTable = ({ data, onDeleteClick, onEditClick, isLoading }: Props) => {
return (
<Table variant="simple" minWidth="600px">
<Thead>
<Tr>
<Th width="60%">Address</Th>
<Th width="40%">Private tag</Th>
<Th width="108px"></Th>
<Th width="116px"></Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item: AddressTag) => (
{ data?.map((item: AddressTag, index: number) => (
<AddressTagTableItem
item={ item }
key={ item.id }
key={ item.id + (isLoading ? index : '') }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
isLoading={ isLoading }
/>
)) }
</Tbody>
......
......@@ -2,6 +2,7 @@ import {
Tag,
Tr,
Td,
Skeleton,
} from '@chakra-ui/react';
import React, { useCallback } from 'react';
......@@ -15,9 +16,10 @@ interface Props {
item: AddressTag;
onEditClick: (data: AddressTag) => void;
onDeleteClick: (data: AddressTag) => void;
isLoading: boolean;
}
const AddressTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const AddressTagTableItem = ({ item, onEditClick, onDeleteClick, isLoading }: Props) => {
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
......@@ -29,17 +31,19 @@ const AddressTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return (
<Tr alignItems="top" key={ item.id }>
<Td>
<AddressSnippet address={ item.address }/>
<AddressSnippet address={ item.address } isLoading={ isLoading }/>
</Td>
<Td whiteSpace="nowrap">
<TruncatedTextTooltip label={ item.name }>
<Tag>
{ item.name }
</Tag>
<Skeleton isLoaded={ !isLoading } display="inline-block" borderRadius="sm">
<Tag>
{ item.name }
</Tag>
</Skeleton>
</TruncatedTextTooltip>
</Td>
<Td>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick } isLoading={ isLoading }/>
</Td>
</Tr>
);
......
......@@ -5,10 +5,9 @@ import type { AddressTag } from 'types/api/account';
import useApiQuery from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import { PRIVATE_TAG_ADDRESS } from 'stubs/account';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import SkeletonListAccount from 'ui/shared/skeletons/SkeletonListAccount';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import AddressModal from './AddressModal/AddressModal';
import AddressTagListItem from './AddressTagTable/AddressTagListItem';
......@@ -16,7 +15,12 @@ import AddressTagTable from './AddressTagTable/AddressTagTable';
import DeletePrivateTagModal from './DeletePrivateTagModal';
const PrivateAddressTags = () => {
const { data: addressTagsData, isLoading, isError } = useApiQuery('private_tags_address', { queryOptions: { refetchOnMount: false } });
const { data: addressTagsData, isError, isPlaceholderData } = useApiQuery('private_tags_address', {
queryOptions: {
refetchOnMount: false,
placeholderData: Array(3).fill(PRIVATE_TAG_ADDRESS),
},
});
const addressModalProps = useDisclosure();
const deleteModalProps = useDisclosure();
......@@ -45,46 +49,25 @@ const PrivateAddressTags = () => {
deleteModalProps.onClose();
}, [ deleteModalProps ]);
const description = (
<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.
</AccountPageDescription>
);
if (isLoading && !addressTagsData) {
const loader = isMobile ? <SkeletonListAccount/> : (
<>
<SkeletonTable columns={ [ '60%', '40%', '108px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
</>
);
return (
<>
{ description }
{ loader }
</>
);
}
if (isError) {
return <DataFetchAlert/>;
}
const list = isMobile ? (
<Box>
{ addressTagsData.map((item: AddressTag) => (
{ addressTagsData?.map((item: AddressTag, index: number) => (
<AddressTagListItem
item={ item }
key={ item.id }
key={ item.id + (isPlaceholderData ? index : '') }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
isLoading={ isPlaceholderData }
/>
)) }
</Box>
) : (
<AddressTagTable
isLoading={ isPlaceholderData }
data={ addressTagsData }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
......@@ -93,15 +76,20 @@ const PrivateAddressTags = () => {
return (
<>
{ description }
<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.
</AccountPageDescription>
{ Boolean(addressTagsData?.length) && list }
<Box marginTop={ 8 }>
<Button
size="lg"
onClick={ addressModalProps.onOpen }
>
Add address tag
</Button>
<Skeleton isLoaded={ !isPlaceholderData } display="inline-block">
<Button
size="lg"
onClick={ addressModalProps.onOpen }
>
Add address tag
</Button>
</Skeleton>
</Box>
<AddressModal { ...addressModalProps } onClose={ onAddressModalClose } data={ addressModalData }/>
{ deleteModalData && (
......
......@@ -11,15 +11,16 @@ import CopyToClipboard from 'ui/shared/CopyToClipboard';
interface Props {
address: AddressParam;
subtitle?: string;
isLoading?: boolean;
}
const AddressSnippet = ({ address, subtitle }: Props) => {
const AddressSnippet = ({ address, subtitle, isLoading }: Props) => {
return (
<Box maxW="100%">
<Address>
<AddressIcon address={ address }/>
<AddressLink type="address" hash={ address.hash } fontWeight="600" ml={ 2 }/>
<CopyToClipboard text={ address.hash } ml={ 1 }/>
<AddressIcon address={ address } isLoading={ isLoading }/>
<AddressLink type="address" hash={ address.hash } fontWeight="600" ml={ 2 } isLoading={ isLoading }/>
<CopyToClipboard text={ address.hash } ml={ 1 } isLoading={ isLoading }/>
</Address>
{ subtitle && <Text fontSize="sm" variant="secondary" mt={ 0.5 } ml={ 8 }>{ subtitle }</Text> }
</Box>
......
import { IconButton, Tooltip, useClipboard, chakra, useDisclosure } from '@chakra-ui/react';
import { IconButton, Tooltip, useClipboard, chakra, useDisclosure, Skeleton } from '@chakra-ui/react';
import React, { useEffect, useState } from 'react';
import CopyIcon from 'icons/copy.svg';
const CopyToClipboard = ({ text, className }: {text: string; className?: string}) => {
interface Props {
text: string;
className?: string;
isLoading?: boolean;
}
const CopyToClipboard = ({ text, className, isLoading }: Props) => {
const { hasCopied, onCopy } = useClipboard(text, 1000);
const [ copied, setCopied ] = useState(false);
// have to implement controlled tooltip because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107
......@@ -17,6 +23,10 @@ const CopyToClipboard = ({ text, className }: {text: string; className?: string}
}
}, [ hasCopied ]);
if (isLoading) {
return <Skeleton boxSize={ 5 } className={ className } borderRadius="sm" flexShrink={ 0 }/>;
}
return (
<Tooltip label={ copied ? 'Copied' : 'Copy to clipboard' } isOpen={ isOpen || copied }>
<IconButton
......
......@@ -26,6 +26,8 @@ const ListItemMobile = ({ children, className, isAnimated }: Props) => {
borderBottomWidth: '1px',
}}
className={ className }
fontSize="16px"
lineHeight="20px"
>
{ children }
</Flex>
......
import { Tooltip, IconButton, Icon, HStack } from '@chakra-ui/react';
import { Tooltip, IconButton, HStack, Skeleton } from '@chakra-ui/react';
import React from 'react';
import DeleteIcon from 'icons/delete.svg';
......@@ -8,33 +8,47 @@ import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModa
type Props = {
onEditClick: () => void;
onDeleteClick: () => void;
isLoading?: boolean;
}
const TableItemActionButtons = ({ onEditClick, onDeleteClick }: Props) => {
const TableItemActionButtons = ({ onEditClick, onDeleteClick, isLoading }: Props) => {
const onFocusCapture = usePreventFocusAfterModalClosing();
if (isLoading) {
return (
<HStack spacing={ 6 } alignSelf="flex-end">
<Skeleton boxSize={ 5 } flexShrink={ 0 } borderRadius="base"/>
<Skeleton boxSize={ 5 } flexShrink={ 0 } borderRadius="base"/>
</HStack>
);
}
return (
<HStack spacing={ 6 } alignSelf="flex-end">
<Tooltip label="Edit">
<IconButton
aria-label="edit"
variant="simple"
w="30px"
h="30px"
boxSize={ 5 }
onClick={ onEditClick }
icon={ <Icon as={ EditIcon } w="20px" h="20px"/> }
icon={ <EditIcon/> }
onFocusCapture={ onFocusCapture }
display="inline-block"
flexShrink={ 0 }
borderRadius="none"
/>
</Tooltip>
<Tooltip label="Delete">
<IconButton
aria-label="delete"
variant="simple"
w="30px"
h="30px"
boxSize={ 5 }
onClick={ onDeleteClick }
icon={ <Icon as={ DeleteIcon } w="20px" h="20px"/> }
icon={ <DeleteIcon/> }
onFocusCapture={ onFocusCapture }
display="inline-block"
flexShrink={ 0 }
borderRadius="none"
/>
</Tooltip>
</HStack>
......
import { Box, chakra, Tooltip } from '@chakra-ui/react';
import { Box, chakra, Skeleton, Tooltip } from '@chakra-ui/react';
import React from 'react';
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon';
......@@ -9,9 +9,14 @@ import AddressContractIcon from 'ui/shared/address/AddressContractIcon';
type Props = {
address: Pick<AddressParam, 'hash' | 'is_contract' | 'implementation_name'>;
className?: string;
isLoading?: boolean;
}
const AddressIcon = ({ address, className }: Props) => {
const AddressIcon = ({ address, className, isLoading }: Props) => {
if (isLoading) {
return <Skeleton boxSize={ 6 } className={ className } borderRadius="full" flexShrink={ 0 }/>;
}
if (address.is_contract) {
return (
<AddressContractIcon className={ className }/>
......@@ -20,7 +25,7 @@ const AddressIcon = ({ address, className }: Props) => {
return (
<Tooltip label={ address.implementation_name }>
<Box className={ className } width="24px" display="inline-flex">
<Box className={ className } boxSize={ 6 } display="inline-flex">
<Jazzicon diameter={ 24 } seed={ jsNumberForAddress(address.hash) }/>
</Box>
</Tooltip>
......
import { chakra, shouldForwardProp, Tooltip, Box } from '@chakra-ui/react';
import { chakra, shouldForwardProp, Tooltip, Box, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import type { HTMLAttributeAnchorTarget } from 'react';
import React from 'react';
......@@ -17,6 +17,7 @@ type CommonProps = {
isDisabled?: boolean;
fontWeight?: string;
alias?: string | null;
isLoading?: boolean;
}
type AddressTokenTxProps = {
......@@ -39,7 +40,7 @@ type AddressTokenProps = {
type Props = CommonProps & (AddressTokenTxProps | BlockProps | AddressTokenProps);
const AddressLink = (props: Props) => {
const { alias, type, className, truncation = 'dynamic', hash, fontWeight, target = '_self', isDisabled } = props;
const { alias, type, className, truncation = 'dynamic', hash, fontWeight, target = '_self', isDisabled, isLoading } = props;
const isMobile = useIsMobile();
let url;
......@@ -81,6 +82,10 @@ const AddressLink = (props: Props) => {
}
})();
if (isLoading) {
return <Skeleton className={ className } overflow="hidden" whiteSpace="nowrap">{ content }</Skeleton>;
}
if (isDisabled) {
return (
<chakra.span
......
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