@plutonhq/core-frontend 0.1.21 → 0.1.23

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.
Files changed (70) hide show
  1. package/dist-lib/@types/plans.d.ts +11 -20
  2. package/dist-lib/@types/plans.d.ts.map +1 -1
  3. package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js +49 -47
  4. package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js.map +1 -1
  5. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettings.d.ts +7 -4
  6. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettings.d.ts.map +1 -1
  7. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettings.js +177 -52
  8. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettings.js.map +1 -1
  9. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettingsTester.d.ts +9 -0
  10. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettingsTester.d.ts.map +1 -0
  11. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettingsTester.js +62 -0
  12. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettingsTester.js.map +1 -0
  13. package/dist-lib/components/Plan/PlanSettings/PlanPruneSettings.js +2 -2
  14. package/dist-lib/components/Plan/PlanSettings/PlanPruneSettings.js.map +1 -1
  15. package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js +56 -40
  16. package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js.map +1 -1
  17. package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.d.ts.map +1 -1
  18. package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.js +16 -8
  19. package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.js.map +1 -1
  20. package/dist-lib/components/Storage/AddStorage/AddStorage.module.scss.js +42 -26
  21. package/dist-lib/components/Storage/AddStorage/AddStorage.module.scss.js.map +1 -1
  22. package/dist-lib/components/Storage/StorageAuthSettings/StorageAuthSettings.d.ts.map +1 -1
  23. package/dist-lib/components/Storage/StorageAuthSettings/StorageAuthSettings.js +119 -53
  24. package/dist-lib/components/Storage/StorageAuthSettings/StorageAuthSettings.js.map +1 -1
  25. package/dist-lib/components/common/Icon/Icon.d.ts.map +1 -1
  26. package/dist-lib/components/common/Icon/Icon.js +15 -1
  27. package/dist-lib/components/common/Icon/Icon.js.map +1 -1
  28. package/dist-lib/components/index.d.ts +1 -0
  29. package/dist-lib/components/index.d.ts.map +1 -1
  30. package/dist-lib/components.js +86 -84
  31. package/dist-lib/components.js.map +1 -1
  32. package/dist-lib/providers/s3compatible.png +0 -0
  33. package/dist-lib/services/plans.d.ts +13 -1
  34. package/dist-lib/services/plans.d.ts.map +1 -1
  35. package/dist-lib/services/plans.js +88 -61
  36. package/dist-lib/services/plans.js.map +1 -1
  37. package/dist-lib/services/settings.d.ts.map +1 -1
  38. package/dist-lib/services/settings.js +25 -24
  39. package/dist-lib/services/settings.js.map +1 -1
  40. package/dist-lib/services/storage.d.ts +10 -0
  41. package/dist-lib/services/storage.d.ts.map +1 -1
  42. package/dist-lib/services/storage.js +55 -20
  43. package/dist-lib/services/storage.js.map +1 -1
  44. package/dist-lib/services.js +106 -101
  45. package/dist-lib/styles/core-frontend.css +1 -1
  46. package/dist-lib/utils/constants.d.ts.map +1 -1
  47. package/dist-lib/utils/constants.js +28 -5
  48. package/dist-lib/utils/constants.js.map +1 -1
  49. package/dist-lib/utils/helpers.d.ts +1 -0
  50. package/dist-lib/utils/helpers.d.ts.map +1 -1
  51. package/dist-lib/utils/helpers.js +17 -10
  52. package/dist-lib/utils/helpers.js.map +1 -1
  53. package/dist-lib/utils.js +28 -27
  54. package/package.json +1 -1
  55. package/src/@types/plans.ts +11 -20
  56. package/src/components/Plan/PlanSettings/PlanAdvancedSettings.tsx +4 -4
  57. package/src/components/Plan/PlanSettings/PlanNotificationSettings.tsx +179 -47
  58. package/src/components/Plan/PlanSettings/PlanNotificationSettingsTester.tsx +85 -0
  59. package/src/components/Plan/PlanSettings/PlanPruneSettings.tsx +2 -2
  60. package/src/components/Plan/PlanSettings/PlanSettings.module.scss +71 -0
  61. package/src/components/Settings/GeneralSettings/GeneralSettings.tsx +6 -1
  62. package/src/components/Storage/AddStorage/AddStorage.module.scss +74 -0
  63. package/src/components/Storage/StorageAuthSettings/StorageAuthSettings.tsx +136 -3
  64. package/src/components/common/Icon/Icon.tsx +16 -0
  65. package/src/components/index.ts +1 -0
  66. package/src/services/plans.ts +38 -1
  67. package/src/services/settings.ts +2 -2
  68. package/src/services/storage.ts +49 -0
  69. package/src/utils/constants.ts +23 -0
  70. package/src/utils/helpers.ts +9 -0
@@ -1,9 +1,10 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import { storageOptionField } from '../../../@types/storages';
3
3
  import Tristate from '../../common/form/Tristate/Tristate';
4
4
  import Icon from '../../common/Icon/Icon';
5
5
  import classes from '../AddStorage/AddStorage.module.scss';
6
6
  import StorageSettings from '../StorageSettings/StorageSettings';
7
+ import { startStorageAuthorize, getStorageAuthorizeStatus, cancelStorageAuthorize } from '../../../services/storage';
7
8
 
8
9
  interface StorageAuthSettingsProps {
9
10
  storageType: string;
@@ -16,6 +17,8 @@ interface StorageAuthSettingsProps {
16
17
  onAuthTypeChange: (authType: string) => void;
17
18
  }
18
19
 
20
+ type OAuthStatus = 'idle' | 'authorizing' | 'success' | 'error';
21
+
19
22
  const StorageAuthSettings = ({
20
23
  storageType,
21
24
  fields,
@@ -29,6 +32,16 @@ const StorageAuthSettings = ({
29
32
  const [showAdvanced, setShowAdvanced] = useState(true);
30
33
  const [showOAuthDoc, setShowOAuthDoc] = useState(false);
31
34
 
35
+ // OAuth auto-authorize state
36
+ const [oauthStatus, setOauthStatus] = useState<OAuthStatus>('idle');
37
+ const [authSessionId, setAuthSessionId] = useState<string | null>(null);
38
+ const [authUrl, setAuthUrl] = useState<string | null>(null);
39
+ const [authError, setAuthError] = useState<string | null>(null);
40
+ const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);
41
+
42
+ const installType = (window as any).plutonInstallType;
43
+ const isDesktop = installType === 'binary' || installType === 'dev';
44
+
32
45
  console.log('availableAuthTypes :', fields, authTypes, currentAuthType);
33
46
 
34
47
  useEffect(() => {
@@ -37,6 +50,76 @@ const StorageAuthSettings = ({
37
50
  }
38
51
  }, [authTypes, currentAuthType, onAuthTypeChange]);
39
52
 
53
+ // Cleanup polling on unmount
54
+ useEffect(() => {
55
+ return () => {
56
+ if (pollingRef.current) {
57
+ clearInterval(pollingRef.current);
58
+ }
59
+ };
60
+ }, []);
61
+
62
+ const stopPolling = useCallback(() => {
63
+ if (pollingRef.current) {
64
+ clearInterval(pollingRef.current);
65
+ pollingRef.current = null;
66
+ }
67
+ }, []);
68
+
69
+ const startOAuthAuthorize = useCallback(async () => {
70
+ setOauthStatus('authorizing');
71
+ setAuthUrl(null);
72
+ setAuthError(null);
73
+
74
+ try {
75
+ const { sessionId } = await startStorageAuthorize(storageType);
76
+ setAuthSessionId(sessionId);
77
+
78
+ // Start polling every 2 seconds
79
+ pollingRef.current = setInterval(async () => {
80
+ try {
81
+ const result = await getStorageAuthorizeStatus(sessionId);
82
+
83
+ if (result.authUrl) {
84
+ setAuthUrl(result.authUrl);
85
+ }
86
+
87
+ if (result.status === 'success' && result.token) {
88
+ stopPolling();
89
+ setOauthStatus('success');
90
+ onUpdate({ ...settings, token: result.token });
91
+ } else if (result.status === 'error') {
92
+ stopPolling();
93
+ setOauthStatus('error');
94
+ setAuthError(result.error || 'Authorization failed');
95
+ }
96
+ } catch (err: any) {
97
+ stopPolling();
98
+ setOauthStatus('error');
99
+ setAuthError(err?.message || 'Failed to check authorization status');
100
+ }
101
+ }, 2000);
102
+ } catch (err: any) {
103
+ setOauthStatus('error');
104
+ setAuthError(err?.message || 'Failed to start authorization');
105
+ }
106
+ }, [storageType, settings, onUpdate, stopPolling]);
107
+
108
+ const handleCancelAuthorize = useCallback(async () => {
109
+ stopPolling();
110
+ if (authSessionId) {
111
+ try {
112
+ await cancelStorageAuthorize(authSessionId);
113
+ } catch {
114
+ // Ignore cancel errors
115
+ }
116
+ }
117
+ setOauthStatus('idle');
118
+ setAuthSessionId(null);
119
+ setAuthUrl(null);
120
+ setAuthError(null);
121
+ }, [authSessionId, stopPolling]);
122
+
40
123
  return (
41
124
  <div className={classes.authSettings}>
42
125
  <div
@@ -67,14 +150,64 @@ const StorageAuthSettings = ({
67
150
  />
68
151
  </div>
69
152
  )}
70
-
71
153
  <StorageSettings
72
154
  fields={fields.filter((f) => f.authFieldType === currentAuthType)}
73
155
  settings={settings}
74
156
  onUpdate={(newSettings) => onUpdate(newSettings)}
75
157
  errors={errors}
76
158
  />
77
- {currentAuthType === 'oauth' && (
159
+
160
+ {currentAuthType === 'oauth' && isDesktop && oauthStatus === 'idle' && (
161
+ <div className={classes.oauthButton}>
162
+ <button className={classes.oauthAuthorizeBtn} onClick={startOAuthAuthorize}>
163
+ <Icon type={'key'} size={14} /> Authorize & Get Access Token
164
+ </button>
165
+ </div>
166
+ )}
167
+
168
+ {currentAuthType === 'oauth' && isDesktop && oauthStatus !== 'idle' && (
169
+ <div className={`${classes.oauthContainer} ${classes[oauthStatus]}`}>
170
+ {oauthStatus === 'authorizing' && (
171
+ <div className={classes.oauthProgress}>
172
+ <p>
173
+ <strong>Waiting for authorization...</strong>
174
+ <br />A browser window should have opened. Please authorize the connection in your browser.
175
+ </p>
176
+ {authUrl && (
177
+ <p>
178
+ If the browser didn't open automatically,{' '}
179
+ <a href={authUrl} target="_blank" rel="noopener noreferrer">
180
+ click here to authorize
181
+ </a>
182
+ .
183
+ </p>
184
+ )}
185
+ <button className={classes.oauthInnerBtn} onClick={handleCancelAuthorize}>
186
+ Cancel
187
+ </button>
188
+ </div>
189
+ )}
190
+ {oauthStatus === 'success' && (
191
+ <div className={classes.oauthProgress}>
192
+ <p>
193
+ <Icon type={'check'} size={14} /> <strong>Authorization successful!</strong> Token has been automatically filled in.
194
+ </p>
195
+ </div>
196
+ )}
197
+ {oauthStatus === 'error' && (
198
+ <div className={classes.oauthProgress}>
199
+ <p>
200
+ <strong>Authorization failed:</strong> {authError}
201
+ </p>
202
+ <button className={classes.oauthInnerBtn} onClick={startOAuthAuthorize}>
203
+ <Icon type={'key'} size={14} /> Try Again
204
+ </button>
205
+ </div>
206
+ )}
207
+ </div>
208
+ )}
209
+
210
+ {currentAuthType === 'oauth' && !isDesktop && (
78
211
  <div className={classes.oauthDoc}>
79
212
  <h4 onClick={() => setShowOAuthDoc(!showOAuthDoc)}>
80
213
  <Icon type={'key'} size={14} /> Acquiring the OAuth Access Token
@@ -1507,6 +1507,22 @@ const Icon = ({ type, color = 'currentColor', size = 16, title = '', classes = '
1507
1507
  ></path>
1508
1508
  </IconWrapper>
1509
1509
  )}
1510
+ {type === 'discord' && (
1511
+ <IconWrapper size={size} viewBox="0 0 256 199">
1512
+ <path
1513
+ fill="#5865f2"
1514
+ d="M216.856 16.597A208.5 208.5 0 0 0 164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046q-29.538-4.442-58.533 0c-1.832-4.4-4.55-9.933-6.846-14.046a207.8 207.8 0 0 0-52.855 16.638C5.618 67.147-3.443 116.4 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193A161 161 0 0 0 79.735 175.3a136.4 136.4 0 0 1-21.846-10.632a109 109 0 0 0 5.356-4.237c42.122 19.702 87.89 19.702 129.51 0a132 132 0 0 0 5.355 4.237a136 136 0 0 1-21.886 10.653c4.006 8.02 8.638 15.67 13.873 22.848c21.142-6.58 42.646-16.637 64.815-33.213c5.316-56.288-9.08-105.09-38.056-148.36M85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2s23.236 11.804 23.015 26.2c.02 14.375-10.148 26.18-23.015 26.18m85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2c0 14.375-10.148 26.18-23.015 26.18"
1515
+ />
1516
+ </IconWrapper>
1517
+ )}
1518
+ {type === 'webhook' && (
1519
+ <IconWrapper size={size} viewBox="0 0 24 24">
1520
+ <path
1521
+ fill={color}
1522
+ d="M7 21q-2.075 0-3.537-1.463T2 16q0-1.4.675-2.537t1.8-1.788q.525-.3 1.025.013t.5.862q0 .275-.112.5t-.313.325q-.7.375-1.137 1.075T4 16q0 1.25.875 2.125T7 19t2.125-.875T10 16q0-.425.238-.712T10.9 15h4.975q.2-.225.488-.363T17 14.5q.625 0 1.063.438T18.5 16t-.437 1.063T17 17.5q-.35 0-.638-.137T15.876 17H11.9q-.35 1.725-1.713 2.863T7 21m0-3.5q-.625 0-1.062-.437T5.5 16q0-.55.35-.95t.85-.525l2.35-3.9q-.725-.675-1.138-1.612T7.5 7q0-2.075 1.463-3.537T12.5 2q1.75 0 3.088 1.063T17.35 5.75q.125.475-.175.863t-.8.387q-.325 0-.612-.238t-.388-.587q-.275-.95-1.05-1.562T12.5 4q-1.25 0-2.125.875T9.5 7q0 .825.413 1.513T10.974 9.6q.35.2.438.5t-.088.6l-2.9 4.85q.05.125.063.225T8.5 16q0 .625-.437 1.063T7 17.5M17 21q-.65 0-1.263-.162T14.6 20.4q-.675-.375-.537-1.137t1.012-.763q.125 0 .275.05t.275.125q.325.175.663.25T17 19q1.25 0 2.125-.875T20 16t-.875-2.125T17 13q-.25 0-.475.038t-.45.112q-.4.125-.75.013t-.525-.388l-2.575-4.3q-.525-.1-.875-.5T11 7q0-.625.438-1.062T12.5 5.5t1.063.438T14 7v.213q0 .087-.05.212l2.175 3.65q.2-.05.425-.062T17 11q2.075 0 3.538 1.463T22 16t-1.463 3.538T17 21"
1523
+ />
1524
+ </IconWrapper>
1525
+ )}
1510
1526
  </span>
1511
1527
  );
1512
1528
  };
@@ -80,6 +80,7 @@ export { default as PlanRemoveModal } from './Plan/PlanRemoveModal/PlanRemoveMod
80
80
  export { default as PlanAdvancedSettings } from './Plan/PlanSettings/PlanAdvancedSettings';
81
81
  export { default as PlanGeneralSettings } from './Plan/PlanSettings/PlanGeneralSettings';
82
82
  export { default as PlanNotificationSettings } from './Plan/PlanSettings/PlanNotificationSettings';
83
+ export { default as PlanNotificationSettingsTester } from './Plan/PlanSettings/PlanNotificationSettingsTester';
83
84
  export { default as PlanPerformanceSettings } from './Plan/PlanSettings/PlanPerformanceSettings';
84
85
  export { default as PlanPruneSettings } from './Plan/PlanSettings/PlanPruneSettings';
85
86
  export { default as PlanScriptsSettings } from './Plan/PlanSettings/PlanScriptsSettings';
@@ -1,7 +1,7 @@
1
1
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
2
2
  import { toast } from 'react-toastify';
3
3
  import { API_URL } from '../utils/constants';
4
- import { NewPlanSettings, Plan } from '../@types/plans';
4
+ import { NewPlanSettings, Plan, PlanNotification } from '../@types/plans';
5
5
 
6
6
  // Get All Plans
7
7
  export async function getAllPlans() {
@@ -467,3 +467,40 @@ export function useCheckPlanIntegrity() {
467
467
  },
468
468
  });
469
469
  }
470
+
471
+ export async function sendTestNotificationRequest(payload: {
472
+ planId: string;
473
+ notificationCase: string;
474
+ notificationChannel: 'webhook' | 'slack' | 'discord';
475
+ channelSettings: PlanNotification['webhook'] | PlanNotification['slack'] | PlanNotification['discord'];
476
+ }) {
477
+ console.log('payload :', payload);
478
+ const headers = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
479
+ const res = await fetch(`${API_URL}/plans/${payload.planId}/action/test-notification`, {
480
+ method: 'POST',
481
+ headers,
482
+ credentials: 'include',
483
+ body: JSON.stringify(payload),
484
+ });
485
+
486
+ // Check if response is ok
487
+ if (!res.ok) {
488
+ const data = await res.json();
489
+ throw new Error(data.error);
490
+ }
491
+
492
+ const data = await res.json();
493
+ return data;
494
+ }
495
+
496
+ export function useTestNotification() {
497
+ return useMutation({
498
+ mutationFn: sendTestNotificationRequest,
499
+ onSuccess: (res) => {
500
+ console.log('# Test notification request sent successfully! :', res);
501
+ },
502
+ onError: (res) => {
503
+ console.log('# Test notification request failed! :', res);
504
+ },
505
+ });
506
+ }
@@ -50,7 +50,6 @@ export function useUpdateSettings() {
50
50
  return useMutation({
51
51
  mutationFn: updateSettings,
52
52
  onSuccess: (res) => {
53
- // TODO: Should Display a Notification Bubble.
54
53
  console.log('# Settings Updated! :', res);
55
54
  queryClient.invalidateQueries({ queryKey: ['settings'] });
56
55
  },
@@ -157,10 +156,11 @@ export async function validateIntegration(updatePayload: {
157
156
  }
158
157
 
159
158
  export function useValidateIntegration() {
159
+ const queryClient = useQueryClient();
160
160
  return useMutation({
161
161
  mutationFn: validateIntegration,
162
162
  onSuccess: (res) => {
163
- // TODO: Should Display a Notification Bubble.
163
+ queryClient.invalidateQueries({ queryKey: ['settings'] });
164
164
  console.log('# Settings Updated! :', res);
165
165
  },
166
166
  });
@@ -198,3 +198,52 @@ export function useVerifyStorage() {
198
198
  },
199
199
  });
200
200
  }
201
+
202
+ // ── OAuth Authorization ─────────────────────────────────────────────
203
+ export async function startStorageAuthorize(type: string): Promise<{ sessionId: string }> {
204
+ const header = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
205
+ const res = await fetch(`${API_URL}/storages/authorize`, {
206
+ method: 'POST',
207
+ credentials: 'include',
208
+ headers: header,
209
+ body: JSON.stringify({ type }),
210
+ });
211
+ const data = await res.json();
212
+ if (!data.success) {
213
+ throw new Error(data.error);
214
+ }
215
+ return data.result;
216
+ }
217
+
218
+ export async function getStorageAuthorizeStatus(sessionId: string): Promise<{
219
+ status: 'pending' | 'success' | 'error';
220
+ token?: string;
221
+ error?: string;
222
+ authUrl?: string;
223
+ }> {
224
+ const url = new URL(`${API_URL}/storages/authorize/status`);
225
+ url.searchParams.set('sessionId', sessionId);
226
+ const res = await fetch(url.toString(), {
227
+ method: 'GET',
228
+ credentials: 'include',
229
+ });
230
+ const data = await res.json();
231
+ if (!data.success) {
232
+ throw new Error(data.error);
233
+ }
234
+ return data.result;
235
+ }
236
+
237
+ export async function cancelStorageAuthorize(sessionId: string): Promise<void> {
238
+ const header = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
239
+ const res = await fetch(`${API_URL}/storages/authorize/cancel`, {
240
+ method: 'POST',
241
+ credentials: 'include',
242
+ headers: header,
243
+ body: JSON.stringify({ sessionId }),
244
+ });
245
+ const data = await res.json();
246
+ if (!data.success) {
247
+ throw new Error(data.error);
248
+ }
249
+ }
@@ -42,6 +42,16 @@ export const DEFAULT_PLAN_SETTINGS: NewPlanSettings = {
42
42
  authToken: '',
43
43
  tags: '',
44
44
  },
45
+ slack: {
46
+ enabled: false,
47
+ case: 'failure',
48
+ url: '',
49
+ },
50
+ discord: {
51
+ enabled: false,
52
+ case: 'failure',
53
+ url: '',
54
+ },
45
55
  },
46
56
  prune: {
47
57
  snapCount: 5,
@@ -61,12 +71,14 @@ export const DEFAULT_PLAN_SETTINGS: NewPlanSettings = {
61
71
  enabled: false,
62
72
  type: 'smtp',
63
73
  emails: '',
74
+ case: 'failure',
64
75
  },
65
76
  webhook: {
66
77
  enabled: false,
67
78
  method: 'POST',
68
79
  contentType: 'application/json',
69
80
  url: '',
81
+ case: 'failure',
70
82
  },
71
83
  push: {
72
84
  enabled: false,
@@ -74,6 +86,17 @@ export const DEFAULT_PLAN_SETTINGS: NewPlanSettings = {
74
86
  authType: 'none',
75
87
  authToken: '',
76
88
  tags: '',
89
+ case: 'failure',
90
+ },
91
+ slack: {
92
+ enabled: false,
93
+ case: 'failure',
94
+ url: '',
95
+ },
96
+ discord: {
97
+ enabled: false,
98
+ case: 'failure',
99
+ url: '',
77
100
  },
78
101
  },
79
102
  },
@@ -343,6 +343,15 @@ export const isValidEmail = (email: string): boolean => {
343
343
  return true;
344
344
  };
345
345
 
346
+ export const isValidURL = (url: string): boolean => {
347
+ try {
348
+ new URL(url);
349
+ return true;
350
+ } catch {
351
+ return false;
352
+ }
353
+ };
354
+
346
355
  export const shouldDisplayStorageField = (field: storageOptionField, settings: Record<string, any>, allFields: storageOptionField[]): boolean => {
347
356
  // If no conditions, always display the field
348
357
  if (!field.condition || field.condition.length === 0) {