Commit c0984838 authored by tom's avatar tom

skeletons for verified addresses

parent b5039524
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import VerifiedAddresses from 'ui/pages/VerifiedAddresses';
import Page from 'ui/shared/Page/Page';
const VerifiedAddresses = dynamic(() => import('ui/pages/VerifiedAddresses'), { ssr: false });
const VerifiedAddressesPage: NextPage = () => {
const title = getNetworkTitle();
return (
......
import type { PublicTag, AddressTag, TransactionTag, ApiKey, CustomAbi } from 'types/api/account';
import type { PublicTag, AddressTag, TransactionTag, ApiKey, CustomAbi, VerifiedAddress, TokenInfoApplication } from 'types/api/account';
import type { TWatchlistItem } from 'types/client/account';
import { ADDRESS_PARAMS, ADDRESS_HASH } from './addressParams';
......@@ -79,3 +79,28 @@ export const CUSTOM_ABI: CustomAbi = {
id: '1',
name: 'placeholder',
};
export const VERIFIED_ADDRESS: VerifiedAddress = {
userId: 'john.doe@gmail.com',
chainId: '5',
contractAddress: ADDRESS_HASH,
verifiedDate: '2022-11-11',
metadata: {
tokenName: 'Placeholder Token',
tokenSymbol: 'PLC',
},
};
export const TOKEN_INFO_APPLICATION: TokenInfoApplication = {
id: '1',
tokenAddress: ADDRESS_HASH,
status: 'IN_PROCESS',
updatedAt: '2022-11-11 13:49:48.031453Z',
requesterName: 'John Doe',
requesterEmail: 'john.doe@gmail.com',
projectWebsite: 'http://example.com',
projectEmail: 'info@example.com',
iconUrl: 'https://example.com/100/100',
projectDescription: 'Hello!',
projectSector: 'DeFi',
};
import { OrderedList, ListItem, chakra, Button, useDisclosure, Show, Hide, Skeleton, Box, Link } from '@chakra-ui/react';
import { OrderedList, ListItem, chakra, Button, useDisclosure, Show, Hide, Skeleton, Link } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -9,12 +9,11 @@ import appConfig from 'configs/app/config';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken';
import getQueryParamString from 'lib/router/getQueryParamString';
import { TOKEN_INFO_APPLICATION, VERIFIED_ADDRESS } from 'stubs/account';
import AddressVerificationModal from 'ui/addressVerification/AddressVerificationModal';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle';
import SkeletonListAccount from 'ui/shared/skeletons/SkeletonListAccount';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import AdminSupportText from 'ui/shared/texts/AdminSupportText';
import TokenInfoForm from 'ui/tokenInfo/TokenInfoForm';
import VerifiedAddressesListItem from 'ui/verifiedAddresses/VerifiedAddressesListItem';
......@@ -35,12 +34,18 @@ const VerifiedAddresses = () => {
}, [ ]);
const modalProps = useDisclosure();
const queryClient = useQueryClient();
const addressesQuery = useApiQuery('verified_addresses', {
pathParams: { chainId: appConfig.network.id },
queryOptions: {
placeholderData: { verifiedAddresses: Array(3).fill(VERIFIED_ADDRESS) },
},
});
const applicationsQuery = useApiQuery('token_info_applications', {
pathParams: { chainId: appConfig.network.id, id: undefined },
queryOptions: {
placeholderData: { submissions: Array(3).fill(TOKEN_INFO_APPLICATION) },
select: (data) => {
return {
...data,
......@@ -49,7 +54,8 @@ const VerifiedAddresses = () => {
},
},
});
const queryClient = useQueryClient();
const isLoading = addressesQuery.isPlaceholderData || applicationsQuery.isPlaceholderData;
const handleGoBack = React.useCallback(() => {
setSelectedAddress(undefined);
......@@ -94,24 +100,11 @@ const VerifiedAddresses = () => {
}, [ queryClient ]);
const addButton = (
<Box marginTop={ 8 }>
<Skeleton mt={ 8 } isLoaded={ !isLoading } display="inline-block">
<Button size="lg" onClick={ modalProps.onOpen }>
Add address
</Button>
</Box>
);
const skeleton = (
<>
<Box display={{ base: 'block', lg: 'none' }}>
<SkeletonListAccount/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
</Box>
<Box display={{ base: 'none', lg: 'block' }}>
<SkeletonTable columns={ [ '100%', '180px', '260px', '160px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
</Box>
</>
</Skeleton>
);
const backLink = React.useMemo(() => {
......@@ -144,13 +137,14 @@ const VerifiedAddresses = () => {
const content = addressesQuery.data?.verifiedAddresses ? (
<>
<Show below="lg" key="content-mobile" ssr={ false }>
{ addressesQuery.data.verifiedAddresses.map((item) => (
{ addressesQuery.data.verifiedAddresses.map((item, index) => (
<VerifiedAddressesListItem
key={ item.contractAddress }
key={ item.contractAddress + (isLoading ? index : '') }
item={ item }
application={ applicationsQuery.data?.submissions?.find(({ tokenAddress }) => tokenAddress.toLowerCase() === item.contractAddress.toLowerCase()) }
onAdd={ handleItemAdd }
onEdit={ handleItemEdit }
isLoading={ isLoading }
/>
)) }
</Show>
......@@ -160,6 +154,7 @@ const VerifiedAddresses = () => {
applications={ applicationsQuery.data?.submissions }
onItemEdit={ handleItemEdit }
onItemAdd={ handleItemAdd }
isLoading={ isLoading }
/>
</Hide>
</>
......@@ -192,12 +187,12 @@ const VerifiedAddresses = () => {
<AdminSupportText mt={ 5 }/>
</AccountPageDescription>
<DataListDisplay
isLoading={ addressesQuery.isLoading || applicationsQuery.isLoading }
isLoading={ false }
isError={ addressesQuery.isError || applicationsQuery.isError }
items={ addressesQuery.data?.verifiedAddresses }
content={ content }
emptyText=""
skeletonProps={{ customSkeleton: skeleton }}
skeletonProps={{ customSkeleton: null }}
/>
{ addButton }
<AddressVerificationModal
......
import { Icon, IconButton, Link, Tooltip } from '@chakra-ui/react';
import { IconButton, Link, Skeleton, Tooltip } from '@chakra-ui/react';
import React from 'react';
import type { TokenInfoApplication, VerifiedAddress } from 'types/api/account';
......@@ -6,6 +6,7 @@ import type { TokenInfoApplication, VerifiedAddress } from 'types/api/account';
import editIcon from 'icons/edit.svg';
import dayjs from 'lib/date/dayjs';
import AddressSnippet from 'ui/shared/AddressSnippet';
import Icon from 'ui/shared/chakra/Icon';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
import VerifiedAddressesStatus from './VerifiedAddressesStatus';
......@@ -16,29 +17,38 @@ interface Props {
application: TokenInfoApplication | undefined;
onAdd: (address: string) => void;
onEdit: (address: string) => void;
isLoading: boolean;
}
const VerifiedAddressesListItem = ({ item, application, onAdd, onEdit }: Props) => {
const VerifiedAddressesListItem = ({ item, application, onAdd, onEdit, isLoading }: Props) => {
const handleAddClick = React.useCallback(() => {
if (isLoading) {
return;
}
onAdd(item.contractAddress);
}, [ item, onAdd ]);
}, [ isLoading, item.contractAddress, onAdd ]);
const handleEditClick = React.useCallback(() => {
if (isLoading) {
return;
}
onEdit(item.contractAddress);
}, [ item, onEdit ]);
}, [ isLoading, item.contractAddress, onEdit ]);
return (
<ListItemMobileGrid.Container>
<ListItemMobileGrid.Label>Address</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value py="3px">
<AddressSnippet address={{ hash: item.contractAddress, is_contract: true, implementation_name: null }}/>
</ListItemMobileGrid.Value>
const tokenInfo = (() => {
if (isLoading) {
return <Skeleton height={ 6 } width="140px"/>;
}
{ item.metadata.tokenName && (
<>
<ListItemMobileGrid.Label>Token Info</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value py={ application ? '3px' : '5px' } display="flex" alignItems="center">
{ application ? (
if (!item.metadata.tokenName) {
return <span>Not a token</span>;
}
if (!application) {
return <Link onClick={ handleAddClick }>Add details</Link>;
}
return (
<>
<VerifiedAddressesTokenSnippet application={ application } name={ item.metadata.tokenName }/>
<Tooltip label="Edit">
......@@ -53,27 +63,43 @@ const VerifiedAddressesListItem = ({ item, application, onAdd, onEdit }: Props)
/>
</Tooltip>
</>
) : (
<Link onClick={ handleAddClick }>Add details</Link>
) }
);
})();
return (
<ListItemMobileGrid.Container>
<ListItemMobileGrid.Label isLoading={ isLoading }>Address</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value py="3px">
<AddressSnippet address={{ hash: item.contractAddress, is_contract: true, implementation_name: null }} isLoading={ isLoading }/>
</ListItemMobileGrid.Value>
{ item.metadata.tokenName && (
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Token Info</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value py={ application ? '3px' : '5px' } display="flex" alignItems="center">
{ tokenInfo }
</ListItemMobileGrid.Value>
</>
) }
{ item.metadata.tokenName && application && (
<>
<ListItemMobileGrid.Label>Status</ListItemMobileGrid.Label>
<ListItemMobileGrid.Label isLoading={ isLoading }>Status</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">
<VerifiedAddressesStatus status={ application.status }/>
</Skeleton>
</ListItemMobileGrid.Value>
</>
) }
{ item.metadata.tokenName && application && (
<>
<ListItemMobileGrid.Label>Date</ListItemMobileGrid.Label>
<ListItemMobileGrid.Label isLoading={ isLoading }>Date</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ dayjs(application.updatedAt).format('MMM DD, YYYY') }
</Skeleton>
</ListItemMobileGrid.Value>
</>
) }
......
......@@ -10,9 +10,10 @@ interface Props {
applications: Array<TokenInfoApplication> | undefined;
onItemAdd: (address: string) => void;
onItemEdit: (address: string) => void;
isLoading: boolean;
}
const VerifiedAddressesTable = ({ data, applications, onItemEdit, onItemAdd }: Props) => {
const VerifiedAddressesTable = ({ data, applications, onItemEdit, onItemAdd, isLoading }: Props) => {
return (
<Table variant="simple">
<Thead>
......@@ -25,13 +26,14 @@ const VerifiedAddressesTable = ({ data, applications, onItemEdit, onItemAdd }: P
</Tr>
</Thead>
<Tbody>
{ data.map((item) => (
{ data.map((item, index) => (
<VerifiedAddressesTableItem
key={ item.contractAddress }
key={ item.contractAddress + (isLoading ? index : '') }
item={ item }
application={ applications?.find(({ tokenAddress }) => tokenAddress.toLowerCase() === item.contractAddress.toLowerCase()) }
onAdd={ onItemAdd }
onEdit={ onItemEdit }
isLoading={ isLoading }
/>
)) }
</Tbody>
......
import { Td, Tr, Link, Tooltip, IconButton, Icon } from '@chakra-ui/react';
import { Td, Tr, Link, Tooltip, IconButton, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { TokenInfoApplication, VerifiedAddress } from 'types/api/account';
......@@ -6,6 +6,7 @@ import type { TokenInfoApplication, VerifiedAddress } from 'types/api/account';
import editIcon from 'icons/edit.svg';
import dayjs from 'lib/date/dayjs';
import AddressSnippet from 'ui/shared/AddressSnippet';
import Icon from 'ui/shared/chakra/Icon';
import VerifiedAddressesStatus from './VerifiedAddressesStatus';
import VerifiedAddressesTokenSnippet from './VerifiedAddressesTokenSnippet';
......@@ -15,19 +16,30 @@ interface Props {
application: TokenInfoApplication | undefined;
onAdd: (address: string) => void;
onEdit: (address: string) => void;
isLoading: boolean;
}
const VerifiedAddressesTableItem = ({ item, application, onAdd, onEdit }: Props) => {
const VerifiedAddressesTableItem = ({ item, application, onAdd, onEdit, isLoading }: Props) => {
const handleAddClick = React.useCallback(() => {
if (isLoading) {
return;
}
onAdd(item.contractAddress);
}, [ item, onAdd ]);
}, [ isLoading, item.contractAddress, onAdd ]);
const handleEditClick = React.useCallback(() => {
if (isLoading) {
return;
}
onEdit(item.contractAddress);
}, [ item, onEdit ]);
}, [ isLoading, item.contractAddress, onEdit ]);
const tokenInfo = (() => {
if (isLoading) {
return <Skeleton height={ 6 } width="140px"/>;
}
if (!item.metadata.tokenName) {
return <span>Not a token</span>;
}
......@@ -42,14 +54,14 @@ const VerifiedAddressesTableItem = ({ item, application, onAdd, onEdit }: Props)
return (
<Tr>
<Td>
<AddressSnippet address={{ hash: item.contractAddress, is_contract: true, implementation_name: null }}/>
<AddressSnippet address={{ hash: item.contractAddress, is_contract: true, implementation_name: null }} isLoading={ isLoading }/>
</Td>
<Td fontSize="sm" verticalAlign="middle" pr={ 1 }>
{ tokenInfo }
</Td>
<Td pl="0">
{ item.metadata.tokenName && application ? (
<Tooltip label="Edit">
{ item.metadata.tokenName && application && !isLoading ? (
<Tooltip label={ isLoading ? undefined : 'Edit' }>
<IconButton
aria-label="edit"
variant="simple"
......@@ -62,8 +74,16 @@ const VerifiedAddressesTableItem = ({ item, application, onAdd, onEdit }: Props)
</Tooltip>
) : null }
</Td>
<Td fontSize="sm"><VerifiedAddressesStatus status={ item.metadata.tokenName ? application?.status : undefined }/></Td>
<Td fontSize="sm" color="text_secondary">{ item.metadata.tokenName && application ? dayjs(application.updatedAt).format('MMM DD, YYYY') : null }</Td>
<Td fontSize="sm">
<Skeleton isLoaded={ !isLoading } display="inline-block">
<VerifiedAddressesStatus status={ item.metadata.tokenName ? application?.status : undefined }/>
</Skeleton>
</Td>
<Td fontSize="sm" color="text_secondary">
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ item.metadata.tokenName && application ? dayjs(application.updatedAt).format('MMM DD, YYYY') : null }
</Skeleton>
</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