"use client" import { forwardRef, useState } from "react" import { cn } from "../../utils/cn" import { Button } from "../ui/button" import { Tag } from "../ui/tag" import { Ban, CheckCircle, XCircle } from "lucide-react" import { ApprovalStatusTag } from "./approval-batch-message" import type { ApprovalRequestMessageProps } from "./types" import type { ApprovalRequestField } from "./types/message.types" /** * Stacked label/value rows for the approval card's structured field * list. Labels are tiny uppercase muted text; values render as primary * text with `whitespace-pre-wrap` so multi-line descriptions * (`content`, `resolution`, etc.) keep their structure. Mirrored across * the pending + resolved branches so an approved ticket reads the same * way it did at decision time. */ function ApprovalFieldList({ fields }: { fields: ApprovalRequestField[] }) { return (
{fields.map((f, i) => (
{f.label}
{f.value}
))}
) } /** * Shared body for both pending and resolved branches of * ``. The pending card adds Approve/Reject * buttons below; the resolved card adds an Approved/Rejected ``. * Everything ABOVE the footer — command bar, icon, structured-fields * stack, explanation paragraph — is identical, so the body lives here * to prevent silent drift between the two render paths (a prior * version already had a `break-words` vs `break-all` mismatch on the * `` element from an out-of-sync copy-paste edit). */ function ApprovalCardBody({ data, }: { data: ApprovalRequestMessageProps['data'] }) { return (
{data.command} {data.icon && (
{data.icon}
)}
{data.fields && data.fields.length > 0 ? ( ) : ( data.explanation && (

{data.explanation}

) )}
) } const ApprovalRequestMessage = forwardRef( // `assistantType` is accepted for prop-parity with the batch card (so hosts // can forward it uniformly); the viewer variant is driven by `variant`. ({ className, data, onApprove, onReject, status = 'pending', assistantType: _assistantType, variant = 'admin', resolvedByName, showFooterActions = true, ...props }, ref) => { const [isProcessing, setIsProcessing] = useState(false) const handleApprove = async () => { setIsProcessing(true) try { await onApprove?.(data.requestId) } finally { setIsProcessing(false) } } const handleReject = async () => { setIsProcessing(true) try { await onReject?.(data.requestId) } finally { setIsProcessing(false) } } // CLIENT (Fae end-user) card — Figma 203-11947 "fae-approval-block". // Shows ONLY the BE-generated title (`explanation`) plus the actions row // or the full-text resolved pill; the raw command is never rendered. if (variant === 'client') { return (

{data.explanation?.trim() || "Approval required"}

{!showFooterActions ? null : status === 'pending' ? (
) : (
)}
) } return (
{!showFooterActions ? null : status === 'pending' ? (
) : (
{status === 'approved' ? ( } /> ) : status === 'cancelled' ? ( } /> ) : ( } /> )}
)}
) } ) ApprovalRequestMessage.displayName = "ApprovalRequestMessage" export { ApprovalRequestMessage }