Skip to content

Commit 33db08a

Browse files
committed
ui: initial balances view
1 parent 71f3eb3 commit 33db08a

File tree

6 files changed

+243
-51
lines changed

6 files changed

+243
-51
lines changed

src/components/AppSidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const items = [
2020
icon: Home,
2121
},
2222
{
23-
title: "Balance",
23+
title: "Balances",
2424
url: "/balances",
2525
icon: Bitcoin,
2626
},

src/components/BalanceChart.tsx

Lines changed: 0 additions & 44 deletions
This file was deleted.

src/lib/api.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ADMIN_QUOTE_ACCEPTED, ADMIN_QUOTE_BY_ID, ADMIN_QUOTE_PENDING, INFO } from "@/constants/endpoints"
1+
import { ADMIN_QUOTE_ACCEPTED, ADMIN_QUOTE_BY_ID, ADMIN_QUOTE_PENDING, BALANCES, INFO } from "@/constants/endpoints"
22
import { apiFetch } from "@/utils/api"
33

44
export interface InfoResponse {
@@ -33,6 +33,33 @@ export async function fetchInfo(): Promise<InfoResponse> {
3333
})
3434
}
3535

36+
export interface BalancesResponse {
37+
bitcoin: {
38+
value: string
39+
currency: string
40+
}
41+
eiou: {
42+
value: string
43+
currency: string
44+
}
45+
debit: {
46+
value: string
47+
currency: string
48+
}
49+
credit: {
50+
value: string
51+
currency: string
52+
}
53+
}
54+
55+
export async function fetchBalances(): Promise<BalancesResponse> {
56+
return apiFetch<BalancesResponse>(BALANCES, {
57+
headers: {
58+
"Content-Type": "application/json",
59+
},
60+
})
61+
}
62+
3663
interface QuotePending {
3764
id: string
3865
bill: string

src/mocks/handlers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { fetchAdminQuotePending } from "./handlers/admin_quotes"
2+
import { fetchBalances } from "./handlers/balances"
23
import { fetchInfo } from "./handlers/info"
34

4-
export const handlers = [fetchInfo, fetchAdminQuotePending]
5+
export const handlers = [fetchInfo, fetchBalances, fetchAdminQuotePending]

src/mocks/handlers/balances.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { http, delay, HttpResponse } from "msw"
2+
import { API_URL } from "@/constants/api"
3+
import type { BalancesResponse } from "@/lib/api"
4+
import { BALANCES } from "@/constants/endpoints"
5+
6+
export const fetchBalances = http.get<never, never, BalancesResponse>(`${API_URL}${BALANCES}`, async () => {
7+
await delay(1_000)
8+
9+
return HttpResponse.json({
10+
bitcoin: {
11+
value: "42.12345678",
12+
currency: "BTC",
13+
},
14+
eiou: {
15+
value: "1.12345678",
16+
currency: "BTC",
17+
},
18+
debit: {
19+
value: "0.12345678",
20+
currency: "BTC",
21+
},
22+
credit: {
23+
value: "0.00000042",
24+
currency: "BTC",
25+
},
26+
})
27+
})
Lines changed: 185 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,197 @@
1-
import { BalanceChart } from "@/components/BalanceChart"
21
import { Breadcrumbs } from "@/components/Breadcrumbs"
32
import { PageTitle } from "@/components/PageTitle"
3+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
4+
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
5+
import { type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent } from "@/components/ui/chart"
6+
import { PropsWithChildren, Suspense } from "react"
7+
import { Skeleton } from "@/components/ui/skeleton"
8+
import { BalancesResponse, fetchBalances } from "@/lib/api"
9+
import { useSuspenseQuery } from "@tanstack/react-query"
10+
11+
function Loader() {
12+
return (
13+
<div className="flex gap-2 py-2">
14+
<Skeleton className="flex-1 h-32 rounded-lg" />
15+
<Skeleton className="flex-1 h-32 rounded-lg" />
16+
<Skeleton className="flex-1 h-32 rounded-lg" />
17+
<Skeleton className="flex-1 h-32 rounded-lg" />
18+
</div>
19+
)
20+
}
21+
22+
export function BitcoinBalanceChart() {
23+
const config = {
24+
bitcoin: {
25+
label: "Bitcoin",
26+
color: "#2563eb",
27+
},
28+
} satisfies ChartConfig
29+
30+
const data = [
31+
{ month: "January", bitcoin: 186 },
32+
{ month: "February", bitcoin: 305 },
33+
{ month: "March", bitcoin: 237 },
34+
{ month: "April", bitcoin: 73 },
35+
{ month: "May", bitcoin: 209 },
36+
{ month: "June", bitcoin: 214 },
37+
{ month: "July", bitcoin: 21 },
38+
{ month: "August", bitcoin: 32 },
39+
{ month: "September", bitcoin: 0 },
40+
{ month: "October", bitcoin: 0 },
41+
{ month: "November", bitcoin: 0 },
42+
{ month: "December", bitcoin: 0 },
43+
]
44+
45+
return (
46+
<ChartContainer config={config} className="min-h-[200px] w-full">
47+
<BarChart accessibilityLayer data={data}>
48+
<CartesianGrid vertical={false} />
49+
<XAxis
50+
dataKey="month"
51+
tickLine={false}
52+
tickMargin={10}
53+
axisLine={false}
54+
tickFormatter={(value: string) => value.slice(0, 3)}
55+
/>
56+
<YAxis dataKey="bitcoin" tickLine={false} tickMargin={10} axisLine={false} />
57+
<Bar dataKey="bitcoin" fill="var(--color-bitcoin)" radius={4} />
58+
<ChartLegend content={<ChartLegendContent />} />
59+
</BarChart>
60+
</ChartContainer>
61+
)
62+
}
63+
64+
export function OtherBalanceChart() {
65+
const config = {
66+
eIOU: {
67+
label: "e-IOU",
68+
color: "#911198",
69+
},
70+
credit: {
71+
label: "Credit token",
72+
color: "#e9d4ff",
73+
},
74+
debit: {
75+
label: "Debit token",
76+
color: "#c27aff",
77+
},
78+
} satisfies ChartConfig
79+
80+
const data = [
81+
{ month: "January", credit: 121, debit: 0 },
82+
{ month: "February", credit: 231, debit: 0 },
83+
{ month: "March", credit: 321, debit: 51 },
84+
{ month: "April", credit: 603, debit: 186 },
85+
{ month: "May", credit: 583, debit: 486 },
86+
{ month: "June", credit: 893, debit: 359 },
87+
{ month: "July", credit: 1023, debit: 192 },
88+
{ month: "August", credit: 2023, debit: 521 },
89+
{ month: "September", credit: 1821, debit: 789 },
90+
{ month: "October", credit: 1782, debit: 1232 },
91+
{ month: "November", credit: 0, debit: 0 },
92+
{ month: "December", credit: 0, debit: 0 },
93+
]
94+
95+
return (
96+
<ChartContainer config={config} className="min-h-[200px] w-full">
97+
<BarChart accessibilityLayer data={data}>
98+
<CartesianGrid vertical={false} />
99+
<XAxis
100+
dataKey="month"
101+
tickLine={false}
102+
tickMargin={10}
103+
axisLine={false}
104+
tickFormatter={(value: string) => value.slice(0, 3)}
105+
/>
106+
<YAxis dataKey="credit" tickLine={false} tickMargin={10} axisLine={false} />
107+
<YAxis dataKey="debit" tickLine={false} tickMargin={10} axisLine={false} />
108+
<Bar dataKey="credit" fill="var(--color-credit)" radius={4} />
109+
<Bar dataKey="debit" fill="var(--color-debit)" radius={4} />
110+
<ChartLegend content={<ChartLegendContent />} />
111+
</BarChart>
112+
</ChartContainer>
113+
)
114+
}
115+
116+
export function BalanceText({ value, children }: PropsWithChildren<{ value: BalancesResponse["bitcoin"] }>) {
117+
return (
118+
<>
119+
<h3 className="scroll-m-20 text-2xl font-extrabold tracking-tight">
120+
{value.value} {value.currency}
121+
</h3>
122+
{children}
123+
</>
124+
)
125+
}
126+
127+
function PageBody() {
128+
const { data } = useSuspenseQuery({
129+
queryKey: ["balances"],
130+
queryFn: fetchBalances,
131+
})
132+
133+
return (
134+
<>
135+
<div className="flex items-center gap-2 my-2">
136+
<Card className="flex-1 bg-indigo-100 self-stretch">
137+
<CardHeader>
138+
<CardTitle>Bitcoin balance</CardTitle>
139+
</CardHeader>
140+
<CardContent>
141+
<BalanceText value={data.bitcoin} />
142+
</CardContent>
143+
</Card>
144+
<Card className="flex-1 bg-orange-100 self-stretch">
145+
<CardHeader>
146+
<CardTitle>e-IOU balance</CardTitle>
147+
</CardHeader>
148+
<CardContent>
149+
<BalanceText value={data.eiou} />
150+
</CardContent>
151+
</Card>
152+
<Card className="flex-1 bg-purple-200 self-stretch">
153+
<CardHeader>
154+
<CardTitle>Credit token balance</CardTitle>
155+
</CardHeader>
156+
<CardContent>
157+
<BalanceText value={data.credit} />
158+
</CardContent>
159+
</Card>
160+
<Card className="flex-1 bg-purple-400 self-stretch">
161+
<CardHeader>
162+
<CardTitle>Debit token balance</CardTitle>
163+
</CardHeader>
164+
<CardContent>
165+
<BalanceText value={data.debit} />
166+
</CardContent>
167+
</Card>
168+
</div>
169+
170+
<div className="flex items-center gap-2 hidden">
171+
<Card className="flex-1 max-h-[500px] py-4">
172+
<BitcoinBalanceChart />
173+
</Card>
174+
<Card className="flex-1 max-h-[500px] py-4">
175+
<OtherBalanceChart />
176+
</Card>
177+
</div>
178+
179+
<pre className="text-sm bg-accent text-accent-foreground rounded-lg p-2 my-2">
180+
{JSON.stringify(data, null, 2)}
181+
</pre>
182+
</>
183+
)
184+
}
4185

5186
export default function BalancesPage() {
6187
return (
7188
<>
8189
<Breadcrumbs>Balances</Breadcrumbs>
9190
<PageTitle>Balances</PageTitle>
10191

11-
<div className="flex max-h-[500px] max-w-[320px] py-4">
12-
<BalanceChart />
13-
</div>
192+
<Suspense fallback={<Loader />}>
193+
<PageBody />
194+
</Suspense>
14195
</>
15196
)
16197
}

0 commit comments

Comments
 (0)