frontend_nyla/src/components/FlowEditor/nodes/configs/EmailNodeConfig.tsx
2026-04-07 00:49:12 +02:00

240 lines
8.5 KiB
TypeScript

/**
* Email node config - connection selector, folder dropdown, query, subject, body.
*/
import React, { useEffect, useState } from 'react';
import type { NodeConfigRendererProps } from './types';
import { fetchConnections, fetchBrowse, type UserConnection, type BrowseEntry } from '../../../../api/workflowApi';
export const EmailNodeConfig: React.FC<NodeConfigRendererProps> = ({
params,
updateParam,
instanceId,
request,
nodeType = 'email.checkEmail',
}) => {
const [connections, setConnections] = useState<UserConnection[]>([]);
const [folders, setFolders] = useState<BrowseEntry[]>([]);
const [loading, setLoading] = useState(false);
const [foldersLoading, setFoldersLoading] = useState(false);
useEffect(() => {
if (instanceId && request) {
setLoading(true);
fetchConnections(request, instanceId)
.then(setConnections)
.catch(() => setConnections([]))
.finally(() => setLoading(false));
}
}, [instanceId, request]);
const connectionId = (params.connectionId as string) ?? '';
const selectedConn = connections.find((c) => c.id === connectionId);
const mailService = selectedConn?.authority === 'google' ? 'gmail' : 'outlook';
useEffect(() => {
if (instanceId && request && connectionId) {
setFoldersLoading(true);
fetchBrowse(request, instanceId, connectionId, mailService, '/')
.then((r) => setFolders(r.items.filter((e) => e.isFolder)))
.catch(() => setFolders([]))
.finally(() => setFoldersLoading(false));
} else {
setFolders([]);
}
}, [instanceId, request, connectionId, mailService]);
const isDraft = nodeType === 'email.draftEmail';
const isSearch = nodeType === 'email.searchEmail';
const folderValue = (params.folder as string) ?? (isSearch ? 'All' : 'Inbox');
return (
<>
<div>
<label>Account</label>
<select
value={connectionId}
onChange={(e) => updateParam('connectionId', e.target.value)}
disabled={loading}
>
<option value="">{loading ? 'Loading...' : 'Select connection'}</option>
{connections.map((c) => (
<option key={c.id} value={c.id}>
{c.externalEmail ?? c.externalUsername ?? c.id}
</option>
))}
</select>
</div>
{!isDraft && (
<div>
<label>Folder</label>
<select
value={folderValue}
onChange={(e) => updateParam('folder', e.target.value)}
disabled={foldersLoading || !connectionId}
>
<option value="">
{foldersLoading ? 'Loading folders...' : !connectionId ? 'Select account first' : 'Select folder'}
</option>
{isSearch && <option value="All">All</option>}
{folders.length > 0
? folders.map((f) => {
const folderId = (f.path ?? '').replace(/^\//, '') || (f.metadata as { id?: string })?.id || '';
const value = folderId || f.name;
if (!value) return null;
return (
<option key={value} value={value}>
{f.name}
</option>
);
})
: !isSearch && (
<>
<option value="Inbox">Inbox</option>
<option value="Drafts">Drafts</option>
<option value="SentItems">Sent Items</option>
<option value="DeletedItems">Deleted Items</option>
<option value="JunkEmail">Junk Email</option>
</>
)}
{folderValue &&
!folders.some(
(f) =>
((f.path ?? '').replace(/^\//, '') || (f.metadata as { id?: string })?.id) === folderValue
) &&
folderValue !== 'All' && (
<option value={folderValue}>{folderValue}</option>
)}
</select>
</div>
)}
{isSearch && (
<>
<div>
<label>Search query (optional)</label>
<input
value={(params.query as string) ?? ''}
onChange={(e) => updateParam('query', e.target.value)}
placeholder="General search term (subject, body, from)"
/>
</div>
<div>
<label>From address (optional)</label>
<input
value={(params.fromAddress as string) ?? ''}
onChange={(e) => updateParam('fromAddress', e.target.value)}
placeholder="e.g. sender@example.com"
/>
</div>
<div>
<label>To address (optional)</label>
<input
value={(params.toAddress as string) ?? ''}
onChange={(e) => updateParam('toAddress', e.target.value)}
placeholder="e.g. recipient@example.com"
/>
</div>
<div>
<label>Subject contains (optional)</label>
<input
value={(params.subjectContains as string) ?? ''}
onChange={(e) => updateParam('subjectContains', e.target.value)}
placeholder="Word or phrase in subject"
/>
</div>
<div>
<label>Body/content contains (optional)</label>
<input
value={(params.bodyContains as string) ?? ''}
onChange={(e) => updateParam('bodyContains', e.target.value)}
placeholder="Word or phrase in email body"
/>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<input
type="checkbox"
id="searchHasAttachment"
checked={!!(params.hasAttachment as boolean)}
onChange={(e) => updateParam('hasAttachment', e.target.checked)}
/>
<label htmlFor="searchHasAttachment">Only emails with attachment</label>
</div>
<div>
<label>Limit</label>
<input
type="number"
value={(params.limit as number) ?? 100}
onChange={(e) => updateParam('limit', parseInt(e.target.value, 10) || 100)}
/>
</div>
</>
)}
{nodeType === 'email.checkEmail' && (
<>
<div>
<label>From address (optional)</label>
<input
value={(params.fromAddress as string) ?? ''}
onChange={(e) => updateParam('fromAddress', e.target.value)}
placeholder="e.g. sender@example.com"
/>
</div>
<div>
<label>Subject contains (optional)</label>
<input
value={(params.subjectContains as string) ?? ''}
onChange={(e) => updateParam('subjectContains', e.target.value)}
placeholder="Word or phrase in subject"
/>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<input
type="checkbox"
id="hasAttachment"
checked={!!(params.hasAttachment as boolean)}
onChange={(e) => updateParam('hasAttachment', e.target.checked)}
/>
<label htmlFor="hasAttachment">Only emails with attachment</label>
</div>
<div>
<label>Limit</label>
<input
type="number"
value={(params.limit as number) ?? 100}
onChange={(e) => updateParam('limit', parseInt(e.target.value, 10) || 100)}
/>
</div>
</>
)}
{isDraft && (
<>
<div>
<label>Subject</label>
<input
value={(params.subject as string) ?? ''}
onChange={(e) => updateParam('subject', e.target.value)}
placeholder="Email subject (or leave empty if connected to AI node above)"
/>
</div>
<div>
<label>Body</label>
<textarea
value={(params.body as string) ?? ''}
onChange={(e) => updateParam('body', e.target.value)}
placeholder="Email body (or leave empty if connected to AI node above)"
rows={4}
/>
</div>
<div>
<label>To (optional)</label>
<input
value={(params.to as string) ?? ''}
onChange={(e) => updateParam('to', e.target.value)}
placeholder="Recipient(s) (or from AI when connected)"
/>
</div>
</>
)}
</>
);
};