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

Feature/aspect #13

Merged
merged 2 commits into from
Jul 15, 2024
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
10 changes: 10 additions & 0 deletions configs/app/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,13 @@ export const parseEnvJson = <DataType>(env: string | undefined): DataType | null
return null;
}
};

export function convertString(str: string) {
const parts = str.split('_');

for (let i = 0; i < parts.length; i++) {
parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].slice(1);
}

return parts.join(' ');
}
10 changes: 7 additions & 3 deletions lib/api/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ import type {
AddressTokenTransferFilters,
AddressTokensFilter,
AddressTokensResponse,
AddressWithdrawalsResponse,
} from 'types/api/address';
AddressWithdrawalsResponse, AddressAspectResponse } from 'types/api/address';
import type { AddressesResponse } from 'types/api/addresses';
import type {
BlocksResponse,
Expand Down Expand Up @@ -556,6 +555,10 @@ export const RESOURCES = {
path: 'api/v2/aspects/:hash/transactions',
pathParams: [ 'hash' as const ],
},
address_aspects: {
path: 'api/v2/addresses/:address/aspects',
pathParams: [ 'address' as const ],
},
};

export type ResourceName = keyof typeof RESOURCES;
Expand Down Expand Up @@ -609,6 +612,7 @@ export type PaginatedResources =
| 'address_token_transfers'
| 'address_blocks_validated'
| 'address_coin_balance'
| 'address_aspects'
| 'search'
| 'address_logs'
| 'address_tokens'
Expand Down Expand Up @@ -797,7 +801,7 @@ export type ResourcePayload<Q extends ResourceName> = Q extends 'user_info'
: Q extends 'aspect_transactions'
? AspectTxsResponse
: Q extends 'aspects'
? AspectDetail
? AspectDetail : Q extends 'address_aspects' ? AddressAspectResponse
: never;
/* eslint-enable @typescript-eslint/indent */

Expand Down
11 changes: 10 additions & 1 deletion stubs/address.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Address, AddressCoinBalanceHistoryItem, AddressCounters, AddressTokenBalance } from 'types/api/address';
import type { Address, AddressAspect, AddressCoinBalanceHistoryItem, AddressCounters, AddressTokenBalance } from 'types/api/address';
import type { AddressesItem } from 'types/api/addresses';

import { ADDRESS_HASH } from './addressParams';
Expand Down Expand Up @@ -83,3 +83,12 @@ export const ADDRESS_TOKEN_BALANCE_ERC_1155: AddressTokenBalance = {
token_instance: TOKEN_INSTANCE,
value: '176',
};

export const ADDRESS_ASPECTS: AddressAspect = {
aspect_hash: '0x9639aea2f55e44a5b352a62c80976fae574b7d97',
join_points: [
'verify_tx',
],
priority: 1,
version: 1,
};
15 changes: 15 additions & 0 deletions types/api/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,18 @@ export type AddressWithdrawalsItem = {
timestamp: string;
validator_index: number;
}

export interface AddressAspectResponse {
items: Array<AddressAspect>;
next_page_params: {
block_number: number;
items_count: number;
} | null;
}

export type AddressAspect = {
aspect_hash: string;
join_points: Array<string>;
priority: number;
version: number;
}
70 changes: 21 additions & 49 deletions ui/address/AddressAspects.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,31 @@
import AspectABI from 'aspect/abi/aspect.json';
import { ethers } from 'ethers';
import { useRouter } from 'next/router';
import React from 'react';

import chain from 'configs/app/chain';
import getQueryParamString from 'lib/router/getQueryParamString';
import { ADDRESS_COIN_BALANCE } from 'stubs/address';
import { ADDRESS_ASPECTS } from 'stubs/address';
import { generateListStub } from 'stubs/utils';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';

import ActionBar from '../shared/ActionBar';
import Pagination from '../shared/pagination/Pagination';
import AddressAspectsHistory from './aspects/AddressAspectsHistory';

export type AspectType = {
aspectId?: string;
version?: number;
priority?: number;
joinPoints?: number;
};
const AddressAspects = () => {
const router = useRouter();
const scrollRef = React.useRef<HTMLDivElement>(null);
const [ aspectList, setAspectList ] = React.useState<Array<Array<AspectType>>>([]);
const addressHash = getQueryParamString(router.query.hash);
const provider = React.useMemo(() => {
return new ethers.JsonRpcProvider(chain.rpcUrl);
}, []);
const contract = React.useMemo(() => {
return new ethers.Contract(chain.aspectAddress, AspectABI, provider);
}, [ provider ]);
const chunkArray = (arr: Array<AspectType>, chunkSize: number) => {
const result = [];

for (let i = 0; i < arr.length; i += chunkSize) {
result.push(arr.slice(i, i + chunkSize));
}
type Props = {
scrollRef?: React.RefObject<HTMLDivElement>;
}

return result;
};
const getAspectsList = React.useCallback(async(addressHash: string) => {
const res = await contract.aspectsOf(addressHash);
const data = chunkArray(res.map((item: AspectType) => {
return {
aspectId: item.aspectId,
version: item.version,
priority: item.priority,
};
}), 10);
const AddressAspects = ({ scrollRef }: Props) => {
const router = useRouter();

setAspectList(data);
}, [ contract ]);
const currentAddress = getQueryParamString(router.query.hash);

React.useEffect(() => {
getAspectsList(addressHash);
}, [ addressHash, getAspectsList ]);
const coinBalanceQuery = useQueryWithPages({
resourceName: 'address_coin_balance',
pathParams: { hash: addressHash },
const addressAspectQuery = useQueryWithPages({
resourceName: 'address_aspects',
pathParams: { address: currentAddress },
scrollRef,
options: {
placeholderData: generateListStub<'address_coin_balance'>(
ADDRESS_COIN_BALANCE,
placeholderData: generateListStub<'address_aspects'>(
ADDRESS_ASPECTS,
50,
{
next_page_params: {
Expand All @@ -72,7 +38,13 @@ const AddressAspects = () => {
});

return (
<AddressAspectsHistory query={ coinBalanceQuery } aspectList={ aspectList }/>
<>
<ActionBar mt={ -6 } justifyContent="flex-end">
<Pagination { ...addressAspectQuery.pagination } isVisible ml={ 8 }/>
</ActionBar>
<AddressAspectsHistory query={ addressAspectQuery }/>
</>

);
};

Expand Down
100 changes: 31 additions & 69 deletions ui/address/aspects/AddressAspectsHistory.tsx
Original file line number Diff line number Diff line change
@@ -1,90 +1,52 @@
import { Hide, Table, Tbody, Th, Tr } from '@chakra-ui/react';
import { Hide, Show, Table, Tbody, Th, Tr } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';

import type { AddressAspectsHistoryResponse } from 'types/api/address';
import type { AddressAspectResponse } from 'types/api/address';
import type { PaginationParams } from 'ui/shared/pagination/types';

import type { AspectType } from 'ui/address/AddressAspects';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/pagination/Pagination';
import { default as Thead } from 'ui/shared/TheadSticky';
import TheadSticky from 'ui/shared/TheadSticky';

import AddressAspectsTableItem from './AddressAspectsTableItem';
import AddressAspectsItem from './AddressAspectsItem';
import AddressAspectsList from './AddressAspectsList';

interface Props {
query: UseQueryResult<AddressAspectsHistoryResponse> & {
query: UseQueryResult<AddressAspectResponse> & {
pagination: PaginationParams;
};
aspectList: Array<Array<AspectType>>;
}

const AddressAspectsHistory = ({ query, aspectList }: Props) => {
const [ page, setPage ] = React.useState<number>(1);
const onNextPageClick =
React.useCallback(() => {
() => {
setPage(page => page + 1);
};
}, [ setPage ]);

const onPrevPageClick =
React.useCallback(() => {
() => {
setPage(page => page - 1);
};
}, [ setPage ]);

const resetPage =
React.useCallback(() => {
() => {
setPage(1);
};
}, [ setPage ]);

const content = query.data?.items ? (
<Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm">
<Thead top={ query.pagination.isVisible ? 80 : 0 }>
<Tr>
<Th width="40%">Aspect ID</Th>
<Th width="20%">Join Points</Th>
<Th width="20%">Version</Th>
<Th width="20%">Priority</Th>
</Tr>
</Thead>
<Tbody>
{ aspectList[0] ?
aspectList[page - 1].map((item: AspectType, index: number) => (
<AddressAspectsTableItem
key={ index }
{ ...item }
page={ query.pagination.page }
isLoading={ query.isPlaceholderData }
/>
)) : '' }
</Tbody>
</Table>
</Hide>
const AddressAspectsHistory = ({ query }: Props) => {
const content = query.data ? (
<>
<Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm">
<TheadSticky top={ 80 }>
<Tr>
<Th width="25%">Aspect Id</Th>
<Th width="35%">Join Points</Th>
<Th width="20%">Version</Th>
<Th width="20%" textAlign="end">Priority</Th>
</Tr>
</TheadSticky>
<Tbody>
{ query.data.items.map(item => <AddressAspectsItem data={ item } key={ item.aspect_hash }/>) }
</Tbody>
</Table>
</Hide>
<Show below="lg" ssr={ false }>
<AddressAspectsList data={ query.data.items }/>
</Show>
</>
) : null;

const actionBar = (
<ActionBar mt={ -6 }>
<Pagination ml="auto" page={ page } isVisible={ true } hasPages={ aspectList.length > 0 }
hasNextPage={ page < aspectList.length } canGoBackwards={ true } isLoading={ false }
onNextPageClick={ onNextPageClick } onPrevPageClick={ onPrevPageClick } resetPage={ resetPage }/>
</ActionBar>
);

return (
<DataListDisplay
mt={ 8 }
isError={ query.isError }
items={ aspectList }
emptyText="There is no aspect bound to this address."
isError={ false }
items={ query.data?.items }
emptyText="There are no aspects."
content={ content }
actionBar={ actionBar }
/>
);
};
Expand Down
43 changes: 43 additions & 0 deletions ui/address/aspects/AddressAspectsItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Td, Tr, VStack } from '@chakra-ui/react';
import React from 'react';

import type { AddressAspect } from '../../../types/api/address';

import { convertString } from '../../../configs/app/utils';
import Address from '../../shared/address/Address';
import AddressLink from '../../shared/address/AddressLink';
import Tag from '../../shared/chakra/Tag';

interface IProps {
data: AddressAspect;
}

export default function AddressAspectsItem({ data }: IProps) {
return (
<Tr>
<Td pr={ 4 }>
<VStack alignItems="start" lineHeight="24px">
<Address w="150px">
<AddressLink
hash={ data.aspect_hash }
type="aspect"
fontWeight="700"
/></Address>
</VStack>
</Td>
<Td>
{ data.join_points.map(item => (
<Tag colorScheme="green" isTruncated maxW={{ base: '115px', lg: 'initial' }} key={ item } style={{ marginRight: '4px' }}>
{ convertString(item) }
</Tag>
)) }
</Td>
<Td>
<VStack alignItems="start" lineHeight="24px"><span>{ data.version }</span></VStack>
</Td>
<Td>
<VStack alignItems="end" lineHeight="24px"><span>{ data.priority }</span></VStack>
</Td>
</Tr>
);
}
Loading
Loading