"use client"; import { ColumnDef } from "@tanstack/react-table"; import { ExtendedColumnDef } from "@app/components/ui/data-table"; import { IdpDataTable } from "@app/components/OrgIdpDataTable"; import { Button } from "@app/components/ui/button"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@app/components/ui/command"; import { Credenza, CredenzaBody, CredenzaClose, CredenzaContent, CredenzaDescription, CredenzaFooter, CredenzaHeader, CredenzaTitle } from "@app/components/Credenza"; import { ArrowRight, ArrowUpDown, KeyRound, MoreHorizontal } from "lucide-react"; import { useMemo, useState } from "react"; import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import { toast } from "@app/hooks/useToast"; import { formatAxiosError } from "@app/lib/api"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { useUserContext } from "@app/hooks/useUserContext"; import { useRouter } from "next/navigation"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@app/components/ui/dropdown-menu"; import Link from "next/link"; import { useTranslations } from "next-intl"; import IdpTypeBadge from "@app/components/IdpTypeBadge"; import IdpTypeIcon from "@app/components/IdpTypeIcon"; import { useQuery } from "@tanstack/react-query"; import { useDebounce } from "use-debounce"; import type { ListUserAdminOrgIdpsResponse } from "@server/routers/orgIdp/types"; import { cn } from "@app/lib/cn"; import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { isIdpGlobalModeBannerVisible } from "@app/components/IdpGlobalModeBanner"; export type IdpRow = { idpId: number; name: string; type: string; variant?: string; }; type AdminIdpRow = ListUserAdminOrgIdpsResponse["idps"][number]; function IdpImportRowIcon({ type, variant }: Pick) { return ; } type Props = { idps: IdpRow[]; orgId: string; }; export default function IdpTable({ idps, orgId }: Props) { const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [selectedIdp, setSelectedIdp] = useState(null); const [isUnassociateModalOpen, setIsUnassociateModalOpen] = useState(false); const [selectedUnassociateIdp, setSelectedUnassociateIdp] = useState(null); const [importDialogOpen, setImportDialogOpen] = useState(false); const [importSearchQuery, setImportSearchQuery] = useState(""); const [importSubmitting, setImportSubmitting] = useState(false); const [debouncedImportSearch] = useDebounce(importSearchQuery, 150); const envContext = useEnvContext(); const api = createApiClient(envContext); const { user } = useUserContext(); const { isPaidUser } = usePaidStatus(); const router = useRouter(); const t = useTranslations(); const canImportOrgOidcIdp = isPaidUser(tierMatrix.orgOidc); const addIdpDisabled = isIdpGlobalModeBannerVisible(envContext.env); const { data: adminIdpsRaw = [] } = useQuery({ queryKey: ["admin-org-idps", user.userId], queryFn: async () => { const res = await api.get<{ data: ListUserAdminOrgIdpsResponse; }>(`/user/${user.userId}/admin-org-idps`); return res.data.data.idps; }, enabled: importDialogOpen && !!user?.userId }); const importableIdps = useMemo(() => { const localIds = new Set(idps.map((i) => i.idpId)); return adminIdpsRaw.filter( (row) => row.orgId !== orgId && !localIds.has(row.idpId) ); }, [adminIdpsRaw, orgId, idps]); const shownImportIdps = useMemo(() => { const q = debouncedImportSearch.trim().toLowerCase(); if (!q) { return importableIdps; } return importableIdps.filter((row) => { const hay = `${row.orgName} ${row.name}`.toLowerCase(); return hay.includes(q); }); }, [importableIdps, debouncedImportSearch]); const deleteIdp = async (idpId: number) => { try { await api.delete(`/org/${orgId}/idp/${idpId}`); toast({ title: t("success"), description: t("idpDeletedDescription") }); setIsDeleteModalOpen(false); router.refresh(); } catch (e) { toast({ title: t("error"), description: formatAxiosError(e), variant: "destructive" }); } }; const importIdp = async (row: AdminIdpRow) => { setImportSubmitting(true); try { await api.post(`/org/${orgId}/idp/${row.idpId}/import`, { sourceOrgId: row.orgId }); toast({ title: t("success"), description: t("idpImportedDescription") }); setImportDialogOpen(false); setImportSearchQuery(""); router.refresh(); router.push(`/${orgId}/settings/idp/${row.idpId}/general`); } catch (e) { toast({ title: t("error"), description: formatAxiosError(e), variant: "destructive" }); } finally { setImportSubmitting(false); } }; const unassociateIdp = async (idpId: number) => { try { await api.delete(`/org/${orgId}/idp/${idpId}/association`); toast({ title: t("success"), description: t("idpUnassociatedDescription") }); setIsUnassociateModalOpen(false); router.refresh(); } catch (e) { toast({ title: t("error"), description: formatAxiosError(e), variant: "destructive" }); } }; const columns: ExtendedColumnDef[] = [ { accessorKey: "name", friendlyName: t("name"), header: ({ column }) => { return ( ); } }, { accessorKey: "type", friendlyName: t("type"), header: ({ column }) => { return ( ); }, cell: ({ row }) => { const type = row.original.type; const variant = row.original.variant; return ; } }, { id: "actions", enableHiding: false, header: () => {t("actions")}, cell: ({ row }) => { const siteRow = row.original; return (
{t("viewSettings")} { setSelectedUnassociateIdp(siteRow); setIsUnassociateModalOpen(true); }} > {t("idpUnassociateMenu")} { setSelectedIdp(siteRow); setIsDeleteModalOpen(true); }} > {t("idpDeleteAllOrgsMenu")}
); } } ]; return ( <> {selectedIdp && ( { setIsDeleteModalOpen(val); setSelectedIdp(null); }} dialog={

{t("idpDeleteGlobalQuestion")}

{t("idpDeleteGlobalDescription")}

} buttonText={t("idpConfirmDelete")} onConfirm={async () => deleteIdp(selectedIdp.idpId)} string={selectedIdp.name} title={t("idpDelete")} /> )} {selectedUnassociateIdp && ( { setIsUnassociateModalOpen(val); setSelectedUnassociateIdp(null); }} dialog={

{t("idpUnassociateQuestion")}

{t("idpUnassociateDescription")}

} buttonText={t("idpUnassociateConfirm")} onConfirm={async () => unassociateIdp(selectedUnassociateIdp.idpId) } string={selectedUnassociateIdp.name} title={t("idpUnassociateTitle")} warningText={t("idpUnassociateWarning")} /> )} { setImportDialogOpen(open); if (!open) { setImportSearchQuery(""); } }} > {t("idpImportDialogTitle")} {t("idpImportDialogDescription")} {t("idpImportEmpty")} {shownImportIdps.map((row) => ( { if (!canImportOrgOidcIdp) { return; } void importIdp(row); }} >
{row.orgName}
{row.name}
))}
{ router.push(`/${orgId}/settings/idp/create`); } }, { label: t("idpAddActionImportFromOrg"), onSelect: () => { setImportDialogOpen(true); } } ]} /> ); }