PluginProbe
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services / 8.8.1
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services v8.8.1
8.8.1 8.8.0 8.7.9 8.7.8 8.7.7 8.7.6 8.7.5 8.7.4 8.7.3 8.7.2 8.7.1 8.7.0 8.6.9 8.6.8 8.6.7 8.6.6 8.6.5 8.6.4 8.6.2 8.6.1 8.6.0 8.5.9 8.5.8 8.5.7 8.5.6 All 537 releases
chatbot / addons / automator / src / App.js

App.js in WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services 8.8.1, at addons/automator/src/App.js

372 lines 15.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useState, useEffect, useRef } from 'react';
2 import Creator from './components/Creator';
3 import SupportModal from './components/SupportModal';
4 import TemplatesModal from './components/TemplatesModal';
5
6 const App = () => {
7 const [view, setView] = useState('list');
8 const [workflows, setWorkflows] = useState([]);
9 const [selectedWorkflow, setSelectedWorkflow] = useState(null);
10 const [loading, setLoading] = useState(true);
11 const [isSupportOpen, setIsSupportOpen] = useState(false);
12 const [isTemplatesModalOpen, setIsTemplatesModalOpen] = useState(false);
13 const [importStatus, setImportStatus] = useState(null); // { type: 'success'|'error', message: '' }
14 const importFileRef = useRef(null);
15
16 useEffect(() => {
17 fetchWorkflows();
18 }, []);
19
20 const fetchWorkflows = async () => {
21 setLoading(true);
22 try {
23 const response = await fetch(`${window.wpbotAutomator.apiUrl}/workflows`, {
24 headers: {
25 'X-WP-Nonce': window.wpbotAutomator.nonce,
26 },
27 });
28 const data = await response.json();
29 setWorkflows(data || []);
30 } catch (error) {
31 console.error('Error fetching workflows:', error);
32 } finally {
33 setLoading(false);
34 }
35 };
36
37 const handleCreateNew = () => {
38 setIsTemplatesModalOpen(true);
39 };
40
41 const handleTemplateSelect = (template) => {
42 setIsTemplatesModalOpen(false);
43 if (template) {
44 // Create a dummy workflow object from template
45 setSelectedWorkflow({
46 id: null,
47 name: template.name,
48 description: template.description,
49 workflow_data: template.workflow_data,
50 status: 'active'
51 });
52 } else {
53 setSelectedWorkflow(null);
54 }
55 setView('creator');
56 };
57
58 const handleEdit = (workflow) => {
59 setSelectedWorkflow(workflow);
60 setView('creator');
61 };
62
63 const handleDelete = async (e, id) => {
64 e.stopPropagation();
65 if (!window.confirm('Are you sure you want to delete this workflow?')) {
66 return;
67 }
68
69 try {
70 const response = await fetch(`${window.wpbotAutomator.apiUrl}/workflows/${id}`, {
71 method: 'DELETE',
72 headers: {
73 'X-WP-Nonce': window.wpbotAutomator.nonce,
74 },
75 });
76 const data = await response.json();
77 if (data.success) {
78 fetchWorkflows();
79 } else {
80 alert('Failed to delete workflow.');
81 }
82 } catch (error) {
83 console.error('Error deleting workflow:', error);
84 }
85 };
86
87 const handleToggleStatus = async (e, workflow) => {
88 e.stopPropagation();
89 const newStatus = workflow.status === 'active' ? 'inactive' : 'active';
90
91 try {
92 const response = await fetch(`${window.wpbotAutomator.apiUrl}/workflows/${workflow.id}`, {
93 method: 'PUT',
94 headers: {
95 'Content-Type': 'application/json',
96 'X-WP-Nonce': window.wpbotAutomator.nonce,
97 },
98 body: JSON.stringify({
99 ...workflow,
100 status: newStatus,
101 }),
102 });
103 const data = await response.json();
104 if (data.success) {
105 fetchWorkflows();
106 } else {
107 alert('Failed to update workflow status.');
108 }
109 } catch (error) {
110 console.error('Error updating workflow status:', error);
111 }
112 };
113
114 const handleBack = () => {
115 setView('list');
116 fetchWorkflows();
117 };
118
119 // ── Export helpers ────────────────────────────────────────────────────────
120
121 /**
122 * Trigger a JSON file download from a plain-object payload.
123 */
124 const downloadJson = (data, filename) => {
125 const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
126 const url = URL.createObjectURL(blob);
127 const a = document.createElement('a');
128 a.href = url;
129 a.download = filename;
130 a.click();
131 URL.revokeObjectURL(url);
132 };
133
134 /**
135 * Export a single workflow by ID.
136 */
137 const handleExportSingle = async (e, workflow) => {
138 e.stopPropagation();
139 try {
140 const response = await fetch(
141 `${window.wpbotAutomator.apiUrl}/workflows/export?id=${workflow.id}`,
142 { headers: { 'X-WP-Nonce': window.wpbotAutomator.nonce } }
143 );
144 const data = await response.json();
145 const safeName = workflow.name.replace(/[^a-z0-9_-]/gi, '_').toLowerCase();
146 downloadJson(data, `wpbot-workflow-${safeName}.json`);
147 } catch (error) {
148 console.error('Error exporting workflow:', error);
149 alert('Failed to export workflow.');
150 }
151 };
152
153 /**
154 * Export all workflows.
155 */
156 const handleExportAll = async () => {
157 try {
158 const response = await fetch(
159 `${window.wpbotAutomator.apiUrl}/workflows/export`,
160 { headers: { 'X-WP-Nonce': window.wpbotAutomator.nonce } }
161 );
162 const data = await response.json();
163 downloadJson(data, 'wpbot-workflows-all.json');
164 } catch (error) {
165 console.error('Error exporting all workflows:', error);
166 alert('Failed to export workflows.');
167 }
168 };
169
170 // ── Import helpers ────────────────────────────────────────────────────────
171
172 const handleImportClick = () => {
173 setImportStatus(null);
174 importFileRef.current && importFileRef.current.click();
175 };
176
177 const handleImportFile = (e) => {
178 const file = e.target.files && e.target.files[0];
179 if (!file) return;
180
181 const reader = new FileReader();
182 reader.onload = async (evt) => {
183 let parsed;
184 try {
185 parsed = JSON.parse(evt.target.result);
186 } catch {
187 setImportStatus({ type: 'error', message: 'Invalid JSON file. Please select a valid export file.' });
188 return;
189 }
190
191 if (!parsed.workflows || !Array.isArray(parsed.workflows)) {
192 setImportStatus({ type: 'error', message: 'Invalid file format. Expected a "workflows" array.' });
193 return;
194 }
195
196 try {
197 const response = await fetch(`${window.wpbotAutomator.apiUrl}/workflows/import`, {
198 method: 'POST',
199 headers: {
200 'Content-Type': 'application/json',
201 'X-WP-Nonce': window.wpbotAutomator.nonce,
202 },
203 body: JSON.stringify({ workflows: parsed.workflows }),
204 });
205 const data = await response.json();
206
207 if (data.success || data.imported > 0) {
208 const msg = `Successfully imported ${data.imported} workflow${data.imported !== 1 ? 's' : ''}.`
209 + (data.errors && data.errors.length ? ` (${data.errors.length} skipped)` : '');
210 setImportStatus({ type: 'success', message: msg });
211 fetchWorkflows();
212 } else {
213 const errMsg = (data.errors && data.errors.join(' ')) || 'Import failed. No workflows were imported.';
214 setImportStatus({ type: 'error', message: errMsg });
215 }
216 } catch (error) {
217 console.error('Error importing workflows:', error);
218 setImportStatus({ type: 'error', message: 'Network error during import. Please try again.' });
219 }
220 };
221 reader.readAsText(file);
222 // Reset so the same file can be selected again
223 e.target.value = '';
224 };
225
226 if (view === 'creator') {
227 return <Creator workflow={selectedWorkflow} onBack={handleBack} />;
228 }
229
230 return (
231 <div className="p-8 bg-background min-h-screen text-foreground">
232 <div className="max-w-6xl mx-auto">
233
234 {/* Import status message */}
235 {importStatus && (
236 <div className={`rounded-lg p-3 mb-6 flex items-center justify-between text-sm ${
237 importStatus.type === 'success'
238 ? 'bg-emerald-50 border border-emerald-200 text-emerald-800'
239 : 'bg-rose-50 border border-rose-200 text-rose-800'
240 }`}>
241 <span>{importStatus.message}</span>
242 <button onClick={() => setImportStatus(null)} className="ml-4 opacity-60 hover:opacity-100 transition-opacity">
243 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
244 </button>
245 </div>
246 )}
247
248 <div className="flex justify-between items-center mb-8">
249 <h1 className="text-2xl font-bold">Workflows</h1>
250 <div className="flex items-center space-x-2">
251 {/* Hidden file input for import */}
252 <input
253 ref={importFileRef}
254 type="file"
255 accept=".json,application/json"
256 style={{ display: 'none' }}
257 onChange={handleImportFile}
258 />
259 {/* Import button */}
260 <button
261 onClick={handleImportClick}
262 title="Import workflows from a JSON file"
263 className="flex items-center space-x-1.5 px-3 py-2 border border-border text-sm rounded-md hover:bg-muted transition-colors"
264 >
265 <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
266 <span>Import</span>
267 </button>
268 {/* Export All button — only shown when workflows exist */}
269 {workflows.length > 0 && (
270 <button
271 onClick={handleExportAll}
272 title="Export all workflows as JSON"
273 className="flex items-center space-x-1.5 px-3 py-2 border border-border text-sm rounded-md hover:bg-muted transition-colors"
274 >
275 <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
276 <span>Export All</span>
277 </button>
278 )}
279 {/* Create New button */}
280 <button
281 onClick={handleCreateNew}
282 className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
283 >
284 Create New Workflow
285 </button>
286 </div>
287 </div>
288
289 {loading ? (
290 <div className="text-center py-12 text-muted-foreground">Loading workflows...</div>
291 ) : workflows.length === 0 ? (
292 <div className="text-center py-12 border-2 border-dashed border-border rounded-lg text-muted-foreground">
293 No workflows found. Create your first one!
294 </div>
295 ) : (
296 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
297 {workflows.map((workflow) => (
298 <div
299 key={workflow.id}
300 className="group relative p-6 bg-card border border-border rounded-lg shadow-sm hover:shadow-md transition-shadow cursor-pointer"
301 onClick={() => handleEdit(workflow)}
302 >
303 <div className="flex justify-between items-start mb-2">
304 <h3 className="text-lg font-semibold">{workflow.name}</h3>
305 <div className="flex space-x-1 opacity-0 group-hover:opacity-100 transition-opacity">
306 {/* Toggle active/inactive */}
307 <button
308 onClick={(e) => handleToggleStatus(e, workflow)}
309 title={workflow.status === 'active' ? 'Deactivate' : 'Activate'}
310 className={`p-1.5 rounded-md transition-colors ${
311 workflow.status === 'active'
312 ? 'text-amber-600 hover:bg-amber-50'
313 : 'text-emerald-600 hover:bg-emerald-50'
314 }`}
315 >
316 {workflow.status === 'active' ? (
317 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
318 ) : (
319 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
320 )}
321 </button>
322 {/* Export single workflow */}
323 <button
324 onClick={(e) => handleExportSingle(e, workflow)}
325 title="Export workflow as JSON"
326 className="p-1.5 text-blue-600 hover:bg-blue-50 rounded-md transition-colors"
327 >
328 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
329 </button>
330 {/* Delete */}
331 <button
332 onClick={(e) => handleDelete(e, workflow.id)}
333 title="Delete"
334 className="p-1.5 text-rose-600 hover:bg-rose-50 rounded-md transition-colors"
335 >
336 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
337 </button>
338 </div>
339 </div>
340 <p className="text-sm text-muted-foreground mb-4 line-clamp-2">
341 {workflow.description || 'No description'}
342 </p>
343 <div className="flex justify-between items-center mt-auto">
344 <span className={`px-2 py-1 text-xs font-medium rounded-full ${
345 workflow.status === 'active'
346 ? 'bg-emerald-100/80 text-emerald-700'
347 : 'bg-slate-100/80 text-slate-700'
348 }`}>
349 {workflow.status.charAt(0).toUpperCase() + workflow.status.slice(1)}
350 </span>
351 <span className="text-xs text-muted-foreground">
352 ID: {workflow.id}
353 </span>
354 </div>
355 </div>
356 ))}
357 </div>
358 )}
359 </div>
360 <SupportModal isOpen={isSupportOpen} onClose={() => setIsSupportOpen(false)} />
361 <TemplatesModal
362 isOpen={isTemplatesModalOpen}
363 onClose={() => setIsTemplatesModalOpen(false)}
364 onSelect={handleTemplateSelect}
365 isProActive={!!window.wpbotAutomator?.isProActive}
366 />
367 </div>
368 );
369 };
370
371 export default App;
372