Skip to content

Commit

Permalink
feat: support rendering asset details (#41)
Browse files Browse the repository at this point in the history
Handles ARC3, 16, 19 and 69
---------

Co-authored-by: Neil Campbell <[email protected]>
  • Loading branch information
PatrickDinh and neilcampbell authored May 14, 2024
1 parent 825d6de commit 25296ea
Show file tree
Hide file tree
Showing 44 changed files with 1,858 additions and 159 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"jotai": "^2.7.2",
"jotai-effect": "^0.6.0",
"lucide-react": "^0.356.0",
"multiformats": "^13.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.3",
Expand Down
4 changes: 3 additions & 1 deletion src/features/applications/data/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ export const getApplicationAtomBuilder = (store: JotaiStore, applicationId: Appl
try {
const applicationResult = await get(fetchApplicationResultAtom)
set(applicationResultsAtom, (prev) => {
return prev.set(applicationResult.id, applicationResult)
const next = new Map(prev)
next.set(applicationResult.id, applicationResult)
return next
})
} catch (e) {
// Ignore any errors as there is nothing to sync
Expand Down
103 changes: 67 additions & 36 deletions src/features/assets/components/asset-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,49 @@ import { isDefined } from '@/utils/is-defined'
import Decimal from 'decimal.js'
import { AccountLink } from '@/features/accounts/components/account-link'
import { ZERO_ADDRESS } from '@/features/common/constants'
import {
assetAddressesLabel,
assetClawbackLabel,
assetCreatorLabel,
assetDecimalsLabel,
assetDefaultFrozenLabel,
assetDetailsLabel,
assetFreezeLabel,
assetIdLabel,
assetJsonLabel,
assetManagerLabel,
assetNameLabel,
assetReserveLabel,
assetTotalSupplyLabel,
assetTransactionsLabel,
assetUnitNameLabel,
assetUrlLabel,
} from './labels'
import { Badge } from '@/features/common/components/badge'
import { AssetMedia } from './asset-media'
import { AssetTraits } from './asset-traits'
import { AssetMetadata } from './asset-metadata'

type Props = {
asset: Asset
}

export const assetIdLabel = 'Asset ID'
export const assetNameLabel = 'Name'
export const assetUnitNameLabel = 'Unit'
export const assetDecimalsLabel = 'Decimals'
export const assetTotalSupplyLabel = 'Total Supply'
export const assetMetadataHashLabel = 'Metadata Hash'
export const assetDefaultFrozenLabel = 'Default Frozen'
export const assetUrlLabel = 'URL'

export const assetAddressesLabel = 'Asset Addresses'
export const assetCreatorLabel = 'Creator'
export const assetManagerLabel = 'Manager'
export const assetReserveLabel = 'Reserve'
export const assetFreezeLabel = 'Freeze'
export const assetClawbackLabel = 'Clawback'

export const assetJsonLabel = 'Asset JSON'

export function AssetDetails({ asset }: Props) {
const assetItems = useMemo(
() => [
{
dt: assetIdLabel,
dd: asset.id,
dd: (
<>
<span>{asset.id}</span>
{asset.standardsUsed.map((s, i) => (
<Badge key={i} variant="outline">
{s}
</Badge>
))}
<Badge variant="outline">{asset.type}</Badge>
</>
),
},
asset.name
? {
Expand All @@ -51,7 +65,7 @@ export function AssetDetails({ asset }: Props) {
: undefined,
{
dt: assetTotalSupplyLabel,
dd: `${new Decimal(asset.total.toString()).div(new Decimal(10).pow(asset.decimals.toString()))} ${asset.unitName}`,
dd: `${new Decimal(asset.total.toString()).div(new Decimal(10).pow(asset.decimals.toString()))} ${asset.unitName ?? ''}`,
},
{
dt: assetDecimalsLabel,
Expand All @@ -72,7 +86,7 @@ export function AssetDetails({ asset }: Props) {
}
: undefined,
],
[asset.decimals, asset.defaultFrozen, asset.id, asset.name, asset.total, asset.unitName, asset.url]
[asset.id, asset.name, asset.standardsUsed, asset.type, asset.unitName, asset.total, asset.decimals, asset.defaultFrozen, asset.url]
).filter(isDefined)

const assetAddresses = useMemo(
Expand Down Expand Up @@ -111,25 +125,42 @@ export function AssetDetails({ asset }: Props) {

return (
<div className={cn('space-y-6 pt-7')}>
<Card className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<DescriptionList items={assetItems} />
</CardContent>
</Card>
<Card className={cn('p-4')}>
<Card aria-label={assetDetailsLabel} className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<h1 className={cn('text-2xl text-primary font-bold')}>{assetAddressesLabel}</h1>
<DescriptionList items={assetAddresses} />
</CardContent>
</Card>
<Card className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<h1 className={cn('text-2xl text-primary font-bold')}>{assetJsonLabel}</h1>
<div className={cn('border-solid border-2 border-border h-96 grid')}>
<pre className={cn('overflow-scroll p-4')}>{asset.json}</pre>
<div className={cn('grid grid-cols-[1fr_max-content]')}>
<DescriptionList items={assetItems} />
<AssetMedia asset={asset} />
</div>
</CardContent>
</Card>
{asset.id !== 0 && (
<>
<Card className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<h1 className={cn('text-2xl text-primary font-bold')}>{assetAddressesLabel}</h1>
<DescriptionList items={assetAddresses} />
</CardContent>
</Card>

<AssetMetadata metadata={asset.metadata} />
<AssetTraits traits={asset.traits} />
<Card className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<h1 className={cn('text-2xl text-primary font-bold')}>{assetJsonLabel}</h1>
<div className={cn('border-solid border-2 border-border h-96 grid')}>
<pre className={cn('overflow-scroll p-4')}>{asset.json}</pre>
</div>
</CardContent>
</Card>

<Card className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<h1 className={cn('text-2xl text-primary font-bold')}>{assetTransactionsLabel}</h1>
<div className={cn('border-solid border-2 grid p-4')}>{/* <TransactionsTable transactions={asset.transactions} /> */}</div>
</CardContent>
</Card>
</>
)}
</div>
)
}
19 changes: 19 additions & 0 deletions src/features/assets/components/asset-media.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { cn } from '@/features/common/utils'
import { Asset, AssetMediaType } from '../models'

type Props = {
asset: Asset
}

export function AssetMedia({ asset }: Props) {
return asset.media ? (
<div className={cn('pl-2 w-32 h-auto')}>
{asset.media.type === AssetMediaType.Image && <img src={asset.media.url} alt={asset.name} />}
{asset.media.type === AssetMediaType.Video && (
<video title={asset.name} autoPlay playsInline loop controls muted>
<source src={asset.media.url} type="video/mp4" />
</video>
)}
</div>
) : null
}
43 changes: 43 additions & 0 deletions src/features/assets/components/asset-metadata.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Card, CardContent } from '@/features/common/components/card'
import { cn } from '@/features/common/utils'
import { Asset } from '../models'
import { assetMetadataLabel } from './labels'
import { useMemo } from 'react'
import { DescriptionList } from '@/features/common/components/description-list'

type Props = {
metadata: Asset['metadata']
}

export function AssetMetadata({ metadata }: Props) {
const items = useMemo(() => {
return Object.entries(metadata ?? {}).map(([key, value]) => {
return {
dt: humanisePropertyKey(key),
dd: value,
}
})
}, [metadata])

if (items.length === 0) {
return undefined
}

return (
<Card className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<h1 className={cn('text-2xl text-primary font-bold')}>{assetMetadataLabel}</h1>
<DescriptionList items={items} />
</CardContent>
</Card>
)
}

const humanisePropertyKey = (key: string): string => {
const upperCaseFirstWord = (str: string): string => {
return str.charAt(0).toUpperCase() + str.slice(1)
}

const chunks = key.split('_')
return chunks.map(upperCaseFirstWord).join(' ')
}
34 changes: 34 additions & 0 deletions src/features/assets/components/asset-traits.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Card, CardContent } from '@/features/common/components/card'
import { cn } from '@/features/common/utils'
import { Asset } from '../models'
import { assetTraitsLabel } from './labels'
import { useMemo } from 'react'
import { DescriptionList } from '@/features/common/components/description-list'

type Props = {
traits: Asset['traits']
}

export function AssetTraits({ traits }: Props) {
const items = useMemo(() => {
return Object.entries(traits ?? {}).map(([key, value]) => {
return {
dt: key,
dd: value,
}
})
}, [traits])

if (items.length === 0) {
return undefined
}

return (
<Card className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<h1 className={cn('text-2xl text-primary font-bold')}>{assetTraitsLabel}</h1>
<DescriptionList items={items} />
</CardContent>
</Card>
)
}
25 changes: 25 additions & 0 deletions src/features/assets/components/labels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const assetDetailsLabel = 'Asset Details'
export const assetIdLabel = 'Asset ID'
export const assetNameLabel = 'Name'
export const assetDescriptionLabel = 'Description'
export const assetUnitNameLabel = 'Unit'
export const assetDecimalsLabel = 'Decimals'
export const assetTotalSupplyLabel = 'Total Supply'
export const assetMetadataHashLabel = 'Metadata Hash'
export const assetDefaultFrozenLabel = 'Default Frozen'
export const assetUrlLabel = 'URL'

export const assetAddressesLabel = 'Asset Addresses'
export const assetCreatorLabel = 'Creator'
export const assetManagerLabel = 'Manager'
export const assetReserveLabel = 'Reserve'
export const assetFreezeLabel = 'Freeze'
export const assetClawbackLabel = 'Clawback'

export const assetTraitsLabel = 'Asset Traits'

export const assetMetadataLabel = 'Asset Metadata'

export const assetJsonLabel = 'Asset JSON'

export const assetTransactionsLabel = 'Asset Transactions'
Loading

0 comments on commit 25296ea

Please sign in to comment.