feat: public events/polls, friends graph, captcha, SSE hardening, and admin UX
Ship product gaps: register/vote pages, friend add/accept/decline/remove, email verify TTL, captcha on login/forgot, soft-fail user actions, SSE abort/shared client, Commando Centrum error toasts, admin delete for events/polls, and IT/NL i18n fills. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
2ff08e5127
commit
ed7db6e048
76 files changed
+4834
-1376
No files matched your search
@@ -1,11 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { Pencil } from "lucide-react";
|
||||
import { Pencil, Trash2 } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { deleteEvent } from "@/actions/events";
|
||||
import { useConfirmDialog } from "@/components/admin/confirm-dialog";
|
||||
import { DataTable } from "@/components/admin/data-table";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useServerAction } from "@/hooks/use-server-action";
|
||||
import type { DataTableColumn, PaginatedResult } from "@/types";
|
||||
|
||||
interface EventRow {
|
||||
@@ -35,6 +38,8 @@ interface EventsTableProps {
|
||||
|
||||
export function EventsTable({ data }: EventsTableProps) {
|
||||
const t = useTranslations("pages.admin.events");
|
||||
const { run, isPending } = useServerAction();
|
||||
const { confirm, dialog: confirmDialog } = useConfirmDialog();
|
||||
|
||||
const statusLabel: Record<string, string> = {
|
||||
published: t("statusPublished"),
|
||||
@@ -43,6 +48,20 @@ export function EventsTable({ data }: EventsTableProps) {
|
||||
completed: t("statusCompleted"),
|
||||
};
|
||||
|
||||
async function handleDelete(row: EventRow) {
|
||||
const ok = await confirm({
|
||||
title: "Delete event",
|
||||
description: `Delete "${row.title}"? This cannot be undone.`,
|
||||
confirmLabel: "Delete",
|
||||
cancelLabel: "Cancel",
|
||||
});
|
||||
if (!ok) return;
|
||||
run(() => deleteEvent({ id: row.id }), {
|
||||
successMessage: "Event deleted!",
|
||||
errorMessage: "Failed to delete event.",
|
||||
});
|
||||
}
|
||||
|
||||
const columns: DataTableColumn<EventRow>[] = [
|
||||
{ key: "id", label: t("colId"), sortable: true },
|
||||
{
|
||||
@@ -88,20 +107,34 @@ export function EventsTable({ data }: EventsTableProps) {
|
||||
key: "actions",
|
||||
label: t("colActions"),
|
||||
render: (_, row) => (
|
||||
<Button variant="ghost" size="icon" asChild>
|
||||
<Link href={`/admin/events/${row.id}`}>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button variant="ghost" size="icon" asChild>
|
||||
<Link href={`/admin/events/${row.id}`}>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
disabled={isPending}
|
||||
onClick={() => handleDelete(row)}
|
||||
aria-label="Delete event"
|
||||
>
|
||||
<Trash2 className="h-4 w-4 text-destructive" />
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
searchPlaceholder={t("searchPlaceholder")}
|
||||
/>
|
||||
<>
|
||||
<DataTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
searchPlaceholder={t("searchPlaceholder")}
|
||||
/>
|
||||
{confirmDialog}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user