oxios 1.10.1

Oxios Agent OS — Agent Operating System powered by oxi-sdk
import { Link } from '@tanstack/react-router'
import { AlertTriangle, Bell, CheckCircle, XCircle } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { useApproveApproval, usePendingApprovals, useRejectApproval } from '@/hooks/use-approvals'
import type { Approval } from '@/types'

/**
 * Approvals queue — full-width, shown on the dashboard.
 *
 * Each row offers inline Approve / Deny buttons that use optimistic
 * mutations. The card stays visible even when the queue is empty,
 * surfacing a positive "all clear" message so a user landing on the
 * dashboard can distinguish a healthy system from a failed query.
 * (RFC §5 originally called for hide-when-empty; the reviewer brief
 * explicitly flagged that as confusing.)
 */
export function ApprovalsQueue() {
  const { t } = useTranslation()
  const { items: pending, isLoading } = usePendingApprovals()
  const approve = useApproveApproval()
  const reject = useRejectApproval()

  const handleApprove = (id: string) => {
    approve.mutate(id, {
      onSuccess: () => toast.success(t('approvals.approveSuccess')),
      onError: (err) => toast.error(t('approvals.mutationError', { error: String(err) })),
    })
  }
  const handleDeny = (id: string) => {
    reject.mutate(id, {
      onSuccess: () => toast.success(t('approvals.rejectSuccess')),
      onError: (err) => toast.error(t('approvals.mutationError', { error: String(err) })),
    })
  }

  const empty = !isLoading && pending.length === 0

  return (
    <Card className={empty ? 'border-success/30' : 'border-warning/40'}>
      <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
        <CardTitle className="flex items-center gap-2 text-base">
          <Bell className={`h-4 w-4 ${empty ? 'text-success' : 'text-warning'}`} />
          {t('approvals.title')}
          <Badge variant={empty ? 'secondary' : 'warning'} className="ml-1">
            {pending.length}
          </Badge>
        </CardTitle>
        <Link
          to="/approvals"
          className="text-xs text-muted-foreground hover:text-foreground underline-offset-4 hover:underline"
        >
          {t('dashboard.viewAll')}
        </Link>
      </CardHeader>
      <CardContent className="pt-2">
        {isLoading ? (
          <p className="text-sm text-muted-foreground py-2">{t('common.loading')}</p>
        ) : empty ? (
          <div className="flex items-center gap-2 py-2 text-sm text-muted-foreground">
            <CheckCircle className="h-4 w-4 text-success" aria-hidden="true" />
            <span>{t('dashboard.approvalsAllClear')}</span>
          </div>
        ) : (
          <ul className="space-y-2">
            {pending.map((approval) => (
              <ApprovalRow
                key={approval.id}
                approval={approval}
                onApprove={handleApprove}
                onDeny={handleDeny}
                busy={approve.isPending || reject.isPending}
                approveLabel={t('approvals.approve')}
                denyLabel={t('approvals.deny')}
                riskLabel={t('dashboard.risk')}
              />
            ))}
          </ul>
        )}
      </CardContent>
    </Card>
  )
}

interface ApprovalRowProps {
  approval: Approval
  onApprove: (id: string) => void
  onDeny: (id: string) => void
  busy: boolean
  approveLabel: string
  denyLabel: string
  riskLabel: string
}

function ApprovalRow({
  approval,
  onApprove,
  onDeny,
  busy,
  approveLabel,
  denyLabel,
  riskLabel,
}: ApprovalRowProps) {
  const action = approval.action || ''
  const resource = approval.resource || ''
  const reason = approval.reason || action

  return (
    <li className="flex flex-wrap items-center gap-3 rounded-lg border bg-warning/5 p-3">
      <AlertTriangle className="h-4 w-4 shrink-0 text-warning" aria-hidden="true" />
      <div className="min-w-0 flex-1">
        <p className="text-sm font-medium text-foreground truncate" title={reason}>
          <span className="font-mono text-xs text-muted-foreground mr-1.5">{action}</span>
          {resource}
        </p>
        <p className="text-xs text-muted-foreground">
          {reason && reason !== action ? `${riskLabel}: ${reason} · ` : ''}
          {new Date(approval.created_at).toLocaleTimeString()}
        </p>
      </div>
      <div className="flex items-center gap-2 shrink-0">
        <Button
          size="sm"
          variant="outline"
          className="text-success border-success/40 hover:bg-success/10"
          onClick={() => onApprove(approval.id)}
          disabled={busy}
          aria-label={approveLabel}
        >
          <CheckCircle className="h-3.5 w-3.5 mr-1" /> {approveLabel}
        </Button>
        <Button
          size="sm"
          variant="outline"
          className="text-error border-error/40 hover:bg-error/10"
          onClick={() => onDeny(approval.id)}
          disabled={busy}
          aria-label={denyLabel}
        >
          <XCircle className="h-3.5 w-3.5 mr-1" /> {denyLabel}
        </Button>
      </div>
    </li>
  )
}