Skip to content

Commit 89069fc

Browse files
feat: Add account info (#43)
* feat: add account info UI --------- Co-authored-by: Neil Campbell <[email protected]>
1 parent e48655c commit 89069fc

File tree

17 files changed

+594
-4
lines changed

17 files changed

+594
-4
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { OverflowAutoTabsContent, Tabs, TabsList, TabsTrigger } from '@/features/common/components/tabs'
2+
import { cn } from '@/features/common/utils'
3+
import { useMemo } from 'react'
4+
5+
const accountVisualTransactionTabId = 'visual'
6+
const accountVisualAssetsTabId = 'table'
7+
const accountVisualCreatedAssetsTabId = 'created-assets'
8+
const accountVisualCreatedApplicationsTabId = 'created-applications'
9+
const accountVisualOptedApplicationsTabId = 'opted-applications'
10+
export const accountDetailsLabel = 'View account Details'
11+
export const accountVisualGraphTabLabel = 'Transactions'
12+
export const accountVisualAssetsTabLabel = 'Assets'
13+
export const accountVisualCreatedAssetsTabLabel = 'Created Assets'
14+
export const accountVisualCreatedApplicationsTabLabel = 'Created Applications'
15+
export const accountVisualOptedApplicationsTabLabel = 'Opted Applications'
16+
17+
export function AccountActivityTabs() {
18+
const tabs = useMemo(
19+
() => [
20+
{
21+
id: accountVisualTransactionTabId,
22+
label: accountDetailsLabel,
23+
children: '',
24+
},
25+
{
26+
id: accountVisualAssetsTabId,
27+
label: accountVisualAssetsTabLabel,
28+
children: '',
29+
},
30+
{
31+
id: accountVisualCreatedAssetsTabId,
32+
label: accountVisualCreatedAssetsTabLabel,
33+
children: '',
34+
},
35+
{
36+
id: accountVisualCreatedApplicationsTabId,
37+
label: accountVisualCreatedApplicationsTabLabel,
38+
children: '',
39+
},
40+
{
41+
id: accountVisualOptedApplicationsTabId,
42+
label: accountVisualOptedApplicationsTabLabel,
43+
children: '',
44+
},
45+
],
46+
[]
47+
)
48+
return (
49+
<Tabs defaultValue={accountVisualTransactionTabId}>
50+
<TabsList aria-label={accountDetailsLabel}>
51+
{tabs.map((tab) => (
52+
<TabsTrigger key={tab.id} className={cn('data-[state=active]:border-primary data-[state=active]:border-b-2 w-44')} value={tab.id}>
53+
{tab.label}
54+
</TabsTrigger>
55+
))}
56+
</TabsList>
57+
{tabs.map((tab) => (
58+
<OverflowAutoTabsContent key={tab.id} value={tab.id} className={cn('border-solid border-2 border-border')}>
59+
<div className="grid">
60+
<div className="overflow-auto p-4">{tab.children}</div>
61+
</div>
62+
</OverflowAutoTabsContent>
63+
))}
64+
</Tabs>
65+
)
66+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Card, CardContent } from '@/features/common/components/card'
2+
import { Account } from '../models'
3+
import { cn } from '@/features/common/utils'
4+
import { AccountActivityTabs } from './account-activity-tabs'
5+
import { AccountInfo } from './account-info'
6+
7+
type Props = {
8+
account: Account
9+
}
10+
11+
export const activityLabel = 'Activity'
12+
export const accountJsonLabel = 'Acount JSON'
13+
14+
export function AccountDetails({ account }: Props) {
15+
return (
16+
<div className={cn('space-y-6 pt-7')}>
17+
<AccountInfo account={account} />
18+
<Card aria-label={activityLabel} className={cn('p-4')}>
19+
<CardContent className={cn('text-sm space-y-2')}>
20+
<h1 className={cn('text-2xl text-primary font-bold')}>{activityLabel}</h1>
21+
<div className={cn('border-solid border-2 border-border grid')}>
22+
<AccountActivityTabs />
23+
</div>
24+
</CardContent>
25+
</Card>
26+
<Card className={cn('p-4')}>
27+
<CardContent aria-label={accountJsonLabel} className={cn('text-sm space-y-2')}>
28+
<h1 className={cn('text-2xl text-primary font-bold')}>{accountJsonLabel}</h1>
29+
<div className={cn('border-solid border-2 border-border h-96 grid')}>
30+
<pre className={cn('overflow-scroll p-4')}>{account.json}</pre>
31+
</div>
32+
</CardContent>
33+
</Card>
34+
</div>
35+
)
36+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { useMemo } from 'react'
2+
import { Account } from '../models'
3+
import { Card, CardContent } from '@/features/common/components/card'
4+
import { DescriptionList } from '@/features/common/components/description-list'
5+
import { cn } from '@/features/common/utils'
6+
import { DisplayAlgo } from '@/features/common/components/display-algo'
7+
import { AccountLink } from './account-link'
8+
9+
export const accountInformationLabel = 'Account Information'
10+
export const accountAddressLabel = 'Address'
11+
export const accountBalanceLabel = 'Balance'
12+
export const accountMinBalanceLabel = 'Min Balance'
13+
export const accountAssetsHeldLabel = 'Holding assets'
14+
export const accountAssetsCreatedLabel = 'Created assets'
15+
export const accountAssetsOptedInLabel = 'Opted assets'
16+
export const accountApplicationsCreatedLabel = 'Created applications'
17+
export const accountApplicationsOptedInLabel = 'Opted applications'
18+
export const accountRekeyedToLabel = 'Rekeyed to'
19+
20+
export function AccountInfo({ account }: { account: Account }) {
21+
const accountInfoItems = useMemo(() => {
22+
const items = [
23+
{
24+
dt: accountAddressLabel,
25+
dd: account.address,
26+
},
27+
{
28+
dt: accountBalanceLabel,
29+
dd: <DisplayAlgo amount={account.balance} />,
30+
},
31+
{
32+
dt: accountMinBalanceLabel,
33+
dd: <DisplayAlgo amount={account.minBalance} />,
34+
},
35+
{
36+
dt: accountAssetsHeldLabel,
37+
dd: account.totalAssetsHeld,
38+
},
39+
{
40+
dt: accountAssetsCreatedLabel,
41+
dd: account.totalAssetsCreated,
42+
},
43+
{
44+
dt: accountAssetsOptedInLabel,
45+
dd: account.totalAssetsOptedIn,
46+
},
47+
{
48+
dt: accountApplicationsCreatedLabel,
49+
dd: account.totalApplicationsCreated ? account.totalApplicationsCreated : 0,
50+
},
51+
{
52+
dt: accountApplicationsOptedInLabel,
53+
dd: account.totalApplicationsOptedIn ? account.totalApplicationsOptedIn : 0,
54+
},
55+
...(account.rekeyedTo
56+
? [
57+
{
58+
dt: accountRekeyedToLabel,
59+
dd: <AccountLink address={account.rekeyedTo}></AccountLink>,
60+
},
61+
]
62+
: []),
63+
]
64+
return items
65+
}, [
66+
account.address,
67+
account.balance,
68+
account.minBalance,
69+
account.totalAssetsHeld,
70+
account.totalAssetsCreated,
71+
account.totalAssetsOptedIn,
72+
account.totalApplicationsCreated,
73+
account.totalApplicationsOptedIn,
74+
account.rekeyedTo,
75+
])
76+
return (
77+
<Card aria-label={accountInformationLabel} className={cn('p-4')}>
78+
<CardContent className={cn('text-sm space-y-2')}>
79+
<DescriptionList items={accountInfoItems} />
80+
</CardContent>
81+
</Card>
82+
)
83+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { atom } from 'jotai'
2+
import { AccountResult, Address } from './types'
3+
import { algod } from '@/features/common/data'
4+
import { atomsInAtom } from '@/features/common/data/atoms-in-atom'
5+
6+
const createAccountResultAtom = (address: Address) =>
7+
atom<Promise<AccountResult> | AccountResult>(async (_get) => {
8+
return await algod
9+
.accountInformation(address)
10+
.do()
11+
.then((result) => {
12+
return result as AccountResult
13+
})
14+
})
15+
16+
export const [accountResultsAtom, getAccountResultAtom] = atomsInAtom(createAccountResultAtom, (address) => address)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { atom, useAtomValue, useStore } from 'jotai'
2+
import { Address } from './types'
3+
import { loadable } from 'jotai/utils'
4+
import { JotaiStore } from '@/features/common/data/types'
5+
import { useMemo } from 'react'
6+
import { asAccount } from '../mappers'
7+
import { getAccountResultAtom } from './account-result'
8+
9+
const createAccountAtom = (store: JotaiStore, address: Address) => {
10+
return atom(async (get) => {
11+
const accountResult = await get(getAccountResultAtom(store, address))
12+
return asAccount(accountResult)
13+
})
14+
}
15+
16+
const useAccountAtom = (address: Address) => {
17+
const store = useStore()
18+
19+
return useMemo(() => {
20+
return createAccountAtom(store, address)
21+
}, [store, address])
22+
}
23+
24+
export const useLoadableAccountAtom = (address: Address) => {
25+
return useAtomValue(loadable(useAccountAtom(address)))
26+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './account'
2+
export * from './account-result'
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { NoStringIndex } from '@/features/common/data/types'
2+
import {
3+
ApplicationResult as IndexerApplicationResult,
4+
AssetHolding as IndexerAssetHolding,
5+
AssetResult as IndexerAssetResult,
6+
AppLocalState as IndexerAppLocalState,
7+
AccountResult as IndexerAccountResult,
8+
SignatureType,
9+
} from '@algorandfoundation/algokit-utils/types/indexer'
10+
11+
export type Address = string
12+
13+
export type AppLocalState = Omit<IndexerAppLocalState, 'closed-out-at-round' | 'deleted' | 'opted-in-at-round'>
14+
export type AssetHolding = Omit<IndexerAssetHolding, 'deleted' | 'opted-in-at-round' | 'opted-out-at-round'>
15+
export type ApplicationResult = Omit<IndexerApplicationResult, 'created-at-round' | 'deleted' | 'deleted-at-round'>
16+
export type AssetResult = {
17+
index: number
18+
params: IndexerAssetResult['params']
19+
}
20+
21+
export type AccountResult = Omit<
22+
NoStringIndex<IndexerAccountResult>,
23+
| 'closed-at-round'
24+
| 'created-at-round'
25+
| 'deleted'
26+
| 'apps-local-state'
27+
| 'assets'
28+
| 'created-apps'
29+
| 'created-assets'
30+
| 'min-balance'
31+
| 'total-box-bytes'
32+
| 'total-boxes'
33+
| 'sig-type'
34+
> & {
35+
'apps-local-state'?: AppLocalState[]
36+
assets?: AssetHolding[]
37+
'created-apps'?: ApplicationResult[]
38+
'created-assets'?: AssetResult[]
39+
'min-balance': number
40+
'total-box-bytes'?: number
41+
'total-boxes'?: number
42+
'sig-type'?: SignatureType
43+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { AccountResult } from '../data/types'
2+
import { Account } from '../models'
3+
import { asJson } from '@/utils/as-json'
4+
import { microAlgos } from '@algorandfoundation/algokit-utils'
5+
6+
export const asAccount = (accountResult: AccountResult): Account => {
7+
return {
8+
address: accountResult.address,
9+
balance: microAlgos(accountResult.amount),
10+
minBalance: microAlgos(accountResult['min-balance']),
11+
totalAssetsCreated: accountResult['total-created-assets'],
12+
totalAssetsOptedIn: accountResult['total-assets-opted-in'],
13+
totalAssetsHeld: (accountResult.assets ?? []).length,
14+
totalApplicationsCreated: accountResult['total-created-apps'],
15+
totalApplicationsOptedIn: accountResult['total-apps-opted-in'],
16+
rekeyedTo: accountResult['auth-addr'],
17+
json: asJson(accountResult),
18+
}
19+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { AlgoAmount } from '@algorandfoundation/algokit-utils/types/amount'
2+
import { Address } from '../data/types'
3+
4+
export type Account = {
5+
address: Address
6+
balance: AlgoAmount
7+
minBalance: AlgoAmount
8+
totalAssetsCreated: number
9+
totalAssetsOptedIn: number
10+
totalAssetsHeld: number
11+
totalApplicationsCreated: number
12+
totalApplicationsOptedIn: number
13+
rekeyedTo?: Address
14+
json: string
15+
}

0 commit comments

Comments
 (0)