import { AnimatePresence, motion } from 'framer-motion'; import { AlertTriangle, Check, ChevronRight, X } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; import { getDomainColor, getSeverityColor } from '../../lib/command/utils'; import type { CommandAction } from '../types'; interface CommandActionsProps { actions: CommandAction[]; onActionResolved?: (id: string) => void; } type ActionState = 'pending' | 'confirming' | 'resolving' | 'done' | 'error'; async function resolveAction(id: string): Promise { const res = await fetch(`/api/command/actions/${encodeURIComponent(id)}/resolve`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, }); if (!res.ok) { throw new Error(`Failed to resolve action: ${res.status}`); } } export function CommandActions({ actions: serverActions, onActionResolved }: CommandActionsProps) { const [localActions, setLocalActions] = useState(serverActions); const [states, setStates] = useState>({}); const statesRef = useRef(states); statesRef.current = states; useEffect(() => { setLocalActions((prev) => { const currentStates = statesRef.current; const inFlightIds = new Set( prev .filter((a) => { const s = currentStates[a.id] ?? 'pending'; return s === 'confirming' || s === 'resolving'; }) .map((a) => a.id), ); const merged = [...serverActions]; for (const a of prev) { if (inFlightIds.has(a.id) && !merged.some((m) => m.id === a.id)) { merged.push(a); } } return merged; }); }, [serverActions]); const requestConfirm = (id: string) => { setStates((prev) => ({ ...prev, [id]: 'confirming' })); }; const cancelConfirm = (id: string) => { setStates((prev) => ({ ...prev, [id]: 'pending' })); }; const confirm = async (id: string) => { setStates((prev) => ({ ...prev, [id]: 'resolving' })); try { await resolveAction(id); setStates((prev) => ({ ...prev, [id]: 'done' })); onActionResolved?.(id); setTimeout(() => { setLocalActions((prev) => prev.filter((a) => a.id !== id)); setStates((prev) => { const next = { ...prev }; delete next[id]; return next; }); }, 1500); } catch { setStates((prev) => ({ ...prev, [id]: 'error' })); setTimeout(() => { setStates((prev) => ({ ...prev, [id]: 'pending' })); }, 2000); } }; if (localActions.length === 0) { return (

Required Actions

All actions resolved
); } return (

Required Actions

{localActions.map((action) => { const state: ActionState = states[action.id] ?? 'pending'; const severityColor = getSeverityColor(action.priority); const domainColor = getDomainColor(action.domain); const isInFlight = state === 'resolving'; return (
{action.domain.toUpperCase()} / {action.priority} {action.text}
{state === 'done' ? (
Done
) : state === 'error' ? (
Failed — retry
) : state === 'confirming' || state === 'resolving' ? ( <>
Confirm?
) : ( )}
); })}
); }