Ctrl K
ComunidadGuías…

src/features/account/planned-pages.tsx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import type { AccessRequestApproval, Agent, DecideAccessRequest } from '@shared/api/agent-api'
import type { OrganizationAccessLevel } from '@shared/organization-access'
import { Link, useNavigate } from '@tanstack/react-router'
import { Plus } from 'lucide-react'
import { type FormEvent, type ReactNode, useEffect, useState } from 'react'
import { DestructiveConfirmation } from '@/components/destructive-confirmation'
import { Field, SelectInput, TextInput } from '@/components/product-form'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import type { AccountOrganizationTeam } from '@/lib/api/account'
import {
  acceptAccountOrganizationInvitation,
  activateAgent,
  addAccountOrganizationTeamMember,
  cancelAccountOrganizationInvitation,
  createAccountOrganization,
  createAccountOrganizationTeam,
  deactivateAgent,
  decideAccountAgentResourceRequest,
  deleteAccountOrganization,
  deleteAccountOrganizationTeam,
  deleteAgent,
  inviteAccountOrganizationMember,
  leaveAccountOrganization,
  rejectAccountOrganizationInvitation,
  removeAccountOrganizationMember,
  removeAccountOrganizationTeamMember,
  revokeAccountApplicationAuthorization,
  updateAccountOrganization,
  updateAccountOrganizationMemberRole,
  updateAccountOrganizationTeam,
} from '@/lib/api/account'
import { toLocalDateTimeValue } from '@/lib/date-time'
import { tt } from '@/lib/i18n'
import * as Drawer from './account-drawer'
import { AccountAccessNavigation } from './account-navigation'
import {
  AccountEmptyState,
  AccountObjectSection,
  AccountPageHeader,
  AccountRow,
  AccountRows,
  AccountSectionContent,
  AccountTabContent,
  AccountTabs,
} from './account-page'
import { AccountSurface } from './account-surface'
import { AgentEnrollmentGuide } from './agent-enrollment-guide'
import { IdentityMark } from './identity-mark'
import { DestructiveConfirmationDialog, useDestructiveConfirmation } from './primitives'
import {
  accountQueryKeys,
  useAccountAccessRequests,
  useAccountAgents,
  useAccountApplicationAuthorizations,
  useAccountMutation,
  useAccountOrganization,
  useAccountOrganizationInvitations,
  useAccountOrganizationRoles,
  useAccountOrganizations,
  useAccountOrganizationTeamMembers,
  useAccountOrganizationTeams,
} from './queries'
import type { AccountApplicationAuthorization } from './types'
import { formatDate } from './utils'

export function AccountApplicationsPage() {
  const applicationsQuery = useAccountApplicationAuthorizations(true)
  const mutate = useAccountMutation()
  const [selected, setSelected] = useState<AccountApplicationAuthorization | null>(null)
  const [confirmation, setConfirmation] = useDestructiveConfirmation()
  const applications = applicationsQuery.data?.items ?? []
  return (
    <AccountSurface>
      {() => (
        <>
          <AccountPageHeader
            description={tt('Review applications authorized to access your Realmroot identity.')}
            title={tt('Access management')}
          />
          <AccountAccessNavigation section="applications" />
          <AccountObjectSection
            description={tt('Provider accounts used by Realmroot and Agents are managed separately in Connections.')}
            surface
            title={tt('Authorized applications')}
          >
            {applicationsQuery.isLoading ? (
              <p className="text-sm text-muted-foreground">{tt('Loading authorized applications…')}</p>
            ) : null}
            {applicationsQuery.error ? (
              <p className="text-sm text-destructive" role="alert">
                {applicationsQuery.error instanceof Error
                  ? applicationsQuery.error.message
                  : tt('Unable to load authorized applications.')}
              </p>
            ) : null}
            {!applicationsQuery.isLoading && !applicationsQuery.error ? (
              <AccountRows>
                {applications.map((application) => (
                  <AccountRow
                    action={
                      <Button onClick={() => setSelected(application)} variant="outline">
                        {tt('Review')}
                      </Button>
                    }
                    description={tt('Authorized {{date}}', { date: formatDate(application.grantedAt) })}
                    key={application.id}
                    label={
                      <span className="accountIdentityLabel">
                        <IdentityMark name={application.application.name} application />
                        <span>{application.application.name}</span>
                      </span>
                    }
                    value={<code>{application.scopes.join(' ')}</code>}
                  />
                ))}
                {!applications.length ? (
                  <AccountEmptyState
                    description={tt('Applications you approve will appear here.')}
                    title={tt('No authorized applications')}
                  />
                ) : null}
              </AccountRows>
            ) : null}
          </AccountObjectSection>
          <ApplicationReviewDialog
            application={selected}
            onClose={() => setSelected(null)}
            onRevoke={(application) => {
              setConfirmation({
                title: tt('Revoke access to {{application}}?', { application: application.application.name }),
                description: tt(
                  'This application will lose access to your identity immediately. You may be asked to authorize it again the next time you use it.',
                ),
                actionLabel: tt('Revoke access'),
                onConfirm: async () => {
                  let failed = false
                  await mutate(
                    'Application access revoked.',
                    () => revokeAccountApplicationAuthorization(application.id),
                    {
                      invalidate: [accountQueryKeys.applications],
                      onError: () => {
                        failed = true
                      },
                    },
                  )
                  if (!failed) setSelected(null)
                },
              })
            }}
          />
          <DestructiveConfirmationDialog confirmation={confirmation} onClose={() => setConfirmation(null)} />
        </>
      )}
    </AccountSurface>
  )
}

function ApplicationReviewDialog({
  application,
  onClose,
  onRevoke,
}: {
  application: AccountApplicationAuthorization | null
  onClose: () => void
  onRevoke: (application: AccountApplicationAuthorization) => void
}) {
  return (
    <Drawer.Dialog onOpenChange={(open) => !open && onClose()} open={application !== null}>
      <Drawer.DialogContent>
        <Drawer.DialogHeader>
          <Drawer.DialogTitle>{tt('Application authorization')}</Drawer.DialogTitle>
          <Drawer.DialogDescription>
            {tt('Review the identity access this application can use on your behalf.')}
          </Drawer.DialogDescription>
        </Drawer.DialogHeader>
        {application ? (
          <AccountRows className="accountDetailFacts">
            <div className="accountDrawerIdentity">
              <IdentityMark name={application.application.name} application />
              <h2>{application.application.name}</h2>
            </div>
            <AccountRow label={tt('Application')} value={application.application.name} />
            <AccountRow label={tt('Authorized')} value={formatDate(application.grantedAt)} />
            <AccountRow
              label={tt('Expires')}
              value={application.expiresAt ? formatDate(application.expiresAt) : tt('Never')}
            />
            <AccountRow label={tt('Scopes')} value={<code>{application.scopes.join(' ')}</code>} />
          </AccountRows>
        ) : null}
        <Drawer.DialogFooter>
          <Button onClick={onClose} variant="outline">
            {tt('Close')}
          </Button>
          {application ? (
            <Button onClick={() => onRevoke(application)} variant="destructive">
              {tt('Revoke access')}
            </Button>
          ) : null}
        </Drawer.DialogFooter>
      </Drawer.DialogContent>
    </Drawer.Dialog>
  )
}

export function AccountAgentsPage() {
  const [tab, setTab] = useState('identities')
  const [selected, setSelected] = useState<Agent | null>(null)
  const [request, setRequest] = useState<AccessRequestApproval | null>(null)
  const agentsQuery = useAccountAgents()
  const requestsQuery = useAccountAccessRequests()
  const mutate = useAccountMutation()
  const [confirmation, setConfirmation] = useDestructiveConfirmation()
  const agents = agentsQuery.data?.items ?? []
  const requests = requestsQuery.data?.items ?? []
  return (
    <AccountSurface>
      {() => (
        <>
          <AccountPageHeader
            description={tt('Review Agent identities you control and approve new resource access requests.')}
            title={tt('Agents')}
            action={<AgentEnrollmentGuide />}
          />
          <AccountTabs
            onValueChange={setTab}
            tabs={[
              { value: 'identities', label: tt('My Agents') },
              { value: 'requests', label: tt('Requests · {{count}}', { count: requests.length }) },
              { value: 'activity', label: tt('Activity') },
            ]}
            value={tab}
          >
            <AccountTabContent surface value="identities">
              {agentsQuery.isLoading ? <p role="status">{tt('Loading Agents…')}</p> : null}
              {agentsQuery.error ? (
                <p role="alert" className="text-destructive">
                  {agentsQuery.error.message}
                </p>
              ) : null}
              <AccountRows>
                {agents.map((agent) => (
                  <AccountRow
                    action={
                      <Button onClick={() => setSelected(agent)} variant="outline">
                        {tt('Manage')}
                      </Button>
                    }
                    key={agent.id}
                    label={
                      <span className="accountIdentityLabel">
                        <IdentityMark name={agent.name} />
                        <span>
                          <span>{agent.name}</span>
                          <small>{agent.subject}</small>
                        </span>
                      </span>
                    }
                    value={
                      <Badge variant={agent.status === 'active' ? 'secondary' : 'outline'}>
                        {tt(agent.status === 'active' ? 'Enabled' : 'Disabled')}
                      </Badge>
                    }
                  />
                ))}
                {!agentsQuery.isLoading && !agentsQuery.error && !agents.length ? (
                  <AccountEmptyState
                    description={tt('Agent identities you control will appear here.')}
                    title={tt('No Agent identities')}
                  />
                ) : null}
              </AccountRows>
            </AccountTabContent>
            <AccountTabContent surface value="requests">
              {requestsQuery.isLoading ? <p role="status">{tt('Loading requests…')}</p> : null}
              {requestsQuery.error ? (
                <p role="alert" className="text-destructive">
                  {requestsQuery.error.message}
                </p>
              ) : null}
              <AccountRows>
                {requests.map((item) => (
                  <AccountRow
                    action={<Button onClick={() => setRequest(item)}>{tt('Review request')}</Button>}
                    description={tt('{{resource}} · {{scopes}}', {
                      resource: item.authorizationDetail?.name ?? item.resourceServer.name,
                      scopes: item.scopes.join(' '),
                    })}
                    key={item.id}
                    label={item.agent.name}
                    value={
                      <Badge className="bg-amber-50 text-amber-800" variant="secondary">
                        {tt(item.status)}
                      </Badge>
                    }
                  />
                ))}
                {!requestsQuery.isLoading && !requestsQuery.error && !requests.length ? (
                  <AccountEmptyState
                    description={tt('New resource access requests will appear here.')}
                    title={tt('No pending requests')}
                  />
                ) : null}
              </AccountRows>
            </AccountTabContent>
            <AccountTabContent surface value="activity">
              <AccountEmptyState
                description={tt('Agent activity will appear here when events are available.')}
                title={tt('No Agent activity')}
              />
            </AccountTabContent>
          </AccountTabs>
          <AgentDialog
            agent={selected}
            onClose={() => setSelected(null)}
            onDelete={async (agent) => {
              setConfirmation({
                title: tt('Delete {{agent}}?', { agent: agent.name }),
                description: tt(
                  'The Agent disappears from every interface. Hosts and active resource access stop immediately, and it cannot be restored.',
                ),
                actionLabel: tt('Delete Agent'),
                onConfirm: async () => {
                  let failed = false
                  await mutate('Agent deleted.', () => deleteAgent(agent.id), {
                    invalidate: [accountQueryKeys.agents],
                    onError: () => {
                      failed = true
                    },
                  })
                  if (!failed) setSelected(null)
                },
              })
            }}
            onStatusChange={async (agent) => {
              await mutate(
                agent.status === 'active' ? 'Agent deactivated.' : 'Agent activated.',
                () => (agent.status === 'active' ? deactivateAgent(agent.id) : activateAgent(agent.id)),
                { invalidate: [accountQueryKeys.agents] },
              )
            }}
          />
          <DestructiveConfirmationDialog confirmation={confirmation} onClose={() => setConfirmation(null)} />
          <AgentRequestDialog
            onClose={() => setRequest(null)}
            onDecision={async (item, input) => {
              let failed = false
              await mutate(
                input.decision === 'approve' ? 'Request approved.' : 'Request denied.',
                () => decideAccountAgentResourceRequest(item.id, input),
                {
                  invalidate: [accountQueryKeys.accessRequests],
                  onError: () => {
                    failed = true
                  },
                },
              )
              if (!failed) setRequest(null)
            }}
            request={request}
          />
        </>
      )}
    </AccountSurface>
  )
}

export function AgentDialog({
  agent,
  onClose,
  onDelete,
  onStatusChange,
}: {
  agent: Agent | null
  onClose: () => void
  onDelete: (agent: Agent) => void
  onStatusChange: (agent: Agent) => void
}) {
  return (
    <Drawer.Dialog onOpenChange={(open) => !open && onClose()} open={agent !== null}>
      <Drawer.DialogContent className="sm:max-w-lg">
        <Drawer.DialogHeader>
          <Drawer.DialogTitle>{agent?.name}</Drawer.DialogTitle>
          <Drawer.DialogDescription>{agent?.id}</Drawer.DialogDescription>
        </Drawer.DialogHeader>
        {agent ? (
          <AccountRows className="rounded-lg border px-2">
            <AccountRow label={tt('Stable subject')} value={<code>{agent.subject}</code>} />
            <AccountRow label={tt('Issuer')} value={<code>{agent.issuer}</code>} />
            <AccountRow label={tt('Status')} value={tt(agent.status)} />
            <AccountRow label={tt('Created')} value={formatDate(agent.createdAt)} />
            <AccountRow label={tt('Last updated')} value={formatDate(agent.updatedAt)} />
          </AccountRows>
        ) : null}
        <Drawer.DialogFooter>
          <Button onClick={onClose} variant="outline">
            {tt('Close')}
          </Button>
          {agent ? (
            <>
              <Button onClick={() => onStatusChange(agent)} variant="outline">
                {tt(agent.status === 'active' ? 'Deactivate Agent' : 'Activate Agent')}
              </Button>
              <Button onClick={() => onDelete(agent)} variant="destructive">
                {tt('Delete Agent')}
              </Button>
            </>
          ) : null}
        </Drawer.DialogFooter>
      </Drawer.DialogContent>
    </Drawer.Dialog>
  )
}

export function AgentRequestDialog({
  onClose,
  onDecision,
  request,
}: {
  onClose: () => void
  onDecision: (request: AccessRequestApproval, input: DecideAccessRequest) => void
  request: AccessRequestApproval | null
}) {
  const [mode, setMode] = useState<'once' | 'until' | 'persistent'>('once')
  const [expiresAt, setExpiresAt] = useState('')
  const requestId = request?.id
  useEffect(() => {
    if (!requestId) return
    setMode('once')
    setExpiresAt('')
  }, [requestId])
  const expiryIsValid = mode !== 'until' || (expiresAt.length > 0 && new Date(expiresAt).getTime() > Date.now())
  return (
    <Drawer.Dialog onOpenChange={(next) => !next && onClose()} open={request !== null}>
      <Drawer.DialogContent className="sm:max-w-md">
        <Drawer.DialogHeader>
          <Drawer.DialogTitle>{tt('Review Agent access request')}</Drawer.DialogTitle>
          <Drawer.DialogDescription>
            {tt('Confirm the Agent, resource, permissions, and access duration before deciding.')}
          </Drawer.DialogDescription>
        </Drawer.DialogHeader>
        {request ? (
          <div className="grid gap-4">
            <dl className="grid divide-y">
              <div className="grid gap-1 pb-3">
                <dt className="text-xs text-muted-foreground">{tt('Agent')}</dt>
                <dd className="font-medium">{request.agent.name}</dd>
                <dd className="break-all font-mono text-xs text-muted-foreground">{request.agent.id}</dd>
              </div>
              <div className="grid gap-1 py-3">
                <dt className="text-xs text-muted-foreground">{tt('Resource server')}</dt>
                <dd className="font-medium">{request.authorizationDetail?.name ?? request.resourceServer.name}</dd>
                <dd className="break-all font-mono text-xs text-muted-foreground">{request.resourceServer.id}</dd>
              </div>
              <div className="grid grid-cols-2 gap-4 pt-3">
                <div className="grid content-start gap-1">
                  <dt className="text-xs text-muted-foreground">{tt('Permissions')}</dt>
                  <dd className="break-words font-mono text-xs">{request.scopes.join(' ')}</dd>
                </div>
                <div className="grid content-start gap-1">
                  <dt className="text-xs text-muted-foreground">{tt('Request expires')}</dt>
                  <dd className="text-xs">{new Date(request.expiresAt).toLocaleString()}</dd>
                </div>
              </div>
              {request.authorizationDetails.length > 0 ? (
                <div className="grid gap-1 pt-3">
                  <dt className="text-xs text-muted-foreground">{tt('Authorization context')}</dt>
                  <dd className="whitespace-pre-wrap break-all font-mono text-xs">
                    {request.authorizationDetails.map((detail) => JSON.stringify(detail)).join('\n')}
                  </dd>
                </div>
              ) : null}
            </dl>
            <Field label={tt('Access duration')}>
              <SelectInput
                name="access-duration"
                onChange={(event) => setMode(event.target.value as typeof mode)}
                value={mode}
              >
                <option value="once">{tt('One use')}</option>
                <option value="until">{tt('Until a date and time')}</option>
                <option value="persistent">{tt('Until revoked')}</option>
              </SelectInput>
            </Field>
            {mode === 'until' ? (
              <Field label={tt('Expiry date and time')}>
                <TextInput
                  aria-invalid={expiresAt.length > 0 && !expiryIsValid}
                  min={toLocalDateTimeValue()}
                  name="expires-at"
                  onChange={(event) => setExpiresAt(event.target.value)}
                  required
                  type="datetime-local"
                  value={expiresAt}
                />
              </Field>
            ) : null}
          </div>
        ) : null}
        <Drawer.DialogFooter>
          <Button onClick={onClose} variant="outline">
            {tt('Cancel')}
          </Button>
          {request ? (
            <Button onClick={() => onDecision(request, { decision: 'deny' })} variant="destructive">
              {tt('Deny')}
            </Button>
          ) : null}
          {request ? (
            <Button
              disabled={!expiryIsValid}
              onClick={() =>
                onDecision(request, {
                  decision: 'approve',
                  mode,
                  authorizationDetails: request.authorizationDetails,
                  ...(mode === 'until' ? { expiresAt: new Date(expiresAt).toISOString() } : {}),
                })
              }
            >
              {tt('Approve')}
            </Button>
          ) : null}
        </Drawer.DialogFooter>
      </Drawer.DialogContent>
    </Drawer.Dialog>
  )
}

export function AccountOrganizationsPage() {
  const organizationsQuery = useAccountOrganizations()
  const invitationsQuery = useAccountOrganizationInvitations()
  const mutate = useAccountMutation()
  const [createOpen, setCreateOpen] = useState(false)
  const organizations = organizationsQuery.data ?? []
  const invitations = (invitationsQuery.data ?? []).filter((invitation) => invitation.status === 'pending')
  return (
    <AccountSurface>
      {(_profile, access) => (
        <>
          <AccountPageHeader
            action={
              access.canCreateOrganization ? (
                <Button onClick={() => setCreateOpen(true)} size="sm">
                  <Plus />
                  {tt('New organization')}
                </Button>
              ) : undefined
            }
            description={tt('Create shared spaces and manage the organizations where you belong.')}
            title={tt('Organizations')}
          />
          {invitations.length ? (
            <AccountObjectSection surface title={tt('Invitations')}>
              <AccountRows>
                {invitations.map((invitation) => (
                  <AccountRow
                    action={
                      <div className="flex gap-2">
                        <Button
                          onClick={() =>
                            mutate('Invitation declined.', () => rejectAccountOrganizationInvitation(invitation.id), {
                              invalidate: [accountQueryKeys.organizationInvitations],
                            })
                          }
                          variant="outline"
                        >
                          {tt('Decline')}
                        </Button>
                        <Button
                          onClick={() =>
                            mutate('Invitation accepted.', () => acceptAccountOrganizationInvitation(invitation.id), {
                              invalidate: [accountQueryKeys.organizationInvitations, accountQueryKeys.organizations],
                            })
                          }
                        >
                          {tt('Accept')}
                        </Button>
                      </div>
                    }
                    description={tt('Invited {{date}} · expires {{expires}}', {
                      date: formatDate(invitation.createdAt),
                      expires: formatDate(invitation.expiresAt),
                    })}
                    key={invitation.id}
                    label={invitation.organizationName}
                    value={organizationAccessLevelLabel(organizationAccessLevel(invitation.role))}
                  />
                ))}
              </AccountRows>
            </AccountObjectSection>
          ) : null}
          {organizations.length ? (
            <div className="accountEntityList">
              {organizations.map((organization) => (
                <section className="accountObjectSection accountOrganizationCard is-surface" key={organization.id}>
                  <header>
                    <OrganizationName id={organization.id} name={organization.name} />
                  </header>
                  <AccountRows>
                    <AccountRow label={tt('Slug')} value={<code>{organization.slug}</code>} />
                    <AccountRow
                      action={<OrganizationActions id={organization.id} />}
                      label={tt('Created')}
                      value={formatDate(organization.createdAt)}
                    />
                  </AccountRows>
                </section>
              ))}
            </div>
          ) : null}
          {!organizationsQuery.isLoading && !organizations.length ? (
            <AccountEmptyState
              description={tt('Create an organization or accept an invitation to get started.')}
              title={tt('No organizations yet')}
            />
          ) : null}
          {access.canCreateOrganization ? (
            <NewOrganizationDialog
              onClose={() => setCreateOpen(false)}
              onCreate={async (input) => {
                let failed = false
                await mutate('Organization created.', () => createAccountOrganization(input), {
                  invalidate: [accountQueryKeys.organizations],
                  onError: () => {
                    failed = true
                  },
                })
                if (!failed) setCreateOpen(false)
              }}
              open={createOpen}
            />
          ) : null}
        </>
      )}
    </AccountSurface>
  )
}

function OrganizationName({ id, name }: { id: string; name: string }) {
  return (
    <div className="min-w-0">
      <strong className="block truncate">{name}</strong>
      <span className="block truncate font-mono text-xs text-muted-foreground">{id}</span>
    </div>
  )
}

function OrganizationActions({ id }: { id: string }) {
  return (
    <div className="flex justify-end">
      <Button asChild variant="outline">
        <Link params={{ organizationId: id }} to="/organizations/$organizationId">
          {tt('Manage')}
        </Link>
      </Button>
    </div>
  )
}

function NewOrganizationDialog({
  onClose,
  onCreate,
  open,
}: {
  onClose: () => void
  onCreate: (input: { name: string; slug: string }) => void
  open: boolean
}) {
  const [name, setName] = useState('')
  const [slug, setSlug] = useState('')
  const [slugEdited, setSlugEdited] = useState(false)
  function submit(event: FormEvent) {
    event.preventDefault()
    const trimmedName = name.trim()
    const trimmedSlug = slug.trim()
    if (!trimmedName || !trimmedSlug) return
    onCreate({ name: trimmedName, slug: trimmedSlug })
    setName('')
    setSlug('')
    setSlugEdited(false)
  }
  return (
    <Dialog onOpenChange={(next) => !next && onClose()} open={open}>
      <DialogContent>
        <form onSubmit={submit}>
          <DialogHeader>
            <DialogTitle>{tt('New organization')}</DialogTitle>
            <DialogDescription>
              {tt('Create a shared identity and authorization context. You become its Owner.')}
            </DialogDescription>
          </DialogHeader>
          <div className="grid gap-4 py-5">
            <Field label={tt('Organization name')}>
              <TextInput
                name="name"
                onChange={(event) => {
                  const nextName = event.target.value
                  setName(nextName)
                  if (!slugEdited) setSlug(organizationSlug(nextName))
                }}
                required
                value={name}
              />
            </Field>
            <Field label={tt('Slug')}>
              <TextInput
                name="slug"
                onChange={(event) => {
                  setSlug(event.target.value.toLowerCase())
                  setSlugEdited(true)
                }}
                pattern="[a-z0-9]+(?:-[a-z0-9]+)*"
                placeholder="payments-team"
                required
                value={slug}
              />
            </Field>
          </div>
          <DialogFooter>
            <Button onClick={onClose} type="button" variant="outline">
              {tt('Cancel')}
            </Button>
            <Button type="submit">{tt('Create organization')}</Button>
          </DialogFooter>
        </form>
      </DialogContent>
    </Dialog>
  )
}

function organizationSlug(name: string) {
  return name
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '')
}

export function AccountOrganizationDetailPage({
  content,
  organizationId,
  section = 'overview',
}: {
  content?: ReactNode
  organizationId: string
  section?:
    | 'overview'
    | 'members'
    | 'teams'
    | 'roles'
    | 'applications'
    | 'resource-servers'
    | 'webhooks'
    | 'activity'
    | 'settings'
}) {
  const navigate = useNavigate()
  const organizationQuery = useAccountOrganization(organizationId)
  const organizationRolesQuery = useAccountOrganizationRoles(organizationId, section === 'members')
  const mutate = useAccountMutation()
  const [editOpen, setEditOpen] = useState(false)
  const [inviteOpen, setInviteOpen] = useState(false)
  const [selectedMember, setSelectedMember] = useState<OrganizationMemberRow | null>(null)
  const [selectedInvitation, setSelectedInvitation] = useState<OrganizationInvitationRow | null>(null)
  const [confirmation, setConfirmation] = useState<'leave' | 'delete' | null>(null)
  const [membershipConfirmation, setMembershipConfirmation] = useState<
    | { type: 'remove-member'; member: OrganizationMemberRow }
    | { type: 'cancel-invitation'; invitation: OrganizationInvitationRow }
    | null
  >(null)
  const organization = organizationQuery.data
  return (
    <AccountSurface>
      {(profile) => {
        if (organizationQuery.isLoading)
          return <p className="text-sm text-muted-foreground">{tt('Loading Organization…')}</p>
        if (organizationQuery.error || !organization)
          return (
            <p className="text-sm text-destructive" role="alert">
              {organizationQuery.error instanceof Error
                ? organizationQuery.error.message
                : tt('Organization not found.')}
            </p>
          )
        const membership = organization.members.find((member) => member.userId === profile.id)
        const accessLevel = organizationAccessLevel(membership?.role)
        const canManageOrganization = organizationMemberRoles(membership?.role).some(
          (role) => role === 'owner' || role === 'admin',
        )
        const pendingInvitations = organization.invitations.filter((invitation) => invitation.status === 'pending')
        if (content) return content
        return (
          <>
            <AccountPageHeader
              description={tt('Manage members, shared authority, and Organization settings.')}
              title={organization.name}
            />
            <div className="accountTabs">
              {section === 'overview' ? (
                <AccountSectionContent surface>
                  <AccountRows>
                    <AccountRow
                      description={tt('Controls Organization administration, not business API authority.')}
                      label={tt('Your access level')}
                      value={organizationAccessLevelLabel(accessLevel)}
                    />
                    <AccountRow label={tt('Organization ID')} value={<code>{organization.id}</code>} />
                    <AccountRow label={tt('Slug')} value={<code>{organization.slug}</code>} />
                    <AccountRow label={tt('Members')} value={String(organization.members.length)} />
                    <AccountRow label={tt('Pending invitations')} value={String(pendingInvitations.length)} />
                    <AccountRow label={tt('Created')} value={formatDate(organization.createdAt)} />
                  </AccountRows>
                </AccountSectionContent>
              ) : null}
              {section === 'members' ? (
                <AccountSectionContent surface>
                  <div className="accountTabBody">
                    {canManageOrganization ? (
                      <div className="accountTabToolbar">
                        <Button onClick={() => setInviteOpen(true)} size="sm">
                          <Plus />
                          {tt('Invite member')}
                        </Button>
                      </div>
                    ) : null}
                    <OrganizationMembersTable
                      canManage={canManageOrganization}
                      currentUserId={profile.id}
                      invitations={pendingInvitations}
                      members={organization.members}
                      onInvitationSelect={setSelectedInvitation}
                      onMemberSelect={setSelectedMember}
                    />
                  </div>
                </AccountSectionContent>
              ) : null}
              {section === 'teams' ? (
                <OrganizationTeamsPanel
                  canManage={canManageOrganization}
                  members={organization.members}
                  organizationId={organization.id}
                />
              ) : null}
              {section === 'roles' ? (
                <AccountSectionContent surface>
                  <AccountObjectSection
                    description={tt('Better Auth Organization Roles assigned to your membership.')}
                    title={tt('Your Organization Roles')}
                  >
                    <AccountRows>
                      <AccountRow
                        description={tt('Roles are resolved to scopes for this Organization only.')}
                        label={tt('Assigned Roles')}
                        value={<code>{membership?.role ?? 'member'}</code>}
                      />
                    </AccountRows>
                  </AccountObjectSection>
                </AccountSectionContent>
              ) : null}
              {section === 'settings' ? (
                <AccountSectionContent surface>
                  <AccountRows>
                    <AccountRow
                      action={
                        canManageOrganization ? (
                          <Button onClick={() => setEditOpen(true)} variant="outline">
                            {tt('Edit')}
                          </Button>
                        ) : null
                      }
                      label={tt('Organization profile')}
                      value={organization.name}
                    />
                    <AccountRow
                      action={
                        accessLevel !== 'owner' ? (
                          <Button onClick={() => setConfirmation('leave')} variant="destructive">
                            {tt('Leave')}
                          </Button>
                        ) : null
                      }
                      description={
                        accessLevel === 'owner'
                          ? tt('Transfer ownership before leaving this Organization.')
                          : tt('Your Organization-scoped access stops immediately.')
                      }
                      label={tt('Leave organization')}
                      value={organizationAccessLevelLabel(accessLevel)}
                    />
                    {accessLevel === 'owner' ? (
                      <AccountRow
                        action={
                          <Button onClick={() => setConfirmation('delete')} variant="destructive">
                            {tt('Delete')}
                          </Button>
                        }
                        description={tt('Permanently delete this Organization after resolving its dependencies.')}
                        label={tt('Delete organization')}
                        value={tt('Permanent')}
                      />
                    ) : null}
                  </AccountRows>
                </AccountSectionContent>
              ) : null}
            </div>
            <EditOrganizationDialog
              onClose={() => setEditOpen(false)}
              onSave={async (input) => {
                let failed = false
                await mutate('Organization updated.', () => updateAccountOrganization(organization.id, input), {
                  invalidate: [accountQueryKeys.organizations, [...accountQueryKeys.organizations, organization.id]],
                  onError: () => {
                    failed = true
                  },
                })
                if (!failed) setEditOpen(false)
              }}
              open={editOpen}
              organization={organization}
            />
            <InviteOrganizationMemberDialog
              onClose={() => setInviteOpen(false)}
              onInvite={async (input) => {
                let failed = false
                await mutate('Invitation sent.', () => inviteAccountOrganizationMember(organization.id, input), {
                  invalidate: [[...accountQueryKeys.organizations, organization.id]],
                  onError: () => {
                    failed = true
                  },
                })
                if (!failed) setInviteOpen(false)
              }}
              open={inviteOpen}
              roleError={organizationRolesQuery.error instanceof Error ? organizationRolesQuery.error.message : null}
              roleLoading={organizationRolesQuery.isLoading}
              roles={organizationRolesQuery.data?.items ?? []}
            />
            <OrganizationMemberDialog
              member={selectedMember}
              onClose={() => setSelectedMember(null)}
              onRemove={(member) => {
                setSelectedMember(null)
                setMembershipConfirmation({ type: 'remove-member', member })
              }}
              onSave={async (member, role) => {
                let failed = false
                const roles = [
                  ...organizationMemberRoles(member.role).filter(
                    (assigned) => !['owner', 'admin', 'developer', 'member'].includes(assigned),
                  ),
                  role,
                ].sort()
                await mutate(
                  'Access level updated.',
                  () => updateAccountOrganizationMemberRole(organization.id, member.id, roles),
                  {
                    invalidate: [[...accountQueryKeys.organizations, organization.id]],
                    onError: () => {
                      failed = true
                    },
                  },
                )
                if (!failed) setSelectedMember(null)
              }}
            />
            <OrganizationInvitationDialog
              invitation={selectedInvitation}
              onCancel={(invitation) => {
                setSelectedInvitation(null)
                setMembershipConfirmation({ type: 'cancel-invitation', invitation })
              }}
              onClose={() => setSelectedInvitation(null)}
            />
            <DestructiveConfirmation
              confirmLabel={
                membershipConfirmation?.type === 'remove-member' ? tt('Remove member') : tt('Cancel invitation')
              }
              description={
                membershipConfirmation?.type === 'remove-member'
                  ? tt('This member immediately loses Organization access and Organization-scoped authority.')
                  : tt('This invitation can no longer be accepted. You can send a new invitation later.')
              }
              onClose={() => setMembershipConfirmation(null)}
              onConfirm={async () => {
                const action = membershipConfirmation
                if (!action) return
                let failed = false
                await mutate(
                  action.type === 'remove-member' ? 'Member removed.' : 'Invitation canceled.',
                  async () => {
                    if (action.type === 'remove-member') {
                      await removeAccountOrganizationMember(organization.id, action.member.id)
                    } else {
                      await cancelAccountOrganizationInvitation(action.invitation.id)
                    }
                  },
                  {
                    invalidate: [[...accountQueryKeys.organizations, organization.id]],
                    onError: () => {
                      failed = true
                    },
                  },
                )
                if (!failed) setMembershipConfirmation(null)
              }}
              open={membershipConfirmation !== null}
              title={
                membershipConfirmation?.type === 'remove-member'
                  ? tt('Remove {{name}}?', { name: membershipConfirmation.member.user.name })
                  : tt('Cancel invitation for {{email}}?', {
                      email:
                        membershipConfirmation?.type === 'cancel-invitation'
                          ? membershipConfirmation.invitation.email
                          : '',
                    })
              }
            />
            <DestructiveConfirmation
              confirmLabel={confirmation === 'delete' ? tt('Delete organization') : tt('Leave organization')}
              description={
                confirmation === 'delete'
                  ? tt('This permanently removes the Organization after its dependencies are resolved.')
                  : tt('Your membership and Organization-scoped access stop immediately.')
              }
              onClose={() => setConfirmation(null)}
              onConfirm={async () => {
                const action = confirmation
                if (!action) return
                let failed = false
                await mutate(
                  action === 'delete' ? 'Organization deleted.' : 'Organization left.',
                  async () => {
                    if (action === 'delete') await deleteAccountOrganization(organization.id)
                    else await leaveAccountOrganization(organization.id)
                  },
                  {
                    invalidateExact: [accountQueryKeys.organizations],
                    onError: () => {
                      failed = true
                    },
                  },
                )
                if (!failed) await navigate({ to: '/organizations' })
              }}
              open={confirmation !== null}
              title={
                confirmation === 'delete'
                  ? tt('Delete {{name}}?', { name: organization.name })
                  : tt('Leave {{name}}?', { name: organization.name })
              }
            />
          </>
        )
      }}
    </AccountSurface>
  )
}

function OrganizationTeamsPanel({
  canManage,
  members,
  organizationId,
}: {
  canManage: boolean
  members: OrganizationMemberRow[]
  organizationId: string
}) {
  const teamsQuery = useAccountOrganizationTeams(organizationId)
  const mutate = useAccountMutation()
  const [editingTeam, setEditingTeam] = useState<AccountOrganizationTeam | 'create' | null>(null)
  const [managedTeam, setManagedTeam] = useState<AccountOrganizationTeam | null>(null)
  const [deleteTeam, setDeleteTeam] = useState<AccountOrganizationTeam | null>(null)
  const [memberToAdd, setMemberToAdd] = useState('')
  const [teamMembersPage, setTeamMembersPage] = useState(1)
  const teamMembersPageSize = 20
  const teamMembersQuery = useAccountOrganizationTeamMembers(organizationId, managedTeam?.id ?? null, {
    page: teamMembersPage,
    pageSize: teamMembersPageSize,
  })
  const memberByUserId = new Map(members.map((member) => [member.userId, member]))
  const teamMemberships = teamMembersQuery.data?.items ?? []
  const teamMembersPagination = teamMembersQuery.data?.pagination
  const invalidateTeams = [accountQueryKeys.organizationTeams(organizationId)] as const
  const closeMemberManagement = () => {
    setManagedTeam(null)
    setMemberToAdd('')
    setTeamMembersPage(1)
  }

  return (
    <AccountSectionContent surface>
      <div className="accountTabBody">
        {canManage ? (
          <div className="accountTabToolbar">
            <Button onClick={() => setEditingTeam('create')} size="sm">
              <Plus />
              {tt('Create Team')}
            </Button>
          </div>
        ) : null}
        {teamsQuery.isLoading ? <p className="text-sm text-muted-foreground">{tt('Loading Teams…')}</p> : null}
        {teamsQuery.error ? (
          <p className="text-sm text-destructive" role="alert">
            {teamsQuery.error.message}
          </p>
        ) : null}
        <AccountRows>
          {(teamsQuery.data ?? []).map((team) => (
            <AccountRow
              action={
                canManage ? (
                  <div className="flex flex-wrap gap-2">
                    <Button
                      onClick={() => {
                        setManagedTeam(team)
                        setMemberToAdd('')
                        setTeamMembersPage(1)
                      }}
                      size="sm"
                      variant="outline"
                    >
                      {tt('Members')}
                    </Button>
                    <Button onClick={() => setEditingTeam(team)} size="sm" variant="outline">
                      {tt('Rename')}
                    </Button>
                    <Button onClick={() => setDeleteTeam(team)} size="sm" variant="destructive">
                      {tt('Delete')}
                    </Button>
                  </div>
                ) : null
              }
              description={tt('Issued verbatim as an OIDC group for members of this Team.')}
              key={team.id}
              label={team.name}
              value={<code>{team.name}</code>}
            />
          ))}
          {!teamsQuery.isLoading && !teamsQuery.data?.length ? (
            <AccountRow
              description={tt('Teams are created explicitly; Realmroot does not create a default Team.')}
              label={tt('No Teams')}
              value="—"
            />
          ) : null}
        </AccountRows>
      </div>
      <TeamNameDialog
        onClose={() => setEditingTeam(null)}
        onSave={async (name) => {
          const current = editingTeam
          if (!current) return
          let failed = false
          await mutate(
            current === 'create' ? 'Team created.' : 'Team renamed.',
            () =>
              current === 'create'
                ? createAccountOrganizationTeam(organizationId, name)
                : updateAccountOrganizationTeam(current.id, name),
            {
              invalidate: [...invalidateTeams],
              onError: () => {
                failed = true
              },
            },
          )
          if (!failed) setEditingTeam(null)
        }}
        team={editingTeam}
      />
      <Dialog onOpenChange={(open) => !open && closeMemberManagement()} open={managedTeam !== null}>
        <DialogContent className="sm:max-w-xl">
          <DialogHeader>
            <DialogTitle>{tt('Manage {{name}} members', { name: managedTeam?.name ?? '' })}</DialogTitle>
            <DialogDescription>{tt('Only Organization members can be added to this OIDC group.')}</DialogDescription>
          </DialogHeader>
          <form
            className="grid gap-3 p-4 pb-0 sm:grid-cols-[1fr_auto] sm:items-end"
            onSubmit={(event) => {
              event.preventDefault()
              const team = managedTeam
              if (!team || !memberToAdd) return
              let failed = false
              void mutate(
                'Team member added.',
                () => addAccountOrganizationTeamMember(organizationId, team.id, memberToAdd),
                {
                  invalidate: [accountQueryKeys.organizationTeamMembers(team.id)],
                  onError: () => {
                    failed = true
                  },
                },
              ).then(() => {
                if (!failed) {
                  setMemberToAdd('')
                  setTeamMembersPage(1)
                }
              })
            }}
          >
            <Field label={tt('Organization member')}>
              <SelectInput
                disabled={members.length === 0}
                onChange={(event) => setMemberToAdd(event.target.value)}
                value={memberToAdd}
              >
                <option disabled value="">
                  {tt('Select a member')}
                </option>
                {members.map((member) => (
                  <option key={member.userId} value={member.userId}>
                    {member.user.name} ({member.user.email})
                  </option>
                ))}
              </SelectInput>
            </Field>
            <Button disabled={!memberToAdd} type="submit">
              {tt('Add member')}
            </Button>
          </form>
          <div className="grid max-h-[42vh] gap-2 overflow-y-auto p-4">
            {teamMembersQuery.isLoading ? (
              <p className="text-sm text-muted-foreground">{tt('Loading Team members…')}</p>
            ) : null}
            {teamMembersQuery.error ? (
              <p className="text-sm text-destructive" role="alert">
                {teamMembersQuery.error.message}
              </p>
            ) : null}
            {!teamMembersQuery.isLoading && !teamMembersQuery.error && teamMemberships.length === 0 ? (
              <p className="text-sm text-muted-foreground">{tt('This Team has no members.')}</p>
            ) : null}
            {teamMemberships.map((membership) => {
              const member = memberByUserId.get(membership.userId)
              return (
                <div className="flex items-center justify-between gap-3 rounded-lg border p-3" key={membership.id}>
                  <span className="min-w-0">
                    <strong className="block truncate text-sm">{member?.user.name ?? membership.userId}</strong>
                    {member ? <small className="text-muted-foreground">{member.user.email}</small> : null}
                  </span>
                  <Button
                    disabled={teamMembersQuery.isPlaceholderData}
                    onClick={() => {
                      const team = managedTeam
                      if (!team) return
                      const moveToPreviousPage = teamMemberships.length === 1 && teamMembersPage > 1
                      let failed = false
                      void mutate(
                        'Team member removed.',
                        () => removeAccountOrganizationTeamMember(organizationId, team.id, membership.userId),
                        {
                          invalidate: [accountQueryKeys.organizationTeamMembers(team.id)],
                          onError: () => {
                            failed = true
                          },
                        },
                      ).then(() => {
                        if (!failed && moveToPreviousPage) {
                          setTeamMembersPage(Math.max(1, teamMembersPage - 1))
                        }
                      })
                    }}
                    size="sm"
                    variant="outline"
                  >
                    {tt('Remove')}
                  </Button>
                </div>
              )
            })}
            {teamMembersPagination && teamMembersPagination.totalPages > 1 ? (
              <div className="flex items-center justify-between gap-3 pt-2">
                <p className="text-xs text-muted-foreground">
                  {tt('{{start}}–{{end}} of {{total}}', {
                    start: (teamMembersPagination.page - 1) * teamMembersPagination.pageSize + 1,
                    end: Math.min(
                      teamMembersPagination.page * teamMembersPagination.pageSize,
                      teamMembersPagination.totalItems,
                    ),
                    total: teamMembersPagination.totalItems,
                  })}
                </p>
                <div className="flex gap-2">
                  <Button
                    disabled={teamMembersQuery.isPlaceholderData || teamMembersPagination.page === 1}
                    onClick={() => setTeamMembersPage(Math.max(1, teamMembersPagination.page - 1))}
                    size="sm"
                    type="button"
                    variant="outline"
                  >
                    {tt('Previous')}
                  </Button>
                  <Button
                    disabled={
                      teamMembersQuery.isPlaceholderData ||
                      teamMembersPagination.page >= teamMembersPagination.totalPages
                    }
                    onClick={() => setTeamMembersPage(teamMembersPagination.page + 1)}
                    size="sm"
                    type="button"
                    variant="outline"
                  >
                    {tt('Next')}
                  </Button>
                </div>
              </div>
            ) : null}
          </div>
          <DialogFooter>
            <Button onClick={closeMemberManagement} variant="outline">
              {tt('Done')}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
      <DestructiveConfirmation
        confirmLabel={tt('Delete Team')}
        description={tt('New Tokens will stop containing this group. Update downstream RBAC mappings if needed.')}
        onClose={() => setDeleteTeam(null)}
        onConfirm={async () => {
          if (!deleteTeam) return
          let failed = false
          await mutate('Team deleted.', () => deleteAccountOrganizationTeam(organizationId, deleteTeam.id), {
            invalidate: [...invalidateTeams],
            onError: () => {
              failed = true
            },
          })
          if (!failed) setDeleteTeam(null)
        }}
        open={deleteTeam !== null}
        title={tt('Delete {{name}}?', { name: deleteTeam?.name ?? '' })}
      />
    </AccountSectionContent>
  )
}

function TeamNameDialog({
  onClose,
  onSave,
  team,
}: {
  onClose: () => void
  onSave: (name: string) => void
  team: AccountOrganizationTeam | 'create' | null
}) {
  const [error, setError] = useState<string | null>(null)
  const existing = team && team !== 'create' ? team : null
  return (
    <Dialog onOpenChange={(open) => !open && onClose()} open={team !== null}>
      <DialogContent>
        <form
          onSubmit={(event) => {
            event.preventDefault()
            const name = String(new FormData(event.currentTarget).get('name') ?? '').trim()
            if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) {
              setError(tt('Use lowercase letters, numbers, and single hyphens.'))
              return
            }
            setError(null)
            onSave(name)
          }}
        >
          <DialogHeader>
            <DialogTitle>{existing ? tt('Rename Team') : tt('Create Team')}</DialogTitle>
            <DialogDescription>
              {existing
                ? tt('Renaming changes the OIDC group in newly issued Tokens; synchronize downstream RBAC first.')
                : tt('The Team name is issued verbatim as the OIDC group identifier.')}
            </DialogDescription>
          </DialogHeader>
          <div className="p-4">
            <Field help={tt('Lowercase kebab-case, for example platform-admins.')} label={tt('Team name')}>
              <TextInput defaultValue={existing?.name ?? ''} name="name" pattern="[a-z0-9]+(?:-[a-z0-9]+)*" required />
            </Field>
            {error ? (
              <p className="mt-2 text-sm text-destructive" role="alert">
                {error}
              </p>
            ) : null}
          </div>
          <DialogFooter>
            <Button onClick={onClose} type="button" variant="outline">
              {tt('Cancel')}
            </Button>
            <Button type="submit">{existing ? tt('Rename Team') : tt('Create Team')}</Button>
          </DialogFooter>
        </form>
      </DialogContent>
    </Dialog>
  )
}

type OrganizationMemberRow = {
  id: string
  userId: string
  role: string
  createdAt: Date
  user: { id: string; name: string; email: string; image?: string | null }
}
type OrganizationInvitationRow = {
  id: string
  email: string
  role: string
  status: string
  createdAt: Date
  expiresAt: Date
}

function OrganizationMembersTable({
  canManage,
  currentUserId,
  invitations,
  members,
  onInvitationSelect,
  onMemberSelect,
}: {
  canManage: boolean
  currentUserId: string
  invitations: OrganizationInvitationRow[]
  members: OrganizationMemberRow[]
  onInvitationSelect: (invitation: OrganizationInvitationRow) => void
  onMemberSelect: (member: OrganizationMemberRow) => void
}) {
  return (
    <div className="rounded-xl border">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>{tt('Member')}</TableHead>
            <TableHead>{tt('Access level')}</TableHead>
            <TableHead>{tt('Status')}</TableHead>
            <TableHead>{tt('Added')}</TableHead>
            {canManage ? (
              <TableHead>
                <span className="sr-only">{tt('Actions')}</span>
              </TableHead>
            ) : null}
          </TableRow>
        </TableHeader>
        <TableBody>
          {members.map((member) => (
            <TableRow key={member.id}>
              <TableCell>
                <strong>{member.user.name}</strong>
                <span className="block text-xs text-muted-foreground">{member.user.email}</span>
              </TableCell>
              <TableCell>{organizationAccessLevelLabel(organizationAccessLevel(member.role))}</TableCell>
              <TableCell>
                <Badge variant="secondary">{tt('Active')}</Badge>
              </TableCell>
              <TableCell>{formatDate(member.createdAt)}</TableCell>
              {canManage ? (
                <TableCell className="text-right">
                  {member.userId !== currentUserId ? (
                    <Button onClick={() => onMemberSelect(member)} size="sm" variant="ghost">
                      {tt('Manage')}
                    </Button>
                  ) : null}
                </TableCell>
              ) : null}
            </TableRow>
          ))}
          {invitations.map((invitation) => (
            <TableRow key={invitation.id}>
              <TableCell>
                <strong>{invitation.email}</strong>
                <span className="block text-xs text-muted-foreground">
                  {tt('Invitation expires {{date}}', { date: formatDate(invitation.expiresAt) })}
                </span>
              </TableCell>
              <TableCell>{organizationAccessLevelLabel(organizationAccessLevel(invitation.role))}</TableCell>
              <TableCell>
                <Badge variant="outline">{tt(invitation.status)}</Badge>
              </TableCell>
              <TableCell>{formatDate(invitation.createdAt)}</TableCell>
              {canManage ? (
                <TableCell className="text-right">
                  <Button onClick={() => onInvitationSelect(invitation)} size="sm" variant="ghost">
                    {tt('Review')}
                  </Button>
                </TableCell>
              ) : null}
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  )
}

function organizationMemberRoles(role?: string | null) {
  return (role ?? '')
    .split(',')
    .map((value) => value.trim())
    .filter(Boolean)
}

function organizationAccessLevel(role?: string | null): OrganizationAccessLevel {
  return (organizationMemberRoles(role).find((value) => ['owner', 'admin', 'developer', 'member'].includes(value)) ??
    'member') as OrganizationAccessLevel
}

function organizationAccessLevelLabel(role?: OrganizationAccessLevel) {
  if (role === 'owner') return tt('Owner')
  if (role === 'admin') return tt('Administrator')
  if (role === 'developer') return tt('Developer')
  return tt('Member')
}

function OrganizationMemberDialog({
  member,
  onClose,
  onRemove,
  onSave,
}: {
  member: OrganizationMemberRow | null
  onClose: () => void
  onRemove: (member: OrganizationMemberRow) => void
  onSave: (member: OrganizationMemberRow, role: OrganizationAccessLevel) => void
}) {
  return (
    <Dialog
      onOpenChange={(open) => {
        if (!open) onClose()
      }}
      open={member !== null}
    >
      <DialogContent>
        {member ? (
          <form
            onSubmit={(event) => {
              event.preventDefault()
              const form = new FormData(event.currentTarget)
              onSave(member, String(form.get('role')) as OrganizationAccessLevel)
            }}
          >
            <DialogHeader>
              <DialogTitle>{tt('Manage {{name}}', { name: member.user.name })}</DialogTitle>
              <DialogDescription>{member.user.email}</DialogDescription>
            </DialogHeader>
            <div className="grid gap-4 py-5">
              <Field label={tt('Access level')}>
                <SelectInput defaultValue={organizationAccessLevel(member.role)} name="role">
                  <option value="owner">{tt('Owner')}</option>
                  <option value="admin">{tt('Administrator')}</option>
                  <option value="developer">{tt('Developer')}</option>
                  <option value="member">{tt('Member')}</option>
                </SelectInput>
              </Field>
            </div>
            <DialogFooter className="sm:justify-between">
              <Button onClick={() => onRemove(member)} type="button" variant="destructive">
                {tt('Remove member')}
              </Button>
              <div className="flex gap-2">
                <Button onClick={onClose} type="button" variant="outline">
                  {tt('Cancel')}
                </Button>
                <Button type="submit">{tt('Save access level')}</Button>
              </div>
            </DialogFooter>
          </form>
        ) : null}
      </DialogContent>
    </Dialog>
  )
}

function OrganizationInvitationDialog({
  invitation,
  onCancel,
  onClose,
}: {
  invitation: OrganizationInvitationRow | null
  onCancel: (invitation: OrganizationInvitationRow) => void
  onClose: () => void
}) {
  return (
    <Dialog
      onOpenChange={(open) => {
        if (!open) onClose()
      }}
      open={invitation !== null}
    >
      <DialogContent>
        <DialogHeader>
          <DialogTitle>{tt('Pending invitation')}</DialogTitle>
          <DialogDescription>
            {tt('Review the recipient and access level before canceling this invitation.')}
          </DialogDescription>
        </DialogHeader>
        {invitation ? (
          <AccountRows className="rounded-lg border px-2">
            <AccountRow label={tt('Email')} value={invitation.email} />
            <AccountRow
              label={tt('Access level')}
              value={organizationAccessLevelLabel(organizationAccessLevel(invitation.role))}
            />
            <AccountRow label={tt('Expires')} value={formatDate(invitation.expiresAt)} />
          </AccountRows>
        ) : null}
        <DialogFooter>
          <Button onClick={onClose} variant="outline">
            {tt('Close')}
          </Button>
          {invitation ? (
            <Button onClick={() => onCancel(invitation)} variant="destructive">
              {tt('Cancel invitation')}
            </Button>
          ) : null}
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

function EditOrganizationDialog({
  onClose,
  onSave,
  open,
  organization,
}: {
  onClose: () => void
  onSave: (input: { name: string; slug: string }) => void
  open: boolean
  organization: { name: string; slug: string }
}) {
  return (
    <Dialog
      onOpenChange={(next) => {
        if (!next) onClose()
      }}
      open={open}
    >
      <DialogContent>
        <form
          onSubmit={(event) => {
            event.preventDefault()
            const form = new FormData(event.currentTarget)
            onSave({ name: String(form.get('name')), slug: String(form.get('slug')) })
          }}
        >
          <DialogHeader>
            <DialogTitle>{tt('Edit organization')}</DialogTitle>
            <DialogDescription>
              {tt('Update the name and stable slug used to recognize this Organization.')}
            </DialogDescription>
          </DialogHeader>
          <div className="grid gap-4 py-5">
            <Field label={tt('Name')}>
              <TextInput defaultValue={organization.name} name="name" required />
            </Field>
            <Field label={tt('Slug')}>
              <TextInput defaultValue={organization.slug} name="slug" pattern="[a-z0-9]+(?:-[a-z0-9]+)*" required />
            </Field>
          </div>
          <DialogFooter>
            <Button onClick={onClose} type="button" variant="outline">
              {tt('Cancel')}
            </Button>
            <Button type="submit">{tt('Save changes')}</Button>
          </DialogFooter>
        </form>
      </DialogContent>
    </Dialog>
  )
}

function InviteOrganizationMemberDialog({
  onClose,
  onInvite,
  open,
  roleError,
  roleLoading,
  roles,
}: {
  onClose: () => void
  onInvite: (input: { email: string; roles: string[] }) => void
  open: boolean
  roleError: string | null
  roleLoading: boolean
  roles: { key: string; displayName: string }[]
}) {
  const [selectionError, setSelectionError] = useState<string | null>(null)
  return (
    <Dialog
      onOpenChange={(next) => {
        if (!next) onClose()
      }}
      open={open}
    >
      <DialogContent>
        <form
          onSubmit={(event) => {
            event.preventDefault()
            const form = new FormData(event.currentTarget)
            const selectedRoles = form.getAll('roles').map(String).sort()
            if (selectedRoles.length === 0) {
              setSelectionError(tt('Select at least one Role.'))
              return
            }
            setSelectionError(null)
            onInvite({
              email: String(form.get('email')),
              roles: selectedRoles,
            })
          }}
        >
          <DialogHeader>
            <DialogTitle>{tt('Invite member')}</DialogTitle>
            <DialogDescription>
              {tt('Invite a Realm user and choose the access level used to administer this Organization.')}
            </DialogDescription>
          </DialogHeader>
          <div className="grid gap-4 py-5">
            <Field label={tt('Email')}>
              <TextInput name="email" required type="email" />
            </Field>
            <Field label={tt('Roles')}>
              <div className="grid gap-2 rounded-md border p-3">
                {roles.map((role) => (
                  <label className="flex items-center gap-2 text-sm" key={role.key}>
                    <input defaultChecked={role.key === 'member'} name="roles" type="checkbox" value={role.key} />
                    {role.displayName}
                  </label>
                ))}
              </div>
            </Field>
            {roleLoading ? <p className="text-sm text-muted-foreground">{tt('Loading Roles…')}</p> : null}
            {roleError ? (
              <p className="text-sm text-destructive" role="alert">
                {roleError}
              </p>
            ) : null}
            {selectionError ? (
              <p className="text-sm text-destructive" role="alert">
                {selectionError}
              </p>
            ) : null}
          </div>
          <DialogFooter>
            <Button onClick={onClose} type="button" variant="outline">
              {tt('Cancel')}
            </Button>
            <Button disabled={roleLoading || Boolean(roleError) || roles.length === 0} type="submit">
              {tt('Send invitation')}
            </Button>
          </DialogFooter>
        </form>
      </DialogContent>
    </Dialog>
  )
}

export { AccountOverviewPage } from './overview-page'