Skip to content

Commit

Permalink
feat: Add account info (#43)
Browse files Browse the repository at this point in the history
* feat: add account info UI

---------

Co-authored-by: Neil Campbell <[email protected]>
  • Loading branch information
negar-abbasi and neilcampbell authored May 16, 2024
1 parent e48655c commit 89069fc
Show file tree
Hide file tree
Showing 17 changed files with 594 additions and 4 deletions.
66 changes: 66 additions & 0 deletions src/features/accounts/components/account-activity-tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { OverflowAutoTabsContent, Tabs, TabsList, TabsTrigger } from '@/features/common/components/tabs'
import { cn } from '@/features/common/utils'
import { useMemo } from 'react'

const accountVisualTransactionTabId = 'visual'
const accountVisualAssetsTabId = 'table'
const accountVisualCreatedAssetsTabId = 'created-assets'
const accountVisualCreatedApplicationsTabId = 'created-applications'
const accountVisualOptedApplicationsTabId = 'opted-applications'
export const accountDetailsLabel = 'View account Details'
export const accountVisualGraphTabLabel = 'Transactions'
export const accountVisualAssetsTabLabel = 'Assets'
export const accountVisualCreatedAssetsTabLabel = 'Created Assets'
export const accountVisualCreatedApplicationsTabLabel = 'Created Applications'
export const accountVisualOptedApplicationsTabLabel = 'Opted Applications'

export function AccountActivityTabs() {
const tabs = useMemo(
() => [
{
id: accountVisualTransactionTabId,
label: accountDetailsLabel,
children: '',
},
{
id: accountVisualAssetsTabId,
label: accountVisualAssetsTabLabel,
children: '',
},
{
id: accountVisualCreatedAssetsTabId,
label: accountVisualCreatedAssetsTabLabel,
children: '',
},
{
id: accountVisualCreatedApplicationsTabId,
label: accountVisualCreatedApplicationsTabLabel,
children: '',
},
{
id: accountVisualOptedApplicationsTabId,
label: accountVisualOptedApplicationsTabLabel,
children: '',
},
],
[]
)
return (
<Tabs defaultValue={accountVisualTransactionTabId}>
<TabsList aria-label={accountDetailsLabel}>
{tabs.map((tab) => (
<TabsTrigger key={tab.id} className={cn('data-[state=active]:border-primary data-[state=active]:border-b-2 w-44')} value={tab.id}>
{tab.label}
</TabsTrigger>
))}
</TabsList>
{tabs.map((tab) => (
<OverflowAutoTabsContent key={tab.id} value={tab.id} className={cn('border-solid border-2 border-border')}>
<div className="grid">
<div className="overflow-auto p-4">{tab.children}</div>
</div>
</OverflowAutoTabsContent>
))}
</Tabs>
)
}
36 changes: 36 additions & 0 deletions src/features/accounts/components/account-details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Card, CardContent } from '@/features/common/components/card'
import { Account } from '../models'
import { cn } from '@/features/common/utils'
import { AccountActivityTabs } from './account-activity-tabs'
import { AccountInfo } from './account-info'

type Props = {
account: Account
}

export const activityLabel = 'Activity'
export const accountJsonLabel = 'Acount JSON'

export function AccountDetails({ account }: Props) {
return (
<div className={cn('space-y-6 pt-7')}>
<AccountInfo account={account} />
<Card aria-label={activityLabel} className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<h1 className={cn('text-2xl text-primary font-bold')}>{activityLabel}</h1>
<div className={cn('border-solid border-2 border-border grid')}>
<AccountActivityTabs />
</div>
</CardContent>
</Card>
<Card className={cn('p-4')}>
<CardContent aria-label={accountJsonLabel} className={cn('text-sm space-y-2')}>
<h1 className={cn('text-2xl text-primary font-bold')}>{accountJsonLabel}</h1>
<div className={cn('border-solid border-2 border-border h-96 grid')}>
<pre className={cn('overflow-scroll p-4')}>{account.json}</pre>
</div>
</CardContent>
</Card>
</div>
)
}
83 changes: 83 additions & 0 deletions src/features/accounts/components/account-info.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useMemo } from 'react'
import { Account } from '../models'
import { Card, CardContent } from '@/features/common/components/card'
import { DescriptionList } from '@/features/common/components/description-list'
import { cn } from '@/features/common/utils'
import { DisplayAlgo } from '@/features/common/components/display-algo'
import { AccountLink } from './account-link'

export const accountInformationLabel = 'Account Information'
export const accountAddressLabel = 'Address'
export const accountBalanceLabel = 'Balance'
export const accountMinBalanceLabel = 'Min Balance'
export const accountAssetsHeldLabel = 'Holding assets'
export const accountAssetsCreatedLabel = 'Created assets'
export const accountAssetsOptedInLabel = 'Opted assets'
export const accountApplicationsCreatedLabel = 'Created applications'
export const accountApplicationsOptedInLabel = 'Opted applications'
export const accountRekeyedToLabel = 'Rekeyed to'

export function AccountInfo({ account }: { account: Account }) {
const accountInfoItems = useMemo(() => {
const items = [
{
dt: accountAddressLabel,
dd: account.address,
},
{
dt: accountBalanceLabel,
dd: <DisplayAlgo amount={account.balance} />,
},
{
dt: accountMinBalanceLabel,
dd: <DisplayAlgo amount={account.minBalance} />,
},
{
dt: accountAssetsHeldLabel,
dd: account.totalAssetsHeld,
},
{
dt: accountAssetsCreatedLabel,
dd: account.totalAssetsCreated,
},
{
dt: accountAssetsOptedInLabel,
dd: account.totalAssetsOptedIn,
},
{
dt: accountApplicationsCreatedLabel,
dd: account.totalApplicationsCreated ? account.totalApplicationsCreated : 0,
},
{
dt: accountApplicationsOptedInLabel,
dd: account.totalApplicationsOptedIn ? account.totalApplicationsOptedIn : 0,
},
...(account.rekeyedTo
? [
{
dt: accountRekeyedToLabel,
dd: <AccountLink address={account.rekeyedTo}></AccountLink>,
},
]
: []),
]
return items
}, [
account.address,
account.balance,
account.minBalance,
account.totalAssetsHeld,
account.totalAssetsCreated,
account.totalAssetsOptedIn,
account.totalApplicationsCreated,
account.totalApplicationsOptedIn,
account.rekeyedTo,
])
return (
<Card aria-label={accountInformationLabel} className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<DescriptionList items={accountInfoItems} />
</CardContent>
</Card>
)
}
16 changes: 16 additions & 0 deletions src/features/accounts/data/account-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { atom } from 'jotai'
import { AccountResult, Address } from './types'
import { algod } from '@/features/common/data'
import { atomsInAtom } from '@/features/common/data/atoms-in-atom'

const createAccountResultAtom = (address: Address) =>
atom<Promise<AccountResult> | AccountResult>(async (_get) => {
return await algod
.accountInformation(address)
.do()
.then((result) => {
return result as AccountResult
})
})

export const [accountResultsAtom, getAccountResultAtom] = atomsInAtom(createAccountResultAtom, (address) => address)
26 changes: 26 additions & 0 deletions src/features/accounts/data/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { atom, useAtomValue, useStore } from 'jotai'
import { Address } from './types'
import { loadable } from 'jotai/utils'
import { JotaiStore } from '@/features/common/data/types'
import { useMemo } from 'react'
import { asAccount } from '../mappers'
import { getAccountResultAtom } from './account-result'

const createAccountAtom = (store: JotaiStore, address: Address) => {
return atom(async (get) => {
const accountResult = await get(getAccountResultAtom(store, address))
return asAccount(accountResult)
})
}

const useAccountAtom = (address: Address) => {
const store = useStore()

return useMemo(() => {
return createAccountAtom(store, address)
}, [store, address])
}

export const useLoadableAccountAtom = (address: Address) => {
return useAtomValue(loadable(useAccountAtom(address)))
}
2 changes: 2 additions & 0 deletions src/features/accounts/data/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './account'
export * from './account-result'
43 changes: 43 additions & 0 deletions src/features/accounts/data/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { NoStringIndex } from '@/features/common/data/types'
import {
ApplicationResult as IndexerApplicationResult,
AssetHolding as IndexerAssetHolding,
AssetResult as IndexerAssetResult,
AppLocalState as IndexerAppLocalState,
AccountResult as IndexerAccountResult,
SignatureType,
} from '@algorandfoundation/algokit-utils/types/indexer'

export type Address = string

export type AppLocalState = Omit<IndexerAppLocalState, 'closed-out-at-round' | 'deleted' | 'opted-in-at-round'>
export type AssetHolding = Omit<IndexerAssetHolding, 'deleted' | 'opted-in-at-round' | 'opted-out-at-round'>
export type ApplicationResult = Omit<IndexerApplicationResult, 'created-at-round' | 'deleted' | 'deleted-at-round'>
export type AssetResult = {
index: number
params: IndexerAssetResult['params']
}

export type AccountResult = Omit<
NoStringIndex<IndexerAccountResult>,
| 'closed-at-round'
| 'created-at-round'
| 'deleted'
| 'apps-local-state'
| 'assets'
| 'created-apps'
| 'created-assets'
| 'min-balance'
| 'total-box-bytes'
| 'total-boxes'
| 'sig-type'
> & {
'apps-local-state'?: AppLocalState[]
assets?: AssetHolding[]
'created-apps'?: ApplicationResult[]
'created-assets'?: AssetResult[]
'min-balance': number
'total-box-bytes'?: number
'total-boxes'?: number
'sig-type'?: SignatureType
}
19 changes: 19 additions & 0 deletions src/features/accounts/mappers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { AccountResult } from '../data/types'
import { Account } from '../models'
import { asJson } from '@/utils/as-json'
import { microAlgos } from '@algorandfoundation/algokit-utils'

export const asAccount = (accountResult: AccountResult): Account => {
return {
address: accountResult.address,
balance: microAlgos(accountResult.amount),
minBalance: microAlgos(accountResult['min-balance']),
totalAssetsCreated: accountResult['total-created-assets'],
totalAssetsOptedIn: accountResult['total-assets-opted-in'],
totalAssetsHeld: (accountResult.assets ?? []).length,
totalApplicationsCreated: accountResult['total-created-apps'],
totalApplicationsOptedIn: accountResult['total-apps-opted-in'],
rekeyedTo: accountResult['auth-addr'],
json: asJson(accountResult),
}
}
15 changes: 15 additions & 0 deletions src/features/accounts/models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AlgoAmount } from '@algorandfoundation/algokit-utils/types/amount'
import { Address } from '../data/types'

export type Account = {
address: Address
balance: AlgoAmount
minBalance: AlgoAmount
totalAssetsCreated: number
totalAssetsOptedIn: number
totalAssetsHeld: number
totalApplicationsCreated: number
totalApplicationsOptedIn: number
rekeyedTo?: Address
json: string
}
Loading

0 comments on commit 89069fc

Please sign in to comment.