add way to reject a pending site

This commit is contained in:
miloschwartz
2026-04-21 20:29:05 -07:00
parent a7c212ffa4
commit 4f9f235398
2 changed files with 94 additions and 26 deletions

View File

@@ -3139,5 +3139,6 @@
"idpUnassociateMenu": "Unassociate", "idpUnassociateMenu": "Unassociate",
"idpDeleteAllOrgsMenu": "Delete", "idpDeleteAllOrgsMenu": "Delete",
"publicIpEndpoint": "Endpoint", "publicIpEndpoint": "Endpoint",
"lastTriggeredAt": "Last Trigger" "lastTriggeredAt": "Last Trigger",
"reject": "Reject"
} }

View File

@@ -1,5 +1,6 @@
"use client"; "use client";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { Badge } from "@app/components/ui/badge"; import { Badge } from "@app/components/ui/badge";
import { Button } from "@app/components/ui/button"; import { Button } from "@app/components/ui/button";
import { import {
@@ -24,7 +25,8 @@ import {
ArrowUpRight, ArrowUpRight,
Check, Check,
ChevronsUpDownIcon, ChevronsUpDownIcon,
MoreHorizontal MoreHorizontal,
X
} from "lucide-react"; } from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import Link from "next/link"; import Link from "next/link";
@@ -62,6 +64,9 @@ export default function PendingSitesTable({
const [isRefreshing, startTransition] = useTransition(); const [isRefreshing, startTransition] = useTransition();
const [approvingIds, setApprovingIds] = useState<Set<number>>(new Set()); const [approvingIds, setApprovingIds] = useState<Set<number>>(new Set());
const [rejectingIds, setRejectingIds] = useState<Set<number>>(new Set());
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedSite, setSelectedSite] = useState<SiteRow | null>(null);
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const t = useTranslations(); const t = useTranslations();
@@ -128,6 +133,33 @@ export default function PendingSitesTable({
} }
} }
async function rejectSite(siteId: number) {
setRejectingIds((prev) => new Set(prev).add(siteId));
try {
await api.delete(`/site/${siteId}`);
toast({
title: t("success"),
description: t("siteDeleted"),
variant: "default"
});
setIsDeleteModalOpen(false);
setSelectedSite(null);
router.refresh();
} catch (e) {
toast({
variant: "destructive",
title: t("siteErrorDelete"),
description: formatAxiosError(e, t("siteErrorDelete"))
});
} finally {
setRejectingIds((prev) => {
const next = new Set(prev);
next.delete(siteId);
return next;
});
}
}
const columns: ExtendedColumnDef<SiteRow>[] = [ const columns: ExtendedColumnDef<SiteRow>[] = [
{ {
accessorKey: "name", accessorKey: "name",
@@ -387,6 +419,7 @@ export default function PendingSitesTable({
cell: ({ row }) => { cell: ({ row }) => {
const siteRow = row.original; const siteRow = row.original;
const isApproving = approvingIds.has(siteRow.id); const isApproving = approvingIds.has(siteRow.id);
const isRejecting = rejectingIds.has(siteRow.id);
return ( return (
<div className="flex items-center gap-2 justify-end"> <div className="flex items-center gap-2 justify-end">
<DropdownMenu> <DropdownMenu>
@@ -409,7 +442,18 @@ export default function PendingSitesTable({
</DropdownMenu> </DropdownMenu>
<Button <Button
variant="outline" variant="outline"
disabled={isApproving} disabled={isApproving || isRejecting}
onClick={() => {
setSelectedSite(siteRow);
setIsDeleteModalOpen(true);
}}
>
<X className="mr-2 w-4 h-4" />
{t("reject")}
</Button>
<Button
variant="outline"
disabled={isApproving || isRejecting}
onClick={() => approveSite(siteRow.id)} onClick={() => approveSite(siteRow.id)}
> >
<Check className="mr-2 w-4 h-4" /> <Check className="mr-2 w-4 h-4" />
@@ -446,28 +490,51 @@ export default function PendingSitesTable({
}, 300); }, 300);
return ( return (
<ControlledDataTable <>
columns={columns} {selectedSite && (
rows={sites} <ConfirmDeleteDialog
tableId="pending-sites-table" open={isDeleteModalOpen}
searchPlaceholder={t("searchSitesProgress")} setOpen={(val) => {
pagination={pagination} setIsDeleteModalOpen(val);
onPaginationChange={handlePaginationChange} if (!val) {
searchQuery={searchParams.get("query")?.toString()} setSelectedSite(null);
onSearch={handleSearchChange} }
onRefresh={refreshData} }}
isRefreshing={isRefreshing || isFiltering} dialog={
refreshButtonDisabled={!canUseSiteProvisioning} <div className="space-y-2">
rowCount={rowCount} <p>{t("siteQuestionRemove")}</p>
columnVisibility={{ <p>{t("siteMessageRemove")}</p>
niceId: false, </div>
nice: false, }
exitNode: false, buttonText={t("siteConfirmDelete")}
address: false onConfirm={async () => rejectSite(selectedSite.id)}
}} string={selectedSite.name}
enableColumnVisibility title={t("siteDelete")}
stickyLeftColumn="name" />
stickyRightColumn="actions" )}
/> <ControlledDataTable
columns={columns}
rows={sites}
tableId="pending-sites-table"
searchPlaceholder={t("searchSitesProgress")}
pagination={pagination}
onPaginationChange={handlePaginationChange}
searchQuery={searchParams.get("query")?.toString()}
onSearch={handleSearchChange}
onRefresh={refreshData}
isRefreshing={isRefreshing || isFiltering}
refreshButtonDisabled={!canUseSiteProvisioning}
rowCount={rowCount}
columnVisibility={{
niceId: false,
nice: false,
exitNode: false,
address: false
}}
enableColumnVisibility
stickyLeftColumn="name"
stickyRightColumn="actions"
/>
</>
); );
} }