Commit c01c4a8d authored by tom's avatar tom

rest of tx, address and block resources

parent 210c2ffb
......@@ -50,6 +50,9 @@ export const RESOURCES = {
paginationFields: [ 'block_number' as const, 'items_count' as const ],
filterFields: [ 'type' as const ],
},
block: {
path: '/api/v2/blocks/:id',
},
block_txs: {
path: '/api/v2/blocks/:id/transactions',
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const ],
......@@ -65,6 +68,9 @@ export const RESOURCES = {
paginationFields: [ 'filter' as const, 'hash' as const, 'inserted_at' as const ],
filterFields: [ 'filter' as const, 'type' as const, 'method' as const ],
},
tx: {
path: '/api/v2/transactions/:id',
},
tx_internal_txs: {
path: '/api/v2/transactions/:id/internal-transactions',
paginationFields: [ 'block_number' as const, 'items_count' as const, 'transaction_hash' as const, 'index' as const, 'transaction_index' as const ],
......@@ -80,8 +86,20 @@ export const RESOURCES = {
paginationFields: [ 'block_number' as const, 'items_count' as const, 'transaction_hash' as const, 'index' as const ],
filterFields: [ 'type' as const ],
},
tx_raw_trace: {
path: '/api/v2/transactions/:id/raw-trace',
},
// ADDRESS
address: {
path: '/api/v2/addresses/:id',
},
address_counters: {
path: '/api/v2/addresses/:id/counters',
},
address_token_balances: {
path: '/api/v2/addresses/:id/token-balances',
},
address_txs: {
path: '/api/v2/addresses/:id/transactions',
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const ],
......
......@@ -3,25 +3,29 @@ import { useQuery } from '@tanstack/react-query';
import type { UserInfo, CustomAbis, PublicTags, AddressTags, TransactionTags, ApiKeys, WatchlistAddress } from 'types/api/account';
import type {
Address,
AddressCounters,
AddressTokenBalance,
AddressTransactionsResponse,
AddressTokenTransferResponse,
AddressCoinBalanceHistoryResponse,
AddressBlocksValidatedResponse,
AddressInternalTxsResponse,
} from 'types/api/address';
import type { BlocksResponse, BlockTransactionsResponse } from 'types/api/block';
import type { BlocksResponse, BlockTransactionsResponse, Block } from 'types/api/block';
import type { InternalTransactionsResponse } from 'types/api/internalTransaction';
import type { LogsResponse } from 'types/api/log';
import type { RawTracesResponse } from 'types/api/rawTrace';
import type { Stats, Charts } from 'types/api/stats';
import type { TokenTransferResponse } from 'types/api/tokenTransfer';
import type { TransactionsResponseValidated, TransactionsResponsePending } from 'types/api/transaction';
import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction';
import type { ResourceError, ResourceName } from './resources';
import type { Params as ApiFetchParams } from './useApiFetch';
import useApiFetch from './useApiFetch';
export interface Params<R extends ResourceName> extends ApiFetchParams {
queryOptions?: Omit<UseQueryOptions<unknown, ResourceError, ResourcePayload<R>>, 'queryKey' | 'queryFn'>;
export interface Params<R extends ResourceName, E = unknown> extends ApiFetchParams {
queryOptions?: Omit<UseQueryOptions<unknown, ResourceError<E>, ResourcePayload<R>>, 'queryKey' | 'queryFn'>;
}
export function getResourceKey<R extends ResourceName>(resource: R, { pathParams, queryParams }: Params<R> = {}) {
......@@ -32,39 +36,47 @@ export function getResourceKey<R extends ResourceName>(resource: R, { pathParams
return [ resource ];
}
export default function useApiQuery<R extends ResourceName>(
export default function useApiQuery<R extends ResourceName, E = unknown>(
resource: R,
{ queryOptions, pathParams, queryParams, fetchParams }: Params<R> = {},
{ queryOptions, pathParams, queryParams, fetchParams }: Params<R, E> = {},
) {
const apiFetch = useApiFetch();
return useQuery<unknown, ResourceError, ResourcePayload<R>>(
return useQuery<unknown, ResourceError<E>, ResourcePayload<R>>(
getResourceKey(resource, { pathParams, queryParams }),
async() => {
return apiFetch<R, ResourcePayload<R>, ResourceError>(resource, { pathParams, queryParams, fetchParams });
}, queryOptions);
}
/* eslint-disable @typescript-eslint/indent */
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 'api_keys' ? ApiKeys :
Q extends 'watchlist' ? Array<WatchlistAddress> :
Q extends 'stats_counters' ? Stats :
Q extends 'stats_charts' ? Charts :
Q extends 'blocks' ? BlocksResponse :
Q extends 'block_txs' ? BlockTransactionsResponse :
Q extends 'txs_validated' ? TransactionsResponseValidated :
Q extends 'txs_pending' ? TransactionsResponsePending :
Q extends 'tx_internal_txs' ? InternalTransactionsResponse :
Q extends 'tx_logs' ? LogsResponse :
Q extends 'tx_token_transfers' ? TokenTransferResponse :
Q extends 'address_txs' ? AddressTransactionsResponse :
Q extends 'address_internal_txs' ? AddressInternalTxsResponse :
Q extends 'address_token_transfers' ? AddressTokenTransferResponse :
Q extends 'address_blocks_validated' ? AddressBlocksValidatedResponse :
Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse :
never;
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 'api_keys' ? ApiKeys :
Q extends 'watchlist' ? Array<WatchlistAddress> :
Q extends 'stats_counters' ? Stats :
Q extends 'stats_charts' ? Charts :
Q extends 'blocks' ? BlocksResponse :
Q extends 'block' ? Block :
Q extends 'block_txs' ? BlockTransactionsResponse :
Q extends 'txs_validated' ? TransactionsResponseValidated :
Q extends 'txs_pending' ? TransactionsResponsePending :
Q extends 'tx' ? Transaction :
Q extends 'tx_internal_txs' ? InternalTransactionsResponse :
Q extends 'tx_logs' ? LogsResponse :
Q extends 'tx_token_transfers' ? TokenTransferResponse :
Q extends 'tx_raw_trace' ? RawTracesResponse :
Q extends 'address' ? Address :
Q extends 'address_counters' ? AddressCounters :
Q extends 'address_token_balances' ? Array<AddressTokenBalance> :
Q extends 'address_txs' ? AddressTransactionsResponse :
Q extends 'address_internal_txs' ? AddressInternalTxsResponse :
Q extends 'address_token_transfers' ? AddressTokenTransferResponse :
Q extends 'address_blocks_validated' ? AddressBlocksValidatedResponse :
Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse :
never;
/* eslint-enable @typescript-eslint/indent */
......@@ -4,16 +4,9 @@ export enum QueryKeys {
indexingStatus='indexingStatus',
stats='stats',
charts='stats',
tx = 'tx',
txRawTrace = 'tx-raw-trace',
block = 'block',
chartsTxs = 'charts-txs',
chartsMarket = 'charts-market',
indexBlocks='indexBlocks',
indexTxs='indexTxs',
jsonRpcUrl='json-rpc-url',
address='address',
addressCounters='address-counters',
addressTokenBalances='address-token-balances',
addressCoinBalanceHistoryByDay='address-coin-balance-history-by-day',
}
import { Box, Flex, Text, Icon, Grid, Link } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
import { useRouter } from 'next/router';
import React from 'react';
import type { Address as TAddress, AddressCounters, AddressTokenBalance } from 'types/api/address';
import { QueryKeys } from 'types/client/queries';
import type { Address as TAddress } from 'types/api/address';
import appConfig from 'configs/app/config';
import blockIcon from 'icons/block.svg';
import useFetch from 'lib/hooks/useFetch';
import useApiQuery from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import link from 'lib/link/link';
import AddressIcon from 'ui/shared/address/AddressIcon';
......@@ -35,24 +33,20 @@ interface Props {
const AddressDetails = ({ addressQuery }: Props) => {
const router = useRouter();
const fetch = useFetch();
const isMobile = useIsMobile();
const countersQuery = useQuery<unknown, unknown, AddressCounters>(
[ QueryKeys.addressCounters, router.query.id ],
async() => await fetch(`/node-api/addresses/${ router.query.id }/counters`),
{
const countersQuery = useApiQuery('address_counters', {
pathParams: { id: router.query.id?.toString() },
queryOptions: {
enabled: Boolean(router.query.id) && Boolean(addressQuery.data),
},
);
const tokenBalancesQuery = useQuery<unknown, unknown, Array<AddressTokenBalance>>(
[ QueryKeys.addressTokenBalances, router.query.id ],
async() => await fetch(`/node-api/addresses/${ router.query.id }/token-balances`),
{
});
const tokenBalancesQuery = useApiQuery('address_token_balances', {
pathParams: { id: router.query.id?.toString() },
queryOptions: {
enabled: Boolean(router.query.id) && Boolean(addressQuery.data),
},
);
});
if (countersQuery.isLoading || addressQuery.isLoading || tokenBalancesQuery.isLoading) {
return <AddressDetailsSkeleton/>;
......
......@@ -3,9 +3,9 @@ import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { Address } from 'types/api/address';
import { QueryKeys } from 'types/client/queries';
import appConfig from 'configs/app/config';
import { getResourceKey } from 'lib/api/useApiQuery';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import CurrencyValue from 'ui/shared/CurrencyValue';
......@@ -26,7 +26,8 @@ const AddressBalance = ({ data }: Props) => {
}
setLastBlockNumber(blockNumber);
queryClient.setQueryData([ QueryKeys.address, data.hash ], (prevData: Address | undefined) => {
const queryKey = getResourceKey('address', { pathParams: { id: data.hash } });
queryClient.setQueryData(queryKey, (prevData: Address | undefined) => {
if (!prevData) {
return;
}
......
......@@ -4,12 +4,11 @@ import { useRouter } from 'next/router';
import React from 'react';
import type { UserInfo } from 'types/api/account';
import { QueryKeys } from 'types/client/queries';
import starFilledIcon from 'icons/star_filled.svg';
import starOutlineIcon from 'icons/star_outline.svg';
import { resourceKey } from 'lib/api/resources';
import useApiQuery from 'lib/api/useApiQuery';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import useLoginUrl from 'lib/hooks/useLoginUrl';
import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing';
import WatchlistAddModal from 'ui/watchlist/AddressModal/AddressModal';
......@@ -42,7 +41,8 @@ const AddressFavoriteButton = ({ className, hash, isAdded }: Props) => {
}, [ addModalProps, deleteModalProps, isAdded, isAuth, loginUrl ]);
const handleAddOrDeleteSuccess = React.useCallback(async() => {
await queryClient.refetchQueries({ queryKey: [ QueryKeys.address, router.query.id ] });
const queryKey = getResourceKey('address', { pathParams: { id: router.query.id?.toString() } });
await queryClient.refetchQueries({ queryKey });
addModalProps.onClose();
}, [ addModalProps, queryClient, router.query.id ]);
......
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
import { QueryKeys } from 'types/client/queries';
import useFetch from 'lib/hooks/useFetch';
import useApiQuery from 'lib/api/useApiQuery';
const MockAddressPage = ({ children }: { children: JSX.Element }): JSX.Element => {
const router = useRouter();
const fetch = useFetch();
const { data } = useQuery(
[ QueryKeys.address, router.query.id ],
async() => await fetch(`/node-api/addresses/${ router.query.id }`),
{
enabled: Boolean(router.query.id),
},
);
const { data } = useApiQuery('address', {
pathParams: { id: router.query.id?.toString() },
queryOptions: { enabled: Boolean(router.query.id) },
});
if (!data) {
return <div/>;
......
import { Box, Flex, Icon, IconButton, Skeleton, Tooltip } from '@chakra-ui/react';
import { useQuery, useQueryClient, useIsFetching } from '@tanstack/react-query';
import { useQueryClient, useIsFetching } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { Address, AddressTokenBalance } from 'types/api/address';
import { QueryKeys } from 'types/client/queries';
import type { Address } from 'types/api/address';
import walletIcon from 'icons/wallet.svg';
import useFetch from 'lib/hooks/useFetch';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
......@@ -18,21 +17,20 @@ import TokenSelectMobile from './TokenSelectMobile';
const TokenSelect = () => {
const router = useRouter();
const fetch = useFetch();
const isMobile = useIsMobile();
const queryClient = useQueryClient();
const [ blockNumber, setBlockNumber ] = React.useState<number>();
const addressQueryData = queryClient.getQueryData<Address>([ QueryKeys.address, router.query.id ]);
const addressResourceKey = getResourceKey('address', { pathParams: { id: router.query.id?.toString() } });
const { data, isError, isLoading, refetch } = useQuery<unknown, unknown, Array<AddressTokenBalance>>(
[ QueryKeys.addressTokenBalances, addressQueryData?.hash ],
async() => await fetch(`/node-api/addresses/${ addressQueryData?.hash }/token-balances`),
{
enabled: Boolean(addressQueryData),
},
);
const balancesIsFetching = useIsFetching({ queryKey: [ QueryKeys.addressTokenBalances, addressQueryData?.hash ] });
const addressQueryData = queryClient.getQueryData<Address>(addressResourceKey);
const { data, isError, isLoading, refetch } = useApiQuery('address_token_balances', {
pathParams: { id: addressQueryData?.hash },
queryOptions: { enabled: Boolean(addressQueryData) },
});
const balancesResourceKey = getResourceKey('address_token_balances', { pathParams: { id: addressQueryData?.hash } });
const balancesIsFetching = useIsFetching({ queryKey: balancesResourceKey });
const handleTokenBalanceMessage: SocketMessage.AddressTokenBalance['handler'] = React.useCallback((payload) => {
if (payload.block_number !== blockNumber) {
......
import { Grid, GridItem, Text, Icon, Link, Box, Tooltip } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize';
import NextLink from 'next/link';
......@@ -7,17 +6,13 @@ import { useRouter } from 'next/router';
import React from 'react';
import { scroller, Element } from 'react-scroll';
import type { Block } from 'types/api/block';
import { QueryKeys } from 'types/client/queries';
import appConfig from 'configs/app/config';
import clockIcon from 'icons/clock.svg';
import flameIcon from 'icons/flame.svg';
import type { ResourceError } from 'lib/api/resources';
import useApiQuery from 'lib/api/useApiQuery';
import getBlockReward from 'lib/block/getBlockReward';
import { WEI, WEI_IN_GWEI, ZERO } from 'lib/consts';
import dayjs from 'lib/date/dayjs';
import useFetch from 'lib/hooks/useFetch';
import { space } from 'lib/html-entities';
import link from 'lib/link/link';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
......@@ -35,15 +30,11 @@ import Utilization from 'ui/shared/Utilization/Utilization';
const BlockDetails = () => {
const [ isExpanded, setIsExpanded ] = React.useState(false);
const router = useRouter();
const fetch = useFetch();
const { data, isLoading, isError, error } = useQuery<unknown, ResourceError<{ status: number }>, Block>(
[ QueryKeys.block, router.query.id ],
async() => await fetch(`/node-api/blocks/${ router.query.id }`),
{
enabled: Boolean(router.query.id),
},
);
const { data, isLoading, isError, error } = useApiQuery<'block', { status: number }>('block', {
pathParams: { id: router.query.id?.toString() },
queryOptions: { enabled: Boolean(router.query.id) },
});
const handleCutClick = React.useCallback(() => {
setIsExpanded((flag) => !flag);
......
import { Flex, Tag } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
import type { Address } from 'types/api/address';
import { QueryKeys } from 'types/client/queries';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import useFetch from 'lib/hooks/useFetch';
import useApiQuery from 'lib/api/useApiQuery';
import AddressBlocksValidated from 'ui/address/AddressBlocksValidated';
import AddressCoinBalance from 'ui/address/AddressCoinBalance';
import AddressDetails from 'ui/address/AddressDetails';
......@@ -20,15 +17,11 @@ import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
const AddressPageContent = () => {
const router = useRouter();
const fetch = useFetch();
const addressQuery = useQuery<unknown, unknown, Address>(
[ QueryKeys.address, router.query.id ],
async() => await fetch(`/node-api/addresses/${ router.query.id }`),
{
enabled: Boolean(router.query.id),
},
);
const addressQuery = useApiQuery('address', {
pathParams: { id: router.query.id?.toString() },
queryOptions: { enabled: Boolean(router.query.id) },
});
const tags = [
...(addressQuery.data?.private_tags || []),
......
import { Flex, Link, Icon, Tag, Tooltip } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import eastArrowIcon from 'icons/arrows/east.svg';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import useFetch from 'lib/hooks/useFetch';
import isBrowser from 'lib/isBrowser';
import networkExplorers from 'lib/networks/networkExplorers';
import AdBanner from 'ui/shared/ad/AdBanner';
......@@ -35,7 +33,6 @@ const TABS: Array<RoutedTab> = [
const TransactionPageContent = () => {
const router = useRouter();
const fetch = useFetch();
const appProps = useAppContext();
const isInBrowser = isBrowser();
......@@ -43,13 +40,10 @@ const TransactionPageContent = () => {
const hasGoBackLink = referrer && referrer.includes('/txs');
const { data } = useQuery<unknown, unknown, Transaction>(
[ 'tx', router.query.id ],
async() => await fetch(`/node-api/transactions/${ router.query.id }`),
{
enabled: Boolean(router.query.id),
},
);
const { data } = useApiQuery('tx', {
pathParams: { id: router.query.id?.toString() },
queryOptions: { enabled: Boolean(router.query.id) },
});
const explorersLinks = networkExplorers
.filter((explorer) => explorer.paths.tx)
......
......@@ -53,6 +53,7 @@ const TokenTransfer = <Resource extends 'tx_token_transfers' | 'address_token_tr
baseAddress,
showTxInfo = true,
enableTimeIncrement,
pathParams,
}: Props<Resource>) => {
const router = useRouter();
const [ filters, setFilters ] = React.useState<AddressTokenTransferFilters & TokenTransferFilters>(
......@@ -61,6 +62,7 @@ const TokenTransfer = <Resource extends 'tx_token_transfers' | 'address_token_tr
const { isError, isLoading, data, pagination, onFilterChange, isPaginationVisible } = useQueryWithPages({
resourceName,
pathParams,
options: { enabled: !isDisabled },
filters: filters as PaginationFiltersX<Resource>,
scroll: { elem: SCROLL_ELEM, offset: SCROLL_OFFSET },
......
import { Flex, Textarea, Skeleton } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
import type { RawTracesResponse } from 'types/api/rawTrace';
import { QueryKeys } from 'types/client/queries';
import useApiQuery from 'lib/api/useApiQuery';
import { SECOND } from 'lib/consts';
import useFetch from 'lib/hooks/useFetch';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import TxPendingAlert from 'ui/tx/TxPendingAlert';
......@@ -16,16 +12,14 @@ import useFetchTxInfo from 'ui/tx/useFetchTxInfo';
const TxRawTrace = () => {
const router = useRouter();
const fetch = useFetch();
const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND });
const { data, isLoading, isError } = useQuery<unknown, unknown, RawTracesResponse>(
[ QueryKeys.txRawTrace, router.query.id ],
async() => await fetch(`/node-api/transactions/${ router.query.id }/raw-trace`),
{
const { data, isLoading, isError } = useApiQuery('tx_raw_trace', {
pathParams: { id: router.query.id?.toString() },
queryOptions: {
enabled: Boolean(router.query.id) && Boolean(txInfo.data?.status),
},
);
});
if (!txInfo.isLoading && !txInfo.isError && !txInfo.data.status) {
return txInfo.socketStatus ? <TxSocketAlert status={ txInfo.socketStatus }/> : <TxPendingAlert/>;
......
import type { UseQueryResult } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { Transaction } from 'types/api/transaction';
import { QueryKeys } from 'types/client/queries';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import delay from 'lib/delay';
import useFetch from 'lib/hooks/useFetch';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
......@@ -23,23 +22,23 @@ type ReturnType = UseQueryResult<Transaction, unknown> & {
export default function useFetchTxInfo({ onTxStatusUpdate, updateDelay }: Params | undefined = {}): ReturnType {
const router = useRouter();
const fetch = useFetch();
const queryClient = useQueryClient();
const [ socketStatus, setSocketStatus ] = React.useState<'close' | 'error'>();
const queryResult = useQuery<unknown, unknown, Transaction>(
[ QueryKeys.tx, router.query.id ],
async() => await fetch(`/node-api/transactions/${ router.query.id }`),
{
const queryResult = useApiQuery('tx', {
pathParams: { id: router.query.id?.toString() },
queryOptions: {
enabled: Boolean(router.query.id),
refetchOnMount: false,
},
);
});
const { data, isError, isLoading } = queryResult;
const handleStatusUpdateMessage: SocketMessage.TxStatusUpdate['handler'] = React.useCallback(async() => {
updateDelay && await delay(updateDelay);
queryClient.invalidateQueries({ queryKey: [ QueryKeys.tx, router.query.id ] });
queryClient.invalidateQueries({
queryKey: getResourceKey('tx', { pathParams: { id: router.query.id?.toString() } }),
});
onTxStatusUpdate?.();
}, [ onTxStatusUpdate, queryClient, router.query.id, updateDelay ]);
......
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