import { useEffect, useState } from "react"; import { useLocation } from "wouter"; import { ChevronLeft, ChevronRight, Globe, Plus, Search } from "lucide-react"; import { can } from "@/lib/adminAccess"; import { describeDirectoryError } from "@/lib/adminDirectory"; import { countAccounts, getDomains, namesOf, queryDomains, type DirectoryDomainFull } from "@/lib/adminDomains"; import { plural, t } from "@/lib/i18n"; import { Empty, Spinner } from "@/ui/misc"; import { usePermissions } from "./usePermissions"; import { DomainSheet, ManagedLabel } from "./DomainSheet"; const PAGE_SIZE = 50; export function DomainsAdmin({ selectedId }: { selectedId?: string }) { const [, navigate] = useLocation(); const perms = usePermissions(); const [text, setText] = useState(""); const [query, setQuery] = useState(""); const [position, setPosition] = useState(0); const [page, setPage] = useState<{ domains: DirectoryDomainFull[]; total: number } | null>(null); const [counts, setCounts] = useState>(new Map()); const [tenants, setTenants] = useState>(new Map()); const [error, setError] = useState(null); const [reload, setReload] = useState(0); useEffect(() => { const id = window.setTimeout(() => { setQuery(text); setPosition(0); }, 250); return () => window.clearTimeout(id); }, [text]); useEffect(() => { let cancelled = false; setError(null); void (async () => { try { const q = await queryDomains({ text: query, position, limit: PAGE_SIZE }); const domains = await getDomains(q.ids); if (cancelled) return; setPage({ domains, total: q.total }); // Both are extras on top of the list, and each needs a permission of its own. if (can(perms, "Account", "Query")) void countAccounts(domains.map((d) => d.id)).then((c) => { if (!cancelled) setCounts(c); }); const tenantIds = [...new Set(domains.map((d) => d.memberTenantId).filter((x): x is string => Boolean(x)))]; if (tenantIds.length && can(perms, "Tenant", "Get")) void namesOf("Tenant", tenantIds).then((n) => { if (!cancelled) setTenants(n); }, () => {}); } catch (err) { if (!cancelled) { setPage({ domains: [], total: 0 }); setError(describeDirectoryError(err, "domain")); } } })(); return () => { cancelled = true; }; }, [query, position, reload, perms]); const close = () => navigate("/admin/domains"); const showTenants = tenants.size > 0; return (

{t("Domains")}

{t("Where your addresses live, and the DNS records that let mail arrive and be trusted.")}

{can(perms, "Domain", "Create") && ( )}
{error &&

{error}

} {page === null ? ( ) : page.domains.length === 0 ? ( !error && } title={query ? t("No domains match") : t("No domains yet")} /> ) : ( <>
{showTenants && } {page.domains.map((d) => ( navigate(`/admin/domains/${d.id}`)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); navigate(`/admin/domains/${d.id}`); } }} aria-label={t("Open {address}", { address: d.name })} > {showTenants && } ))}
{t("Domain")} {t("Accounts")} {t("DNS records")} {t("DKIM")} {t("Certificate")}{t("Tenant")}
{d.name} {d.isEnabled === false && {t("Disabled")}}
{Object.keys(d.aliases ?? {}).length > 0 && (
{t("also {names}", { names: Object.keys(d.aliases ?? {}).join(", ") })}
)}
{counts.has(d.id) ? counts.get(d.id) : "—"} {d.memberTenantId ? (tenants.get(d.memberTenantId) ?? "—") : "—"}
{page.total <= PAGE_SIZE && position === 0 ? (

{plural(page.total, { one: "{n} domain", other: "{n} domains" })}

) : (
{t("{from}–{to} of {total}", { from: position + 1, to: position + page.domains.length, total: page.total })}
)} )} {selectedId && ( setReload((n) => n + 1)} onCreated={(id) => { setReload((n) => n + 1); navigate(`/admin/domains/${id}`); }} onDeleted={() => { setReload((n) => n + 1); close(); }} /> )}
); }