diff options
Diffstat (limited to 'cmd/mirumd/web/components/pages/dashboard.tsx')
| -rw-r--r-- | cmd/mirumd/web/components/pages/dashboard.tsx | 68 | +68 −0 |
1 files changed, 68 insertions, 0 deletions
diff --git a/cmd/mirumd/web/components/pages/dashboard.tsx b/cmd/mirumd/web/components/pages/dashboard.tsx new file mode 100644 --- /dev/null +++ b/cmd/mirumd/web/components/pages/dashboard.tsx @@ -0,0 +1,68 @@ +// Copyright (c) 2026 Nikolay Govorov +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { useEffect, useMemo, useState } from "react" +import { createAdminClient } from "@/api/client" +import { formatError } from "@/lib/errors" +import type { Org } from "@/gen/admin_pb" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" + +export type DashboardProps = { + user: { email: string } + csrf: string +} + +export function Page({ user, csrf }: DashboardProps) { + const client = useMemo(() => createAdminClient(csrf), [csrf]) + const [orgs, setOrgs] = useState<Org[] | null>(null) + const [error, setError] = useState<string | null>(null) + + useEffect(() => { + const ac = new AbortController() + client + .orgList({}, { signal: ac.signal }) + .then((res) => setOrgs(res.organizations)) + .catch((err: unknown) => { + if (ac.signal.aborted) return + setError(formatError(err)) + }) + return () => ac.abort() + }, [client]) + + return ( + <div className="mx-auto max-w-3xl p-8 space-y-6"> + <header className="flex items-center justify-between"> + <h1 className="text-2xl font-semibold">Mirum</h1> + <div className="flex items-center gap-3"> + <span className="text-sm text-muted-foreground">{user.email}</span> + <form method="POST" action="/auth/logout"> + <input type="hidden" name="csrf" value={csrf} /> + <Button type="submit" variant="outline" size="sm">Sign out</Button> + </form> + </div> + </header> + + <Card> + <CardHeader> + <CardTitle>Organizations</CardTitle> + </CardHeader> + <CardContent> + {error && <p className="text-destructive">{error}</p>} + {orgs === null && !error && <p className="text-muted-foreground">Loading…</p>} + {orgs?.length === 0 && <p className="text-muted-foreground">No organizations yet.</p>} + {orgs && orgs.length > 0 && ( + <ul className="divide-y"> + {orgs.map((org) => ( + <li key={org.slug} className="py-2"> + <span className="font-medium">{org.name}</span>{" "} + <span className="text-muted-foreground">({org.slug})</span> + </li> + ))} + </ul> + )} + </CardContent> + </Card> + </div> + ) +} |
