Skip to content

Commit

Permalink
Merge branch 'main' into blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
neilcampbell committed Apr 16, 2024
2 parents 9f592d2 + 0dc6dbe commit 8251d62
Show file tree
Hide file tree
Showing 25 changed files with 885 additions and 98 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 @@ -37,6 +37,7 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"date-fns": "^3.5.0",
"decimal.js": "^10.4.3",
"jotai": "^2.7.2",
"jotai-effect": "^0.6.0",
"lucide-react": "^0.356.0",
Expand Down
54 changes: 54 additions & 0 deletions src/features/assets/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { atom, useAtomValue, useStore } from 'jotai'
import { useMemo } from 'react'
import { AssetLookupResult, AssetResult } from '@algorandfoundation/algokit-utils/types/indexer'
import { atomEffect } from 'jotai-effect'
import { loadable } from 'jotai/utils'
import { indexer } from '../common/data'

// TODO: Size should be capped at some limit, so memory usage doesn't grow indefinitely
export const assetsAtom = atom<AssetResult[]>([])

export const useAssetAtom = (assetIndex: number) => {
const store = useStore()

return useMemo(() => {
const syncEffect = atomEffect((get, set) => {
;(async () => {
try {
const asset = await get(assetAtom)
set(assetsAtom, (prev) => {
return prev.concat(asset)
})
} catch (e) {
// Ignore any errors as there is nothing to sync
}
})()
})
const assetAtom = atom((get) => {
// store.get prevents the atom from being subscribed to changes in assetsAtom
const assets = store.get(assetsAtom)
const asset = assets.find((a) => a.index === assetIndex)
if (asset) {
return asset
}

get(syncEffect)

return indexer
.lookupAssetByID(assetIndex)
.do()
.then((result) => {
return (result as AssetLookupResult).asset
})
})
return assetAtom
}, [store, assetIndex])
}

export const useLoadableAsset = (assetId: number) => {
return useAtomValue(
// Unfortunately we can't leverage Suspense here, as react doesn't support async useMemo inside the Suspense component
// https://github.com/facebook/react/issues/20877
loadable(useAssetAtom(assetId))
)
}
12 changes: 12 additions & 0 deletions src/features/assets/mappers/asset-mappers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { AssetResult } from '@algorandfoundation/algokit-utils/types/indexer'
import { AssetModel } from '../models'

export const asAsset = (assetResult: AssetResult): AssetModel => {
return {
id: assetResult.index,
name: assetResult.params.name,
total: assetResult.params.total,
decimals: assetResult.params.decimals,
unitName: assetResult.params['unit-name'],
}
}
7 changes: 7 additions & 0 deletions src/features/assets/models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type AssetModel = {
id: number
name?: string
total: number | bigint
decimals: number | bigint
unitName?: string
}
20 changes: 20 additions & 0 deletions src/features/common/components/display-asset-amount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AssetModel } from '@/features/assets/models'
import Decimal from 'decimal.js'

type Props = {
amount: number | bigint
asset: AssetModel
}

export const DisplayAssetAmount = ({ amount, asset }: Props) => {
// asset decimals value must be from 0 to 19 so it is safe to use .toString() here
const decimals = asset.decimals.toString()
// the amount is uint64, should be safe to be .toString()
const amountAsString = amount.toString()

return (
<div>
{new Decimal(amountAsString).div(new Decimal(10).pow(decimals)).toString()} {asset.unitName ?? ''}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<div>
<div
class="relative grid"
style="grid-template-columns: minmax(128px, 128px) repeat(3, 128px); grid-template-rows: repeat(2, 40px);"
>
<div />
<div
class="p-2 flex justify-center"
>
<h1
class="text-l font-semibold"
>
6MO6...HSJM
</h1>
</div>
<div
class="p-2 flex justify-center"
>
<h1
class="text-l font-semibold"
>
OCD5...4IFA
</h1>
</div>
<div />
<div
class="absolute right-0 -z-10"
style="top: 40px;"
>
<div>
<div
class="p-0"
/>
<div
class="p-0"
style="height: 40px; width: 256px;"
>
<div
class="grid h-full"
style="grid-template-columns: repeat(2, minmax(0, 1fr)); height: 40px;"
>
<div
class="flex justify-center"
>
<div
class="border-muted h-full border-dashed"
style="border-left-width: 2px;"
/>
</div>
<div
class="flex justify-center"
>
<div
class="border-muted h-full border-dashed"
style="border-left-width: 2px;"
/>
</div>
</div>
</div>
</div>
</div>
<div
class="p-0 relative pr-8"
>
<div
class="relative h-full p-0 flex items-center px-0"
style="margin-left: 0px;"
>
<div
class="inline"
style="margin-left: 16px;"
>
JBDSQEI...
</div>
</div>
</div>
<div
class="flex items-center justify-center"
data-state="closed"
style="grid-column-start: 2; grid-column-end: 4; color: rgb(126 200 191);"
>
<svg
height="20"
viewBox="0 0 21 21"
width="20"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<g
transform="matrix(1 0 0 1 -153 -143 )"
>
<path
d="M 163.5 143 C 169.38 143 174 147.62 174 153.5 C 174 159.38 169.38 164 163.5 164 C 157.62 164 153 159.38 153 153.5 C 153 147.62 157.62 143 163.5 143 Z "
fill="currentColor"
fill-rule="nonzero"
stroke="none"
/>
</g>
</svg>
<div
class="relative"
style="width: calc(50.00% - 20px); height: 20px;"
>
<div
class="h-1/2"
style="border-bottom-width: 2px;"
/>
<svg
class="absolute top-0 right-0"
height="19px"
preserveAspectRatio="xMinYMid meet"
viewBox="340 139 1 13"
width="11px"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<path
d="M 340.3 151.3 L 347 145.3 L 340.3 139.3 L 342.6 145.3 L 340.3 151.3 Z"
fill="currentColor"
fill-rule="nonzero"
stroke="none"
/>
</svg>
</div>
<div
class="absolute z-20 bg-card p-2 text-foreground w-20 text-xs"
>
Transfer
<div>
0.3

AKTA
</div>
</div>
<svg
height="20"
viewBox="0 0 21 21"
width="20"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<g
transform="matrix(1 0 0 1 -153 -143 )"
>
<path
d="M 163.5 143 C 169.38 143 174 147.62 174 153.5 C 174 159.38 169.38 164 163.5 164 C 157.62 164 153 159.38 153 153.5 C 153 147.62 157.62 143 163.5 143 Z "
fill="currentColor"
fill-rule="nonzero"
stroke="none"
/>
</g>
</svg>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { cn } from '@/features/common/utils'
import { useMemo } from 'react'
import { AssetTransferTransactionModel } from '../models'
import { DescriptionList } from '@/features/common/components/description-list'
import { transactionSenderLabel, transactionReceiverLabel, transactionAmountLabel } from './transaction-view-table'
import { DisplayAssetAmount } from '@/features/common/components/display-asset-amount'

type Props = {
transaction: AssetTransferTransactionModel
}

export const assetLabel = 'Asset'
export const transactionCloseRemainderToLabel = 'Close Remainder To'
export const transactionCloseRemainderAmountLabel = 'Close Remainder Amount'

export function AssetTransferTransactionInfo({ transaction }: Props) {
const items = useMemo(
() => [
{
dt: transactionSenderLabel,
dd: (
<a href="#" className={cn('text-primary underline')}>
{transaction.sender}
</a>
),
},
{
dt: transactionReceiverLabel,
dd: (
<a href="#" className={cn('text-primary underline')}>
{transaction.receiver}
</a>
),
},
{
dt: assetLabel,
dd: (
<a href="#" className={cn('text-primary underline')}>
{transaction.asset.id} {`${transaction.asset.name ? `(${transaction.asset.name})` : ''}`}
</a>
),
},
{
dt: transactionAmountLabel,
dd: <DisplayAssetAmount amount={transaction.amount} asset={transaction.asset} />,
},
...(transaction.closeRemainder
? [
{
dt: transactionCloseRemainderToLabel,
dd: (
<a href="#" className={cn('text-primary underline')}>
{transaction.closeRemainder.to}
</a>
),
},
{
dt: transactionCloseRemainderAmountLabel,
dd: <DisplayAssetAmount amount={transaction.closeRemainder.amount} asset={transaction.asset} />,
},
]
: []),
],
[transaction.sender, transaction.receiver, transaction.asset, transaction.amount, transaction.closeRemainder]
)

return (
<div className={cn('space-y-2')}>
<div className={cn('flex items-center justify-between')}>
<h1 className={cn('text-2xl text-primary font-bold')}>Asset Transfer</h1>
</div>
<DescriptionList items={items} />
</div>
)
}
Loading

0 comments on commit 8251d62

Please sign in to comment.