Commit 3ac5cf01 authored by tom's avatar tom

display social links and website

parent bcd6ad2b
......@@ -44,6 +44,7 @@ import type {
TokenInventoryResponse,
TokenInstance,
TokenInstanceTransfersCount,
TokenVerifiedInfo,
} from 'types/api/token';
import type { TokensResponse, TokensFilters, TokenInstanceTransferResponse } from 'types/api/tokens';
import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer';
......@@ -322,6 +323,12 @@ export const RESOURCES = {
path: '/api/v2/tokens/:hash',
pathParams: [ 'hash' as const ],
},
token_verified_info: {
path: '/api/v1/chains/:chainId/token-infos/:hash',
pathParams: [ 'chainId' as const, 'hash' as const ],
endpoint: appConfig.contractInfoApi.endpoint,
basePath: appConfig.contractInfoApi.basePath,
},
token_counters: {
path: '/api/v2/tokens/:hash/counters',
pathParams: [ 'hash' as const ],
......@@ -559,6 +566,7 @@ Q extends 'address_coin_balance_chart' ? AddressCoinBalanceHistoryChart :
Q extends 'address_logs' ? LogsResponseAddress :
Q extends 'address_tokens' ? AddressTokensResponse :
Q extends 'token' ? TokenInfo :
Q extends 'token_verified_info' ? TokenVerifiedInfo :
Q extends 'token_counters' ? TokenCounters :
Q extends 'token_transfers' ? TokenTransferResponse :
Q extends 'token_holders' ? TokenHolders :
......
import type { TokenInfoApplication } from './account';
import type { AddressParam } from './addressParams';
export type TokenType = 'ERC-20' | 'ERC-721' | 'ERC-1155';
......@@ -59,3 +60,5 @@ export interface TokenInventoryResponse {
export type TokenInventoryPagination = {
unique_token: number;
}
export type TokenVerifiedInfo = Omit<TokenInfoApplication, 'id' | 'status'>;
......@@ -4,6 +4,7 @@ import React, { useEffect } from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import appConfig from 'configs/app/config';
import iconSuccess from 'icons/status/success.svg';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
......@@ -90,6 +91,12 @@ const TokenPageContent = () => {
queryOptions: { enabled: Boolean(router.query.hash) },
});
const isVerifiedInfoEnabled = Boolean(appConfig.contractInfoApi.endpoint);
const verifiedInfoQuery = useApiQuery('token_verified_info', {
pathParams: { hash: hashString, chainId: appConfig.network.id },
queryOptions: { enabled: Boolean(tokenQuery.data) && isVerifiedInfoEnabled },
});
const contractTabs = useContractTabs(contractQuery.data);
const tabs: Array<RoutedTab> = [
......@@ -187,7 +194,7 @@ const TokenPageContent = () => {
</>
) }
<TokenContractInfo tokenQuery={ tokenQuery }/>
<TokenDetails tokenQuery={ tokenQuery }/>
<TokenDetails tokenQuery={ tokenQuery } verifiedInfoQuery={ verifiedInfoQuery } isVerifiedInfoEnabled={ isVerifiedInfoEnabled }/>
{ /* should stay before tabs to scroll up with pagination */ }
<Box ref={ scrollRef }></Box>
......
......@@ -4,7 +4,7 @@ import { useRouter } from 'next/router';
import React, { useCallback } from 'react';
import { scroller } from 'react-scroll';
import type { TokenInfo } from 'types/api/token';
import type { TokenInfo, TokenVerifiedInfo } from 'types/api/token';
import useApiQuery from 'lib/api/useApiQuery';
import getCurrencyValue from 'lib/getCurrencyValue';
......@@ -14,11 +14,15 @@ import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import DetailsSkeletonRow from 'ui/shared/skeletons/DetailsSkeletonRow';
import TokenDetailsVerifiedInfo from './TokenDetails/TokenDetailsVerifiedInfo';
interface Props {
tokenQuery: UseQueryResult<TokenInfo>;
verifiedInfoQuery: UseQueryResult<TokenVerifiedInfo>;
isVerifiedInfoEnabled: boolean;
}
const TokenDetails = ({ tokenQuery }: Props) => {
const TokenDetails = ({ tokenQuery, verifiedInfoQuery, isVerifiedInfoEnabled }: Props) => {
const router = useRouter();
const tokenCountersQuery = useApiQuery('token_counters', {
......@@ -56,7 +60,7 @@ const TokenDetails = ({ tokenQuery }: Props) => {
throw Error('Token fetch error', { cause: tokenQuery.error as unknown as Error });
}
if (tokenQuery.isLoading) {
if (tokenQuery.isLoading || (isVerifiedInfoEnabled && verifiedInfoQuery.isLoading)) {
return (
<Grid mt={ 10 } columnGap={ 8 } rowGap={{ base: 5, lg: 7 }} templateColumns={{ base: '1fr', lg: '210px 1fr' }} maxW="1000px">
<DetailsSkeletonRow w="10%"/>
......@@ -95,6 +99,14 @@ const TokenDetails = ({ tokenQuery }: Props) => {
rowGap={{ base: 1, lg: 3 }}
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden"
>
{ verifiedInfoQuery.data && (
<DetailsInfoItem
title="Links"
hint="Links"
>
<TokenDetailsVerifiedInfo data={ verifiedInfoQuery.data }/>
</DetailsInfoItem>
) }
{ exchangeRate && (
<DetailsInfoItem
title="Price"
......
import { Flex, Link, Icon } from '@chakra-ui/react';
import React from 'react';
import type { TokenVerifiedInfo } from 'types/api/token';
import githubIcon from 'icons/social/git.svg';
import placeholderIcon from 'icons/social/stats.svg';
import telegramIcon from 'icons/social/telega.svg';
import twitterIcon from 'icons/social/tweet.svg';
import LinkExternal from 'ui/shared/LinkExternal';
interface Props {
data: TokenVerifiedInfo;
}
interface SocialLink {
field: keyof TokenVerifiedInfo;
icon: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
hint: string;
}
const SOCIAL_LINKS: Array<SocialLink> = [
{ field: 'github', icon: githubIcon, hint: 'Github account' },
{ field: 'twitter', icon: twitterIcon, hint: 'Twitter account' },
{ field: 'telegram', icon: telegramIcon, hint: 'Telegram account' },
{ field: 'openSea', icon: placeholderIcon, hint: 'OpenSea page' },
{ field: 'linkedin', icon: placeholderIcon, hint: 'LinkedIn page' },
{ field: 'facebook', icon: placeholderIcon, hint: 'Facebook account' },
{ field: 'discord', icon: placeholderIcon, hint: 'Discord account' },
{ field: 'medium', icon: placeholderIcon, hint: 'Medium account' },
{ field: 'slack', icon: placeholderIcon, hint: 'Slack account' },
{ field: 'reddit', icon: placeholderIcon, hint: 'Reddit account' },
];
const TokenDetailsVerifiedInfo = ({ data }: Props) => {
const websiteName = (() => {
try {
const url = new URL(data.projectWebsite);
return url.host;
} catch (error) { }
})();
const socialLinks = SOCIAL_LINKS
.map((link) => ({ ...link, href: data[link.field] }))
.filter(({ href }) => href);
return (
<Flex alignItems={{ base: 'flex-start', lg: 'center' }} flexDir={{ base: 'column', lg: 'row' }} rowGap={ 2 } columnGap={ 6 }>
{ websiteName && <LinkExternal href={ data.projectWebsite }>{ websiteName }</LinkExternal> }
{ socialLinks.length > 0 && (
<Flex columnGap={ 2 }>
{ socialLinks.map((link) => (
<Link href={ link.href } key={ link.field } variant="secondary" boxSize={ 5 } aria-label={ link.hint } title={ link.hint } target="_blank">
<Icon as={ link.icon } boxSize={ 5 }/>
</Link>
)) }
</Flex>
) }
</Flex>
);
};
export default React.memo(TokenDetailsVerifiedInfo);
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