(`${API_URL}${BALANCES}`, async () => {
+ await delay(1_000)
+
+ return HttpResponse.json({
+ bitcoin: {
+ value: "42.12345678",
+ currency: "BTC",
+ },
+ eiou: {
+ value: "1.12345678",
+ currency: "BTC",
+ },
+ debit: {
+ value: "0.12345678",
+ currency: "BTC",
+ },
+ credit: {
+ value: "0.00000042",
+ currency: "BTC",
+ },
+ })
+})
diff --git a/src/pages/balances/BalancesPage.tsx b/src/pages/balances/BalancesPage.tsx
index 42ad8ad..807c72b 100644
--- a/src/pages/balances/BalancesPage.tsx
+++ b/src/pages/balances/BalancesPage.tsx
@@ -1,6 +1,202 @@
-import { BalanceChart } from "@/components/BalanceChart"
import { Breadcrumbs } from "@/components/Breadcrumbs"
import { PageTitle } from "@/components/PageTitle"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
+import { type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent } from "@/components/ui/chart"
+import { PropsWithChildren, Suspense } from "react"
+import { Skeleton } from "@/components/ui/skeleton"
+import { BalancesResponse, fetchBalances } from "@/lib/api"
+import { useSuspenseQuery } from "@tanstack/react-query"
+import useLocalStorage from "@/hooks/use-local-storage"
+
+function Loader() {
+ return (
+
+
+
+
+
+
+ )
+}
+
+export function BitcoinBalanceChart() {
+ const config = {
+ bitcoin: {
+ label: "Bitcoin",
+ color: "#2563eb",
+ },
+ } satisfies ChartConfig
+
+ const data = [
+ { month: "January", bitcoin: 186 },
+ { month: "February", bitcoin: 305 },
+ { month: "March", bitcoin: 237 },
+ { month: "April", bitcoin: 73 },
+ { month: "May", bitcoin: 209 },
+ { month: "June", bitcoin: 214 },
+ { month: "July", bitcoin: 21 },
+ { month: "August", bitcoin: 32 },
+ { month: "September", bitcoin: 0 },
+ { month: "October", bitcoin: 0 },
+ { month: "November", bitcoin: 0 },
+ { month: "December", bitcoin: 0 },
+ ]
+
+ return (
+
+
+
+ value.slice(0, 3)}
+ />
+
+
+ } />
+
+
+ )
+}
+
+export function OtherBalanceChart() {
+ const config = {
+ eIOU: {
+ label: "e-IOU",
+ color: "#911198",
+ },
+ credit: {
+ label: "Credit token",
+ color: "#e9d4ff",
+ },
+ debit: {
+ label: "Debit token",
+ color: "#c27aff",
+ },
+ } satisfies ChartConfig
+
+ const data = [
+ { month: "January", credit: 121, debit: 0 },
+ { month: "February", credit: 231, debit: 0 },
+ { month: "March", credit: 321, debit: 51 },
+ { month: "April", credit: 603, debit: 186 },
+ { month: "May", credit: 583, debit: 486 },
+ { month: "June", credit: 893, debit: 359 },
+ { month: "July", credit: 1023, debit: 192 },
+ { month: "August", credit: 2023, debit: 521 },
+ { month: "September", credit: 1821, debit: 789 },
+ { month: "October", credit: 1782, debit: 1232 },
+ { month: "November", credit: 0, debit: 0 },
+ { month: "December", credit: 0, debit: 0 },
+ ]
+
+ return (
+
+
+
+ value.slice(0, 3)}
+ />
+
+
+
+
+ } />
+
+
+ )
+}
+
+export function BalanceText({ value, children }: PropsWithChildren<{ value: BalancesResponse["bitcoin"] }>) {
+ return (
+ <>
+
+ {value.value} {value.currency}
+
+ {children}
+ >
+ )
+}
+
+function PageBody() {
+ const { data } = useSuspenseQuery({
+ queryKey: ["balances"],
+ queryFn: fetchBalances,
+ })
+
+ return (
+ <>
+
+
+
+ Bitcoin balance
+
+
+
+
+
+
+
+ e-IOU balance
+
+
+
+
+
+
+
+ Credit token balance
+
+
+
+
+
+
+
+ Debit token balance
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+function DevSection() {
+ const [devMode] = useLocalStorage("devMode", false)
+ const { data } = useSuspenseQuery({
+ queryKey: ["balances"],
+ queryFn: fetchBalances,
+ })
+
+ return (
+ <>
+ {devMode && (
+
+ {JSON.stringify(data, null, 2)}
+
+ )}
+ >
+ )
+}
export default function BalancesPage() {
return (
@@ -8,9 +204,10 @@ export default function BalancesPage() {
Balances
Balances
-
-
-
+ }>
+
+
+
>
)
}
diff --git a/src/pages/quotes/QuotePage.tsx b/src/pages/quotes/QuotePage.tsx
new file mode 100644
index 0000000..a455bfb
--- /dev/null
+++ b/src/pages/quotes/QuotePage.tsx
@@ -0,0 +1,114 @@
+import { Breadcrumbs } from "@/components/Breadcrumbs"
+import { PageTitle } from "@/components/PageTitle"
+import { Skeleton } from "@/components/ui/skeleton"
+import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table"
+import { InfoReply } from "@/generated/client"
+import { adminLookupQuoteOptions } from "@/generated/client/@tanstack/react-query.gen"
+import useLocalStorage from "@/hooks/use-local-storage"
+import { useSuspenseQuery } from "@tanstack/react-query"
+import { Suspense } from "react"
+import { Link, useParams } from "react-router"
+
+function Loader() {
+ return (
+
+
+
+ )
+}
+
+function Quote({ value }: { value: InfoReply }) {
+ return (
+ <>
+
+
+
+
+ id:
+ {value.id}
+
+
+ status:
+ {value.status}
+
+
+ endorser:
+ {value.endorser || "(empty)"}
+
+
+ bill:
+ {value.bill || "(empty)"}
+
+
+
+
+ >
+ )
+}
+
+function DevSection({ id }: { id: InfoReply["id"] }) {
+ const [devMode] = useLocalStorage("devMode", false)
+
+ const { data } = useSuspenseQuery({
+ ...adminLookupQuoteOptions({
+ path: {
+ id,
+ },
+ }),
+ })
+
+ return (
+ <>
+ {devMode && (
+ <>
+
+ {JSON.stringify(data, null, 2)}
+
+ >
+ )}
+ >
+ )
+}
+
+function PageBody({ id }: { id: InfoReply["id"] }) {
+ const { data } = useSuspenseQuery({
+ ...adminLookupQuoteOptions({
+ path: {
+ id,
+ },
+ }),
+ })
+
+ return (
+ <>
+
+ >
+ )
+}
+
+export default function QuotePage() {
+ const { id } = useParams<{ id: InfoReply["id"] }>()
+
+ if (!id) {
+ throw Error("Missing `id` param.")
+ }
+
+ return (
+ <>
+
+ Quotes
+ >,
+ ]}
+ >
+ {id}
+
+ Quote {id}
+ }>
+
+
+
+ >
+ )
+}
diff --git a/src/pages/quotes/QuotesPage.tsx b/src/pages/quotes/QuotesPage.tsx
index a389940..8519a54 100644
--- a/src/pages/quotes/QuotesPage.tsx
+++ b/src/pages/quotes/QuotesPage.tsx
@@ -3,10 +3,12 @@ import { H3 } from "@/components/Headings"
import { PageTitle } from "@/components/PageTitle"
import { Button } from "@/components/ui/button"
import { Skeleton } from "@/components/ui/skeleton"
-import { fetchAdminQuotePending } from "@/lib/api"
+import { listPendingQuotesOptions } from "@/generated/client/@tanstack/react-query.gen"
+import useLocalStorage from "@/hooks/use-local-storage"
import { useSuspenseQuery } from "@tanstack/react-query"
import { ViewIcon } from "lucide-react"
import { Suspense } from "react"
+import { Link, useNavigate } from "react-router"
function Loader() {
return (
@@ -18,8 +20,7 @@ function Loader() {
function QuoteListPendingRaw() {
const { data } = useSuspenseQuery({
- queryKey: ["quotes-pending"],
- queryFn: fetchAdminQuotePending,
+ ...listPendingQuotesOptions({}),
})
return (
@@ -32,9 +33,10 @@ function QuoteListPendingRaw() {
}
function QuoteListPending() {
+ const navigate = useNavigate()
+
const { data } = useSuspenseQuery({
- queryKey: ["quotes-pending"],
- queryFn: fetchAdminQuotePending,
+ ...listPendingQuotesOptions({}),
})
return (
@@ -43,8 +45,13 @@ function QuoteListPending() {
{data.quotes.map((it, index) => {
return (
- {it}
-
@@ -55,13 +62,18 @@ function QuoteListPending() {
)
}
+function DevSection() {
+ const [devMode] = useLocalStorage("devMode", false)
+
+ return <>{devMode && }>
+}
+
function PageBody() {
return (
<>
Pending
}>
-
>
)
@@ -73,6 +85,9 @@ export default function QuotesPage() {
Quotes
Quotes
+
+
+
>
)
}
diff --git a/src/pages/settings/SettingsPage.tsx b/src/pages/settings/SettingsPage.tsx
index 1eb45b3..9178ac0 100644
--- a/src/pages/settings/SettingsPage.tsx
+++ b/src/pages/settings/SettingsPage.tsx
@@ -1,11 +1,50 @@
import { Breadcrumbs } from "@/components/Breadcrumbs"
import { PageTitle } from "@/components/PageTitle"
+import { Label } from "@/components/ui/label"
+import { Skeleton } from "@/components/ui/skeleton"
+import { Switch } from "@/components/ui/switch"
+import useLocalStorage from "@/hooks/use-local-storage"
+import { Suspense } from "react"
+
+function Loader() {
+ return (
+
+
+
+ )
+}
+
+function PageBody() {
+ const [devMode, setDevMode] = useLocalStorage("devMode", false)
+
+ return (
+ <>
+
+
+ {
+ setDevMode((it) => !it)
+ }}
+ />
+
+
+
+ >
+ )
+}
export default function SettingsPage() {
return (
<>
Settings
Settings
+
+ }>
+
+
>
)
}
diff --git a/src/utils/api.ts b/src/utils/api.ts
deleted file mode 100644
index ae08133..0000000
--- a/src/utils/api.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { API_URL } from "@/constants/api"
-
-export const apiFetch = async (endpoint: string, options: RequestInit = {}): Promise => {
- const url = `${API_URL}${endpoint}`
-
- const response = await fetch(url, {
- ...options,
- /* headers: {
- "Content-Type": "application/json",
- ...(options.headers || {}),
- }, */
- headers: options.headers ?? [],
- })
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.statusText}`)
- }
-
- const contentLength = response.headers.get("Content-Length")
-
- if (contentLength === "0" || response.headers.get("Content-Type")?.includes("application/json") === false) {
- return {} as T
- }
-
- return response.json() as Promise
-}
diff --git a/src/utils/local-storage.ts b/src/utils/local-storage.ts
new file mode 100644
index 0000000..df628c1
--- /dev/null
+++ b/src/utils/local-storage.ts
@@ -0,0 +1,24 @@
+export function setItem(key: string, value: unknown) {
+ try {
+ window.localStorage.setItem(key, JSON.stringify(value))
+ } catch (err) {
+ console.error(err)
+ }
+}
+
+export function getItem(key: string): T | undefined {
+ try {
+ const data = window.localStorage.getItem(key)
+ return data ? (JSON.parse(data) as T) : undefined
+ } catch (err) {
+ console.error(err)
+ }
+}
+
+export function removeItem(key: string) {
+ try {
+ window.localStorage.removeItem(key)
+ } catch (err) {
+ console.error(err)
+ }
+}
diff --git a/tsconfig.app.json b/tsconfig.app.json
index 6cc8494..2db790a 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -29,6 +29,7 @@
},
"include": [
"src",
- "vitest-setup.ts"
+ "vitest-setup.ts",
+ "openapi-ts.config.ts"
]
}