"use client"; /** * `` — compact list of the 10 most recent audit * entries for the `/admin` overview landing page. The full filterable * viewer lives at `/admin/login-overview`. * * Reuses `/api/admin/audit-log` so the data source matches the * detail-page section (``). */ import Link from "next/link"; import { useQuery } from "@tanstack/react-query"; import { ArrowRight, CheckCircle2, Loader2, ScrollText, XCircle, } from "lucide-react"; import { EmptyState } from "@/components/ui/empty-state"; import { formatDateTime } from "@/lib/format"; import { useTranslations } from "@/lib/i18n/context"; import { queryKeys } from "@/lib/query-keys"; import { type AdminAuditEntry, useAuthActionLabels } from "./_shared"; const PREVIEW_LIMIT = 10; interface AuditLogResponse { entries: AdminAuditEntry[]; meta: { total: number; limit: number; offset: number }; } export function RecentAuditPreview() { const { t } = useTranslations(); const { data, isLoading, isError } = useQuery({ queryKey: queryKeys.adminAuditOverview(), queryFn: async () => { const res = await fetch( `/api/admin/audit-log?limit=${PREVIEW_LIMIT}&filter=auth`, ); if (!res.ok) throw new Error("Failed to load audit log"); return (await res.json()).data as AuditLogResponse; }, // Avoid hitting the DB on every page focus; the overview is the // type of page admins keep open and re-render frequently. staleTime: 30_000, }); const AUTH_ACTION_LABELS = useAuthActionLabels(); const entries = data?.entries ?? []; return (
{t("admin.overview.auditViewAll")}
{isLoading ? (
) : isError ? (
{t("admin.overview.auditLoadError")}
) : entries.length === 0 ? ( // v1.4.15 phase-C5: compact + plain so it nests inside the // overview card without doubling the dashed border. Reuses // the existing audit-empty string. } title={t("admin.overview.auditEmpty")} /> ) : (
    {entries.map((entry) => { const isFailed = entry.action === "auth.login.failed"; // F-31 (v1.4.19): each row links into the full // /admin/login-overview viewer so admins can investigate // failed logins without re-finding the row by hand. return (
  • {entry.user?.username ?? t("common.unknown")} {AUTH_ACTION_LABELS[entry.action] ?? entry.action} {entry.ipAddress && ( {entry.ipAddress} )} {formatDateTime(entry.createdAt)}
  • ); })}
)}
); }