/** * Allowed Domains Settings - Self-signup domain management * * Only available when using passkey auth. When external auth (e.g., Cloudflare Access) * is configured, this page shows an informational message instead. */ import { Button, Dialog, Input, Select, Switch } from "@cloudflare/kumo"; import { useLingui } from "@lingui/react/macro"; import { Globe, Plus, CheckCircle, WarningCircle, Trash, Pencil, Info, } from "@phosphor-icons/react"; import { X } from "@phosphor-icons/react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import * as React from "react"; import { fetchAllowedDomains, createAllowedDomain, updateAllowedDomain, deleteAllowedDomain, fetchManifest, type AllowedDomain, } from "../../lib/api"; import { BackToSettingsLink } from "./BackToSettingsLink.js"; import { useAllowedDomainsRolesConfig } from "./useAllowedDomainsRolesConfig.js"; export function AllowedDomainsSettings() { const { t } = useLingui(); const { getRoleLabel, signupRoles, signupRoleItems } = useAllowedDomainsRolesConfig(); const queryClient = useQueryClient(); const [isAddingDomain, setIsAddingDomain] = React.useState(false); const [editingDomain, setEditingDomain] = React.useState(null); const [deletingDomain, setDeletingDomain] = React.useState(null); const [saveStatus, setSaveStatus] = React.useState<{ type: "success" | "error"; message: string; } | null>(null); // Form state const [newDomain, setNewDomain] = React.useState(""); const [newRole, setNewRole] = React.useState(30); // Default to Author // Fetch manifest for auth mode const { data: manifest, isLoading: manifestLoading } = useQuery({ queryKey: ["manifest"], queryFn: fetchManifest, }); const isExternalAuth = manifest?.authMode && manifest.authMode !== "passkey"; // Fetch domains (only when using passkey auth) const { data: domains, isLoading, error, } = useQuery({ queryKey: ["allowed-domains"], queryFn: fetchAllowedDomains, enabled: !isExternalAuth && !manifestLoading, }); // Clear status message after 3 seconds React.useEffect(() => { if (saveStatus) { const timer = setTimeout(setSaveStatus, 3000, null); return () => clearTimeout(timer); } }, [saveStatus]); // Create mutation const createMutation = useMutation({ mutationFn: createAllowedDomain, onSuccess: () => { void queryClient.invalidateQueries({ queryKey: ["allowed-domains"] }); setIsAddingDomain(false); setNewDomain(""); setNewRole(30); setSaveStatus({ type: "success", message: t`Domain added successfully` }); }, onError: (mutationError) => { setSaveStatus({ type: "error", message: mutationError instanceof Error ? mutationError.message : t`Failed to add domain`, }); }, }); // Update mutation const updateMutation = useMutation({ mutationFn: ({ domain, data, }: { domain: string; data: { enabled?: boolean; defaultRole?: number }; }) => updateAllowedDomain(domain, data), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: ["allowed-domains"] }); setEditingDomain(null); setSaveStatus({ type: "success", message: t`Domain updated` }); }, onError: (mutationError) => { setSaveStatus({ type: "error", message: mutationError instanceof Error ? mutationError.message : t`Failed to update domain`, }); }, }); // Delete mutation const deleteMutation = useMutation({ mutationFn: deleteAllowedDomain, onSuccess: () => { void queryClient.invalidateQueries({ queryKey: ["allowed-domains"] }); setDeletingDomain(null); setSaveStatus({ type: "success", message: t`Domain removed` }); }, onError: (mutationError) => { setSaveStatus({ type: "error", message: mutationError instanceof Error ? mutationError.message : t`Failed to remove domain`, }); }, }); const handleAddDomain = () => { if (!newDomain.trim()) return; createMutation.mutate({ domain: newDomain.trim().toLowerCase(), defaultRole: newRole, }); }; const handleToggleEnabled = (domain: AllowedDomain) => { updateMutation.mutate({ domain: domain.domain, data: { enabled: !domain.enabled }, }); }; const handleUpdateRole = (domain: string, role: number) => { updateMutation.mutate({ domain, data: { defaultRole: role }, }); setEditingDomain(null); }; const handleDelete = () => { if (deletingDomain) { deleteMutation.mutate(deletingDomain); } }; const settingsHeader = (

{t`Self-Signup Domains`}

); if (manifestLoading || isLoading) { return (
{settingsHeader}

{t`Loading...`}

); } // Show message when external auth is configured if (isExternalAuth) { return (
{settingsHeader}

{t`User access is managed by an external provider (${manifest?.authMode}). Self-signup domain settings are not available when using external authentication.`}

); } if (error) { return (
{settingsHeader}

{error instanceof Error ? error.message : t`Failed to load allowed domains`}

); } return (
{settingsHeader} {/* Status message */} {saveStatus && (
{saveStatus.type === "success" ? ( ) : ( )} {saveStatus.message}
)} {/* Domains Section */}

{t`Allowed Domains`}

{t`Users with email addresses from these domains can sign up without an invite. They will be assigned the specified role automatically.`}

{/* Domain list */} {domains && domains.length > 0 ? (
{domains.map((domain) => (
handleToggleEnabled(domain)} disabled={updateMutation.isPending} />
{domain.domain}
{t`Default role:`} {getRoleLabel(domain.defaultRole)}
))}
) : (
{t`No domains configured. Users must be invited individually.`}
)} {/* Add domain section */}
{isAddingDomain ? (

{t`Add an allowed domain`}

setNewDomain(e.target.value)} />
) : ( )}
{/* Edit Domain Dialog */} !open && setEditingDomain(null)} >
{t`Edit Domain`} {t`Update settings for ${editingDomain?.domain}`}
( )} />
{/* Delete Confirmation */} !open && setDeletingDomain(null)} disablePointerDismissal > {t`Remove Domain?`} {t`Users from`} {deletingDomain}{" "} {t`will no longer be able to sign up without an invite. Existing users are not affected.`}
( )} /> ( )} />
); } export default AllowedDomainsSettings;