import { useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { Link } from "react-router-dom"; import { AlertCircle, ArrowRight, ExternalLink, Inbox, KeyRound, Link2, Loader2, } from "lucide-react"; import type { CompanySecretProviderConfig, SecretProposalView } from "@paperclipai/shared"; import { secretsApi } from "../../api/secrets"; import { queryKeys } from "../../lib/queryKeys"; import { cn } from "../../lib/utils"; import { EmptyState } from "../../components/EmptyState"; import { SecretPathName } from "./SecretPathName"; import { AgentRefChip, DeliveryBadge, FingerprintChip, ProposalActions, ProposalJustification, ProposedBadge, bindingEnvKey, bindingSecretLabel, useProposalReview, } from "./proposal-review"; /** ISO expiry → "expires in 12d" / "expires in 5h" / "expired". */ function expiryLabel(expiresAt: string): { text: string; urgent: boolean } { const ms = new Date(expiresAt).getTime() - Date.now(); if (Number.isNaN(ms)) return { text: "no expiry", urgent: false }; if (ms <= 0) return { text: "expired", urgent: true }; const hours = Math.floor(ms / 3_600_000); if (hours < 24) return { text: `expires in ${hours}h`, urgent: true }; const days = Math.floor(hours / 24); return { text: `expires in ${days}d`, urgent: days <= 2 }; } function ProposalRow({ proposal, onApprove, onReject, disabled, }: { proposal: SecretProposalView; onApprove: (p: SecretProposalView) => void; onReject: (p: SecretProposalView) => void; disabled: boolean; }) { const isSecret = proposal.kind === "secret"; const expiry = expiryLabel(proposal.expiresAt); const secret = bindingSecretLabel(proposal); const envKey = bindingEnvKey(proposal); return (
{envKey || proposal.configPath}
Agents propose credentials and access bindings; you approve or reject them here. Proposed values are never shown — only a fingerprint and length.
{sorted.map((proposal) => (