@stackone/hub 0.2.0 → 0.4.0

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 (53) hide show
  1. package/dist/index.d.ts +19 -0
  2. package/dist/index.esm.js +1 -0
  3. package/dist/index.js +1 -0
  4. package/dist/webcomponent.js +11 -0
  5. package/dist/webcomponent.js.map +1 -0
  6. package/package.json +17 -4
  7. package/.github/workflows/node-ci.yml +0 -20
  8. package/.github/workflows/release-please.yml +0 -37
  9. package/.github/workflows/semantic-pull-request.yml +0 -31
  10. package/.nvmrc +0 -1
  11. package/.release-please-manifest.json +0 -1
  12. package/CHANGELOG.md +0 -50
  13. package/biome.json +0 -77
  14. package/dev/index.html +0 -11
  15. package/dev/main.css +0 -80
  16. package/dev/main.tsx +0 -98
  17. package/dev/vite-env.d.ts +0 -15
  18. package/index.html +0 -14
  19. package/release-please-config.json +0 -5
  20. package/rollup.config.mjs +0 -72
  21. package/src/Hub.tsx +0 -50
  22. package/src/StackOneHub.tsx +0 -138
  23. package/src/WebComponentWrapper.tsx +0 -14
  24. package/src/index.ts +0 -1
  25. package/src/modules/csv-importer.tsx/CsvImporter.tsx +0 -35
  26. package/src/modules/integration-picker/IntegrationPicker.tsx +0 -97
  27. package/src/modules/integration-picker/components/IntegrationFields.tsx +0 -129
  28. package/src/modules/integration-picker/components/IntegrationList.tsx +0 -71
  29. package/src/modules/integration-picker/components/IntegrationPickerContent.tsx +0 -107
  30. package/src/modules/integration-picker/components/cardFooter.tsx +0 -88
  31. package/src/modules/integration-picker/components/cardTitle.tsx +0 -53
  32. package/src/modules/integration-picker/components/views/ErrorView.tsx +0 -9
  33. package/src/modules/integration-picker/components/views/IntegrationFormView.tsx +0 -22
  34. package/src/modules/integration-picker/components/views/IntegrationListView.tsx +0 -19
  35. package/src/modules/integration-picker/components/views/LoadingView.tsx +0 -11
  36. package/src/modules/integration-picker/components/views/SuccessView.tsx +0 -10
  37. package/src/modules/integration-picker/components/views/index.ts +0 -5
  38. package/src/modules/integration-picker/hooks/useIntegrationPicker.ts +0 -394
  39. package/src/modules/integration-picker/queries.ts +0 -78
  40. package/src/modules/integration-picker/types.ts +0 -70
  41. package/src/shared/categories.ts +0 -55
  42. package/src/shared/components/error.tsx +0 -32
  43. package/src/shared/components/errorBoundary.tsx +0 -31
  44. package/src/shared/components/loading.tsx +0 -30
  45. package/src/shared/components/success.tsx +0 -40
  46. package/src/shared/contexts/featureFlagContext.tsx +0 -26
  47. package/src/shared/hooks/useFeatureFlags.ts +0 -24
  48. package/src/shared/httpClient.ts +0 -79
  49. package/src/shared/queries.ts +0 -12
  50. package/src/shared/types/featureFlags.ts +0 -1
  51. package/src/types/types.ts +0 -1
  52. package/tsconfig.json +0 -19
  53. package/vite.config.ts +0 -11
@@ -1,53 +0,0 @@
1
- import {
2
- Button,
3
- Flex,
4
- FlexAlign,
5
- FlexDirection,
6
- FlexGapSize,
7
- FlexJustify,
8
- Typography,
9
- } from '@stackone/malachite';
10
- import { Integration } from '../types';
11
-
12
- interface CardTitleProps {
13
- selectedIntegration: Integration;
14
- onBack?: () => void;
15
- guide?: { supportLink?: string; description: string };
16
- }
17
-
18
- const CardTitle: React.FC<CardTitleProps> = ({ selectedIntegration, onBack, guide }) => {
19
- return (
20
- <Flex
21
- direction={FlexDirection.Horizontal}
22
- align={FlexAlign.Center}
23
- gapSize={FlexGapSize.Small}
24
- justify={FlexJustify.SpaceBetween}
25
- >
26
- <Flex
27
- direction={FlexDirection.Horizontal}
28
- align={FlexAlign.Center}
29
- gapSize={FlexGapSize.Small}
30
- justify={FlexJustify.Left}
31
- >
32
- {onBack && <Button variant="ghost" onClick={onBack} icon="←" size="small" />}
33
- <img
34
- src={`https://app.stackone.com/assets/logos/${selectedIntegration.provider}.png`}
35
- alt={selectedIntegration.provider}
36
- style={{ width: '24px', height: '24px' }}
37
- />
38
- <Typography.Text fontWeight="semi-bold" size="large">
39
- {selectedIntegration.name}
40
- </Typography.Text>
41
- </Flex>
42
- {guide?.supportLink && (
43
- <Typography.LinkText href={guide?.supportLink} target="_blank">
44
- <Button variant="outline" size="medium">
45
- Connection guide
46
- </Button>
47
- </Typography.LinkText>
48
- )}
49
- </Flex>
50
- );
51
- };
52
-
53
- export default CardTitle;
@@ -1,9 +0,0 @@
1
- import React from 'react';
2
-
3
- interface ErrorViewProps {
4
- message: string;
5
- }
6
-
7
- export const ErrorView: React.FC<ErrorViewProps> = ({ message }) => {
8
- return <div>Error: {message}</div>;
9
- };
@@ -1,22 +0,0 @@
1
- import React from 'react';
2
- import { ConnectorConfigField } from '../../types';
3
- import { IntegrationForm } from '../IntegrationFields';
4
-
5
- interface IntegrationFormViewProps {
6
- fields: ConnectorConfigField[];
7
- error?: {
8
- message: string;
9
- provider_response: string;
10
- };
11
- guide?: { supportLink?: string; description: string };
12
- onChange: (data: Record<string, string>) => void;
13
- }
14
-
15
- export const IntegrationFormView: React.FC<IntegrationFormViewProps> = ({
16
- fields,
17
- error,
18
- guide,
19
- onChange,
20
- }) => {
21
- return <IntegrationForm fields={fields} error={error} onChange={onChange} guide={guide} />;
22
- };
@@ -1,19 +0,0 @@
1
- import React from 'react';
2
- import { Integration } from '../../types';
3
- import { IntegrationList } from '../IntegrationList';
4
-
5
- interface IntegrationListViewProps {
6
- integrations: Integration[];
7
- onSelect: (integration: Integration) => void;
8
- }
9
-
10
- export const IntegrationListView: React.FC<IntegrationListViewProps> = ({
11
- integrations,
12
- onSelect,
13
- }) => {
14
- if (!integrations.length) {
15
- return <div>No integrations found.</div>;
16
- }
17
-
18
- return <IntegrationList integrations={integrations} onSelect={onSelect} />;
19
- };
@@ -1,11 +0,0 @@
1
- import React from 'react';
2
- import { Loading } from '../../../../shared/components/loading';
3
-
4
- interface LoadingViewProps {
5
- title: string;
6
- description: string;
7
- }
8
-
9
- export const LoadingView: React.FC<LoadingViewProps> = ({ title, description }) => {
10
- return <Loading title={title} description={description} />;
11
- };
@@ -1,10 +0,0 @@
1
- import React from 'react';
2
- import Success from '../../../../shared/components/success';
3
-
4
- interface SuccessViewProps {
5
- integrationName: string;
6
- }
7
-
8
- export const SuccessView: React.FC<SuccessViewProps> = ({ integrationName }) => {
9
- return <Success integrationName={integrationName} />;
10
- };
@@ -1,5 +0,0 @@
1
- export { IntegrationListView } from './IntegrationListView';
2
- export { IntegrationFormView } from './IntegrationFormView';
3
- export { LoadingView } from './LoadingView';
4
- export { ErrorView } from './ErrorView';
5
- export { SuccessView } from './SuccessView';
@@ -1,394 +0,0 @@
1
- import { evaluate } from '@stackone/expressions';
2
- import { useQuery } from '@tanstack/react-query';
3
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
- import {
5
- connectAccount,
6
- getAccountData,
7
- getConnectorConfig,
8
- getHubData,
9
- updateAccount,
10
- } from '../queries';
11
- import { ConnectorConfigField, Integration } from '../types';
12
-
13
- const DUMMY_VALUE = 'totally-fake-value';
14
-
15
- interface UseIntegrationPickerProps {
16
- token: string;
17
- baseUrl: string;
18
- accountId?: string;
19
- onSuccess?: () => void;
20
- dashboardUrl?: string;
21
- }
22
-
23
- export enum EventType {
24
- AccountConnected = 'AccountConnected',
25
- CloseModal = 'CloseModal',
26
- CloseOAuth2 = 'CloseOAuth2',
27
- }
28
-
29
- export const useIntegrationPicker = ({
30
- token,
31
- baseUrl,
32
- accountId,
33
- onSuccess,
34
- dashboardUrl,
35
- }: UseIntegrationPickerProps) => {
36
- const [selectedIntegration, setSelectedIntegration] = useState<Integration | null>(null);
37
- const [formData, setFormData] = useState<Record<string, string>>({});
38
- const connectWindow = useRef<Window | null>(null);
39
- const checkStateTimeoutRef = useRef<number | null>(null);
40
- const successTimeoutRef = useRef<number | null>(null);
41
- const [connectionState, setConnectionState] = useState<{
42
- loading: boolean;
43
- success: boolean;
44
- error?: {
45
- message: string;
46
- provider_response: string;
47
- };
48
- }>({
49
- loading: false,
50
- success: false,
51
- });
52
-
53
- useEffect(() => {
54
- return () => {
55
- if (checkStateTimeoutRef.current !== null) {
56
- clearTimeout(checkStateTimeoutRef.current);
57
- }
58
- if (successTimeoutRef.current !== null) {
59
- clearTimeout(successTimeoutRef.current);
60
- }
61
- };
62
- }, []);
63
-
64
- const processMessageCallback = useCallback((event: MessageEvent) => {
65
- if (event.data.type === EventType.AccountConnected) {
66
- setConnectionState({ loading: false, success: true });
67
- parent.postMessage(event.data, '*');
68
- if (connectWindow.current) {
69
- connectWindow.current.close();
70
- connectWindow.current = null;
71
- }
72
- window.removeEventListener('message', processMessageCallback, false);
73
- } else if (event.data.type === EventType.CloseOAuth2) {
74
- if (event.data.error) {
75
- setConnectionState({
76
- loading: false,
77
- success: false,
78
- error: {
79
- message: event.data.error,
80
- provider_response: event.data.errorDescription || 'No description',
81
- },
82
- });
83
- } else {
84
- setConnectionState({ loading: false, success: false, error: undefined });
85
- }
86
-
87
- if (connectWindow.current) {
88
- connectWindow.current.close();
89
- connectWindow.current = null;
90
- }
91
- window.removeEventListener('message', processMessageCallback, false);
92
- }
93
- }, []);
94
-
95
- const {
96
- data: accountData,
97
- isLoading: isLoadingAccountData,
98
- error: errorAccountData,
99
- } = useQuery({
100
- queryKey: ['accountData', accountId],
101
- queryFn: async () => {
102
- if (!accountId) return null;
103
- return getAccountData(baseUrl, token, accountId);
104
- },
105
- enabled: !!accountId,
106
- });
107
-
108
- const {
109
- data: hubData,
110
- isLoading: isLoadingHubData,
111
- error: errorHubData,
112
- } = useQuery({
113
- queryKey: ['hubData', accountData?.provider],
114
- queryFn: () => {
115
- if (accountData?.provider) {
116
- return getHubData(token, baseUrl, accountData.provider);
117
- }
118
- return getHubData(token, baseUrl);
119
- },
120
- enabled: !accountId || !!accountData,
121
- });
122
-
123
- useEffect(() => {
124
- if (accountData && hubData) {
125
- const matchingIntegration = hubData.integrations.find(
126
- (integration) => integration.provider === accountData.provider,
127
- );
128
- setSelectedIntegration(matchingIntegration ?? null);
129
- }
130
- }, [accountData, hubData]);
131
-
132
- const {
133
- data: connectorData,
134
- isLoading: isLoadingConnectorData,
135
- error: errorConnectorData,
136
- } = useQuery({
137
- queryKey: ['connectorData', selectedIntegration?.provider, accountData?.provider],
138
- queryFn: async () => {
139
- if (selectedIntegration) {
140
- return getConnectorConfig(baseUrl, token, selectedIntegration.provider);
141
- }
142
- if (accountData) {
143
- return getConnectorConfig(baseUrl, token, accountData.provider);
144
- }
145
- return null;
146
- },
147
- enabled: Boolean(selectedIntegration) || Boolean(accountData),
148
- });
149
-
150
- const { fields, guide } = useMemo(() => {
151
- if (!connectorData || !selectedIntegration) {
152
- const fields: ConnectorConfigField[] = [];
153
- return { fields };
154
- }
155
-
156
- const authConfig =
157
- connectorData.config.authentication?.[selectedIntegration.authentication_config_key];
158
- const authConfigForEnvironment = authConfig?.[selectedIntegration.environment];
159
-
160
- const baseFields = authConfigForEnvironment?.fields || [];
161
-
162
- const fieldsWithPrefilledValues: ConnectorConfigField[] = baseFields
163
- .map((field) => {
164
- const setupValue = accountData?.setupInformation?.[field.key];
165
-
166
- if (accountData && (field.secret || field.type === 'password')) {
167
- return {
168
- ...field,
169
- key: field.key,
170
- value: DUMMY_VALUE,
171
- };
172
- }
173
-
174
- if (field.key === 'external-trigger-token') {
175
- return {
176
- ...field,
177
- key: field.key,
178
- value: hubData?.external_trigger_token,
179
- };
180
- }
181
-
182
- const evaluationContext = {
183
- ...formData,
184
- ...accountData?.setupInformation,
185
- external_trigger_token: hubData?.external_trigger_token,
186
- hub_settings: connectorData.hub_settings,
187
- };
188
-
189
- if (field.condition) {
190
- const evaluated = evaluate(field.condition, evaluationContext);
191
-
192
- const shouldShow = evaluated != null && evaluated !== 'false';
193
-
194
- if (!shouldShow) {
195
- return;
196
- }
197
- }
198
-
199
- if (!field.value) {
200
- return {
201
- ...field,
202
- key: field.key,
203
- };
204
- }
205
-
206
- const valueToEvaluate = setupValue !== undefined ? setupValue : field.value;
207
- let evaluatedValue = evaluate(valueToEvaluate?.toString(), evaluationContext);
208
-
209
- if (typeof evaluatedValue === 'object' && evaluatedValue !== null) {
210
- evaluatedValue = JSON.stringify(evaluatedValue);
211
- }
212
-
213
- return {
214
- ...field,
215
- key: field.key,
216
- value: evaluatedValue as string | number | undefined,
217
- };
218
- })
219
- .filter((value) => value != null);
220
-
221
- return {
222
- fields: fieldsWithPrefilledValues,
223
- guide: authConfigForEnvironment?.guide,
224
- };
225
- }, [connectorData, selectedIntegration, accountData, formData, hubData]);
226
-
227
- const authConfig = useMemo(() => {
228
- if (!connectorData || !selectedIntegration) {
229
- return null;
230
- }
231
- return connectorData.config.authentication?.[
232
- selectedIntegration.authentication_config_key
233
- ]?.[selectedIntegration.environment];
234
- }, [connectorData, selectedIntegration]);
235
-
236
- const handleConnect = useCallback(async () => {
237
- if (!selectedIntegration) {
238
- return;
239
- }
240
-
241
- setConnectionState({ loading: true, success: false });
242
-
243
- try {
244
- const cleanedFormData = { ...formData };
245
- if (accountData) {
246
- fields.forEach((field) => {
247
- if (
248
- (field.secret || field.type === 'password') &&
249
- cleanedFormData[field.key] === DUMMY_VALUE
250
- ) {
251
- delete cleanedFormData[field.key];
252
- }
253
- });
254
- }
255
-
256
- if (authConfig?.type === 'oauth2') {
257
- window.addEventListener('message', processMessageCallback, false);
258
- const callbackEmbeddedAccountsUrl = encodeURIComponent(
259
- `${dashboardUrl}/embedded/accounts/callback`,
260
- );
261
- let windowUrl = `${baseUrl}/connect/oauth2/${selectedIntegration.provider}?redirect_uri=${callbackEmbeddedAccountsUrl}&token=${token}`;
262
-
263
- Object.keys(cleanedFormData).forEach((key) => {
264
- windowUrl += `&${key}=${encodeURIComponent(cleanedFormData[key])}`;
265
- });
266
-
267
- const width = 1024;
268
- const height = 800;
269
- const screenX =
270
- typeof window.screenX != 'undefined' ? window.screenX : window.screenLeft;
271
- const screenY =
272
- typeof window.screenY != 'undefined' ? window.screenY : window.screenTop;
273
- const outerWidth =
274
- typeof window.outerWidth != 'undefined'
275
- ? window.outerWidth
276
- : document.body.clientWidth;
277
- const outerHeight =
278
- typeof window.outerHeight != 'undefined'
279
- ? window.outerHeight
280
- : document.body.clientHeight - 22;
281
- const left = screenX + (outerWidth - width) / 2;
282
- const top = screenY + (outerHeight - height) / 2.5;
283
- const features =
284
- 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top;
285
-
286
- connectWindow.current = window.open(windowUrl, 'Connect Account', features);
287
-
288
- if (connectWindow.current) {
289
- connectWindow.current.focus();
290
- const checkWindowState = () => {
291
- if (connectWindow.current?.closed) {
292
- setConnectionState({ loading: false, success: false });
293
- window.removeEventListener('message', processMessageCallback, false);
294
- connectWindow.current = null;
295
- if (checkStateTimeoutRef.current !== null) {
296
- clearTimeout(checkStateTimeoutRef.current);
297
- checkStateTimeoutRef.current = null;
298
- }
299
- } else if (connectWindow.current) {
300
- checkStateTimeoutRef.current = window.setTimeout(
301
- checkWindowState,
302
- 1000,
303
- );
304
- }
305
- };
306
- checkStateTimeoutRef.current = window.setTimeout(checkWindowState, 1000);
307
- }
308
-
309
- return;
310
- }
311
-
312
- if (accountId) {
313
- await updateAccount(
314
- baseUrl,
315
- accountId,
316
- token,
317
- selectedIntegration.provider,
318
- cleanedFormData,
319
- );
320
- } else {
321
- await connectAccount(baseUrl, token, selectedIntegration.provider, cleanedFormData);
322
- }
323
-
324
- setConnectionState({ loading: false, success: true });
325
- if (successTimeoutRef.current !== null) {
326
- clearTimeout(successTimeoutRef.current);
327
- }
328
- successTimeoutRef.current = window.setTimeout(() => {
329
- onSuccess?.();
330
- successTimeoutRef.current = null;
331
- }, 2000);
332
- } catch (error) {
333
- const parsedError = JSON.parse((error as Error).message) as {
334
- status: number;
335
- message: string;
336
- };
337
-
338
- const doubleParsedError = JSON.parse(parsedError.message) as {
339
- message: string;
340
- provider_response: string;
341
- };
342
-
343
- setConnectionState({
344
- loading: false,
345
- success: false,
346
- error: {
347
- message: doubleParsedError.message,
348
- provider_response: doubleParsedError.provider_response,
349
- },
350
- });
351
- }
352
- }, [
353
- baseUrl,
354
- dashboardUrl,
355
- token,
356
- selectedIntegration,
357
- formData,
358
- onSuccess,
359
- accountData,
360
- fields,
361
- accountId,
362
- authConfig,
363
- processMessageCallback,
364
- ]);
365
-
366
- const isLoading = isLoadingHubData || isLoadingConnectorData || isLoadingAccountData;
367
- const hasError = !!(errorHubData || errorConnectorData || errorAccountData);
368
-
369
- return {
370
- // Data
371
- hubData,
372
- accountData,
373
- connectorData,
374
- selectedIntegration,
375
- fields,
376
- guide,
377
-
378
- // State
379
- formData,
380
- connectionState,
381
- isLoading,
382
- hasError,
383
-
384
- // Errors
385
- errorHubData,
386
- errorConnectorData,
387
- errorAccountData,
388
-
389
- // Actions
390
- setSelectedIntegration,
391
- setFormData,
392
- handleConnect,
393
- };
394
- };
@@ -1,78 +0,0 @@
1
- import { getRequest, patchRequest, postRequest } from '../../shared/httpClient';
2
- import { AccountData, ConnectorConfig, HubConnectorConfig, HubData } from './types';
3
-
4
- export const getHubData = async (token: string, baseUrl: string, provider?: string) => {
5
- const headers: Record<string, string> = {
6
- 'Content-Type': 'application/json',
7
- 'x-hub-session-token': token,
8
- };
9
-
10
- // Add provider header when filtering by specific provider
11
- if (provider) {
12
- headers['x-hub-provider'] = provider;
13
- }
14
-
15
- return await getRequest<HubData>({
16
- url: `${baseUrl}/hub/connectors`,
17
- headers,
18
- });
19
- };
20
-
21
- export const getConnectorConfig = async (baseUrl: string, token: string, connectorKey: string) => {
22
- return await getRequest<HubConnectorConfig>({
23
- url: `${baseUrl}/hub/connectors/${connectorKey}`,
24
- headers: {
25
- 'Content-Type': 'application/json',
26
- 'x-hub-session-token': token,
27
- },
28
- });
29
- };
30
-
31
- export const connectAccount = async (
32
- baseUrl: string,
33
- token: string,
34
- provider: string,
35
- credentials: Record<string, unknown>,
36
- ) => {
37
- return await postRequest<ConnectorConfig>({
38
- url: `${baseUrl}/hub/accounts`,
39
- headers: {
40
- 'Content-Type': 'application/json',
41
- 'x-hub-session-token': token,
42
- },
43
- body: {
44
- provider,
45
- credentials,
46
- },
47
- });
48
- };
49
-
50
- export const updateAccount = async (
51
- baseUrl: string,
52
- accountId: string,
53
- token: string,
54
- provider: string,
55
- credentials: Record<string, unknown>,
56
- ) => {
57
- return await patchRequest<ConnectorConfig>({
58
- url: `${baseUrl}/hub/accounts/${accountId}`,
59
- headers: {
60
- 'Content-Type': 'application/json',
61
- 'x-hub-session-token': token,
62
- },
63
- body: {
64
- provider,
65
- credentials,
66
- },
67
- });
68
- };
69
-
70
- export const getAccountData = async (baseUrl: string, token: string, accountId: string) => {
71
- return await getRequest<AccountData>({
72
- url: `${baseUrl}/hub/accounts/${accountId}`,
73
- headers: {
74
- 'Content-Type': 'application/json',
75
- 'x-hub-session-token': token,
76
- },
77
- });
78
- };
@@ -1,70 +0,0 @@
1
- export interface Integration {
2
- active: boolean;
3
- name: string;
4
- provider: string;
5
- type: string;
6
- version: string;
7
- authentication_config_key: string;
8
- environment: string;
9
- }
10
-
11
- export interface HubData {
12
- integrations: Array<Integration>;
13
- external_trigger_token?: string;
14
- }
15
-
16
- export interface ConnectorConfigField {
17
- type?: 'text' | 'password' | 'number' | 'select' | 'text_area';
18
- label: string;
19
- key: string;
20
- required: boolean;
21
- readOnly: boolean;
22
- secret: boolean;
23
- placeholder: string;
24
- options?: Array<{
25
- label: string;
26
- value: string;
27
- }>;
28
- guide?: {
29
- description: string;
30
- tooltip: string;
31
- };
32
- value?: string | number;
33
- condition?: string;
34
- validation?: {
35
- type: 'html-pattern' | 'domain';
36
- pattern: string;
37
- error?: string;
38
- };
39
- }
40
-
41
- export interface ConnectorConfig {
42
- key: string;
43
- name: string;
44
- authentication: {
45
- [authKey: string]: {
46
- [environment: string]: {
47
- fields: Array<ConnectorConfigField>;
48
- guide?: {
49
- supportLink?: string;
50
- description: string;
51
- };
52
- type: 'oauth2' | 'oidc' | 'custom';
53
- };
54
- };
55
- };
56
- }
57
-
58
- export interface HubConnectorConfig {
59
- config: ConnectorConfig;
60
- hub_settings: {
61
- configured_webhook_events: Record<string, Set<string>>;
62
- project_settings: Record<string, string | object>;
63
- };
64
- }
65
-
66
- export interface AccountData {
67
- account_id: string;
68
- provider: string;
69
- setupInformation: Record<string, string>;
70
- }