Commit ced55838 authored by tom's avatar tom

name, description and attributes

parent 1bd8a059
import _capitalize from 'lodash/capitalize';
import type { Metadata } from 'types/client/token';
import dayjs from 'lib/date/dayjs';
function formatValue(value: string | number, display: string | undefined): string {
// https://docs.opensea.io/docs/metadata-standards#attributes
switch (display) {
case 'boost_number': {
return `+${ value } boost`;
}
case 'boost_percentage': {
return `${ value }% boost`;
}
case 'date': {
return dayjs(value).format('YYYY-MM-DD');
}
default: {
return String(value);
}
}
}
export default function attributesParser(attributes: Array<unknown>): Metadata['attributes'] {
return attributes
.map((item) => {
if (typeof item !== 'object' || !item) {
return;
}
const value = 'value' in item && (typeof item.value === 'string' || typeof item.value === 'number') ? item.value : undefined;
const trait = 'trait_type' in item && typeof item.trait_type === 'string' ? item.trait_type : undefined;
const display = 'display_type' in item && typeof item.display_type === 'string' ? item.display_type : undefined;
if (!value) {
return;
}
return {
value: formatValue(value, display),
trait_type: _capitalize(trait || 'property'),
};
})
.filter(Boolean);
}
import type { TokenInstance } from 'types/api/token';
import type { Metadata } from 'types/client/token';
import attributesParser from './metadata/attributesParser';
export default function parseMetadata(raw: TokenInstance['metadata'] | undefined): Metadata | undefined {
if (!raw) {
return;
}
const parsed: Metadata = {};
if ('name' in raw && typeof raw.name === 'string') {
parsed.name = raw.name;
}
if ('description' in raw && typeof raw.description === 'string') {
parsed.description = raw.description;
}
if ('attributes' in raw && Array.isArray(raw.attributes)) {
parsed.attributes = attributesParser(raw.attributes);
}
if (Object.keys(parsed).length === 0) {
return;
}
return parsed;
}
export interface Metadata {
name?: string;
description?: string;
attributes?: Array<MetadataAttributes>;
}
export interface MetadataAttributes {
value: string;
trait_type: string;
}
......@@ -8,6 +8,7 @@ import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import parseMetadata from 'lib/token/parseMetadata';
import TextAd from 'ui/shared/ad/TextAd';
import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo';
import PageTitle from 'ui/shared/Page/PageTitle';
......@@ -49,6 +50,10 @@ const TokenInstanceContent = () => {
},
});
const metadata = React.useMemo(() => {
return parseMetadata(tokenInstanceQuery.data?.metadata);
}, [ tokenInstanceQuery.data?.metadata ]);
const tabs: Array<RoutedTab> = [
{ id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery } tokenId={ id }/> },
// there is no api for this tab yet
......@@ -86,7 +91,7 @@ const TokenInstanceContent = () => {
<AddressHeadingInfo address={ address } token={ tokenInstanceQuery.data.token }/>
<TokenInstanceDetails data={ tokenInstanceQuery.data } scrollRef={ scrollRef }/>
<TokenInstanceDetails data={ tokenInstanceQuery.data } metadata={ metadata } scrollRef={ scrollRef }/>
{ /* should stay before tabs to scroll up with pagination */ }
<Box ref={ scrollRef }></Box>
......
import { Box, Flex, Grid } from '@chakra-ui/react';
import { Box, Flex, Grid, GridItem, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { TokenInstance } from 'types/api/token';
import type { Metadata } from 'types/client/token';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
......@@ -19,9 +20,10 @@ import TokenInstanceTransfersCount from './details/TokenInstanceTransfersCount';
interface Props {
data: TokenInstance;
scrollRef?: React.RefObject<HTMLDivElement>;
metadata?: Metadata;
}
const TokenInstanceDetails = ({ data, scrollRef }: Props) => {
const TokenInstanceDetails = ({ data, scrollRef, metadata }: Props) => {
const handleCounterItemClick = React.useCallback(() => {
window.setTimeout(() => {
// cannot do scroll instantly, have to wait a little
......@@ -29,53 +31,109 @@ const TokenInstanceDetails = ({ data, scrollRef }: Props) => {
}, 500);
}, [ scrollRef ]);
const hasMetadata = metadata && Boolean((metadata.name || metadata.description || metadata.attributes));
const attributeBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return (
<Flex alignItems="flex-start" mt={ 8 } flexDir={{ base: 'column-reverse', lg: 'row' }} columnGap={ 6 } rowGap={ 6 }>
<Grid
flexGrow={ 1 }
columnGap={ 8 }
rowGap={{ base: 1, lg: 3 }}
templateColumns={{ base: 'minmax(0, 1fr)', lg: '200px minmax(0, 1fr)' }} overflow="hidden"
>
<DetailsInfoItem
title="Token"
hint="Token name"
>
<TokenSnippet hash={ data.token.address } name={ data.token.name }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Owner"
hint="Current owner of this token instance"
<>
<Flex alignItems="flex-start" mt={ 8 } flexDir={{ base: 'column-reverse', lg: 'row' }} columnGap={ 6 } rowGap={ 6 }>
<Grid
flexGrow={ 1 }
columnGap={ 8 }
rowGap={{ base: 1, lg: 3 }}
templateColumns={{ base: 'minmax(0, 1fr)', lg: '200px minmax(0, 1fr)' }}
overflow="hidden"
>
<Address>
<AddressIcon address={ data.owner }/>
<AddressLink type="address" hash={ data.owner.hash } ml={ 2 }/>
<CopyToClipboard text={ data.owner.hash }/>
</Address>
</DetailsInfoItem>
<TokenInstanceCreatorAddress hash={ data.token.address }/>
<DetailsInfoItem
title="Token ID"
hint="This token instance unique token ID"
<DetailsInfoItem
title="Token"
hint="Token name"
>
<TokenSnippet hash={ data.token.address } name={ data.token.name }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Owner"
hint="Current owner of this token instance"
>
<Address>
<AddressIcon address={ data.owner }/>
<AddressLink type="address" hash={ data.owner.hash } ml={ 2 }/>
<CopyToClipboard text={ data.owner.hash }/>
</Address>
</DetailsInfoItem>
<TokenInstanceCreatorAddress hash={ data.token.address }/>
<DetailsInfoItem
title="Token ID"
hint="This token instance unique token ID"
>
<Flex alignItems="center" overflow="hidden">
<Box overflow="hidden" display="inline-block" w="100%">
<HashStringShortenDynamic hash={ data.id }/>
</Box>
<CopyToClipboard text={ data.id } ml={ 1 }/>
</Flex>
</DetailsInfoItem>
<TokenInstanceTransfersCount hash={ data.token.address } id={ data.id } onClick={ handleCounterItemClick }/>
<DetailsSponsoredItem/>
</Grid>
<NftMedia
imageUrl={ data.image_url }
animationUrl={ data.animation_url }
w="250px"
flexShrink={ 0 }
alignSelf={{ base: 'center', lg: 'flex-start' }}
/>
</Flex>
{ hasMetadata && (
<Grid
mt={ 8 }
columnGap={ 8 }
rowGap={{ base: 1, lg: 3 }}
templateColumns={{ base: 'minmax(0, 1fr)', lg: '200px minmax(0, 1fr)' }}
overflow="hidden"
>
<Flex alignItems="center" overflow="hidden">
<Box overflow="hidden" display="inline-block" w="100%">
<HashStringShortenDynamic hash={ data.id }/>
</Box>
<CopyToClipboard text={ data.id } ml={ 1 }/>
</Flex>
</DetailsInfoItem>
<TokenInstanceTransfersCount hash={ data.token.address } id={ data.id } onClick={ handleCounterItemClick }/>
<DetailsSponsoredItem/>
</Grid>
<NftMedia
imageUrl={ data.image_url }
animationUrl={ data.animation_url }
w="250px"
flexShrink={ 0 }
alignSelf={{ base: 'center', lg: 'flex-start' }}
/>
</Flex>
{ metadata?.name && (
<DetailsInfoItem
title="Name"
hint="NFT name"
whiteSpace="normal"
>
{ metadata.name }
</DetailsInfoItem>
) }
{ metadata?.description && (
<DetailsInfoItem
title="Description"
hint="NFT description"
whiteSpace="normal"
>
{ metadata.description }
</DetailsInfoItem>
) }
{ metadata?.attributes && (
<DetailsInfoItem
title="Attributes"
hint="NFT attributes"
whiteSpace="normal"
>
<Grid gap={ 2 } templateColumns="repeat(auto-fit, minmax(160px, 1fr))" w="100%">
{ metadata.attributes.map((attribute, index) => (
<GridItem
key={ index }
bgColor={ attributeBgColor }
borderRadius="md"
px={ 4 }
py={ 2 }
>
<Box fontSize="xs" color="text_secondary" fontWeight={ 500 }>{ attribute.trait_type }</Box>
<Box fontSize="sm">{ attribute.value }</Box>
</GridItem>
)) }
</Grid>
</DetailsInfoItem>
) }
</Grid>
) }
</>
);
};
......
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