Comprehensive admin + user dashboards (production-ready)

This commit is contained in:
Leon Serfaty
2026-06-07 17:54:30 -04:00
parent 155507f21a
commit f033f00379
122 changed files with 7878 additions and 805 deletions
+24 -17
View File
@@ -2,11 +2,13 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import { Loader2, Copy, KeyRound, Trash2, Check } from "lucide-react";
import { Loader2, Copy, KeyRound, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent } from "@/components/ui/card";
import { ConfirmDialog } from "@/components/admin/ui/confirm-dialog";
import { EmptyState } from "@/components/ui/empty-state";
import { createApiKeyAction, revokeApiKeyAction } from "@/app/(app)/api-keys/actions";
interface KeyRow {
@@ -37,17 +39,6 @@ export function ApiKeysClient({ keys }: { keys: KeyRow[] }) {
router.refresh();
}
async function revoke(id: string) {
if (!confirm("Revoke this key? Apps using it will stop working.")) return;
const res = await revokeApiKeyAction(id);
if (res.ok) {
toast.success("Key revoked");
router.refresh();
} else {
toast.error(res.error ?? "Could not revoke");
}
}
return (
<div className="max-w-2xl space-y-6">
{newKey && (
@@ -88,9 +79,13 @@ export function ApiKeysClient({ keys }: { keys: KeyRow[] }) {
</Card>
{keys.length === 0 ? (
<p className="text-center text-sm text-muted-foreground">No API keys yet.</p>
<EmptyState
icon={KeyRound}
title="No API keys yet"
description="Create a key above to start generating episodes programmatically."
/>
) : (
<div className="divide-y rounded-lg border">
<div className="divide-y rounded-2xl border">
{keys.map((k) => (
<div key={k.id} className="flex items-center justify-between gap-3 p-4">
<div className="min-w-0">
@@ -100,9 +95,21 @@ export function ApiKeysClient({ keys }: { keys: KeyRow[] }) {
{k.lastUsedAt ? ` · last used ${new Date(k.lastUsedAt).toLocaleDateString()}` : " · never used"}
</p>
</div>
<Button variant="ghost" size="sm" className="text-destructive" onClick={() => revoke(k.id)}>
<Trash2 className="h-4 w-4" /> Revoke
</Button>
<ConfirmDialog
trigger={
<Button variant="ghost" size="sm" className="text-destructive">
<Trash2 className="h-4 w-4" /> Revoke
</Button>
}
title="Revoke this key?"
description={`Apps using "${k.name}" will immediately stop working. This cannot be undone.`}
confirmLabel="Revoke key"
successMessage="Key revoked"
onConfirm={async () => {
const res = await revokeApiKeyAction(k.id);
return { ok: res.ok, error: res.error };
}}
/>
</div>
))}
</div>