240 lines
8.5 KiB
TypeScript
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/automation2Api';
|
|
|
|
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>
|
|
</>
|
|
)}
|
|
</>
|
|
);
|
|
};
|