/** * CardActionModal — CARD Asset Action Modal * * Shows the full CARD owner record for an asset and allows * confirm/decline/redirect operations with full context. */ import React, { useState, useEffect } from 'react'; import { X, Loader, AlertCircle, CheckCircle, ArrowRightLeft, XCircle } from 'lucide-react'; // ⚠️ CONVENTION: Removed unused `Shield` import to satisfy no-unused-vars lint rule const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api'; const OVERLAY = { position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.75)', backdropFilter: 'blur(4px)', zIndex: 10200, display: 'flex', alignItems: 'center', justifyContent: 'center', }; const MODAL = { background: 'linear-gradient(135deg, #1E293B, #0F172A)', borderRadius: '1rem', border: '1px solid rgba(124, 58, 237, 0.25)', width: '90vw', maxWidth: '600px', maxHeight: '85vh', overflow: 'auto', padding: '1.5rem', position: 'relative', }; const SECTION = { background: 'rgba(15, 23, 42, 0.6)', border: '1px solid rgba(51, 65, 85, 0.5)', borderRadius: '0.5rem', padding: '0.75rem', marginBottom: '0.75rem', }; const LABEL = { fontSize: '0.6rem', color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '0.2rem' }; const VALUE = { fontSize: '0.75rem', color: '#E2E8F0', fontFamily: "'JetBrains Mono', monospace" }; const TEAM_BADGE = (color) => ({ display: 'inline-block', padding: '0.15rem 0.5rem', borderRadius: '0.25rem', fontSize: '0.7rem', fontWeight: '600', fontFamily: 'monospace', background: `${color}15`, border: `1px solid ${color}40`, color, }); const INPUT = { width: '100%', boxSizing: 'border-box', background: 'rgba(15, 23, 42, 0.8)', border: '1px solid rgba(51, 65, 85, 0.6)', borderRadius: '0.375rem', color: '#E2E8F0', padding: '0.5rem 0.75rem', fontSize: '0.75rem', fontFamily: "'JetBrains Mono', monospace", outline: 'none', }; const BTN = { padding: '0.5rem 1.25rem', borderRadius: '0.375rem', border: 'none', fontSize: '0.75rem', fontWeight: '600', cursor: 'pointer', transition: 'all 0.12s', }; export default function CardActionModal({ isOpen, onClose, item, initialAction, cardTeams, onSuccess }) { const [ownerData, setOwnerData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [action, setAction] = useState(initialAction || 'confirm'); const [teamName, setTeamName] = useState(''); const [fromTeam, setFromTeam] = useState(''); const [toTeam, setToTeam] = useState(''); const [comment, setComment] = useState(''); const [executing, setExecuting] = useState(false); const [execError, setExecError] = useState(null); // Fetch owner data when modal opens useEffect(() => { if (!isOpen || !item?.ip_address) return; setLoading(true); setError(null); setOwnerData(null); setExecError(null); fetch(`${API_BASE}/card/owner-lookup/${encodeURIComponent(item.ip_address)}`, { credentials: 'include' }) .then(r => { if (!r.ok) return r.json().then(d => { throw new Error(d.error || `HTTP ${r.status}`); }); return r.json(); }) .then(data => { setOwnerData(data); // Pre-fill team fields based on owner data if (data.confirmed) { setTeamName(data.confirmed.name || ''); setFromTeam(data.confirmed.name || ''); } else if (data.unconfirmed) { setTeamName(data.unconfirmed.name || ''); setFromTeam(data.unconfirmed.name || ''); } setLoading(false); }) .catch(err => { setError(err.message); setLoading(false); }); }, [isOpen, item]); // Reset action when initialAction changes useEffect(() => { if (initialAction) setAction(initialAction); }, [initialAction]); const handleExecute = async () => { setExecuting(true); setExecError(null); try { let url, body; if (action === 'confirm') { url = `${API_BASE}/card/queue/${item.id}/confirm`; body = { teamName: teamName.trim(), assetId: item.ip_address, comment: comment.trim() }; } else if (action === 'decline') { url = `${API_BASE}/card/queue/${item.id}/decline`; body = { teamName: teamName.trim(), assetId: item.ip_address, comment: comment.trim() }; } else if (action === 'redirect') { url = `${API_BASE}/card/queue/${item.id}/redirect`; body = { fromTeam: fromTeam.trim(), toTeam: toTeam.trim(), assetId: item.ip_address }; } const res = await fetch(url, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); const data = await res.json(); if (!res.ok) { setExecError(data.error || data.message || `${action} failed.`); setExecuting(false); return; } setExecuting(false); if (onSuccess) onSuccess(item.id, action); onClose(); } catch (err) { setExecError(err.message || 'Network error.'); setExecuting(false); } }; if (!isOpen) return null; const canExecute = () => { if (action === 'confirm' || action === 'decline') return teamName.trim().length > 0; if (action === 'redirect') return fromTeam.trim().length > 0 && toTeam.trim().length > 0; return false; }; return (