feat: jwt cache, redis health, help-ticket admin, and write rate limits
Cut Auth.js DB load with cached jwtVersion checks, surface Redis in /api/health and deploy warnings, add admin help-center ticket reply UI, rate-limit API tickets/reactions/referral claims, and revoke PATs on sign-out-everywhere. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
3bb96eb6f3
commit
968ca15c27
23 files changed
+1344
-205
No files matched your search
@@ -0,0 +1,89 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { DataTable } from "@/components/admin/data-table";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import type { DataTableColumn, PaginatedResult } from "@/types";
|
||||
|
||||
export interface HelpTicketRow {
|
||||
id: string;
|
||||
title: string;
|
||||
user: string;
|
||||
userId: number | null;
|
||||
replies: number;
|
||||
open: boolean;
|
||||
date: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
export function HelpTicketsTable({
|
||||
data,
|
||||
}: {
|
||||
data: PaginatedResult<HelpTicketRow>;
|
||||
}) {
|
||||
const t = useTranslations("pages.admin.helpTickets");
|
||||
|
||||
const columns: DataTableColumn<HelpTicketRow>[] = [
|
||||
{
|
||||
key: "title",
|
||||
label: t("colTicket"),
|
||||
sortable: true,
|
||||
render: (_value, row) => (
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground font-mono">
|
||||
#{row.id}
|
||||
</span>
|
||||
<Link
|
||||
href={`/admin/help-tickets/${row.id}`}
|
||||
className="font-medium truncate hover:underline"
|
||||
>
|
||||
{row.title}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{t("meta", { user: row.user, count: row.replies })}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "open",
|
||||
label: t("colStatus"),
|
||||
sortable: true,
|
||||
filterKey: "filter_status",
|
||||
filterOptions: [
|
||||
{ label: t("presetOpen"), value: "open" },
|
||||
{ label: t("statusClosed"), value: "closed" },
|
||||
],
|
||||
render: (value) => {
|
||||
const open = value === true || value === "true";
|
||||
return (
|
||||
<Badge
|
||||
variant={open ? "default" : "secondary"}
|
||||
className="text-[0.7rem]"
|
||||
>
|
||||
{open ? t("statusOpen") : t("statusClosed")}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{ key: "user", label: t("colUser"), sortable: true },
|
||||
{ key: "date", label: t("colCreated"), sortable: true },
|
||||
{ key: "updated", label: t("colUpdated"), sortable: true },
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
searchPlaceholder={t("searchPlaceholder")}
|
||||
presets={[
|
||||
{ label: t("presetOpen"), params: { filter_status: "open" } },
|
||||
{ label: t("statusClosed"), params: { filter_status: "closed" } },
|
||||
{ label: t("presetAll"), params: { filter_status: "all" } },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user