Skip to content

Commit 25296ea

Browse files
feat: support rendering asset details (#41)
Handles ARC3, 16, 19 and 69 --------- Co-authored-by: Neil Campbell <[email protected]>
1 parent 825d6de commit 25296ea

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1858
-159
lines changed

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"jotai": "^2.7.2",
4545
"jotai-effect": "^0.6.0",
4646
"lucide-react": "^0.356.0",
47+
"multiformats": "^13.1.0",
4748
"react": "^18.2.0",
4849
"react-dom": "^18.2.0",
4950
"react-router-dom": "^6.22.3",

src/features/applications/data/application.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ export const getApplicationAtomBuilder = (store: JotaiStore, applicationId: Appl
2929
try {
3030
const applicationResult = await get(fetchApplicationResultAtom)
3131
set(applicationResultsAtom, (prev) => {
32-
return prev.set(applicationResult.id, applicationResult)
32+
const next = new Map(prev)
33+
next.set(applicationResult.id, applicationResult)
34+
return next
3335
})
3436
} catch (e) {
3537
// Ignore any errors as there is nothing to sync

src/features/assets/components/asset-details.tsx

Lines changed: 67 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,49 @@ import { isDefined } from '@/utils/is-defined'
77
import Decimal from 'decimal.js'
88
import { AccountLink } from '@/features/accounts/components/account-link'
99
import { ZERO_ADDRESS } from '@/features/common/constants'
10+
import {
11+
assetAddressesLabel,
12+
assetClawbackLabel,
13+
assetCreatorLabel,
14+
assetDecimalsLabel,
15+
assetDefaultFrozenLabel,
16+
assetDetailsLabel,
17+
assetFreezeLabel,
18+
assetIdLabel,
19+
assetJsonLabel,
20+
assetManagerLabel,
21+
assetNameLabel,
22+
assetReserveLabel,
23+
assetTotalSupplyLabel,
24+
assetTransactionsLabel,
25+
assetUnitNameLabel,
26+
assetUrlLabel,
27+
} from './labels'
28+
import { Badge } from '@/features/common/components/badge'
29+
import { AssetMedia } from './asset-media'
30+
import { AssetTraits } from './asset-traits'
31+
import { AssetMetadata } from './asset-metadata'
1032

1133
type Props = {
1234
asset: Asset
1335
}
1436

15-
export const assetIdLabel = 'Asset ID'
16-
export const assetNameLabel = 'Name'
17-
export const assetUnitNameLabel = 'Unit'
18-
export const assetDecimalsLabel = 'Decimals'
19-
export const assetTotalSupplyLabel = 'Total Supply'
20-
export const assetMetadataHashLabel = 'Metadata Hash'
21-
export const assetDefaultFrozenLabel = 'Default Frozen'
22-
export const assetUrlLabel = 'URL'
23-
24-
export const assetAddressesLabel = 'Asset Addresses'
25-
export const assetCreatorLabel = 'Creator'
26-
export const assetManagerLabel = 'Manager'
27-
export const assetReserveLabel = 'Reserve'
28-
export const assetFreezeLabel = 'Freeze'
29-
export const assetClawbackLabel = 'Clawback'
30-
31-
export const assetJsonLabel = 'Asset JSON'
32-
3337
export function AssetDetails({ asset }: Props) {
3438
const assetItems = useMemo(
3539
() => [
3640
{
3741
dt: assetIdLabel,
38-
dd: asset.id,
42+
dd: (
43+
<>
44+
<span>{asset.id}</span>
45+
{asset.standardsUsed.map((s, i) => (
46+
<Badge key={i} variant="outline">
47+
{s}
48+
</Badge>
49+
))}
50+
<Badge variant="outline">{asset.type}</Badge>
51+
</>
52+
),
3953
},
4054
asset.name
4155
? {
@@ -51,7 +65,7 @@ export function AssetDetails({ asset }: Props) {
5165
: undefined,
5266
{
5367
dt: assetTotalSupplyLabel,
54-
dd: `${new Decimal(asset.total.toString()).div(new Decimal(10).pow(asset.decimals.toString()))} ${asset.unitName}`,
68+
dd: `${new Decimal(asset.total.toString()).div(new Decimal(10).pow(asset.decimals.toString()))} ${asset.unitName ?? ''}`,
5569
},
5670
{
5771
dt: assetDecimalsLabel,
@@ -72,7 +86,7 @@ export function AssetDetails({ asset }: Props) {
7286
}
7387
: undefined,
7488
],
75-
[asset.decimals, asset.defaultFrozen, asset.id, asset.name, asset.total, asset.unitName, asset.url]
89+
[asset.id, asset.name, asset.standardsUsed, asset.type, asset.unitName, asset.total, asset.decimals, asset.defaultFrozen, asset.url]
7690
).filter(isDefined)
7791

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

112126
return (
113127
<div className={cn('space-y-6 pt-7')}>
114-
<Card className={cn('p-4')}>
115-
<CardContent className={cn('text-sm space-y-2')}>
116-
<DescriptionList items={assetItems} />
117-
</CardContent>
118-
</Card>
119-
<Card className={cn('p-4')}>
128+
<Card aria-label={assetDetailsLabel} className={cn('p-4')}>
120129
<CardContent className={cn('text-sm space-y-2')}>
121-
<h1 className={cn('text-2xl text-primary font-bold')}>{assetAddressesLabel}</h1>
122-
<DescriptionList items={assetAddresses} />
123-
</CardContent>
124-
</Card>
125-
<Card className={cn('p-4')}>
126-
<CardContent className={cn('text-sm space-y-2')}>
127-
<h1 className={cn('text-2xl text-primary font-bold')}>{assetJsonLabel}</h1>
128-
<div className={cn('border-solid border-2 border-border h-96 grid')}>
129-
<pre className={cn('overflow-scroll p-4')}>{asset.json}</pre>
130+
<div className={cn('grid grid-cols-[1fr_max-content]')}>
131+
<DescriptionList items={assetItems} />
132+
<AssetMedia asset={asset} />
130133
</div>
131134
</CardContent>
132135
</Card>
136+
{asset.id !== 0 && (
137+
<>
138+
<Card className={cn('p-4')}>
139+
<CardContent className={cn('text-sm space-y-2')}>
140+
<h1 className={cn('text-2xl text-primary font-bold')}>{assetAddressesLabel}</h1>
141+
<DescriptionList items={assetAddresses} />
142+
</CardContent>
143+
</Card>
144+
145+
<AssetMetadata metadata={asset.metadata} />
146+
<AssetTraits traits={asset.traits} />
147+
<Card className={cn('p-4')}>
148+
<CardContent className={cn('text-sm space-y-2')}>
149+
<h1 className={cn('text-2xl text-primary font-bold')}>{assetJsonLabel}</h1>
150+
<div className={cn('border-solid border-2 border-border h-96 grid')}>
151+
<pre className={cn('overflow-scroll p-4')}>{asset.json}</pre>
152+
</div>
153+
</CardContent>
154+
</Card>
155+
156+
<Card className={cn('p-4')}>
157+
<CardContent className={cn('text-sm space-y-2')}>
158+
<h1 className={cn('text-2xl text-primary font-bold')}>{assetTransactionsLabel}</h1>
159+
<div className={cn('border-solid border-2 grid p-4')}>{/* <TransactionsTable transactions={asset.transactions} /> */}</div>
160+
</CardContent>
161+
</Card>
162+
</>
163+
)}
133164
</div>
134165
)
135166
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { cn } from '@/features/common/utils'
2+
import { Asset, AssetMediaType } from '../models'
3+
4+
type Props = {
5+
asset: Asset
6+
}
7+
8+
export function AssetMedia({ asset }: Props) {
9+
return asset.media ? (
10+
<div className={cn('pl-2 w-32 h-auto')}>
11+
{asset.media.type === AssetMediaType.Image && <img src={asset.media.url} alt={asset.name} />}
12+
{asset.media.type === AssetMediaType.Video && (
13+
<video title={asset.name} autoPlay playsInline loop controls muted>
14+
<source src={asset.media.url} type="video/mp4" />
15+
</video>
16+
)}
17+
</div>
18+
) : null
19+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Card, CardContent } from '@/features/common/components/card'
2+
import { cn } from '@/features/common/utils'
3+
import { Asset } from '../models'
4+
import { assetMetadataLabel } from './labels'
5+
import { useMemo } from 'react'
6+
import { DescriptionList } from '@/features/common/components/description-list'
7+
8+
type Props = {
9+
metadata: Asset['metadata']
10+
}
11+
12+
export function AssetMetadata({ metadata }: Props) {
13+
const items = useMemo(() => {
14+
return Object.entries(metadata ?? {}).map(([key, value]) => {
15+
return {
16+
dt: humanisePropertyKey(key),
17+
dd: value,
18+
}
19+
})
20+
}, [metadata])
21+
22+
if (items.length === 0) {
23+
return undefined
24+
}
25+
26+
return (
27+
<Card className={cn('p-4')}>
28+
<CardContent className={cn('text-sm space-y-2')}>
29+
<h1 className={cn('text-2xl text-primary font-bold')}>{assetMetadataLabel}</h1>
30+
<DescriptionList items={items} />
31+
</CardContent>
32+
</Card>
33+
)
34+
}
35+
36+
const humanisePropertyKey = (key: string): string => {
37+
const upperCaseFirstWord = (str: string): string => {
38+
return str.charAt(0).toUpperCase() + str.slice(1)
39+
}
40+
41+
const chunks = key.split('_')
42+
return chunks.map(upperCaseFirstWord).join(' ')
43+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Card, CardContent } from '@/features/common/components/card'
2+
import { cn } from '@/features/common/utils'
3+
import { Asset } from '../models'
4+
import { assetTraitsLabel } from './labels'
5+
import { useMemo } from 'react'
6+
import { DescriptionList } from '@/features/common/components/description-list'
7+
8+
type Props = {
9+
traits: Asset['traits']
10+
}
11+
12+
export function AssetTraits({ traits }: Props) {
13+
const items = useMemo(() => {
14+
return Object.entries(traits ?? {}).map(([key, value]) => {
15+
return {
16+
dt: key,
17+
dd: value,
18+
}
19+
})
20+
}, [traits])
21+
22+
if (items.length === 0) {
23+
return undefined
24+
}
25+
26+
return (
27+
<Card className={cn('p-4')}>
28+
<CardContent className={cn('text-sm space-y-2')}>
29+
<h1 className={cn('text-2xl text-primary font-bold')}>{assetTraitsLabel}</h1>
30+
<DescriptionList items={items} />
31+
</CardContent>
32+
</Card>
33+
)
34+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export const assetDetailsLabel = 'Asset Details'
2+
export const assetIdLabel = 'Asset ID'
3+
export const assetNameLabel = 'Name'
4+
export const assetDescriptionLabel = 'Description'
5+
export const assetUnitNameLabel = 'Unit'
6+
export const assetDecimalsLabel = 'Decimals'
7+
export const assetTotalSupplyLabel = 'Total Supply'
8+
export const assetMetadataHashLabel = 'Metadata Hash'
9+
export const assetDefaultFrozenLabel = 'Default Frozen'
10+
export const assetUrlLabel = 'URL'
11+
12+
export const assetAddressesLabel = 'Asset Addresses'
13+
export const assetCreatorLabel = 'Creator'
14+
export const assetManagerLabel = 'Manager'
15+
export const assetReserveLabel = 'Reserve'
16+
export const assetFreezeLabel = 'Freeze'
17+
export const assetClawbackLabel = 'Clawback'
18+
19+
export const assetTraitsLabel = 'Asset Traits'
20+
21+
export const assetMetadataLabel = 'Asset Metadata'
22+
23+
export const assetJsonLabel = 'Asset JSON'
24+
25+
export const assetTransactionsLabel = 'Asset Transactions'

0 commit comments

Comments
 (0)