Files
provenance/frontend/app/trees/page.tsx
T
justin b8f5c35045 Apply brand identity to the frontend (ink + bronze + paper)
Replaces the default black/gray with the docs/brand palette: warm ink text on paper surfaces, bronze accent, serif headings and the Origin-mark wordmark in the header, favicon, and the 'where it came from matters' tagline. Light/dark adapt via CSS vars (ink/paper flip); bronze and paper are constant. Tailwind v4 @theme exposes bronze/paper/ink tokens and the serif stack. Buttons/inputs/cards restyled to match; brand SVGs vendored into public/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
2026-06-06 11:49:58 -04:00

100 lines
2.9 KiB
TypeScript

"use client";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { api } from "@/lib/api/client";
import type { components } from "@/lib/api/schema";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
type Tree = components["schemas"]["TreeRead"];
export default function TreesPage() {
const router = useRouter();
const [trees, setTrees] = useState<Tree[]>([]);
const [name, setName] = useState("");
const [ready, setReady] = useState(false);
const load = useCallback(async () => {
const { data, response } = await api.GET("/api/v1/trees");
if (response.status === 401) {
router.push("/login");
return;
}
setTrees(data ?? []);
setReady(true);
}, [router]);
useEffect(() => {
load();
}, [load]);
async function createTree(e: React.FormEvent) {
e.preventDefault();
if (!name.trim()) return;
const { error } = await api.POST("/api/v1/trees", { body: { name } });
if (!error) {
setName("");
load();
}
}
async function logout() {
await api.POST("/api/v1/auth/logout");
router.push("/login");
}
if (!ready) return <p className="text-[var(--muted)]">Loading</p>;
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">Your trees</h1>
<Button variant="ghost" size="sm" onClick={logout}>
Sign out
</Button>
</div>
<Card>
<CardHeader>
<CardTitle className="text-base">New tree</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={createTree} className="flex gap-2">
<Input
placeholder="Family name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Button type="submit">Create</Button>
</form>
</CardContent>
</Card>
{trees.length === 0 ? (
<p className="text-[var(--muted)]">No trees yet create your first one above.</p>
) : (
<ul className="space-y-2">
{trees.map((tree) => (
<li key={tree.id}>
<Link href={`/trees/${tree.id}`}>
<Card className="transition-colors hover:border-bronze/50">
<CardContent className="flex items-center justify-between p-4">
<span className="font-medium">{tree.name}</span>
<span className="text-xs uppercase tracking-wide text-bronze">
{tree.visibility}
</span>
</CardContent>
</Card>
</Link>
</li>
))}
</ul>
)}
</div>
);
}