@integry/sdk 4.6.39 → 4.6.40

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.
@@ -0,0 +1,565 @@
1
+ import { html } from 'htm/preact';
2
+ import { useState, useRef, useEffect } from 'preact/hooks';
3
+ import { ListBox } from '@/components/MultipurposeField/Dropdown';
4
+ import FunctionForm from '@/features/common/FunctionForm';
5
+ import AuthSelectorV2 from '@/features/common/AuthSelectorV2';
6
+ import { LargeLoader } from '@/components/LargeLoader';
7
+ import { Hint } from '@/components/Tooltip';
8
+ import styles from './styles.module.scss';
9
+
10
+ // Types for our component
11
+
12
+ type Authorization = {
13
+ id: number;
14
+ display_name: string;
15
+ };
16
+
17
+ type FunctionFieldProps = {
18
+ onChange: (value: any) => void;
19
+ value?: any;
20
+ label?: string;
21
+ name: string;
22
+ description?: string;
23
+ field: Record<string, any>;
24
+ apiHandler?: any;
25
+ };
26
+
27
+ const FunctionField = (props: FunctionFieldProps) => {
28
+ const { value, label, description, field } = props;
29
+ const [isOpen, setIsOpen] = useState(false);
30
+ const [selectedFunction, setSelectedFunction] = useState<string>('');
31
+ const [activeTab, setActiveTab] = useState('authorization');
32
+ const [functionDetails, setFunctionDetails] = useState<any>(null);
33
+ const [loadingFunctionDetails, setLoadingFunctionDetails] = useState<boolean>(
34
+ false,
35
+ );
36
+ const [connectedAccountId, setConnectedAccountId] = useState<number | null>(
37
+ null,
38
+ );
39
+ const [connectedAccounts, setConnectedAccounts] = useState<any>([]);
40
+ const [visibleFields, setVisibleFields] = useState<any>([]);
41
+ const [functionValue, setFunctionValue] = useState<any>(value);
42
+ const [formHasInvalidFields, setFormHasInvalidFields] = useState<boolean>(
43
+ false,
44
+ );
45
+ const [userFilledData, setUserFilledData] = useState<any>(
46
+ value?.default_arguments || {},
47
+ );
48
+
49
+ const modalRef = useRef<HTMLDivElement | null>(null);
50
+ const FUNCTIONS_LIST_URL = `https://api.integry.io/functions/list/`;
51
+
52
+ // Find the selected function from the saved value
53
+ useEffect(() => {
54
+ // if (value?.functionId) {
55
+ // const func = FUNCTION_OPTIONS.find((f) => f.id === value.functionId);
56
+ // if (func) {
57
+ // setSelectedFunction(func);
58
+ // setConfigValues(value.config || {});
59
+ // }
60
+ // }
61
+ }, [value]);
62
+
63
+ // Handle clicking outside the modal to close it
64
+ useEffect(() => {
65
+ function handleClickOutside(event: MouseEvent) {
66
+ if (
67
+ modalRef.current &&
68
+ !modalRef.current.contains(event.target as Node)
69
+ ) {
70
+ setIsOpen(false);
71
+ }
72
+ }
73
+
74
+ if (isOpen) {
75
+ document.addEventListener('mousedown', handleClickOutside);
76
+ }
77
+
78
+ return () => {
79
+ document.removeEventListener('mousedown', handleClickOutside);
80
+ };
81
+ }, [isOpen]);
82
+
83
+ const handleOpenModal = () => {
84
+ setIsOpen(true);
85
+ };
86
+
87
+ const handleCloseModal = () => {
88
+ setIsOpen(false);
89
+ };
90
+
91
+ const getVisibleFields = (properties: any[]) => {
92
+ const response: any[] = [];
93
+
94
+ Object.entries(properties).forEach(([key, property]) => {
95
+ if (property.meta?.ui?.is_visible) {
96
+ response.push({ key, ...property });
97
+ }
98
+ });
99
+
100
+ return response;
101
+ };
102
+
103
+ const getInvalidFields = (properties: any[]) => {
104
+ const response: any[] = [];
105
+
106
+ Object.entries(properties).forEach(([key, property]) => {
107
+ if (
108
+ property.meta?.ui?.is_visible &&
109
+ property.meta?.is_required &&
110
+ !userFilledData[key]
111
+ ) {
112
+ response.push({ key, ...property });
113
+ }
114
+ });
115
+
116
+ return response;
117
+ };
118
+
119
+ const handleFunctionSelect = (val: any) => {
120
+ setSelectedFunction(val);
121
+ setActiveTab('authorization');
122
+ setLoadingFunctionDetails(true);
123
+ props.apiHandler.getFunctionDetails(val, {}).then((response: any) => {
124
+ setFunctionDetails(response);
125
+ setLoadingFunctionDetails(false);
126
+ setConnectedAccounts(response?.meta?.app?.connected_accounts || []);
127
+ setVisibleFields(
128
+ getVisibleFields(response?.parameters?.properties || []),
129
+ );
130
+ setFormHasInvalidFields(
131
+ getInvalidFields(response?.parameters?.properties || []).length > 0,
132
+ );
133
+ if (response && response.meta?.app?.connected_accounts?.length > 0) {
134
+ setConnectedAccountId(response.meta.app.connected_accounts[0].id);
135
+ } else {
136
+ setConnectedAccountId(null);
137
+ }
138
+ });
139
+
140
+ // const functionId = (e.target as HTMLSelectElement).value;
141
+ // const func = FUNCTION_OPTIONS.find((f) => f.id === functionId);
142
+ // setSelectedFunction(func || null);
143
+ // setConfigValues({});
144
+ };
145
+
146
+ const removeMeta = (obj: any): any => {
147
+ if (Array.isArray(obj)) {
148
+ return obj.map(removeMeta);
149
+ }
150
+ if (typeof obj === 'object' && obj !== null) {
151
+ const newObj: any = {};
152
+ Object.keys(obj).forEach((key) => {
153
+ if (key !== 'meta') {
154
+ newObj[key] = removeMeta(obj[key]);
155
+ }
156
+ });
157
+ return newObj;
158
+ }
159
+ return obj;
160
+ };
161
+
162
+ const handleSave = () => {
163
+ if (selectedFunction && functionDetails) {
164
+ const schema = {
165
+ json_schema: {
166
+ type: 'function',
167
+ function: removeMeta(functionDetails),
168
+ },
169
+ meta: {
170
+ id: functionDetails.meta.id,
171
+ version: functionDetails.meta.version,
172
+ connected_account_id: connectedAccountId,
173
+ form_page_machine_name: null,
174
+ name: functionDetails.name,
175
+ title: functionDetails.meta.ui.title,
176
+ app_name: functionDetails.meta.app.title,
177
+ app_icon_url: functionDetails.meta.app.icon_url,
178
+ },
179
+ default_arguments: userFilledData,
180
+ parameters_visible_to_user: {},
181
+ };
182
+ setFunctionValue(schema);
183
+ if (props.onChange) {
184
+ props.onChange(schema);
185
+ }
186
+ // onChange({
187
+ // functionId: selectedFunction.id,
188
+ // functionName: selectedFunction.name,
189
+ // config: configValues,
190
+ // });
191
+ setIsOpen(false);
192
+ }
193
+ };
194
+
195
+ const handleAuthCreated = (auth: Authorization) => {
196
+ setActiveTab('input');
197
+ const existingAuth = connectedAccounts.find(
198
+ (account: Authorization) => account.id === auth.id,
199
+ );
200
+ if (!existingAuth) {
201
+ setConnectedAccounts((prev: Authorization[]) => [...prev, auth]);
202
+ setConnectedAccountId(auth.id);
203
+ }
204
+ };
205
+
206
+ const handleAuthDeleted = (authId: number) => {
207
+ setConnectedAccounts((prev: Authorization[]) =>
208
+ prev.filter((account: Authorization) => account.id !== authId),
209
+ );
210
+ setConnectedAccountId(null);
211
+ };
212
+
213
+ const handleFunctionDeleted = () => {
214
+ setFunctionValue(null);
215
+ setFunctionDetails(null);
216
+ setSelectedFunction('');
217
+ setConnectedAccountId(null);
218
+ };
219
+
220
+ const handleCustomaSave = (customSaveResponse: any) => {
221
+ setFormHasInvalidFields(customSaveResponse.hasInvalidFields);
222
+ setUserFilledData(customSaveResponse.data);
223
+ };
224
+
225
+ return html`
226
+ <div class=${styles.functionFieldContainer || 'function-field-container'}>
227
+ ${label &&
228
+ html`<label class=${styles.functionFieldLabel || 'function-field-label'}
229
+ >${label}
230
+ ${description &&
231
+ html`
232
+ <div
233
+ class=${styles.functionFieldDescription ||
234
+ 'function-field-description'}
235
+ >
236
+ ${description}
237
+ </div>
238
+ `}
239
+ </label>`}
240
+ ${functionValue?.meta?.id
241
+ ? html`
242
+ <div
243
+ class=${styles.functionFieldSummary || 'function-field-summary'}
244
+ >
245
+ <div
246
+ class=${styles.functionFieldSelected ||
247
+ 'function-field-selected'}
248
+ >
249
+ <div class=${styles.functionFieldInfo || 'function-field-info'}>
250
+ <img
251
+ src=${functionValue?.meta?.app_icon_url}
252
+ alt=${functionValue.meta?.app_name || 'App'}
253
+ class=${styles.functionFieldAppIcon ||
254
+ 'function-field-app-icon'}
255
+ />
256
+ <div
257
+ class=${styles.functionFieldNameContainer ||
258
+ 'function-field-name-container'}
259
+ >
260
+ <span
261
+ class=${styles.functionFieldName || 'function-field-name'}
262
+ >
263
+ ${functionValue.meta?.title || 'Function'}
264
+ </span>
265
+ <span
266
+ class=${styles.functionFieldAppName ||
267
+ 'function-field-app-name'}
268
+ >
269
+ ${functionValue.meta?.name || ''}
270
+ </span>
271
+ </div>
272
+ </div>
273
+ <div
274
+ class=${styles.functionFieldActions ||
275
+ 'function-field-actions'}
276
+ >
277
+ <button
278
+ type="button"
279
+ class=${styles.functionFieldDeleteBtn ||
280
+ 'function-field-delete-btn'}
281
+ onClick=${(e: any) => {
282
+ e.stopPropagation();
283
+ handleFunctionDeleted();
284
+ }}
285
+ aria-label="Delete"
286
+ >
287
+ <svg
288
+ width="16"
289
+ height="16"
290
+ viewBox="0 0 24 24"
291
+ fill="none"
292
+ xmlns="http://www.w3.org/2000/svg"
293
+ >
294
+ <path
295
+ d="M3 6H5H21"
296
+ stroke="currentColor"
297
+ stroke-width="2"
298
+ stroke-linecap="round"
299
+ stroke-linejoin="round"
300
+ />
301
+ <path
302
+ d="M8 6V4C8 3.46957 8.21071 2.96086 8.58579 2.58579C8.96086 2.21071 9.46957 2 10 2H14C14.5304 2 15.0391 2.21071 15.4142 2.58579C15.7893 2.96086 16 3.46957 16 4V6M19 6V20C19 20.5304 18.7893 21.0391 18.4142 21.4142C18.0391 21.7893 17.5304 22 17 22H7C6.46957 22 5.96086 21.7893 5.58579 21.4142C5.21071 21.0391 5 20.5304 5 20V6H19Z"
303
+ stroke="currentColor"
304
+ stroke-width="2"
305
+ stroke-linecap="round"
306
+ stroke-linejoin="round"
307
+ />
308
+ </svg>
309
+ </button>
310
+ <button
311
+ type="button"
312
+ class=${styles.functionFieldEditBtn ||
313
+ 'function-field-edit-btn'}
314
+ onClick=${handleOpenModal}
315
+ >
316
+ Edit
317
+ </button>
318
+ </div>
319
+ </div>
320
+ </div>
321
+ `
322
+ : html`
323
+ <button
324
+ type="button"
325
+ class=${styles.functionFieldAddBtn || 'function-field-add-btn'}
326
+ onClick=${handleOpenModal}
327
+ >
328
+ Add action
329
+ </button>
330
+ `}
331
+ ${isOpen &&
332
+ html`
333
+ <div class=${styles.functionFieldOverlay || 'function-field-overlay'}>
334
+ <div
335
+ class=${styles.functionFieldModal || 'function-field-modal'}
336
+ ref=${modalRef}
337
+ >
338
+ <div
339
+ class=${styles.functionFieldModalHeader ||
340
+ 'function-field-modal-header'}
341
+ >
342
+ <h3
343
+ class=${styles.functionFieldModalTitle ||
344
+ 'function-field-modal-title'}
345
+ >
346
+ Add action
347
+ </h3>
348
+ <p
349
+ class=${styles.functionFieldModalDescription ||
350
+ 'function-field-modal-description'}
351
+ >
352
+ The AI will use this action
353
+ </p>
354
+ <button
355
+ type="button"
356
+ class=${styles.functionFieldCloseBtn ||
357
+ 'function-field-close-btn'}
358
+ onClick=${handleCloseModal}
359
+ >
360
+ ×
361
+ </button>
362
+ </div>
363
+
364
+ <div
365
+ class=${styles.functionFieldModalBody ||
366
+ 'function-field-modal-body'}
367
+ >
368
+ <div
369
+ class=${styles.functionFieldInlineGroup ||
370
+ 'function-field-inline-group'}
371
+ >
372
+ <${ListBox}
373
+ key=${field.activity_field?.id}
374
+ fieldId=${field.activity_field?.id ||
375
+ field.id ||
376
+ field?.activity_field?.machine_name}
377
+ title=${`Action`}
378
+ placeholder=${`Select an action to use`}
379
+ isRequired=${true}
380
+ isHidden=${false}
381
+ isSearchable=${true}
382
+ value=${selectedFunction}
383
+ onChange=${(val: string) => {
384
+ handleFunctionSelect(val);
385
+ }}
386
+ isDynamic=${true}
387
+ endpointUrl=${FUNCTIONS_LIST_URL}
388
+ endpointData=${`{"include": "meta"}`}
389
+ apiHandler=${props.apiHandler}
390
+ optionKeyPath=${`name`}
391
+ valueKeyPath=${`meta.ui.title`}
392
+ enableServerSideSearch=${true}
393
+ serverSideSearchParamName=${`search`}
394
+ iconKeyPath=${`meta.app.icon_url`}
395
+ optionDescriptionKeyPath=${`description`}
396
+ ><//>
397
+ </div>
398
+
399
+ ${selectedFunction &&
400
+ html`
401
+ <div
402
+ class=${styles.functionFieldConfig || 'function-field-config'}
403
+ >
404
+ <div
405
+ class=${styles.functionFieldTabs || 'function-field-tabs'}
406
+ >
407
+ <button
408
+ type="button"
409
+ class=${`${
410
+ styles.functionFieldTab || 'function-field-tab'
411
+ } ${
412
+ activeTab === 'authorization'
413
+ ? styles.functionFieldTabActive ||
414
+ 'function-field-tab-active'
415
+ : ''
416
+ }`}
417
+ onClick=${() => setActiveTab('authorization')}
418
+ >
419
+ Authorization
420
+ </button>
421
+ <${Hint} dismissOnClick=${false} position="top" deltaY=${0}>
422
+ <button
423
+ data-hint=${connectedAccountId
424
+ ? ''
425
+ : `Please connect your account to proceed.`}
426
+ type="button"
427
+ class=${`${
428
+ styles.functionFieldTab || 'function-field-tab'
429
+ } ${
430
+ activeTab === 'input'
431
+ ? styles.functionFieldTabActive ||
432
+ 'function-field-tab-active'
433
+ : ''
434
+ } ${connectedAccountId ? '' : styles.disabled}`}
435
+ onClick=${() => {
436
+ if (connectedAccountId) {
437
+ setActiveTab('input');
438
+ }
439
+ }}
440
+ >
441
+ Input
442
+ ${visibleFields.length > 0
443
+ ? ` (${visibleFields.length})`
444
+ : ''}
445
+ </button>
446
+ <//>
447
+ </div>
448
+
449
+ <div
450
+ class=${styles.functionFieldTabContent ||
451
+ 'function-field-tab-content'}
452
+ >
453
+ ${activeTab === 'authorization'
454
+ ? html`
455
+ <div
456
+ class=${styles.functionFieldAuthContent ||
457
+ 'function-field-auth-content'}
458
+ >
459
+ ${loadingFunctionDetails
460
+ ? html`
461
+ <div
462
+ class=${styles.loader ||
463
+ 'function-field-loader'}
464
+ >
465
+ <${LargeLoader} />
466
+ </div>
467
+ `
468
+ : functionDetails &&
469
+ html`<${AuthSelectorV2}
470
+ authorizations=${connectedAccounts}
471
+ appName=${functionDetails.meta.app.title}
472
+ apiHandler=${props.apiHandler}
473
+ onAuthSelected=${(authId: number) => {
474
+ setConnectedAccountId(authId);
475
+ }}
476
+ onAuthCreated=${handleAuthCreated}
477
+ onAuthDeleted=${handleAuthDeleted}
478
+ selectedAuthId=${connectedAccountId ||
479
+ functionDetails.meta.app.connected_accounts
480
+ .length > 0
481
+ ? functionDetails.meta.app
482
+ .connected_accounts[0]?.id
483
+ : null}
484
+ loginUrl=${functionDetails.meta.app.login_url}
485
+ ><//>`}
486
+ </div>
487
+ `
488
+ : html`
489
+ <div
490
+ class=${styles.functionFieldInputContent ||
491
+ 'function-field-input-content'}
492
+ >
493
+ <${FunctionForm}
494
+ functionName=${selectedFunction}
495
+ apiHandler=${props.apiHandler}
496
+ functionArguments=${userFilledData}
497
+ connectedAccountId=${connectedAccountId}
498
+ customSaveCallback=${handleCustomaSave}
499
+ ><//>
500
+ </div>
501
+ `}
502
+ </div>
503
+ </div>
504
+ `}
505
+ </div>
506
+
507
+ ${activeTab === 'input' &&
508
+ html`<div
509
+ class=${styles.functionFieldModalFooter ||
510
+ 'function-field-modal-footer'}
511
+ >
512
+ <button
513
+ type="button"
514
+ class=${styles.functionFieldCancelBtn ||
515
+ 'function-field-cancel-btn'}
516
+ onClick=${handleCloseModal}
517
+ >
518
+ Cancel
519
+ </button>
520
+ <button
521
+ type="button"
522
+ class=${styles.functionFieldSaveBtn ||
523
+ 'function-field-save-btn'}
524
+ onClick=${handleSave}
525
+ disabled=${!selectedFunction ||
526
+ formHasInvalidFields ||
527
+ !connectedAccountId}
528
+ >
529
+ Save
530
+ </button>
531
+ </div>`}
532
+ ${activeTab === 'authorization' &&
533
+ selectedFunction &&
534
+ html`<div
535
+ class=${styles.functionFieldModalFooter ||
536
+ 'function-field-modal-footer'}
537
+ >
538
+ <button
539
+ type="button"
540
+ class=${styles.functionFieldCancelBtn ||
541
+ 'function-field-cancel-btn'}
542
+ onClick=${handleCloseModal}
543
+ >
544
+ Cancel
545
+ </button>
546
+ <button
547
+ type="button"
548
+ class=${styles.functionFieldSaveBtn ||
549
+ 'function-field-save-btn'}
550
+ onClick="${(e: any) => {
551
+ setActiveTab('input');
552
+ }}"
553
+ disabled=${!selectedFunction || !connectedAccountId}
554
+ >
555
+ Next
556
+ </button>
557
+ </div>`}
558
+ </div>
559
+ </div>
560
+ `}
561
+ </div>
562
+ `;
563
+ };
564
+
565
+ export { FunctionField, FunctionFieldProps };