import { useMutation, useSuspenseQuery } from "@tanstack/react-query"; import { useNavigate } from "@tanstack/react-router"; import { Ban, Trash2 } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { deleteSsoInvitationMutation, deleteSsoProviderMutation, getSsoSettingsOptions, updateSsoProviderAutoLinkingMutation, } from "~/client/api-client/@tanstack/react-query.gen"; import type { GetSsoSettingsResponse } from "~/client/api-client/types.gen"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "~/client/components/ui/alert-dialog"; import { Alert, AlertDescription } from "~/client/components/ui/alert"; import { Button } from "~/client/components/ui/button"; import { Input } from "~/client/components/ui/input"; import { Label } from "~/client/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select"; import { Switch } from "~/client/components/ui/switch"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/client/components/ui/table"; import { useOrganizationContext } from "~/client/hooks/use-org-context"; import { useTimeFormat } from "~/client/lib/datetime"; import { getOrigin } from "~/client/functions/get-origin"; import { authClient } from "~/client/lib/auth-client"; import { cn } from "~/client/lib/utils"; import { useServerFn } from "@tanstack/react-start"; type InvitationRole = "member" | "admin" | "owner"; type Props = { initialSettings?: GetSsoSettingsResponse; initialOrigin?: string; }; export function SsoSettingsSection({ initialSettings, initialOrigin }: Props) { const originQuery = useServerFn(getOrigin); const { data } = useSuspenseQuery({ queryKey: ["app-origin"], queryFn: originQuery, initialData: initialOrigin }); const navigate = useNavigate(); const { formatDateWithMonth } = useTimeFormat(); const { activeOrganization } = useOrganizationContext(); const [inviteEmail, setInviteEmail] = useState(""); const [inviteRole, setInviteRole] = useState("member"); const ssoSettingsQuery = useSuspenseQuery({ ...getSsoSettingsOptions(), initialData: initialSettings }); const updateProviderAutoLinkingMutation = useMutation({ ...updateSsoProviderAutoLinkingMutation(), onSuccess: (_, v) => { toast.success(v.body?.enabled ? "Automatic account linking enabled" : "Automatic account linking disabled"); }, onError: (error) => { toast.error("Failed to update provider", { description: error.message }); }, }); const deleteProviderMutation = useMutation({ ...deleteSsoProviderMutation(), onSuccess: () => { toast.success("SSO provider deleted"); }, onError: (error) => { toast.error("Failed to delete provider", { description: error.message }); }, }); const inviteMemberMutation = useMutation({ mutationFn: async () => { if (!activeOrganization) { throw new Error("No active organization found in session"); } const normalizedEmail = inviteEmail.trim().toLowerCase(); if (!normalizedEmail) { throw new Error("Email is required"); } const { error } = await authClient.organization.inviteMember({ email: normalizedEmail, role: inviteRole, organizationId: activeOrganization.id, }); if (error) { throw error; } }, onSuccess: () => { toast.success("Invitation created"); setInviteEmail(""); setInviteRole("member"); }, onError: (error) => { toast.error("Failed to create invitation", { description: error.message }); }, }); const cancelInvitationMutation = useMutation({ mutationFn: async (invitationId: string) => { const { error } = await authClient.organization.cancelInvitation({ invitationId }); if (error) { throw error; } }, onSuccess: () => { toast.success("Invitation cancelled"); }, onError: (error) => { toast.error("Failed to cancel invitation", { description: error.message }); }, }); const deleteInvitationMutation = useMutation({ ...deleteSsoInvitationMutation(), onSuccess: () => { toast.success("Invitation deleted"); }, onError: (error) => { toast.error("Failed to delete invitation", { description: error.message }); }, }); const providers = ssoSettingsQuery.data.providers; const invitations = ssoSettingsQuery.data.invitations; return (

Registered providers

Manage identity providers used for organization sign-in.

Only enable automatic account linking for identity providers you trust. You can change this per provider at any time.
Provider ID Domain Issuer Type Auto-link existing account Callback URL Actions {providers.map((provider) => ( {provider.providerId} {provider.domain} {provider.issuer}
{provider.type}
{ updateProviderAutoLinkingMutation.mutate({ path: { providerId: provider.providerId }, body: { enabled }, }); }} /> {provider.autoLinkMatchingEmails ? "On" : "Off"}
e.currentTarget.select()} /> Delete SSO provider Are you sure you want to delete the SSO provider {provider.providerId}? This action cannot be undone. Cancel deleteProviderMutation.mutate({ path: { providerId: provider.providerId } })} > Delete
))} 0 })}> No providers registered yet.

Invite-only access

Users must be invited or already have an account before they can sign in using SSO.

setInviteEmail(event.target.value)} placeholder="teammate@example.com" disabled={!activeOrganization || inviteMemberMutation.isPending} />
Email Role Status Expires Actions {invitations.map((invitation) => ( {invitation.email} {invitation.role} {invitation.status} {formatDateWithMonth(invitation.expiresAt)} ))} 0 })}> No invitations yet.
); }