Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

watchlist and private tags pagination #1333

Merged
merged 1 commit into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions lib/api/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import type {
UserInfo,
CustomAbis,
PublicTags,
AddressTags,
TransactionTags,
ApiKeys,
WatchlistAddress,
VerifiedAddressResponse,
TokenInfoApplicationConfig,
TokenInfoApplications,
WatchlistResponse,
TransactionTagsResponse,
AddressTagsResponse,
} from 'types/api/account';
import type {
Address,
Expand Down Expand Up @@ -90,20 +90,23 @@ export const RESOURCES = {
pathParams: [ 'id' as const ],
},
watchlist: {
path: '/api/account/v1/user/watchlist/:id?',
path: '/api/account/v2/user/watchlist/:id?',
pathParams: [ 'id' as const ],
filterFields: [ ],
},
public_tags: {
path: '/api/account/v1/user/public_tags/:id?',
pathParams: [ 'id' as const ],
},
private_tags_address: {
path: '/api/account/v1/user/tags/address/:id?',
path: '/api/account/v2/user/tags/address/:id?',
pathParams: [ 'id' as const ],
filterFields: [ ],
},
private_tags_tx: {
path: '/api/account/v1/user/tags/transaction/:id?',
path: '/api/account/v2/user/tags/transaction/:id?',
pathParams: [ 'id' as const ],
filterFields: [ ],
},
api_keys: {
path: '/api/account/v1/user/api_keys/:id?',
Expand Down Expand Up @@ -579,7 +582,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' |
'verified_contracts' |
'l2_output_roots' | 'l2_withdrawals' | 'l2_txn_batches' | 'l2_deposits' |
'zkevm_l2_txn_batches' | 'zkevm_l2_txn_batch_txs' |
'withdrawals' | 'address_withdrawals' | 'block_withdrawals';
'withdrawals' | 'address_withdrawals' | 'block_withdrawals' |
'watchlist' | 'private_tags_address' | 'private_tags_tx';

export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;

Expand All @@ -588,10 +592,10 @@ export type ResourcePayload<Q extends ResourceName> =
Q extends 'user_info' ? UserInfo :
Q extends 'custom_abi' ? CustomAbis :
Q extends 'public_tags' ? PublicTags :
Q extends 'private_tags_address' ? AddressTags :
Q extends 'private_tags_tx' ? TransactionTags :
Q extends 'private_tags_address' ? AddressTagsResponse :
Q extends 'private_tags_tx' ? TransactionTagsResponse :
Q extends 'api_keys' ? ApiKeys :
Q extends 'watchlist' ? Array<WatchlistAddress> :
Q extends 'watchlist' ? WatchlistResponse :
Q extends 'verified_addresses' ? VerifiedAddressResponse :
Q extends 'token_info_applications_config' ? TokenInfoApplicationConfig :
Q extends 'token_info_applications' ? TokenInfoApplications :
Expand Down
24 changes: 24 additions & 0 deletions types/api/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ export interface AddressTag {

export type AddressTags = Array<AddressTag>

export type AddressTagsResponse = {
items: AddressTags;
next_page_params: {
id: number;
items_count: number;
} | null;
}

export interface ApiKey {
api_key: string;
name: string;
Expand Down Expand Up @@ -48,6 +56,14 @@ export interface TransactionTag {

export type TransactionTags = Array<TransactionTag>

export type TransactionTagsResponse = {
items: TransactionTags;
next_page_params: {
id: number;
items_count: number;
} | null;
}

export type Transactions = Array<Transaction>

export interface UserInfo {
Expand Down Expand Up @@ -78,6 +94,14 @@ export interface WatchlistAddressNew {

export type WatchlistAddresses = Array<WatchlistAddress>

export type WatchlistResponse = {
items: WatchlistAddresses;
next_page_params: {
id: number;
items_count: number;
} | null;
}

export interface PublicTag {
website: string;
tags: string; // tag_1;tag_2;tag_3 etc.
Expand Down
48 changes: 32 additions & 16 deletions ui/pages/Watchlist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,29 @@ import { Box, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';

import type { WatchlistAddress } from 'types/api/account';
import type { WatchlistAddress, WatchlistResponse } from 'types/api/account';

import { resourceKey } from 'lib/api/resources';
import useApiQuery from 'lib/api/useApiQuery';
import { getResourceKey } from 'lib/api/useApiQuery';
import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken';
import { WATCH_LIST_ITEM_WITH_TOKEN_INFO } from 'stubs/account';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
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 = () => {
const { data, isPlaceholderData, isError } = useApiQuery('watchlist', {
queryOptions: {
placeholderData: Array(3).fill(WATCH_LIST_ITEM_WITH_TOKEN_INFO),

const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({
resourceName: 'watchlist',
options: {
placeholderData: { items: Array(5).fill(WATCH_LIST_ITEM_WITH_TOKEN_INFO), next_page_params: null },
},
});
const queryClient = useQueryClient();
Expand Down Expand Up @@ -58,9 +63,11 @@ const WatchList: React.FC = () => {
}, [ deleteModalProps ]);

const onDeleteSuccess = useCallback(async() => {
queryClient.setQueryData([ resourceKey('watchlist') ], (prevData: Array<WatchlistAddress> | undefined) => {
return prevData?.filter((item) => item.id !== deleteModalData?.id);
});
queryClient.setQueryData(getResourceKey('watchlist'), (prevData: WatchlistResponse | undefined) => {
const newItems = prevData?.items.filter((item: WatchlistAddress) => item.id !== deleteModalData?.id);
return { ...prevData, items: newItems };
},
);
}, [ deleteModalData?.id, queryClient ]);

const description = (
Expand All @@ -69,15 +76,17 @@ const WatchList: React.FC = () => {
</AccountPageDescription>
);

if (isError) {
return <DataFetchAlert/>;
}

const content = (() => {
const actionBar = pagination.isVisible ? (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
) : null;

const list = (
<>
<Box display={{ base: 'block', lg: 'none' }}>
{ data?.map((item, index) => (
{ data?.items.map((item, index) => (
<WatchListItem
key={ item.address_hash + (isPlaceholderData ? index : '') }
item={ item }
Expand All @@ -89,10 +98,11 @@ const WatchList: React.FC = () => {
</Box>
<Box display={{ base: 'none', lg: 'block' }}>
<WatchlistTable
data={ data }
data={ data?.items }
isLoading={ isPlaceholderData }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
top={ pagination.isVisible ? 80 : 0 }
/>
</Box>
</>
Expand All @@ -101,7 +111,13 @@ const WatchList: React.FC = () => {
return (
<>
{ description }
{ Boolean(data?.length) && list }
<DataListDisplay
isError={ isError }
items={ data?.items }
emptyText=""
content={ list }
actionBar={ actionBar }
/>
<Skeleton mt={ 8 } isLoaded={ !isPlaceholderData } display="inline-block">
<Button
size="lg"
Expand Down
10 changes: 6 additions & 4 deletions ui/privateTags/AddressTagTable/AddressTagTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
Table,
Thead,
Tbody,
Tr,
Th,
Expand All @@ -9,25 +8,28 @@ import React from 'react';

import type { AddressTags, AddressTag } from 'types/api/account';

import TheadSticky from 'ui/shared/TheadSticky';

import AddressTagTableItem from './AddressTagTableItem';

interface Props {
data?: AddressTags;
onEditClick: (data: AddressTag) => void;
onDeleteClick: (data: AddressTag) => void;
isLoading: boolean;
top: number;
}

const AddressTagTable = ({ data, onDeleteClick, onEditClick, isLoading }: Props) => {
const AddressTagTable = ({ data, onDeleteClick, onEditClick, isLoading, top }: Props) => {
return (
<Table variant="simple" minWidth="600px">
<Thead>
<TheadSticky top={ top }>
<Tr>
<Th width="60%">Address</Th>
<Th width="40%">Private tag</Th>
<Th width="116px"></Th>
</Tr>
</Thead>
</TheadSticky>
<Tbody>
{ data?.map((item: AddressTag, index: number) => (
<AddressTagTableItem
Expand Down
15 changes: 9 additions & 6 deletions ui/privateTags/DeletePrivateTagModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { Text } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import React, { useCallback } from 'react';

import type { AddressTag, TransactionTag, AddressTags, TransactionTags } from 'types/api/account';
import type { AddressTag, TransactionTag, AddressTagsResponse, TransactionTagsResponse } from 'types/api/account';

import { resourceKey } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch';
import { getResourceKey } from 'lib/api/useApiQuery';
import DeleteModal from 'ui/shared/DeleteModal';

type Props = {
Expand All @@ -32,12 +32,15 @@ const DeletePrivateTagModal: React.FC<Props> = ({ isOpen, onClose, data, type })

const onSuccess = useCallback(async() => {
if (type === 'address') {
queryClient.setQueryData([ resourceKey('private_tags_address') ], (prevData: AddressTags | undefined) => {
return prevData?.filter((item: AddressTag) => item.id !== id);
queryClient.setQueryData(getResourceKey('private_tags_address'), (prevData: AddressTagsResponse | undefined) => {
const newItems = prevData?.items.filter((item: AddressTag) => item.id !== id);
return { ...prevData, items: newItems };

});
} else {
queryClient.setQueryData([ resourceKey('private_tags_tx') ], (prevData: TransactionTags | undefined) => {
return prevData?.filter((item: TransactionTag) => item.id !== id);
queryClient.setQueryData(getResourceKey('private_tags_tx'), (prevData: TransactionTagsResponse | undefined) => {
const newItems = prevData?.items.filter((item: TransactionTag) => item.id !== id);
return { ...prevData, items: newItems };
});
}
}, [ type, id, queryClient ]);
Expand Down
36 changes: 24 additions & 12 deletions ui/privateTags/PrivateAddressTags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@ import React, { useCallback, useState } from 'react';

import type { AddressTag } from 'types/api/account';

import useApiQuery from 'lib/api/useApiQuery';
import { PAGE_TYPE_DICT } from 'lib/mixpanel/getPageType';
import { PRIVATE_TAG_ADDRESS } from 'stubs/account';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';

import AddressModal from './AddressModal/AddressModal';
import AddressTagListItem from './AddressTagTable/AddressTagListItem';
import AddressTagTable from './AddressTagTable/AddressTagTable';
import DeletePrivateTagModal from './DeletePrivateTagModal';

const PrivateAddressTags = () => {
const { data: addressTagsData, isError, isPlaceholderData, refetch } = useApiQuery('private_tags_address', {
queryOptions: {
const { data: addressTagsData, isError, isPlaceholderData, refetch, pagination } = useQueryWithPages({
resourceName: 'private_tags_address',
options: {
refetchOnMount: false,
placeholderData: Array(3).fill(PRIVATE_TAG_ADDRESS),
placeholderData: { items: Array(5).fill(PRIVATE_TAG_ADDRESS), next_page_params: null },
},
});

Expand Down Expand Up @@ -52,14 +55,10 @@ const PrivateAddressTags = () => {
deleteModalProps.onClose();
}, [ deleteModalProps ]);

if (isError) {
return <DataFetchAlert/>;
}

const list = (
<>
<Box display={{ base: 'block', lg: 'none' }}>
{ addressTagsData?.map((item: AddressTag, index: number) => (
{ addressTagsData?.items.map((item: AddressTag, index: number) => (
<AddressTagListItem
item={ item }
key={ item.id + (isPlaceholderData ? index : '') }
Expand All @@ -72,21 +71,34 @@ const PrivateAddressTags = () => {
<Box display={{ base: 'none', lg: 'block' }}>
<AddressTagTable
isLoading={ isPlaceholderData }
data={ addressTagsData }
data={ addressTagsData?.items }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
top={ pagination.isVisible ? 80 : 0 }
/>
</Box>
</>
);

const actionBar = pagination.isVisible ? (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
) : null;

return (
<>
<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 }
<DataListDisplay
isError={ isError }
items={ addressTagsData?.items }
emptyText=""
content={ list }
actionBar={ actionBar }
/>
<Skeleton mt={ 8 } isLoaded={ !isPlaceholderData } display="inline-block">
<Button
size="lg"
Expand Down
Loading
Loading