"use client"; import React, { useState, useEffect } from "react"; import { ChevronRight, Database, Settings, Activity, Search, ArrowLeft, Lock } from "lucide-react"; import Modal from "./ui/Modal"; export default function NodeConfigModal({ node, isOpen, onClose, onUpdateNode }) { const [step, setStep] = useState(1); const [search, setSearch] = useState(""); const [selectedAction, setSelectedAction] = useState(null); const [fieldValues, setFieldValues] = useState({}); const [fieldOptions, setFieldOptions] = useState({}); const [loadingOptions, setLoadingOptions] = useState({}); const [isTesting, setIsTesting] = useState(false); const [testResponse, setTestResponse] = useState(null); const [credentials, setCredentials] = useState([]); const [isLoadingCredentials, setIsLoadingCredentials] = useState(false); const [isAddingCredential, setIsAddingCredential] = useState(false); const [newCredential, setNewCredential] = useState({ name: "", data: {} }); const [isSavingCredential, setIsSavingCredential] = useState(false); useEffect(() => { if (isOpen) { setStep(1); setSearch(""); setTestResponse(null); if (node?.data?.actionId) { // Find action in metadata based on node.data.actionId let foundAction = null; if (node.appData?.categories) { node.appData.categories.forEach(cat => { const act = cat.actions.find(a => a.id === node.data.actionId); if (act) foundAction = act; }); } else if (node.appData?.actions) { foundAction = node.appData.actions.find(a => a.id === node.data.actionId); } setSelectedAction(foundAction); setFieldValues(node.data?.config || {}); } else { setSelectedAction(null); setFieldValues({}); } } }, [isOpen, node]); useEffect(() => { if (isOpen && node?.appData?.id) { const fetchCredentials = async () => { setIsLoadingCredentials(true); try { const response = await fetch(`${window.wpbotAutomator.apiUrl}/credentials?app_id=${node.appData.id}`, { headers: { 'X-WP-Nonce': window.wpbotAutomator.nonce }, }); const data = await response.json(); if (Array.isArray(data)) setCredentials(data); } catch (error) { console.error("Failed to fetch credentials:", error); } finally { setIsLoadingCredentials(false); } }; setIsAddingCredential(false); setNewCredential({ name: "", data: {} }); fetchCredentials(); } }, [isOpen, node?.appData?.id]); const handleSaveCredential = async () => { try { if (!newCredential.name) { alert("Validation Error: Please enter a typical Name for this credential."); return; } setIsSavingCredential(true); const payload = { name: newCredential.name, app_id: node.appData.id, data: newCredential.data }; const response = await fetch(`${window.wpbotAutomator.apiUrl}/credentials`, { method: "POST", headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': window.wpbotAutomator.nonce }, body: JSON.stringify(payload) }); const result = await response.json(); if (result.success) { setCredentials(prev => [...prev, result.credential]); if (selectedAction) { const credField = selectedAction.fields?.find(f => f.type === 'credential'); if (credField) handleFieldChange(credField.id, result.credential.id); } setIsAddingCredential(false); setNewCredential({ name: "", data: {} }); alert("Credential saved successfully!"); } else { alert("Failed to save credential on server: " + (result.message || JSON.stringify(result))); } } catch (error) { console.error("Error saving credential:", error); alert("Error saving credential. Check your browser console: " + error.message); } finally { setIsSavingCredential(false); } }; useEffect(() => { if (selectedAction?.fields) { selectedAction.fields.forEach(field => { if (field.type === 'select' && field.optionsUrl && !fieldOptions[field.id]) { fetchOptions(field); } }); } }, [selectedAction]); const fetchOptions = async (field) => { setLoadingOptions(prev => ({ ...prev, [field.id]: true })); try { const baseUrl = window.wpbotAutomator.apiUrl.replace(/\/$/, ''); const response = await fetch(`${baseUrl}${field.optionsUrl}`, { headers: { 'X-WP-Nonce': window.wpbotAutomator.nonce, }, }); const data = await response.json(); setFieldOptions(prev => ({ ...prev, [field.id]: Array.isArray(data) ? data : [] })); } catch (error) { console.error(`Error fetching options for ${field.id}:`, error); } finally { setLoadingOptions(prev => ({ ...prev, [field.id]: false })); } }; if (!node) return null; const handleSelectAction = (action) => { setSelectedAction(action); setStep(2); }; const handleSave = () => { onUpdateNode({ ...node, subtitle: selectedAction ? selectedAction.name : "Configure action", data: { ...node.data, actionId: selectedAction?.id, config: fieldValues } }); onClose(); }; const handleFieldChange = (fieldId, value) => { setFieldValues(prev => ({ ...prev, [fieldId]: value })); }; const handleTestRun = async () => { setIsTesting(true); setTestResponse(null); try { const response = await fetch(`${window.wpbotAutomator.apiUrl}/test-action`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': window.wpbotAutomator.nonce, }, body: JSON.stringify({ appId: node.appData.id, actionId: selectedAction.id, config: fieldValues, }), }); const data = await response.json(); setTestResponse(data); } catch (error) { console.error("Test Run error:", error); setTestResponse({ success: false, message: "Request failed. Check your connection or URL." }); } finally { setIsTesting(false); } }; const renderStep1 = () => { const categories = node.appData?.categories || (node.appData?.actions ? [{ id: "main", name: "Actions", actions: node.appData.actions }] : []); return (
setSearch(e.target.value)} />
{categories.map((category) => { const filteredActions = category.actions.filter(a => a.name.toLowerCase().includes(search.toLowerCase()) ); if (filteredActions.length === 0) return null; return (

{category.name}

{filteredActions.map((action) => { const isLocked = action.isPro && !window.wpbotAutomator.isProActive; return ( ); })}
); })}
); }; // Renders one labeled input per column of the selected table. // fieldOptions[tableIdField] is the /tables array already fetched for the table select. const TableColumnsMapper = ({ field }) => { const tableId = fieldValues[field.tableIdField] || ''; const allTables = fieldOptions[field.tableIdField] || []; const selected = allTables.find(t => String(t.id) === String(tableId)); const columns = selected?.columns || []; let current = {}; try { current = JSON.parse(fieldValues[field.id] || '{}') || {}; } catch {} const update = (slug, val) => { const next = { ...current, [slug]: val }; handleFieldChange(field.id, JSON.stringify(next)); }; if (!tableId) return (
Select a table above to see its fields.
); if (columns.length === 0) return (
{allTables.length === 0 ? 'Loading table columns…' : 'This table has no columns.'}
); return (
{columns.map(col => (

{col.name}{col.required && *}

{col.type}

update(col.slug, e.target.value)} />
))}

Use {'{token}'} to map values from the trigger.

); }; const KeyValueField = ({ values, onChange, placeholder }) => { const pairs = values ? (typeof values === 'string' ? JSON.parse(values) : values) : [{ key: '', value: '' }]; const updatePair = (index, k, v) => { const newPairs = [...pairs]; newPairs[index] = { ...newPairs[index], key: k, value: v }; onChange(newPairs); }; const addPair = () => { onChange([...pairs, { key: '', value: '' }]); }; const removePair = (index) => { if (pairs.length === 1) { onChange([{ key: '', value: '' }]); } else { const newPairs = pairs.filter((_, i) => i !== index); onChange(newPairs); } }; return (
{pairs.map((pair, index) => (
updatePair(index, e.target.value, pair.value)} /> updatePair(index, pair.key, e.target.value)} />
))}
); }; const renderStep2 = () => (

{selectedAction?.name}

Configure the details for this action

{selectedAction?.description}

{node.type === 'trigger' && selectedAction?.id === 'incoming_webhook' && (

Use this URL to send GET or POST requests to trigger this workflow.

)} {node.type === 'trigger' && selectedAction?.id === 'facebook_lead_ads' && (

Use this URL in your Facebook Developer App configuration.

)}
{selectedAction?.fields ? (
{selectedAction.fields.map(field => { const fieldLocked = field.isPro && !window.wpbotAutomator?.isProActive; return (
{fieldLocked ? (
{field.placeholder || 'Pro feature'}

Upgrade to Pro to unlock this field.

) : field.type === 'credential' ? (
{!isAddingCredential ? (
) : (

Add New Credential

setNewCredential(prev => ({ ...prev, name: e.target.value }))} />
{node.appData?.credentialConfig?.fields?.map(cField => (
{cField.type === 'textarea' ? (