Skip to content

Commit c8ae8ca

Browse files
authored
Merge pull request #5 from MakerXStudio/base-data-setup
feat: add base data setup with jotai
2 parents ee32e8b + eca2740 commit c8ae8ca

File tree

22 files changed

+1438
-840
lines changed

22 files changed

+1438
-840
lines changed

.github/workflows/pr.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ jobs:
1818
node-version: 20.x
1919
audit-script: npm run audit
2020
compile-script: npm run check-types
21-
test-script: npm run tests
21+
test-script: npm run test
2222
run-build: false

.github/workflows/release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
node-version: 20.x
3434
audit-script: npm run audit
3535
compile-script: npm run check-types
36-
test-script: npm run tests
36+
test-script: npm run test
3737

3838
build-website:
3939
name: Build Website

package-lock.json

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

package.json

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
1515
"lint:fix": "eslint . --ext ts,tsx --fix",
1616
"preview": "vite preview",
17-
"tests": "vitest",
17+
"test": "vitest",
1818
"audit": "better-npm-audit audit",
19-
"check-types": "tsc --noEmit",
19+
"check-types": "tsc --noEmit && npx --yes madge src/App.tsx --circular",
2020
"tauri": "tauri"
2121
},
2222
"dependencies": {
23+
"@algorandfoundation/algokit-subscriber": "^1.2.0",
24+
"@algorandfoundation/algokit-utils": "^6.0.0-beta.1",
2325
"@radix-ui/react-dialog": "^1.0.5",
2426
"@radix-ui/react-dropdown-menu": "^2.0.6",
2527
"@radix-ui/react-label": "^2.0.2",
@@ -32,14 +34,15 @@
3234
"class-variance-authority": "^0.7.0",
3335
"clsx": "^2.1.0",
3436
"date-fns": "^3.5.0",
35-
"decimal.js": "^10.4.3",
36-
"jotai": "^2.7.0",
37+
"jotai": "^2.7.2",
38+
"jotai-effect": "^0.6.0",
3739
"lucide-react": "^0.356.0",
3840
"react": "^18.2.0",
3941
"react-dom": "^18.2.0",
4042
"react-router-dom": "^6.22.3",
4143
"tailwind-merge": "^2.2.1",
4244
"tailwindcss-animate": "^1.0.7",
45+
"tiny-invariant": "^1.3.3",
4346
"vaul": "^0.9.0"
4447
},
4548
"devDependencies": {
@@ -135,4 +138,4 @@
135138
"semantic-release-export-data"
136139
]
137140
}
138-
}
141+
}

src/App.routes.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import { evalTemplates } from './routes/templated-route'
55
import { TransactionPage } from './features/transactions/pages/transaction-page'
66
import { ExplorePage } from './features/explore/pages/explore-page'
77
import { GroupPage } from './features/transactions/pages/group-page'
8+
import ErrorBoundary from './features/errors/components/error-boundary'
89

910
export const routes = evalTemplates([
1011
{
1112
template: Urls.Index,
1213
element: (
1314
<LayoutPage>
14-
<Outlet />
15+
<ErrorBoundary>
16+
<Outlet />
17+
</ErrorBoundary>
1518
</LayoutPage>
1619
),
1720
children: [
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useLatestBlocks } from '../data'
2+
3+
export function LatestBlocks() {
4+
const latestBlocks = useLatestBlocks()
5+
6+
return (
7+
<div>
8+
<h3>Latest Blocks:</h3>
9+
{latestBlocks.map((block, i) => (
10+
<p key={i}>
11+
{block.round} - {block.parentTransactionCount}
12+
</p>
13+
))}
14+
</div>
15+
)
16+
}

src/features/blocks/data.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { atom, useAtom, useAtomValue } from 'jotai'
2+
import * as algokit from '@algorandfoundation/algokit-utils'
3+
import { atomEffect } from 'jotai-effect'
4+
import { transactionsAtom } from '@/features/transactions/data'
5+
import { AlgorandSubscriber } from '@algorandfoundation/algokit-subscriber'
6+
// import { BlockMetadata } from '@algorandfoundation/algokit-subscriber/types/subscription'
7+
import { Buffer } from 'buffer'
8+
9+
type BlockMetadata = {
10+
round: number
11+
parentTransactionCount: number
12+
}
13+
14+
// TODO: NC - Remove once https://github.com/algorandfoundation/algokit-subscriber-ts/pull/49 is merged
15+
window.Buffer = Buffer
16+
17+
// TODO: Move this elsewhere and make it configurable once we start using it more
18+
const algod = algokit.getAlgoClient({
19+
server: 'https://testnet-api.algonode.cloud/',
20+
port: 443,
21+
})
22+
23+
const syncedRoundAtom = atom<number | undefined>(undefined)
24+
25+
// TODO: Size should be capped at some limit, so memory usage doesn't grow indefinitely
26+
const blocksAtom = atom<BlockMetadata[]>([])
27+
28+
const latestBlockAtom = atom((get) => {
29+
return get(blocksAtom).slice(0, 5)
30+
})
31+
32+
const subscribeToBlocksEffect = atomEffect((get, set) => {
33+
algokit.Config.configure({
34+
logger: algokit.Config.getLogger(true),
35+
})
36+
const subscriber = new AlgorandSubscriber(
37+
{
38+
filters: [
39+
{
40+
name: 'all-transactions',
41+
filter: {
42+
customFilter: () => true,
43+
},
44+
},
45+
],
46+
maxRoundsToSync: 1, // TODO: NC - Do we want this higher?
47+
waitForBlockWhenAtTip: true,
48+
syncBehaviour: 'skip-sync-newest',
49+
watermarkPersistence: {
50+
get: async () => get(syncedRoundAtom) ?? 0,
51+
set: async (watermark) => {
52+
set(syncedRoundAtom, watermark)
53+
},
54+
},
55+
},
56+
algod
57+
)
58+
59+
subscriber.onPoll(async (result) => {
60+
// TODO: NC - Add this back in once subscriber supports it
61+
// const blocks = result.blockMetadata
62+
const blocks = [
63+
{
64+
round: result.syncedRoundRange[0],
65+
parentTransactionCount: result.subscribedTransactions.length,
66+
} satisfies BlockMetadata,
67+
]
68+
69+
if (blocks) {
70+
set(blocksAtom, (previous) => {
71+
return blocks.concat(previous)
72+
})
73+
}
74+
75+
set(transactionsAtom, (previous) => {
76+
return result.subscribedTransactions.concat(previous)
77+
})
78+
})
79+
80+
subscriber.start()
81+
82+
return async () => {
83+
await subscriber.stop('unmounted')
84+
}
85+
})
86+
87+
export const useSubscribeToBlocksEffect = () => {
88+
useAtom(subscribeToBlocksEffect)
89+
}
90+
91+
export const useLatestBlocks = () => {
92+
return useAtomValue(latestBlockAtom)
93+
}

src/features/common/components/display-algo.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { algoFormatter } from '@/utils/format'
21
import SvgAlgorand from './icons/algorand'
32
import { cn } from '../utils'
3+
import { AlgoAmount } from '@algorandfoundation/algokit-utils/types/amount'
44

55
export type Props = {
6-
microAlgo: number
6+
amount: AlgoAmount
77
}
88

9-
export function DisplayAlgo({ microAlgo }: Props) {
9+
export function DisplayAlgo({ amount }: Props) {
1010
return (
1111
<div className={cn('flex items-center')}>
12-
{algoFormatter.asAlgo(microAlgo)}
12+
{amount.algos}
1313
<SvgAlgorand />
1414
</div>
1515
)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Component, ErrorInfo, PropsWithChildren } from 'react'
2+
3+
type ErrorBoundaryProps = PropsWithChildren
4+
5+
function ErrorBoundary(props: ErrorBoundaryProps) {
6+
return <ErrorBoundaryImpl {...{ ...props }} />
7+
}
8+
9+
type ErrorBoundaryImplProps = ErrorBoundaryProps
10+
type ErrorBoundaryImplState = {
11+
errorType: ErrorType
12+
}
13+
14+
enum ErrorType {
15+
None,
16+
Other,
17+
}
18+
19+
class ErrorBoundaryImpl extends Component<ErrorBoundaryImplProps, ErrorBoundaryImplState> {
20+
constructor(props: ErrorBoundaryImplProps) {
21+
super(props)
22+
23+
this.state = {
24+
errorType: ErrorType.None,
25+
}
26+
}
27+
28+
public static getDerivedStateFromError(_e: unknown): ErrorBoundaryImplState {
29+
return {
30+
errorType: ErrorType.Other,
31+
}
32+
}
33+
34+
public async componentDidCatch(_e: unknown, _errorInfo: ErrorInfo) {}
35+
36+
public componentDidUpdate() {}
37+
38+
public render() {
39+
if (this.state.errorType === ErrorType.None) {
40+
return this.props.children
41+
}
42+
43+
return <p>Error</p>
44+
}
45+
}
46+
47+
export default ErrorBoundary

src/features/explore/pages/explore-page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { LatestBlocks } from '@/features/blocks/components/latest-blocks'
12
import { cn } from '@/features/common/utils'
23
import { TemplatedNavLink } from '@/features/routing/components/templated-nav-link/templated-nav-link'
34
import { Urls } from '@/routes/urls'
@@ -14,6 +15,8 @@ export function ExplorePage() {
1415
<TemplatedNavLink urlTemplate={Urls.Explore.Group.ById} urlParams={{ groupId: 'foo' }}>
1516
View sample group
1617
</TemplatedNavLink>
18+
19+
<LatestBlocks />
1720
</div>
1821
)
1922
}

0 commit comments

Comments
 (0)