@flowuent-org/diagramming-core 1.3.8 → 1.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -749,6 +749,52 @@ export const automationDefaultNodes = [
749
749
  measured: { width: 300, height: 200 },
750
750
  },
751
751
  // =====================================
752
+ // Email Node
753
+ // =====================================
754
+ {
755
+ id: 'email-node',
756
+ type: 'AutomationEmailNode',
757
+ position: { x: 2200, y: 450 },
758
+ data: {
759
+ label: 'Send Email',
760
+ description: 'Send an email notification',
761
+ operationType: 'send-email',
762
+ status: 'Ready',
763
+ parameters: {
764
+ to: ['recipient@example.com'],
765
+ cc: [],
766
+ bcc: [],
767
+ subject: 'Workflow Notification',
768
+ body: 'Your workflow has completed successfully!',
769
+ htmlBody: '',
770
+ attachments: [],
771
+ emailAccessToken: '',
772
+ emailTokenExpiresAt: undefined,
773
+ emailProvider: 'smtp',
774
+ emailAddress: '',
775
+ },
776
+ emailAuth: {
777
+ provider: 'smtp',
778
+ isAuthenticated: false,
779
+ isLoading: false,
780
+ email: '',
781
+ },
782
+ formData: {
783
+ nodeId: 'email-node',
784
+ title: 'Send Email',
785
+ type: 'email',
786
+ operationType: 'send-email',
787
+ to: ['recipient@example.com'],
788
+ subject: 'Workflow Notification',
789
+ body: 'Your workflow has completed successfully!',
790
+ },
791
+ lastRun: 'Never',
792
+ },
793
+ width: 300,
794
+ height: 200,
795
+ measured: { width: 300, height: 200 },
796
+ },
797
+ // =====================================
752
798
  // Interaction Node
753
799
  // =====================================
754
800
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowuent-org/diagramming-core",
3
- "version": "1.3.8",
3
+ "version": "1.3.10",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -336,11 +336,13 @@ export const AutomationApiNode: React.FC<AutomationApiNodeProps> = ({ data, sele
336
336
  sx={{
337
337
  width: '32px',
338
338
  height: '32px',
339
+ minWidth: '32px',
339
340
  backgroundColor: '#1E3A8A',
340
341
  borderRadius: '50%',
341
342
  display: 'flex',
342
343
  alignItems: 'center',
343
344
  justifyContent: 'center',
345
+ flexShrink: 0,
344
346
  }}
345
347
  >
346
348
  <ApiNodeIcon/>
@@ -0,0 +1,773 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { Handle, Position, useNodeId } from '@xyflow/react';
4
+ import {
5
+ Box,
6
+ Typography,
7
+ Chip,
8
+ IconButton,
9
+ Card,
10
+ CardContent,
11
+ Button,
12
+ CircularProgress,
13
+ Tooltip,
14
+ LinearProgress,
15
+ } from '@mui/material';
16
+ import {
17
+ Email as EmailIcon,
18
+ Send as SendIcon,
19
+ AttachFile as AttachFileIcon,
20
+ AccessTime as AccessTimeIcon,
21
+ } from '@mui/icons-material';
22
+ import { RiCloseLine } from 'react-icons/ri';
23
+ import ReactJson from 'react-json-view';
24
+ import { useTranslation } from 'react-i18next';
25
+ import { useDiagram } from '../../contexts/DiagramProvider';
26
+ import { useSearch } from '../../contexts/SearchContext';
27
+ import { NodeActionButtons } from './NodeActionButtons';
28
+ import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
29
+ import { getStatusColor } from './statusColors';
30
+
31
+ // ========================
32
+ // Types
33
+ // ========================
34
+
35
+ export type EmailOperationType =
36
+ | 'send-email'
37
+ | 'send-with-attachment'
38
+ | 'send-template'
39
+ | 'send-bulk'
40
+ | 'reply-to-email'
41
+ | 'forward-email';
42
+
43
+ export interface EmailTokenData {
44
+ accessToken: string;
45
+ expiresAt?: number;
46
+ email?: string;
47
+ provider?: 'smtp' | 'gmail' | 'outlook' | 'sendgrid' | 'mailgun';
48
+ }
49
+
50
+ export interface AutomationEmailNodeData {
51
+ label: string;
52
+ description: string;
53
+ operationType?: EmailOperationType;
54
+ status: 'Ready' | 'Running' | 'Completed' | 'Failed' | 'Need to Config' | 'authenticated';
55
+ parameters?: {
56
+ // Email operations
57
+ to?: string[];
58
+ cc?: string[];
59
+ bcc?: string[];
60
+ subject?: string;
61
+ body?: string;
62
+ htmlBody?: string;
63
+ attachments?: Array<{
64
+ filename: string;
65
+ content: string;
66
+ contentType?: string;
67
+ }>;
68
+
69
+ // Template operations
70
+ templateId?: string;
71
+ templateVariables?: Record<string, any>;
72
+
73
+ // Bulk operations
74
+ recipients?: Array<{
75
+ email: string;
76
+ variables?: Record<string, any>;
77
+ }>;
78
+
79
+ // Reply/Forward operations
80
+ replyTo?: string;
81
+ inReplyTo?: string;
82
+ forwardTo?: string[];
83
+
84
+ // Token data
85
+ emailAccessToken?: string;
86
+ emailTokenExpiresAt?: number;
87
+ emailProvider?: string;
88
+ emailAddress?: string;
89
+
90
+ [key: string]: any;
91
+ };
92
+ emailAuth?: {
93
+ provider?: string;
94
+ isAuthenticated?: boolean;
95
+ isLoading?: boolean;
96
+ email?: string;
97
+ };
98
+ formData?: {
99
+ nodeId?: string;
100
+ title?: string;
101
+ type?: string;
102
+ operationType?: string;
103
+ [key: string]: any;
104
+ };
105
+ lastRun?: string;
106
+ duration?: string;
107
+ executionResult?: {
108
+ success: boolean;
109
+ data?: any;
110
+ error?: string;
111
+ };
112
+ onEmailLogin?: () => void;
113
+ onEmailDisconnect?: () => void;
114
+ }
115
+
116
+ export interface AutomationEmailNodeProps {
117
+ data: AutomationEmailNodeData;
118
+ selected?: boolean;
119
+ }
120
+
121
+ // ========================
122
+ // Operation Configuration
123
+ // ========================
124
+
125
+ const OPERATION_CONFIG: Record<
126
+ EmailOperationType,
127
+ {
128
+ icon: React.ElementType;
129
+ label: string;
130
+ color: string;
131
+ description: string;
132
+ }
133
+ > = {
134
+ 'send-email': {
135
+ icon: SendIcon,
136
+ label: 'Send Email',
137
+ color: '#4285F4',
138
+ description: 'Send a simple email',
139
+ },
140
+ 'send-with-attachment': {
141
+ icon: AttachFileIcon,
142
+ label: 'Send with Attachment',
143
+ color: '#34A853',
144
+ description: 'Send an email with file attachments',
145
+ },
146
+ 'send-template': {
147
+ icon: EmailIcon,
148
+ label: 'Send Template',
149
+ color: '#FBBC04',
150
+ description: 'Send an email using a template',
151
+ },
152
+ 'send-bulk': {
153
+ icon: EmailIcon,
154
+ label: 'Send Bulk',
155
+ color: '#EA4335',
156
+ description: 'Send bulk emails to multiple recipients',
157
+ },
158
+ 'reply-to-email': {
159
+ icon: EmailIcon,
160
+ label: 'Reply to Email',
161
+ color: '#4285F4',
162
+ description: 'Reply to an existing email',
163
+ },
164
+ 'forward-email': {
165
+ icon: EmailIcon,
166
+ label: 'Forward Email',
167
+ color: '#34A853',
168
+ description: 'Forward an email to recipients',
169
+ },
170
+ };
171
+
172
+ // ========================
173
+ // Helper Functions
174
+ // ========================
175
+
176
+ const isTokenExpired = (expiresAt?: number): boolean => {
177
+ if (!expiresAt) return false;
178
+ return Date.now() > expiresAt;
179
+ };
180
+
181
+ const getTimeUntilExpiry = (expiresAt?: number): string => {
182
+ if (!expiresAt) return 'No expiry';
183
+ const diff = expiresAt - Date.now();
184
+ if (diff <= 0) return 'Expired';
185
+ const minutes = Math.floor(diff / 60000);
186
+ if (minutes < 60) return `${minutes}m`;
187
+ const hours = Math.floor(minutes / 60);
188
+ return `${hours}h ${minutes % 60}m`;
189
+ };
190
+
191
+ // ========================
192
+ // Main Component
193
+ // ========================
194
+
195
+ export const AutomationEmailNode: React.FC<AutomationEmailNodeProps> = ({
196
+ data,
197
+ selected,
198
+ }) => {
199
+ const { t } = useTranslation();
200
+ const { highlightText } = useSearch();
201
+ const [isJsonOpen, setIsJsonOpen] = useState(false);
202
+ const rootRef = useRef<any>(null);
203
+ const portalRef = useRef<HTMLDivElement | null>(null);
204
+ const nodeRef = useRef<HTMLDivElement | null>(null);
205
+ const nodeId = useNodeId();
206
+ const setSelectedNode = useDiagram((state) => state.setSelectedNode);
207
+ const enableJson = useDiagram((state) => state.enableNodeJsonPopover ?? true);
208
+ const onNodesChange = useDiagram((state) => state.onNodesChange);
209
+ const nodes = useDiagram((state) => state.nodes);
210
+ const setNodes = useDiagram((state) => state.setNodes);
211
+
212
+ // Get operation configuration
213
+ const operationTypeRaw = data.operationType || data.formData?.operationType || 'send-email';
214
+ const operationType = (Object.keys(OPERATION_CONFIG).includes(operationTypeRaw) ? operationTypeRaw : 'send-email') as EmailOperationType;
215
+ const operationConfig = OPERATION_CONFIG[operationType];
216
+ const OperationIcon = operationConfig.icon;
217
+
218
+ // Auth state
219
+ const isAuthenticated = data.emailAuth?.isAuthenticated || !!data.parameters?.emailAccessToken;
220
+ const isAuthLoading = data.emailAuth?.isLoading || false;
221
+ const emailAddress = data.emailAuth?.email || data.parameters?.emailAddress;
222
+ const tokenExpired = isTokenExpired(data.parameters?.emailTokenExpiresAt);
223
+
224
+ // Execution state
225
+ const status = data.status || 'Ready';
226
+ const executionProgress = status === 'Running' ? 50 : 0;
227
+
228
+ // Status configuration - using centralized status colors
229
+ const statusConfig = getStatusColor(status, status === 'authenticated' ? 'authenticated' : 'ready');
230
+
231
+ // Handle JSON view
232
+ const handleJsonClick = () => {
233
+ if (nodeId) setSelectedNode(nodeId);
234
+ if (!enableJson) return;
235
+ setIsJsonOpen(!isJsonOpen);
236
+ };
237
+
238
+ const handleClose = () => {
239
+ setIsJsonOpen(false);
240
+ // Clean up portal
241
+ if (rootRef.current) {
242
+ rootRef.current.unmount();
243
+ rootRef.current = null;
244
+ }
245
+ if (portalRef.current) {
246
+ document.body.removeChild(portalRef.current);
247
+ portalRef.current = null;
248
+ }
249
+ };
250
+
251
+ // Render email auth button
252
+ const renderEmailAuthButton = () => {
253
+ if (isAuthenticated && !tokenExpired) {
254
+ return (
255
+ <Button
256
+ onClick={data.onEmailDisconnect}
257
+ disabled={isAuthLoading}
258
+ fullWidth
259
+ sx={{
260
+ backgroundColor: '#4285F4',
261
+ borderRadius: '8px',
262
+ padding: '8px 12px',
263
+ gap: '8px',
264
+ color: '#FFFFFF',
265
+ textTransform: 'none',
266
+ fontSize: '13px',
267
+ fontWeight: 500,
268
+ '&:hover': {
269
+ backgroundColor: '#357ae8',
270
+ },
271
+ }}
272
+ >
273
+ <EmailIcon sx={{ fontSize: 18 }} />
274
+ {emailAddress ? emailAddress : 'Connected'}
275
+ </Button>
276
+ );
277
+ }
278
+
279
+ return (
280
+ <Button
281
+ onClick={data.onEmailLogin}
282
+ disabled={isAuthLoading}
283
+ fullWidth
284
+ sx={{
285
+ backgroundColor: '#171C29',
286
+ border: '1px solid #374151',
287
+ borderRadius: '8px',
288
+ padding: '8px 12px',
289
+ gap: '8px',
290
+ color: '#B2BCD8',
291
+ textTransform: 'none',
292
+ fontSize: '13px',
293
+ fontWeight: 500,
294
+ '&:hover': {
295
+ backgroundColor: '#1F2937',
296
+ borderColor: '#4285F4',
297
+ },
298
+ '&:disabled': {
299
+ opacity: 0.6,
300
+ },
301
+ }}
302
+ >
303
+ {isAuthLoading ? (
304
+ <>
305
+ <CircularProgress size={16} sx={{ color: '#B2BCD8' }} />
306
+ Connecting...
307
+ </>
308
+ ) : (
309
+ <>
310
+ <EmailIcon sx={{ fontSize: 18 }} />
311
+ Connect Email
312
+ </>
313
+ )}
314
+ </Button>
315
+ );
316
+ };
317
+
318
+ // Render status badge
319
+ const renderStatusBadge = () => {
320
+ return (
321
+ <Chip
322
+ label={status}
323
+ size="small"
324
+ sx={{
325
+ backgroundColor: statusConfig.bgColor,
326
+ color: statusConfig.color,
327
+ fontWeight: 500,
328
+ fontSize: '11px',
329
+ height: '22px',
330
+ borderRadius: '11px',
331
+ }}
332
+ />
333
+ );
334
+ };
335
+
336
+ useEffect(() => {
337
+ const handleClickOutside = (event: MouseEvent) => {
338
+ if (isJsonOpen && !(event.target as Element).closest('#automation-json-popover')) {
339
+ handleClose();
340
+ }
341
+ };
342
+ document.addEventListener('mousedown', handleClickOutside);
343
+ return () => {
344
+ document.removeEventListener('mousedown', handleClickOutside);
345
+ };
346
+ }, [isJsonOpen]);
347
+
348
+ useEffect(() => {
349
+ if (isJsonOpen) {
350
+ const portalRoot = document.createElement('div');
351
+ document.body.appendChild(portalRoot);
352
+ portalRef.current = portalRoot;
353
+
354
+ const root = createRoot(portalRoot);
355
+ rootRef.current = root;
356
+
357
+ root.render(
358
+ <Card
359
+ id="automation-json-popover"
360
+ sx={{
361
+ position: 'fixed',
362
+ top: 0,
363
+ right: 0,
364
+ zIndex: 9999,
365
+ width: '400px',
366
+ height: '100vh',
367
+ overflow: 'auto',
368
+ bgcolor: '#242424',
369
+ color: '#fff',
370
+ border: '1px solid #333',
371
+ '&::-webkit-scrollbar': {
372
+ width: '6px',
373
+ },
374
+ '&::-webkit-scrollbar-track': {
375
+ background: 'transparent',
376
+ },
377
+ '&::-webkit-scrollbar-thumb': {
378
+ background: '#444',
379
+ borderRadius: '3px',
380
+ '&:hover': {
381
+ background: '#666',
382
+ },
383
+ },
384
+ }}
385
+ >
386
+ <CardContent sx={{ bgcolor: '#242424', color: '#fff' }}>
387
+ <IconButton
388
+ aria-label="close"
389
+ onClick={handleClose}
390
+ sx={{
391
+ color: '#999',
392
+ '&:hover': {
393
+ color: '#fff',
394
+ bgcolor: 'rgba(255, 255, 255, 0.1)',
395
+ },
396
+ }}
397
+ >
398
+ <RiCloseLine />
399
+ </IconButton>
400
+ {/* Show execution result prominently if available */}
401
+ {data.formData?.executionResult && (
402
+ <Box sx={{ mb: 2 }}>
403
+ <Typography variant="h6" sx={{ color: '#fff', mb: 1 }}>
404
+ {t('automation.common.executionResult')}
405
+ </Typography>
406
+ <Box sx={{
407
+ bgcolor: data.formData.executionResult.success ? '#1e3a8a' : '#dc2626',
408
+ p: 1,
409
+ borderRadius: 1,
410
+ mb: 1
411
+ }}>
412
+ <Typography variant="body2" sx={{ color: '#fff' }}>
413
+ {t('automation.common.status')}: {data.formData.executionResult.success ? t('automation.common.success') : t('automation.common.failed')}
414
+ </Typography>
415
+ <Typography variant="body2" sx={{ color: '#fff' }}>
416
+ {t('automation.common.timestamp')}: {new Date(data.formData.executionResult.timestamp).toLocaleString()}
417
+ </Typography>
418
+ {data.formData.executionResult.error && (
419
+ <Typography variant="body2" sx={{ color: '#fff' }}>
420
+ {t('automation.common.error')}: {data.formData.executionResult.error}
421
+ </Typography>
422
+ )}
423
+ </Box>
424
+ <Typography variant="h6" sx={{ color: '#fff', mb: 1 }}>
425
+ {t('automation.emailNode.executionData')}
426
+ </Typography>
427
+ <ReactJson
428
+ theme={'monokai'}
429
+ src={data.formData.executionResult.data}
430
+ collapsed={false}
431
+ />
432
+ </Box>
433
+ )}
434
+
435
+ {/* Show full node data */}
436
+ <Typography variant="h6" sx={{ color: '#fff', mb: 1 }}>
437
+ {t('automation.common.fullNodeData')}
438
+ </Typography>
439
+ <ReactJson theme={'monokai'} src={data.formData || data} collapsed={false} />
440
+ </CardContent>
441
+ </Card>
442
+ );
443
+ } else {
444
+ // Clean up when closing
445
+ if (rootRef.current) {
446
+ rootRef.current.unmount();
447
+ rootRef.current = null;
448
+ }
449
+ if (portalRef.current) {
450
+ document.body.removeChild(portalRef.current);
451
+ portalRef.current = null;
452
+ }
453
+ }
454
+ }, [isJsonOpen, data]);
455
+
456
+ // ========================
457
+ // Render
458
+ // ========================
459
+ return (
460
+ <Box
461
+ sx={{
462
+ position: 'relative',
463
+ width: '336px',
464
+ overflow: 'visible',
465
+ }}
466
+ >
467
+ <Box
468
+ ref={nodeRef}
469
+ sx={{
470
+ width: '336px',
471
+ minHeight: '150px',
472
+ backgroundColor: '#181C25',
473
+ border: selected ? '2px solid #3b82f6' : '1px solid #1e293b',
474
+ borderRadius: '12px',
475
+ color: '#ffffff',
476
+ position: 'relative',
477
+ boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
478
+ transition: 'all 0.2s ease',
479
+ cursor: 'pointer',
480
+ overflow: 'hidden',
481
+ ...(status === 'Running' && {
482
+ animation: 'pulse-glow 2s ease-in-out infinite',
483
+ '@keyframes pulse-glow': {
484
+ '0%, 100%': {
485
+ boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
486
+ borderColor: 'rgba(59, 130, 246, 0.6)',
487
+ },
488
+ '50%': {
489
+ boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
490
+ borderColor: 'rgba(59, 130, 246, 0.9)',
491
+ },
492
+ },
493
+ }),
494
+ }}
495
+ onClick={handleJsonClick}
496
+ >
497
+ {/* Top Header Section */}
498
+ <Box sx={{
499
+ backgroundColor: "rgba(67, 93, 132, 0.1)",
500
+ padding: '8px 16px',
501
+ borderRadius: '12px 12px 0 0'
502
+ }}>
503
+ <Typography variant="body2" sx={{
504
+ color: '#ffffff',
505
+ fontSize: '12px',
506
+ fontWeight: 500
507
+ }}>
508
+ {highlightText(data.description || operationConfig.description)}
509
+ </Typography>
510
+ </Box>
511
+
512
+ {/* Main Content */}
513
+ <Box sx={{ padding: '16px' }}>
514
+ {/* Title Section */}
515
+ <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
516
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
517
+ <Box
518
+ sx={{
519
+ width: '32px',
520
+ height: '32px',
521
+ backgroundColor: '#0ea5e9',
522
+ borderRadius: '50%',
523
+ display: 'flex',
524
+ alignItems: 'center',
525
+ justifyContent: 'center',
526
+ }}
527
+ >
528
+ <EmailIcon sx={{ fontSize: 18, color: '#FFFFFF' }} />
529
+ </Box>
530
+ <Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px' }}>
531
+ {highlightText(data.label || operationConfig.label)}
532
+ </Typography>
533
+ </Box>
534
+ <Chip
535
+ label={status}
536
+ size="small"
537
+ sx={{
538
+ backgroundColor: statusConfig.bgColor,
539
+ color: statusConfig.color,
540
+ fontWeight: 500,
541
+ fontSize: '12px',
542
+ height: '24px',
543
+ borderRadius: '12px',
544
+ }}
545
+ />
546
+ </Box>
547
+
548
+ {/* Description Box */}
549
+ <Box sx={{
550
+ backgroundColor: '#1F2937',
551
+ borderRadius: '8px',
552
+ padding: '12px',
553
+ mb: 2,
554
+ border: '1px solid #374151'
555
+ }}>
556
+ {/* Inner text boundary box */}
557
+ <Box sx={{
558
+ backgroundColor: 'transparent',
559
+ borderRadius: '4px',
560
+ padding: '8px',
561
+ border: '1px solid #4B5563',
562
+ minHeight: '40px',
563
+ display: 'flex',
564
+ alignItems: 'center'
565
+ }}>
566
+ <Typography variant="body2" sx={{
567
+ color: '#9CA3AF',
568
+ fontSize: '12px',
569
+ lineHeight: 1.4,
570
+ margin: 0
571
+ }}>
572
+ {data.description || operationConfig.description}
573
+ </Typography>
574
+ </Box>
575
+ </Box>
576
+
577
+ {/* Email Auth Button */}
578
+ {renderEmailAuthButton()}
579
+
580
+ {/* Operation-specific Parameters Display */}
581
+ {(operationType === 'send-email' && (data.parameters?.to || data.parameters?.subject)) && (
582
+ <Box sx={{ mt: 2, mb: 1 }}>
583
+ {data.parameters?.to && (
584
+ <Typography variant="caption" sx={{ color: '#9CA3AF', fontSize: '11px', display: 'block' }}>
585
+ To: {data.parameters.to.join(', ')}
586
+ </Typography>
587
+ )}
588
+ {data.parameters?.subject && (
589
+ <Typography variant="caption" sx={{ color: '#9CA3AF', fontSize: '11px', display: 'block', mt: 0.5 }}>
590
+ Subject: {data.parameters.subject}
591
+ </Typography>
592
+ )}
593
+ </Box>
594
+ )}
595
+
596
+ {/* Last Run Info */}
597
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 1 }}>
598
+ <AccessTimeIcon sx={{ fontSize: '14px', color: '#9CA3AF' }} />
599
+ <Typography variant="body2" sx={{ color: '#9CA3AF', fontSize: '11px' }}>
600
+ {t('automation.common.lastRan')}: {data.lastRun || 'Never'}
601
+ </Typography>
602
+ </Box>
603
+ </Box>
604
+
605
+ {/* Connection Handles - Bidirectional (source + target at each position) */}
606
+ {/* Top - Source */}
607
+ <Handle
608
+ type="source"
609
+ position={Position.Top}
610
+ id="top-source"
611
+ className="connection-handle"
612
+ style={{
613
+ background: selected ? '#10B981' : '#1a1a2e',
614
+ width: '14px',
615
+ height: '14px',
616
+ border: '3px solid #10B981',
617
+ top: '-8px',
618
+ opacity: selected ? 1 : 0,
619
+ transition: 'all 0.2s ease-in-out',
620
+ cursor: 'crosshair',
621
+ zIndex: 10,
622
+ }}
623
+ />
624
+ {/* Top - Target (hidden but functional) */}
625
+ <Handle
626
+ type="target"
627
+ position={Position.Top}
628
+ id="top-target"
629
+ style={{
630
+ background: 'transparent',
631
+ width: '14px',
632
+ height: '14px',
633
+ border: 'none',
634
+ top: '-8px',
635
+ opacity: 0,
636
+ pointerEvents: selected ? 'all' : 'none',
637
+ }}
638
+ />
639
+ {/* Bottom - Source */}
640
+ <Handle
641
+ type="source"
642
+ position={Position.Bottom}
643
+ id="bottom-source"
644
+ className="connection-handle"
645
+ style={{
646
+ background: selected ? '#10B981' : '#1a1a2e',
647
+ width: '14px',
648
+ height: '14px',
649
+ border: '3px solid #10B981',
650
+ bottom: '-8px',
651
+ opacity: selected ? 1 : 0,
652
+ transition: 'all 0.2s ease-in-out',
653
+ cursor: 'crosshair',
654
+ zIndex: 10,
655
+ }}
656
+ />
657
+ {/* Bottom - Target (hidden but functional) */}
658
+ <Handle
659
+ type="target"
660
+ position={Position.Bottom}
661
+ id="bottom-target"
662
+ style={{
663
+ background: 'transparent',
664
+ width: '14px',
665
+ height: '14px',
666
+ border: 'none',
667
+ bottom: '-8px',
668
+ opacity: 0,
669
+ pointerEvents: selected ? 'all' : 'none',
670
+ }}
671
+ />
672
+ {/* Left - Source */}
673
+ <Handle
674
+ type="source"
675
+ position={Position.Left}
676
+ id="left-source"
677
+ className="connection-handle"
678
+ style={{
679
+ background: selected ? '#10B981' : '#1a1a2e',
680
+ width: '14px',
681
+ height: '14px',
682
+ border: '3px solid #10B981',
683
+ left: '-8px',
684
+ opacity: selected ? 1 : 0,
685
+ transition: 'all 0.2s ease-in-out',
686
+ cursor: 'crosshair',
687
+ zIndex: 10,
688
+ }}
689
+ />
690
+ {/* Left - Target (hidden but functional) */}
691
+ <Handle
692
+ type="target"
693
+ position={Position.Left}
694
+ id="left-target"
695
+ style={{
696
+ background: 'transparent',
697
+ width: '14px',
698
+ height: '14px',
699
+ border: 'none',
700
+ left: '-8px',
701
+ opacity: 0,
702
+ pointerEvents: selected ? 'all' : 'none',
703
+ }}
704
+ />
705
+ {/* Right - Source */}
706
+ <Handle
707
+ type="source"
708
+ position={Position.Right}
709
+ id="right-source"
710
+ className="connection-handle"
711
+ style={{
712
+ background: selected ? '#10B981' : '#1a1a2e',
713
+ width: '14px',
714
+ height: '14px',
715
+ border: '3px solid #10B981',
716
+ right: '-8px',
717
+ opacity: selected ? 1 : 0,
718
+ transition: 'all 0.2s ease-in-out',
719
+ cursor: 'crosshair',
720
+ zIndex: 10,
721
+ }}
722
+ />
723
+ {/* Right - Target (hidden but functional) */}
724
+ <Handle
725
+ type="target"
726
+ position={Position.Right}
727
+ id="right-target"
728
+ style={{
729
+ background: 'transparent',
730
+ width: '14px',
731
+ height: '14px',
732
+ border: 'none',
733
+ right: '-8px',
734
+ opacity: 0,
735
+ pointerEvents: selected ? 'all' : 'none',
736
+ }}
737
+ />
738
+ </Box>
739
+
740
+ {/* Node Action Buttons - Shows when selected */}
741
+ <NodeActionButtons
742
+ selected={selected}
743
+ onOpenAIAssistant={(buttonElement) => {
744
+ if (nodeId) {
745
+ showNodeAIAssistantPopup(nodeId, 'Email Node', buttonElement);
746
+ }
747
+ }}
748
+ onDelete={() => {
749
+ if (nodeId && onNodesChange) {
750
+ onNodesChange([{ id: nodeId, type: 'remove' }]);
751
+ }
752
+ }}
753
+ onDuplicate={() => {
754
+ if (nodeId) {
755
+ const currentNode = nodes.find(n => n.id === nodeId);
756
+ if (currentNode) {
757
+ const newNode = {
758
+ ...currentNode,
759
+ id: `${currentNode.id}-copy-${Date.now()}`,
760
+ position: {
761
+ x: currentNode.position.x + 50,
762
+ y: currentNode.position.y + 50,
763
+ },
764
+ selected: false,
765
+ };
766
+ setNodes([...nodes, newNode]);
767
+ }
768
+ }
769
+ }}
770
+ />
771
+ </Box>
772
+ );
773
+ };
@@ -338,9 +338,25 @@ export const AutomationFormattingNode: React.FC<AutomationFormattingNodeProps> =
338
338
  {/* Title Section */}
339
339
  <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
340
340
  <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
341
-
342
- <FormattingNodeIcon />
343
-
341
+ <Box
342
+ sx={{
343
+ width: '32px',
344
+ height: '32px',
345
+ minWidth: '32px',
346
+ backgroundColor: '#1E3A8A',
347
+ borderRadius: '50%',
348
+ display: 'flex',
349
+ alignItems: 'center',
350
+ justifyContent: 'center',
351
+ flexShrink: 0,
352
+ }}
353
+ >
354
+ {isArticleAnalyzer ? (
355
+ <ArticleAnalyzerIcon size={18} color="#FFFFFF" />
356
+ ) : (
357
+ <FormattingNodeIcon size={18} color="#FFFFFF" />
358
+ )}
359
+ </Box>
344
360
  <Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px' }}>
345
361
  {highlightText(data.label)}
346
362
  </Typography>
@@ -463,7 +463,7 @@ export const AutomationGoogleServicesNode: React.FC<AutomationGoogleServicesNode
463
463
  borderRadius: '12px',
464
464
  color: '#ffffff',
465
465
  position: 'relative',
466
- boxShadow: selected ? `0 0 0 2px ${serviceConfig.color}50` : '0 4px 8px rgba(0, 0, 0, 0.3)',
466
+ boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
467
467
  transition: 'all 0.2s ease',
468
468
  cursor: 'pointer',
469
469
  overflow: 'hidden',
@@ -471,12 +471,12 @@ export const AutomationGoogleServicesNode: React.FC<AutomationGoogleServicesNode
471
471
  animation: 'pulse-glow 2s ease-in-out infinite',
472
472
  '@keyframes pulse-glow': {
473
473
  '0%, 100%': {
474
- boxShadow: `0 0 20px ${serviceConfig.color}40, 0 0 40px ${serviceConfig.color}20`,
475
- borderColor: `${serviceConfig.color}60`,
474
+ boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
475
+ borderColor: 'rgba(59, 130, 246, 0.6)',
476
476
  },
477
477
  '50%': {
478
- boxShadow: `0 0 30px ${serviceConfig.color}60, 0 0 60px ${serviceConfig.color}30`,
479
- borderColor: `${serviceConfig.color}90`,
478
+ boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
479
+ borderColor: 'rgba(59, 130, 246, 0.9)',
480
480
  },
481
481
  },
482
482
  }),
@@ -485,7 +485,7 @@ export const AutomationGoogleServicesNode: React.FC<AutomationGoogleServicesNode
485
485
  >
486
486
  {/* Top Header Section */}
487
487
  <Box sx={{
488
- backgroundColor: `${serviceConfig.color}15`,
488
+ backgroundColor: "rgba(67, 93, 132, 0.1)",
489
489
  padding: '8px 16px',
490
490
  borderRadius: '12px 12px 0 0'
491
491
  }}>
@@ -507,11 +507,13 @@ export const AutomationGoogleServicesNode: React.FC<AutomationGoogleServicesNode
507
507
  sx={{
508
508
  width: '32px',
509
509
  height: '32px',
510
+ minWidth: '32px',
510
511
  backgroundColor: '#1E3A8A',
511
512
  borderRadius: '50%',
512
513
  display: 'flex',
513
514
  alignItems: 'center',
514
515
  justifyContent: 'center',
516
+ flexShrink: 0,
515
517
  }}
516
518
  >
517
519
  <ServiceIcon sx={{ color: 'white', fontSize: '18px' }} />
@@ -403,7 +403,7 @@ export const AutomationInteractionNode: React.FC<AutomationInteractionNodeProps>
403
403
  borderRadius: '12px',
404
404
  color: '#ffffff',
405
405
  position: 'relative',
406
- boxShadow: selected ? `0 0 0 2px ${interactionConfig.color}50` : '0 4px 8px rgba(0, 0, 0, 0.3)',
406
+ boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
407
407
  transition: 'all 0.2s ease',
408
408
  cursor: 'pointer',
409
409
  overflow: 'hidden',
@@ -411,12 +411,12 @@ export const AutomationInteractionNode: React.FC<AutomationInteractionNodeProps>
411
411
  animation: 'pulse-glow 2s ease-in-out infinite',
412
412
  '@keyframes pulse-glow': {
413
413
  '0%, 100%': {
414
- boxShadow: `0 0 20px ${interactionConfig.color}40, 0 0 40px ${interactionConfig.color}20`,
415
- borderColor: `${interactionConfig.color}60`,
414
+ boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
415
+ borderColor: 'rgba(59, 130, 246, 0.6)',
416
416
  },
417
417
  '50%': {
418
- boxShadow: `0 0 30px ${interactionConfig.color}60, 0 0 60px ${interactionConfig.color}30`,
419
- borderColor: `${interactionConfig.color}90`,
418
+ boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
419
+ borderColor: 'rgba(59, 130, 246, 0.9)',
420
420
  },
421
421
  },
422
422
  }),
@@ -425,7 +425,7 @@ export const AutomationInteractionNode: React.FC<AutomationInteractionNodeProps>
425
425
  >
426
426
  {/* Top Header Section */}
427
427
  <Box sx={{
428
- backgroundColor: `${interactionConfig.color}15`,
428
+ backgroundColor: "rgba(67, 93, 132, 0.1)",
429
429
  padding: '8px 16px',
430
430
  borderRadius: '12px 12px 0 0'
431
431
  }}>
@@ -447,11 +447,13 @@ export const AutomationInteractionNode: React.FC<AutomationInteractionNodeProps>
447
447
  sx={{
448
448
  width: '32px',
449
449
  height: '32px',
450
+ minWidth: '32px',
450
451
  backgroundColor: '#1E3A8A',
451
452
  borderRadius: '50%',
452
453
  display: 'flex',
453
454
  alignItems: 'center',
454
455
  justifyContent: 'center',
456
+ flexShrink: 0,
455
457
  }}
456
458
  >
457
459
  <InteractionIcon sx={{ color: 'white', fontSize: '18px' }} />
@@ -190,24 +190,22 @@ export const AutomationMonitoringNode: React.FC<AutomationMonitoringNodeProps> =
190
190
  sx={{
191
191
  bgcolor: '#1E1E2E',
192
192
  borderRadius: 2,
193
- border: selected ? `2px solid ${monitoringColor}` : '1px solid #374151',
193
+ border: selected ? '2px solid rgba(59, 130, 246, 0.5)' : '1px solid #374151',
194
194
  minWidth: 280,
195
195
  maxWidth: 320,
196
196
  overflow: 'hidden',
197
- boxShadow: selected
198
- ? `0 0 20px ${monitoringColor}30`
199
- : '0 4px 12px rgba(0, 0, 0, 0.3)',
197
+ boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
200
198
  transition: 'all 0.2s ease',
201
199
  ...(status === 'Running' && {
202
200
  animation: 'pulse-glow 2s ease-in-out infinite',
203
201
  '@keyframes pulse-glow': {
204
202
  '0%, 100%': {
205
- boxShadow: `0 0 20px ${monitoringColor}40, 0 0 40px ${monitoringColor}20`,
206
- borderColor: `${monitoringColor}60`,
203
+ boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
204
+ borderColor: 'rgba(59, 130, 246, 0.6)',
207
205
  },
208
206
  '50%': {
209
- boxShadow: `0 0 30px ${monitoringColor}60, 0 0 60px ${monitoringColor}30`,
210
- borderColor: `${monitoringColor}90`,
207
+ boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
208
+ borderColor: 'rgba(59, 130, 246, 0.9)',
211
209
  },
212
210
  },
213
211
  }),
@@ -295,9 +295,21 @@ export const AutomationNavigationNode: React.FC<AutomationNavigationNodeProps> =
295
295
  {/* Title Section */}
296
296
  <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
297
297
  <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
298
-
298
+ <Box
299
+ sx={{
300
+ width: '32px',
301
+ height: '32px',
302
+ minWidth: '32px',
303
+ backgroundColor: '#8b5cf6',
304
+ borderRadius: '50%',
305
+ display: 'flex',
306
+ alignItems: 'center',
307
+ justifyContent: 'center',
308
+ flexShrink: 0,
309
+ }}
310
+ >
299
311
  <NavigationIcon />
300
-
312
+ </Box>
301
313
  <Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px' }}>
302
314
  {highlightText(data.label)}
303
315
  </Typography>
@@ -746,6 +746,21 @@ Data: ${JSON.stringify(data, null, 2)}
746
746
  }}
747
747
  onClick={handleJsonClick}
748
748
  >
749
+ {/* Top Header Section */}
750
+ <Box sx={{
751
+ backgroundColor: "rgba(67, 93, 132, 0.1)",
752
+ padding: '8px 16px',
753
+ borderRadius: '12px 12px 0 0'
754
+ }}>
755
+ <Typography variant="body2" sx={{
756
+ color: '#ffffff',
757
+ fontSize: '12px',
758
+ fontWeight: 500
759
+ }}>
760
+ {data.description || 'Export data to Google Sheets'}
761
+ </Typography>
762
+ </Box>
763
+
749
764
  {/* Header */}
750
765
  <Box sx={{
751
766
  display: 'flex',
@@ -759,11 +774,13 @@ Data: ${JSON.stringify(data, null, 2)}
759
774
  sx={{
760
775
  width: '32px',
761
776
  height: '32px',
777
+ minWidth: '32px',
762
778
  backgroundColor: '#1E3A8A',
763
779
  borderRadius: '50%',
764
780
  display: 'flex',
765
781
  alignItems: 'center',
766
782
  justifyContent: 'center',
783
+ flexShrink: 0,
767
784
  }}
768
785
  >
769
786
  <IconComponent sx={{ color: 'white', fontSize: '18px' }} />
@@ -434,14 +434,25 @@ export const AutomationSlackNode: React.FC<AutomationSlackNodeProps> = ({
434
434
  sx={{
435
435
  bgcolor: '#1E1E2E',
436
436
  borderRadius: 2,
437
- border: selected ? `2px solid ${operationConfig.color}` : '1px solid #374151',
437
+ border: selected ? '2px solid rgba(59, 130, 246, 0.5)' : '1px solid #374151',
438
438
  minWidth: 280,
439
439
  maxWidth: 320,
440
440
  overflow: 'hidden',
441
- boxShadow: selected
442
- ? `0 0 20px ${operationConfig.color}30`
443
- : '0 4px 12px rgba(0, 0, 0, 0.3)',
441
+ boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
444
442
  transition: 'all 0.2s ease',
443
+ ...(status === 'Running' && {
444
+ animation: 'pulse-glow 2s ease-in-out infinite',
445
+ '@keyframes pulse-glow': {
446
+ '0%, 100%': {
447
+ boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
448
+ borderColor: 'rgba(59, 130, 246, 0.6)',
449
+ },
450
+ '50%': {
451
+ boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
452
+ borderColor: 'rgba(59, 130, 246, 0.9)',
453
+ },
454
+ },
455
+ }),
445
456
  }}
446
457
  onClick={() => setSelectedNode(nodeId || '')}
447
458
  >
@@ -457,11 +468,26 @@ export const AutomationSlackNode: React.FC<AutomationSlackNodeProps> = ({
457
468
  }}
458
469
  />
459
470
 
471
+ {/* Top Header Section */}
472
+ <Box sx={{
473
+ backgroundColor: "rgba(67, 93, 132, 0.1)",
474
+ padding: '8px 16px',
475
+ borderRadius: '12px 12px 0 0'
476
+ }}>
477
+ <Typography variant="body2" sx={{
478
+ color: '#ffffff',
479
+ fontSize: '12px',
480
+ fontWeight: 500
481
+ }}>
482
+ {data.description || operationConfig.description}
483
+ </Typography>
484
+ </Box>
485
+
460
486
  {/* Header */}
461
487
  <Box
462
488
  sx={{
463
- bgcolor: `${operationConfig.color}15`,
464
- borderBottom: `1px solid ${operationConfig.color}30`,
489
+ bgcolor: "rgba(67, 93, 132, 0.1)",
490
+ borderBottom: `1px solid rgba(67, 93, 132, 0.2)`,
465
491
  px: 2,
466
492
  py: 1.5,
467
493
  display: 'flex',
@@ -474,11 +500,13 @@ export const AutomationSlackNode: React.FC<AutomationSlackNodeProps> = ({
474
500
  sx={{
475
501
  width: '32px',
476
502
  height: '32px',
503
+ minWidth: '32px',
477
504
  backgroundColor: '#1E3A8A',
478
505
  borderRadius: '50%',
479
506
  display: 'flex',
480
507
  alignItems: 'center',
481
508
  justifyContent: 'center',
509
+ flexShrink: 0,
482
510
  }}
483
511
  >
484
512
  <OperationIcon sx={{ fontSize: 18, color: '#fff' }} />
@@ -490,12 +518,6 @@ export const AutomationSlackNode: React.FC<AutomationSlackNodeProps> = ({
490
518
  >
491
519
  {highlightText(data.label || operationConfig.label)}
492
520
  </Typography>
493
- <Typography
494
- variant="caption"
495
- sx={{ color: '#9CA3AF', fontSize: 10 }}
496
- >
497
- {data.description || operationConfig.description}
498
- </Typography>
499
521
  </Box>
500
522
  </Box>
501
523
  {renderStatusBadge()}
@@ -427,14 +427,25 @@ export const AutomationTelegramNode: React.FC<AutomationTelegramNodeProps> = ({
427
427
  sx={{
428
428
  bgcolor: '#1E1E2E',
429
429
  borderRadius: 2,
430
- border: selected ? `2px solid ${operationConfig.color}` : '1px solid #374151',
430
+ border: selected ? '2px solid rgba(59, 130, 246, 0.5)' : '1px solid #374151',
431
431
  minWidth: 280,
432
432
  maxWidth: 320,
433
433
  overflow: 'hidden',
434
- boxShadow: selected
435
- ? `0 0 20px ${operationConfig.color}30`
436
- : '0 4px 12px rgba(0, 0, 0, 0.3)',
434
+ boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
437
435
  transition: 'all 0.2s ease',
436
+ ...(status === 'Running' && {
437
+ animation: 'pulse-glow 2s ease-in-out infinite',
438
+ '@keyframes pulse-glow': {
439
+ '0%, 100%': {
440
+ boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
441
+ borderColor: 'rgba(59, 130, 246, 0.6)',
442
+ },
443
+ '50%': {
444
+ boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
445
+ borderColor: 'rgba(59, 130, 246, 0.9)',
446
+ },
447
+ },
448
+ }),
438
449
  }}
439
450
  onClick={() => setSelectedNode(nodeId || '')}
440
451
  >
@@ -450,11 +461,26 @@ export const AutomationTelegramNode: React.FC<AutomationTelegramNodeProps> = ({
450
461
  }}
451
462
  />
452
463
 
464
+ {/* Top Header Section */}
465
+ <Box sx={{
466
+ backgroundColor: "rgba(67, 93, 132, 0.1)",
467
+ padding: '8px 16px',
468
+ borderRadius: '12px 12px 0 0'
469
+ }}>
470
+ <Typography variant="body2" sx={{
471
+ color: '#ffffff',
472
+ fontSize: '12px',
473
+ fontWeight: 500
474
+ }}>
475
+ {data.description || operationConfig.description}
476
+ </Typography>
477
+ </Box>
478
+
453
479
  {/* Header */}
454
480
  <Box
455
481
  sx={{
456
- bgcolor: `${operationConfig.color}15`,
457
- borderBottom: `1px solid ${operationConfig.color}30`,
482
+ bgcolor: "rgba(67, 93, 132, 0.1)",
483
+ borderBottom: `1px solid rgba(67, 93, 132, 0.2)`,
458
484
  px: 2,
459
485
  py: 1.5,
460
486
  display: 'flex',
@@ -467,11 +493,13 @@ export const AutomationTelegramNode: React.FC<AutomationTelegramNodeProps> = ({
467
493
  sx={{
468
494
  width: '32px',
469
495
  height: '32px',
496
+ minWidth: '32px',
470
497
  backgroundColor: '#1E3A8A',
471
498
  borderRadius: '50%',
472
499
  display: 'flex',
473
500
  alignItems: 'center',
474
501
  justifyContent: 'center',
502
+ flexShrink: 0,
475
503
  }}
476
504
  >
477
505
  <OperationIcon sx={{ fontSize: 18, color: '#fff' }} />
@@ -483,12 +511,6 @@ export const AutomationTelegramNode: React.FC<AutomationTelegramNodeProps> = ({
483
511
  >
484
512
  {highlightText(data.label || operationConfig.label)}
485
513
  </Typography>
486
- <Typography
487
- variant="caption"
488
- sx={{ color: '#9CA3AF', fontSize: 10 }}
489
- >
490
- {data.description || operationConfig.description}
491
- </Typography>
492
514
  </Box>
493
515
  </Box>
494
516
  {renderStatusBadge()}
@@ -43,6 +43,14 @@ export type {
43
43
  AutomationInteractionNodeData,
44
44
  AutomationInteractionNodeProps,
45
45
  } from './AutomationInteractionNode';
46
+ // Email Node
47
+ export { AutomationEmailNode } from './AutomationEmailNode';
48
+ export type {
49
+ EmailOperationType,
50
+ EmailTokenData,
51
+ AutomationEmailNodeData,
52
+ AutomationEmailNodeProps,
53
+ } from './AutomationEmailNode';
46
54
  // Monitoring Node
47
55
  export { AutomationMonitoringNode } from './AutomationMonitoringNode';
48
56
  export type {
@@ -16,6 +16,7 @@ import {
16
16
  AutomationSlackNode,
17
17
  AutomationTelegramNode,
18
18
  AutomationInteractionNode,
19
+ AutomationEmailNode,
19
20
  AutomationMonitoringNode,
20
21
  } from '../components/automation';
21
22
 
@@ -46,6 +47,8 @@ export default {
46
47
  AutomationTelegramNode,
47
48
  // Interaction Node
48
49
  AutomationInteractionNode,
50
+ // Email Node
51
+ AutomationEmailNode,
49
52
  // Monitoring Node
50
53
  AutomationMonitoringNode,
51
54
  };
@@ -67,9 +67,9 @@ export const ArticleAnalyzerIcon: React.FC<SvgIconProps> = ({ size = 15, color =
67
67
  export const StartNodeIcon: React.FC<SvgIconProps> = ({ size, color, style }) => (
68
68
  <svg width="35" height="35" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
69
69
  <path d="M0 14C0 6.26801 6.26801 0 14 0C21.732 0 28 6.26801 28 14C28 21.732 21.732 28 14 28C6.26801 28 0 21.732 0 14Z" fill="#1E3A8A"/>
70
- <g clip-path="url(#clip0_8650_35616)">
71
- <path d="M14.0007 20.6663C17.6825 20.6663 20.6673 17.6816 20.6673 13.9997C20.6673 10.3178 17.6825 7.33301 14.0007 7.33301C10.3188 7.33301 7.33398 10.3178 7.33398 13.9997C7.33398 17.6816 10.3188 20.6663 14.0007 20.6663Z" stroke="#86EFAC" stroke-linecap="round" stroke-linejoin="round"/>
72
- <path d="M12.666 11.333L16.666 13.9997L12.666 16.6663V11.333Z" stroke="#86EFAC" stroke-linecap="round" stroke-linejoin="round"/>
70
+ <g clipPath="url(#clip0_8650_35616)">
71
+ <path d="M14.0007 20.6663C17.6825 20.6663 20.6673 17.6816 20.6673 13.9997C20.6673 10.3178 17.6825 7.33301 14.0007 7.33301C10.3188 7.33301 7.33398 10.3178 7.33398 13.9997C7.33398 17.6816 10.3188 20.6663 14.0007 20.6663Z" stroke="#86EFAC" strokeLinecap="round" strokeLinejoin="round"/>
72
+ <path d="M12.666 11.333L16.666 13.9997L12.666 16.6663V11.333Z" stroke="#86EFAC" strokeLinecap="round" strokeLinejoin="round"/>
73
73
  </g>
74
74
  <defs>
75
75
  <clipPath id="clip0_8650_35616">
@@ -85,10 +85,10 @@ export const StartNodeIcon: React.FC<SvgIconProps> = ({ size, color, style }) =>
85
85
  export const NavigationIcon: React.FC<SvgIconProps> = ({ size, color, style }) => (
86
86
  <svg width="35" height="35" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
87
87
  <path d="M0 14C0 6.26801 6.26801 0 14 0C21.732 0 28 6.26801 28 14C28 21.732 21.732 28 14 28C6.26801 28 0 21.732 0 14Z" fill="#1E3A8A"/>
88
- <g clip-path="url(#clip0_8650_35642)">
89
- <path d="M14.0007 20.6663C17.6825 20.6663 20.6673 17.6816 20.6673 13.9997C20.6673 10.3178 17.6825 7.33301 14.0007 7.33301C10.3188 7.33301 7.33398 10.3178 7.33398 13.9997C7.33398 17.6816 10.3188 20.6663 14.0007 20.6663Z" stroke="#93C5FD" stroke-linecap="round" stroke-linejoin="round"/>
90
- <path d="M14.0007 7.33301C12.2888 9.13044 11.334 11.5175 11.334 13.9997C11.334 16.4818 12.2888 18.8689 14.0007 20.6663C15.7125 18.8689 16.6673 16.4818 16.6673 13.9997C16.6673 11.5175 15.7125 9.13044 14.0007 7.33301Z" stroke="#93C5FD" stroke-linecap="round" stroke-linejoin="round"/>
91
- <path d="M7.33398 14H20.6673" stroke="#93C5FD" stroke-linecap="round" stroke-linejoin="round"/>
88
+ <g clipPath="url(#clip0_8650_35642)">
89
+ <path d="M14.0007 20.6663C17.6825 20.6663 20.6673 17.6816 20.6673 13.9997C20.6673 10.3178 17.6825 7.33301 14.0007 7.33301C10.3188 7.33301 7.33398 10.3178 7.33398 13.9997C7.33398 17.6816 10.3188 20.6663 14.0007 20.6663Z" stroke="#93C5FD" strokeLinecap="round" strokeLinejoin="round"/>
90
+ <path d="M14.0007 7.33301C12.2888 9.13044 11.334 11.5175 11.334 13.9997C11.334 16.4818 12.2888 18.8689 14.0007 20.6663C15.7125 18.8689 16.6673 16.4818 16.6673 13.9997C16.6673 11.5175 15.7125 9.13044 14.0007 7.33301Z" stroke="#93C5FD" strokeLinecap="round" strokeLinejoin="round"/>
91
+ <path d="M7.33398 14H20.6673" stroke="#93C5FD" strokeLinecap="round" strokeLinejoin="round"/>
92
92
  </g>
93
93
  <defs>
94
94
  <clipPath id="clip0_8650_35642">
@@ -269,7 +269,7 @@ export const EntityIcon: React.FC<SvgIconProps> = ({ size, color = '#86EFAC', st
269
269
  export const EndNodeIcon: React.FC<SvgIconProps> = ({ size, color , style }) => (
270
270
  <svg width="35" height="35" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
271
271
  <path d="M0 14C0 6.26801 6.26801 0 14 0C21.732 0 28 6.26801 28 14C28 21.732 21.732 28 14 28C6.26801 28 0 21.732 0 14Z" fill="#1E3A8A"/>
272
- <path d="M18.6667 8H9.33333C8.59695 8 8 8.59695 8 9.33333V18.6667C8 19.403 8.59695 20 9.33333 20H18.6667C19.403 20 20 19.403 20 18.6667V9.33333C20 8.59695 19.403 8 18.6667 8Z" stroke="#FCA5A5" stroke-linecap="round" stroke-linejoin="round"/>
272
+ <path d="M18.6667 8H9.33333C8.59695 8 8 8.59695 8 9.33333V18.6667C8 19.403 8.59695 20 9.33333 20H18.6667C19.403 20 20 19.403 20 18.6667V9.33333C20 8.59695 19.403 8 18.6667 8Z" stroke="#FCA5A5" strokeLinecap="round" strokeLinejoin="round"/>
273
273
  </svg>
274
274
 
275
275
  );
@@ -277,16 +277,16 @@ export const EndNodeIcon: React.FC<SvgIconProps> = ({ size, color , style }) =>
277
277
  export const FormattingNodeIcon: React.FC<SvgIconProps> = ({ size, color , style }) => (
278
278
  <svg width="35" height="35" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
279
279
  <path d="M0 14C0 6.26801 6.26801 0 14 0C21.732 0 28 6.26801 28 14C28 21.732 21.732 28 14 28C6.26801 28 0 21.732 0 14Z" fill="#1E3A8A"/>
280
- <path d="M15.9993 7.33301H9.99935C9.64573 7.33301 9.30659 7.47348 9.05654 7.72353C8.80649 7.97358 8.66602 8.31272 8.66602 8.66634V19.333C8.66602 19.6866 8.80649 20.0258 9.05654 20.2758C9.30659 20.5259 9.64573 20.6663 9.99935 20.6663H17.9993C18.353 20.6663 18.6921 20.5259 18.9422 20.2758C19.1922 20.0258 19.3327 19.6866 19.3327 19.333V10.6663L15.9993 7.33301Z" stroke="#D8B4FE" stroke-linecap="round" stroke-linejoin="round"/>
281
- <path d="M15.334 7.33301V9.99967C15.334 10.3533 15.4745 10.6924 15.7245 10.9425C15.9746 11.1925 16.3137 11.333 16.6673 11.333H19.334" stroke="#D8B4FE" stroke-linecap="round" stroke-linejoin="round"/>
282
- <path d="M12.6673 12H11.334" stroke="#D8B4FE" stroke-linecap="round" stroke-linejoin="round"/>
283
- <path d="M16.6673 14.667H11.334" stroke="#D8B4FE" stroke-linecap="round" stroke-linejoin="round"/>
284
- <path d="M16.6673 17.333H11.334" stroke="#D8B4FE" stroke-linecap="round" stroke-linejoin="round"/>
280
+ <path d="M15.9993 7.33301H9.99935C9.64573 7.33301 9.30659 7.47348 9.05654 7.72353C8.80649 7.97358 8.66602 8.31272 8.66602 8.66634V19.333C8.66602 19.6866 8.80649 20.0258 9.05654 20.2758C9.30659 20.5259 9.64573 20.6663 9.99935 20.6663H17.9993C18.353 20.6663 18.6921 20.5259 18.9422 20.2758C19.1922 20.0258 19.3327 19.6866 19.3327 19.333V10.6663L15.9993 7.33301Z" stroke="#D8B4FE" strokeLinecap="round" strokeLinejoin="round"/>
281
+ <path d="M15.334 7.33301V9.99967C15.334 10.3533 15.4745 10.6924 15.7245 10.9425C15.9746 11.1925 16.3137 11.333 16.6673 11.333H19.334" stroke="#D8B4FE" strokeLinecap="round" strokeLinejoin="round"/>
282
+ <path d="M12.6673 12H11.334" stroke="#D8B4FE" strokeLinecap="round" strokeLinejoin="round"/>
283
+ <path d="M16.6673 14.667H11.334" stroke="#D8B4FE" strokeLinecap="round" strokeLinejoin="round"/>
284
+ <path d="M16.6673 17.333H11.334" stroke="#D8B4FE" strokeLinecap="round" strokeLinejoin="round"/>
285
285
  </svg>
286
286
  );
287
287
 
288
288
  export const ApiNodeIcon: React.FC<SvgIconProps> = ({ size, color , style }) => (
289
289
  <svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
290
- <path d="M3.89167 3.92167L2.66667 2.68583M10.4242 3.92167L11.6492 2.68583M2.66583 11.7525L3.89083 10.5158M7.1575 2.27417V0.625M2.2575 7.21917H0.625M12.1875 12.155L15.3658 10.9C15.4425 10.8693 15.5083 10.8164 15.5546 10.748C15.6009 10.6796 15.6256 10.5988 15.6256 10.5162C15.6256 10.4337 15.6009 10.3529 15.5546 10.2845C15.5083 10.2161 15.4425 10.1632 15.3658 10.1325L7.72833 7.12C7.65398 7.09123 7.57285 7.08475 7.49488 7.10138C7.4169 7.118 7.34547 7.157 7.28931 7.21359C7.23316 7.27019 7.19472 7.34193 7.17871 7.42003C7.1627 7.49813 7.16981 7.57921 7.19917 7.65333L10.1842 15.3625C10.3192 15.7125 10.8092 15.7125 10.9442 15.3625L12.1875 12.155Z" stroke="#FCFAFF" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
290
+ <path d="M3.89167 3.92167L2.66667 2.68583M10.4242 3.92167L11.6492 2.68583M2.66583 11.7525L3.89083 10.5158M7.1575 2.27417V0.625M2.2575 7.21917H0.625M12.1875 12.155L15.3658 10.9C15.4425 10.8693 15.5083 10.8164 15.5546 10.748C15.6009 10.6796 15.6256 10.5988 15.6256 10.5162C15.6256 10.4337 15.6009 10.3529 15.5546 10.2845C15.5083 10.2161 15.4425 10.1632 15.3658 10.1325L7.72833 7.12C7.65398 7.09123 7.57285 7.08475 7.49488 7.10138C7.4169 7.118 7.34547 7.157 7.28931 7.21359C7.23316 7.27019 7.19472 7.34193 7.17871 7.42003C7.1627 7.49813 7.16981 7.57921 7.19917 7.65333L10.1842 15.3625C10.3192 15.7125 10.8092 15.7125 10.9442 15.3625L12.1875 12.155Z" stroke="#FCFAFF" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round"/>
291
291
  </svg>
292
292
  );