Skip to content

Commit

Permalink
feat: add base data setup with jotai
Browse files Browse the repository at this point in the history
  • Loading branch information
neilcampbell committed Apr 7, 2024
1 parent 1d25f11 commit 5737fd1
Show file tree
Hide file tree
Showing 14 changed files with 1,278 additions and 2,431 deletions.
3,416 changes: 1,058 additions & 2,358 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"tauri": "tauri"
},
"dependencies": {
"@algorandfoundation/algokit-subscriber": "^1.2.0",
"@algorandfoundation/algokit-utils": "^6.0.0-beta.1",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
Expand All @@ -33,7 +35,8 @@
"clsx": "^2.1.0",
"date-fns": "^3.5.0",
"decimal.js": "^10.4.3",
"jotai": "^2.7.0",
"jotai": "^2.7.2",
"jotai-effect": "^0.6.0",
"lucide-react": "^0.356.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down Expand Up @@ -135,4 +138,4 @@
"semantic-release-export-data"
]
}
}
}
16 changes: 16 additions & 0 deletions src/features/blocks/components/latest-blocks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useLatestBlocks } from '../data'

export function LatestBlocks() {
const latestBlocks = useLatestBlocks()

return (
<div>
<h3>Latest Blocks:</h3>
{latestBlocks.map((block, i) => (
<p key={i}>
{block.round} - {block.parentTransactionCount}
</p>
))}
</div>
)
}
93 changes: 93 additions & 0 deletions src/features/blocks/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { atom, useAtom, useAtomValue } from 'jotai'
import * as algokit from '@algorandfoundation/algokit-utils'
import { atomEffect } from 'jotai-effect'
import { transactionsAtom } from '@/features/transactions/data'
import { AlgorandSubscriber } from '@algorandfoundation/algokit-subscriber'
// import { BlockMetadata } from '@algorandfoundation/algokit-subscriber/types/subscription'
import { Buffer } from 'buffer'

type BlockMetadata = {
round: number
parentTransactionCount: number
}

// TODO: NC - Remove once https://github.com/algorandfoundation/algokit-subscriber-ts/pull/49 is merged
window.Buffer = Buffer

// TODO: Move this elsewhere and make it configurable once we start using it more
const algod = algokit.getAlgoClient({
server: 'https://testnet-api.algonode.cloud/',
port: 443,
})

const syncedRoundAtom = atom<number | undefined>(undefined)

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

const latestBlockAtom = atom((get) => {
return get(blocksAtom).slice(0, 5)
})

const subscribeToBlocksEffect = atomEffect((get, set) => {
algokit.Config.configure({
logger: algokit.Config.getLogger(true),
})
const subscriber = new AlgorandSubscriber(
{
filters: [
{
name: 'all-transactions',
filter: {
customFilter: () => true,
},
},
],
maxRoundsToSync: 1, // TODO: NC - Do we want this higher?
waitForBlockWhenAtTip: true,
syncBehaviour: 'skip-sync-newest',
watermarkPersistence: {
get: async () => get(syncedRoundAtom) ?? 0,
set: async (watermark) => {
set(syncedRoundAtom, watermark)
},
},
},
algod
)

subscriber.onPoll(async (result) => {
// TODO: NC - Add this back in once subscriber supports it
// const blocks = result.blockMetadata
const blocks = [
{
round: result.syncedRoundRange[0],
parentTransactionCount: result.subscribedTransactions.length,
} satisfies BlockMetadata,
]

if (blocks) {
set(blocksAtom, (previous) => {
return blocks.concat(previous)
})
}

set(transactionsAtom, (previous) => {
return result.subscribedTransactions.concat(previous)
})
})

subscriber.start()

return async () => {
await subscriber.stop('unmounted')
}
})

export const useSubscribeToBlocksEffect = () => {
useAtom(subscribeToBlocksEffect)
}

export const useLatestBlocks = () => {
return useAtomValue(latestBlockAtom)
}
3 changes: 3 additions & 0 deletions src/features/explore/pages/explore-page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LatestBlocks } from '@/features/blocks/components/latest-blocks'
import { cn } from '@/features/common/utils'
import { TemplatedNavLink } from '@/features/routing/components/templated-nav-link/templated-nav-link'
import { Urls } from '@/routes/urls'
Expand All @@ -14,6 +15,8 @@ export function ExplorePage() {
<TemplatedNavLink urlTemplate={Urls.Explore.Group.ById} urlParams={{ groupId: 'foo' }}>
View sample group
</TemplatedNavLink>

<LatestBlocks />
</div>
)
}
3 changes: 3 additions & 0 deletions src/features/layout/pages/layout-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import { ReactNode } from 'react'
import { Header } from '../components/header'
import { LeftSideBarMenu } from '../components/left-side-bar-menu'
import { cn } from '@/features/common/utils'
import { useSubscribeToBlocksEffect } from '@/features/blocks/data'

export interface LayoutPageProps {
children?: ReactNode
}

export function LayoutPage({ children }: LayoutPageProps) {
useSubscribeToBlocksEffect()

return (
<>
<Header className={cn('mb-1')} />
Expand Down
12 changes: 6 additions & 6 deletions src/features/transactions/components/payment-transaction.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Card, CardContent } from '@/features/common/components/card'
import { PaymentTransactionModel } from '../models/models'
import { cn } from '@/features/common/utils'
import { DisplayAlgo } from '@/features/common/components/display-algo'
import { Button } from '@/features/common/components/button'
import { TransactionInfo } from './transaction-info'
import { TransactionNote } from './transaction-note'
import { TransactionJson } from './transaction-json'
import { useMemo } from 'react'
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'

export type Props = {
transaction: PaymentTransactionModel
transaction: TransactionResult
}

export function PaymentTransaction({ transaction }: Props) {
Expand All @@ -27,16 +27,16 @@ export function PaymentTransaction({ transaction }: Props) {
dt: 'Receiver',
dd: (
<a href="#" className={cn('text-primary underline')}>
{transaction.receiver}
{transaction['payment-transaction']!.receiver}
</a>
),
},
{
dt: 'Amount',
dd: <DisplayAlgo microAlgo={transaction.amount} />,
dd: <DisplayAlgo microAlgo={transaction['payment-transaction']!.amount} />,
},
],
[transaction.amount, transaction.receiver, transaction.sender]
[transaction]
)

return (
Expand All @@ -46,7 +46,7 @@ export function PaymentTransaction({ transaction }: Props) {
<CardContent className={cn('text-sm space-y-4')}>
<div className={cn('space-y-2')}>
<div className={cn('flex items-center justify-between')}>
<h1 className={cn('text-2xl text-primary font-bold')}>Transfer</h1>
<h1 className={cn('text-2xl text-primary font-bold')}>Payment</h1>
<Button>Replay</Button>
</div>
{transactionCardItems.map((item, index) => (
Expand Down
12 changes: 6 additions & 6 deletions src/features/transactions/components/transaction-info.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Card, CardContent } from '@/features/common/components/card'
import { cn } from '@/features/common/utils'
import { TransactionModel } from '../models/models'
import { dateFormatter } from '@/utils/format'
import { DisplayAlgo } from '@/features/common/components/display-algo'
import { useMemo } from 'react'
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'

export type Props = {
transaction: TransactionModel
transaction: TransactionResult
}

export function TransactionInfo({ transaction }: Props) {
Expand All @@ -18,18 +18,18 @@ export function TransactionInfo({ transaction }: Props) {
},
{
dt: 'Type',
dd: transaction.type,
dd: transaction['tx-type'],
},
{
dt: 'Timestamp',
// TODO: check timezone
dd: dateFormatter.asLongDateTime(transaction.roundTime),
dd: dateFormatter.asLongDateTime(new Date(transaction['round-time']! * 1000)),
},
{
dt: 'Block',
dd: (
<a href="#" className={cn('text-primary underline')}>
{transaction.confirmedRound}
{transaction['confirmed-round']}
</a>
),
},
Expand All @@ -46,7 +46,7 @@ export function TransactionInfo({ transaction }: Props) {
dd: <DisplayAlgo microAlgo={transaction.fee} />,
},
],
[transaction.confirmedRound, transaction.fee, transaction.group, transaction.id, transaction.roundTime, transaction.type]
[transaction]
)

return (
Expand Down
6 changes: 3 additions & 3 deletions src/features/transactions/components/transaction-json.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { cn } from '@/features/common/utils'
import { TransactionModel } from '../models/models'
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'

export type Props = {
transaction: TransactionModel
transaction: TransactionResult
}

export function TransactionJson({ transaction }: Props) {
return (
<div className={cn('space-y-2')}>
<h2 className={cn('text-xl font-bold')}>Transction JSON</h2>
<div className={cn('border-solid border-2 border-border h-96 p-4 overflow-y-scroll')}>
<pre>{JSON.stringify(JSON.parse(transaction.json), null, 4)}</pre>
<pre>{JSON.stringify(transaction, null, 4)}</pre>
</div>
</div>
)
Expand Down
14 changes: 9 additions & 5 deletions src/features/transactions/components/transaction-note.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { cn } from '@/features/common/utils'
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@radix-ui/react-tabs'
import { TransactionModel } from '../models/models'

export type Props = {
transaction: TransactionModel
transaction: TransactionResult
}

export function TransactionNote({ transaction }: Props) {
const textNote = transaction.note
const base64Note = transaction.note
const messagePackNote = transaction.note

return (
<div className={cn('space-y-2')}>
<h2 className={cn('text-xl font-bold')}>Note</h2>
Expand All @@ -23,13 +27,13 @@ export function TransactionNote({ transaction }: Props) {
</TabsTrigger>
</TabsList>
<TabsContent value="text" className={cn('border-solid border-2 border-border h-60 p-4')}>
{transaction.textNote}
{textNote}
</TabsContent>
<TabsContent value="base64" className={cn('border-solid border-2 border-border h-60 p-4')}>
{transaction.base64Note}
{base64Note}
</TabsContent>
<TabsContent value="messagePack" className={cn('border-solid border-2 border-border h-60 p-4')}>
{transaction.messagePackNote}
{messagePackNote}
</TabsContent>
</Tabs>
</div>
Expand Down
7 changes: 4 additions & 3 deletions src/features/transactions/components/transaction.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { cn } from '@/features/common/utils'
import { PaymentTransactionModel, TransactionType } from '../models/models'
import { PaymentTransaction } from './payment-transaction'
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
import { TransactionType } from 'algosdk'

type Props = {
transaction: PaymentTransactionModel
transaction: TransactionResult
}

export function Transaction({ transaction }: Props) {
return (
<div>
<h1 className={cn('text-2xl text-primary font-bold')}>Transaction</h1>
{transaction.type === TransactionType.Payment && <PaymentTransaction transaction={transaction} />}
{transaction['tx-type'] === TransactionType.pay && <PaymentTransaction transaction={transaction} />}
</div>
)
}
56 changes: 56 additions & 0 deletions src/features/transactions/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { atom, useAtomValue, getDefaultStore } from 'jotai'
import * as algokit from '@algorandfoundation/algokit-utils'
import { useMemo } from 'react'
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
import { atomEffect } from 'jotai-effect'
import { loadable } from 'jotai/utils'

// TODO: Move this elsewhere and make it configurable once we start using it more
const indexer = algokit.getAlgoIndexerClient({
server: 'https://testnet-idx.algonode.cloud/',
port: 443,
})

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

const useTransactionAtom = (transactionId: string) => {
return useMemo(() => {
const syncEffect = atomEffect((get, set) => {
;(async () => {
try {
const transaction = await get(transactionAtom)
set(transactionsAtom, (prev) => {
return prev.concat(transaction)
})
} catch (e) {
// Ignore any errors as there is nothing to sync
}
})()
})
const store = getDefaultStore()
const transactionAtom = atom((get) => {
// store.get prevents the atom from being subscribed to changes in transactionsAtom
const transactions = store.get(transactionsAtom)
const transaction = transactions.find((t) => t.id === transactionId)
if (transaction) {
return transaction
}

get(syncEffect)

return algokit.lookupTransactionById(transactionId, indexer).then((result) => {
return result.transaction
})
})
return transactionAtom
}, [transactionId])
}

export const useLoadableTransaction = (transactionId: string) => {
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(useTransactionAtom(transactionId))
)
}
Loading

0 comments on commit 5737fd1

Please sign in to comment.