ui/ health checklist
This commit is contained in:
147
src/components/setup-checklist.tsx
Normal file
147
src/components/setup-checklist.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
type DiagnosticsResponse = {
|
||||
timestamp: string;
|
||||
env: {
|
||||
DATABASE_URL: boolean;
|
||||
BETTER_AUTH_SECRET: boolean;
|
||||
GOOGLE_CLIENT_ID: boolean;
|
||||
GOOGLE_CLIENT_SECRET: boolean;
|
||||
OPENAI_API_KEY: boolean;
|
||||
NEXT_PUBLIC_APP_URL: boolean;
|
||||
};
|
||||
database: {
|
||||
connected: boolean;
|
||||
schemaApplied: boolean;
|
||||
error?: string;
|
||||
};
|
||||
auth: {
|
||||
configured: boolean;
|
||||
routeResponding: boolean | null;
|
||||
};
|
||||
ai: {
|
||||
configured: boolean;
|
||||
};
|
||||
overallStatus: "ok" | "warn" | "error";
|
||||
};
|
||||
|
||||
function StatusIcon({ ok }: { ok: boolean }) {
|
||||
return ok ? (
|
||||
<span aria-label="ok" title="ok">
|
||||
✅
|
||||
</span>
|
||||
) : (
|
||||
<span aria-label="not-ok" title="not ok">
|
||||
❌
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function SetupChecklist() {
|
||||
const [data, setData] = useState<DiagnosticsResponse | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await fetch("/api/diagnostics", { cache: "no-store" });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const json = (await res.json()) as DiagnosticsResponse;
|
||||
setData(json);
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : "Failed to load diagnostics");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
}, []);
|
||||
|
||||
const steps = [
|
||||
{
|
||||
key: "env",
|
||||
label: "Environment variables",
|
||||
ok:
|
||||
!!data?.env.DATABASE_URL &&
|
||||
!!data?.env.BETTER_AUTH_SECRET &&
|
||||
!!data?.env.GOOGLE_CLIENT_ID &&
|
||||
!!data?.env.GOOGLE_CLIENT_SECRET,
|
||||
detail:
|
||||
"Requires DATABASE_URL, BETTER_AUTH_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET",
|
||||
},
|
||||
{
|
||||
key: "db",
|
||||
label: "Database connected & schema",
|
||||
ok: !!data?.database.connected && !!data?.database.schemaApplied,
|
||||
detail: data?.database.error
|
||||
? `Error: ${data.database.error}`
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
key: "auth",
|
||||
label: "Auth configured",
|
||||
ok: !!data?.auth.configured,
|
||||
detail:
|
||||
data?.auth.routeResponding === false
|
||||
? "Auth route not responding"
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
key: "ai",
|
||||
label: "AI integration (optional)",
|
||||
ok: !!data?.ai.configured,
|
||||
detail: !data?.ai.configured
|
||||
? "Set OPENAI_API_KEY for AI chat"
|
||||
: undefined,
|
||||
},
|
||||
] as const;
|
||||
|
||||
const completed = steps.filter((s) => s.ok).length;
|
||||
|
||||
return (
|
||||
<div className="p-6 border rounded-lg text-left">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h3 className="font-semibold">Setup checklist</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{completed}/{steps.length} completed
|
||||
</p>
|
||||
</div>
|
||||
<Button size="sm" onClick={load} disabled={loading} className="glow">
|
||||
{loading ? "Checking..." : "Re-check"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{error ? <div className="text-sm text-red-500">{error}</div> : null}
|
||||
|
||||
<ul className="space-y-2">
|
||||
{steps.map((s) => (
|
||||
<li key={s.key} className="flex items-start gap-2">
|
||||
<div className="mt-0.5">
|
||||
<StatusIcon ok={Boolean(s.ok)} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">{s.label}</div>
|
||||
{s.detail ? (
|
||||
<div className="text-sm text-muted-foreground">{s.detail}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{data ? (
|
||||
<div className="mt-4 text-xs text-muted-foreground">
|
||||
Last checked: {new Date(data.timestamp).toLocaleString()}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user