@visns-studio/visns-components 5.15.10 → 5.15.12
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.
- package/package.json +10 -10
- package/src/components/Breadcrumb.jsx +19 -11
- package/src/components/DataGrid.jsx +0 -5
- package/src/components/Field.jsx +58 -8
- package/src/components/Form.jsx +263 -101
- package/src/components/MultiCheckbox.jsx +335 -0
- package/src/components/MultiSelect.jsx +30 -1
- package/src/components/columns/ColumnRenderers.jsx +2 -38
- package/src/components/generic/GenericDetail.jsx +0 -10
- package/src/components/oauth/DataPreviewModal.jsx +372 -0
- package/src/components/oauth/DataSyncWizard.jsx +850 -0
- package/src/components/oauth/OAuthConnectionStatus.jsx +85 -0
- package/src/components/oauth/OAuthIntegrationsPage.jsx +185 -0
- package/src/components/oauth/OAuthManager.jsx +260 -0
- package/src/components/oauth/OAuthProviderCard.jsx +291 -0
- package/src/components/styles/DataPreviewModal.module.scss +658 -0
- package/src/components/styles/DataSyncWizard.module.scss +1211 -0
- package/src/components/styles/MultiCheckbox.module.scss +321 -0
- package/src/components/styles/OAuthConnectionStatus.module.scss +143 -0
- package/src/components/styles/OAuthManager.module.scss +119 -0
- package/src/components/styles/OAuthProviderCard.module.scss +760 -0
- package/src/index.js +14 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Ticket,
|
|
4
|
+
Building2,
|
|
5
|
+
Cloud,
|
|
6
|
+
Link,
|
|
7
|
+
CheckCircle,
|
|
8
|
+
XCircle,
|
|
9
|
+
AlertCircle,
|
|
10
|
+
Loader,
|
|
11
|
+
RefreshCw,
|
|
12
|
+
TestTube,
|
|
13
|
+
Download,
|
|
14
|
+
Upload,
|
|
15
|
+
ArrowRightLeft,
|
|
16
|
+
Users,
|
|
17
|
+
Building
|
|
18
|
+
} from 'lucide-react';
|
|
19
|
+
import OAuthConnectionStatus from './OAuthConnectionStatus';
|
|
20
|
+
import styles from '../styles/OAuthProviderCard.module.scss';
|
|
21
|
+
|
|
22
|
+
const OAuthProviderCard = ({
|
|
23
|
+
provider,
|
|
24
|
+
isConnected,
|
|
25
|
+
connectionStatus,
|
|
26
|
+
isSyncing,
|
|
27
|
+
onConnect,
|
|
28
|
+
onDisconnect,
|
|
29
|
+
onTest,
|
|
30
|
+
onSync,
|
|
31
|
+
onOpenWizard,
|
|
32
|
+
compact = false,
|
|
33
|
+
}) => {
|
|
34
|
+
const [isTestingConnection, setIsTestingConnection] = useState(false);
|
|
35
|
+
const [testResult, setTestResult] = useState(null);
|
|
36
|
+
|
|
37
|
+
const handleTestConnection = async () => {
|
|
38
|
+
setIsTestingConnection(true);
|
|
39
|
+
setTestResult(null);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const result = await onTest();
|
|
43
|
+
setTestResult(result);
|
|
44
|
+
} finally {
|
|
45
|
+
setIsTestingConnection(false);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handlePreview = (dataType) => {
|
|
50
|
+
if (onOpenWizard) {
|
|
51
|
+
onOpenWizard(provider, dataType);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const handleSync = async (dataType, options = {}) => {
|
|
56
|
+
try {
|
|
57
|
+
await onSync(dataType, options);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('Sync failed:', error);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
const getProviderIcon = (providerName) => {
|
|
65
|
+
const iconProps = { size: 24, className: styles.providerIcon };
|
|
66
|
+
|
|
67
|
+
switch (providerName) {
|
|
68
|
+
case 'zoho_desk':
|
|
69
|
+
return <Ticket {...iconProps} />;
|
|
70
|
+
case 'hubspot':
|
|
71
|
+
return <Building2 {...iconProps} />;
|
|
72
|
+
case 'salesforce':
|
|
73
|
+
return <Cloud {...iconProps} />;
|
|
74
|
+
default:
|
|
75
|
+
return <Link {...iconProps} />;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const getStatusColor = (status) => {
|
|
80
|
+
switch (status) {
|
|
81
|
+
case 'connected':
|
|
82
|
+
return styles.statusConnected;
|
|
83
|
+
case 'expired':
|
|
84
|
+
return styles.statusExpired;
|
|
85
|
+
case 'error':
|
|
86
|
+
return styles.statusError;
|
|
87
|
+
default:
|
|
88
|
+
return styles.statusDisconnected;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className={`${styles.providerCard} ${isConnected ? styles.connected : styles.disconnected} ${compact ? styles.compact : ''}`}>
|
|
94
|
+
<div className={styles.header}>
|
|
95
|
+
<div className={styles.icon}>
|
|
96
|
+
{getProviderIcon(provider.name)}
|
|
97
|
+
</div>
|
|
98
|
+
<div className={styles.info}>
|
|
99
|
+
<h3>{provider.display_name}</h3>
|
|
100
|
+
<div className={`${styles.status} ${getStatusColor(connectionStatus?.status)}`}>
|
|
101
|
+
{connectionStatus?.message || 'Not connected'}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{!compact && (
|
|
107
|
+
<div className={styles.content}>
|
|
108
|
+
{/* Data Types */}
|
|
109
|
+
{provider.data_types && Object.keys(provider.data_types).length > 0 && (
|
|
110
|
+
<div className={styles.dataTypes}>
|
|
111
|
+
<h4>Available Data</h4>
|
|
112
|
+
<div className={styles.dataTypesList}>
|
|
113
|
+
{Object.entries(provider.data_types).map(([key, dataType]) => {
|
|
114
|
+
const getDataIcon = (type) => {
|
|
115
|
+
switch (type) {
|
|
116
|
+
case 'contacts':
|
|
117
|
+
return <Users size={16} className={styles.dataIcon} />;
|
|
118
|
+
case 'accounts':
|
|
119
|
+
return <Building size={16} className={styles.dataIcon} />;
|
|
120
|
+
default:
|
|
121
|
+
return <Link size={16} className={styles.dataIcon} />;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const getSyncDirection = (direction) => {
|
|
126
|
+
switch (direction) {
|
|
127
|
+
case 'pull':
|
|
128
|
+
return <Download size={14} className={styles.directionIcon} title="Pull from external service" />;
|
|
129
|
+
case 'push':
|
|
130
|
+
return <Upload size={14} className={styles.directionIcon} title="Push to external service" />;
|
|
131
|
+
case 'bidirectional':
|
|
132
|
+
return <ArrowRightLeft size={14} className={styles.directionIcon} title="Two-way sync" />;
|
|
133
|
+
default:
|
|
134
|
+
return <Download size={14} className={styles.directionIcon} title="Pull from external service" />;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div key={key} className={styles.dataType}>
|
|
140
|
+
<div className={styles.dataTypeHeader}>
|
|
141
|
+
{getDataIcon(key)}
|
|
142
|
+
<span className={styles.dataTypeName}>{dataType.name}</span>
|
|
143
|
+
<div className={styles.syncDirection}>
|
|
144
|
+
{getSyncDirection(dataType.sync_direction || 'pull')}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
<span className={styles.dataTypeDescription}>{dataType.description}</span>
|
|
148
|
+
<div className={styles.syncInfo}>
|
|
149
|
+
<small className={styles.syncDirectionText}>
|
|
150
|
+
{dataType.sync_direction === 'push' ? 'Push to ' :
|
|
151
|
+
dataType.sync_direction === 'bidirectional' ? 'Two-way sync with ' : 'Pull from '}
|
|
152
|
+
{provider.display_name}
|
|
153
|
+
</small>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
})}
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{/* Test Result */}
|
|
163
|
+
{testResult && (
|
|
164
|
+
<div className={`${styles.testResult} ${testResult.success ? styles.success : styles.error}`}>
|
|
165
|
+
{testResult.message}
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
{/* Compact mode data types */}
|
|
172
|
+
{compact && provider.data_types && Object.keys(provider.data_types).length > 0 && (
|
|
173
|
+
<div className={styles.compactDataTypes}>
|
|
174
|
+
{Object.entries(provider.data_types).map(([key, dataType]) => {
|
|
175
|
+
const getDataIcon = (type) => {
|
|
176
|
+
switch (type) {
|
|
177
|
+
case 'contacts':
|
|
178
|
+
return <Users size={14} className={styles.dataIcon} />;
|
|
179
|
+
case 'accounts':
|
|
180
|
+
return <Building size={14} className={styles.dataIcon} />;
|
|
181
|
+
default:
|
|
182
|
+
return <Link size={14} className={styles.dataIcon} />;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const getSyncDirection = (direction) => {
|
|
187
|
+
switch (direction) {
|
|
188
|
+
case 'pull':
|
|
189
|
+
return <Download size={12} className={styles.directionIcon} title="Configure sync settings" />;
|
|
190
|
+
case 'push':
|
|
191
|
+
return <Upload size={12} className={styles.directionIcon} title="Configure sync settings" />;
|
|
192
|
+
case 'bidirectional':
|
|
193
|
+
return <ArrowRightLeft size={12} className={styles.directionIcon} title="Configure sync settings" />;
|
|
194
|
+
default:
|
|
195
|
+
return <Download size={12} className={styles.directionIcon} title="Configure sync settings" />;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<div
|
|
201
|
+
key={key}
|
|
202
|
+
className={`${styles.compactDataType} ${styles.clickableRow}`}
|
|
203
|
+
onClick={() => !isSyncing && handlePreview(key)}
|
|
204
|
+
title={`Configure sync settings for ${dataType.name}`}
|
|
205
|
+
>
|
|
206
|
+
{getDataIcon(key)}
|
|
207
|
+
<span className={styles.dataTypeName}>{dataType.name}</span>
|
|
208
|
+
<div className={styles.syncSettingsIndicator}>
|
|
209
|
+
{getSyncDirection(dataType.sync_direction || 'pull')}
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
);
|
|
213
|
+
})}
|
|
214
|
+
</div>
|
|
215
|
+
)}
|
|
216
|
+
|
|
217
|
+
<div className={styles.actions}>
|
|
218
|
+
{!isConnected ? (
|
|
219
|
+
<button
|
|
220
|
+
onClick={onConnect}
|
|
221
|
+
className={`${styles.button} ${styles.primary} ${compact ? styles.compactButton : ''}`}
|
|
222
|
+
>
|
|
223
|
+
<Link size={compact ? 14 : 16} className={styles.buttonIcon} />
|
|
224
|
+
{compact ? 'Connect' : 'Connect'}
|
|
225
|
+
</button>
|
|
226
|
+
) : (
|
|
227
|
+
<>
|
|
228
|
+
{!compact && (
|
|
229
|
+
<button
|
|
230
|
+
onClick={handleTestConnection}
|
|
231
|
+
disabled={isTestingConnection}
|
|
232
|
+
className={`${styles.button} ${styles.secondary}`}
|
|
233
|
+
>
|
|
234
|
+
{isTestingConnection ? (
|
|
235
|
+
<Loader size={16} className={`${styles.buttonIcon} ${styles.spinning}`} />
|
|
236
|
+
) : (
|
|
237
|
+
<TestTube size={16} className={styles.buttonIcon} />
|
|
238
|
+
)}
|
|
239
|
+
{isTestingConnection ? 'Testing...' : 'Test Connection'}
|
|
240
|
+
</button>
|
|
241
|
+
)}
|
|
242
|
+
|
|
243
|
+
{/* Preview and Sync buttons for each data type */}
|
|
244
|
+
{provider.data_types && Object.entries(provider.data_types).map(([key, dataType]) => (
|
|
245
|
+
<div key={key} className={`${styles.dataTypeActions} ${compact ? styles.compactActions : ''}`}>
|
|
246
|
+
{!compact && (
|
|
247
|
+
<button
|
|
248
|
+
onClick={() => handlePreview(key)}
|
|
249
|
+
disabled={isSyncing}
|
|
250
|
+
className={`${styles.button} ${styles.preview}`}
|
|
251
|
+
title={`Preview ${dataType.name} before syncing`}
|
|
252
|
+
>
|
|
253
|
+
<TestTube size={16} className={styles.buttonIcon} />
|
|
254
|
+
Configure {dataType.name}
|
|
255
|
+
</button>
|
|
256
|
+
)}
|
|
257
|
+
|
|
258
|
+
<button
|
|
259
|
+
onClick={() => handleSync(key)}
|
|
260
|
+
disabled={isSyncing}
|
|
261
|
+
className={`${styles.button} ${styles.sync} ${compact ? styles.compactButton : ''}`}
|
|
262
|
+
title={`Sync ${dataType.name} (${dataType.sync_direction || 'pull'})`}
|
|
263
|
+
>
|
|
264
|
+
{isSyncing ? (
|
|
265
|
+
<Loader size={compact ? 14 : 16} className={`${styles.buttonIcon} ${styles.spinning}`} />
|
|
266
|
+
) : (
|
|
267
|
+
<RefreshCw size={compact ? 14 : 16} className={styles.buttonIcon} />
|
|
268
|
+
)}
|
|
269
|
+
{compact ? dataType.name : `Sync ${dataType.name}`}
|
|
270
|
+
</button>
|
|
271
|
+
</div>
|
|
272
|
+
))}
|
|
273
|
+
|
|
274
|
+
{!compact && (
|
|
275
|
+
<button
|
|
276
|
+
onClick={onDisconnect}
|
|
277
|
+
className={`${styles.button} ${styles.danger}`}
|
|
278
|
+
>
|
|
279
|
+
<XCircle size={16} className={styles.buttonIcon} />
|
|
280
|
+
Disconnect
|
|
281
|
+
</button>
|
|
282
|
+
)}
|
|
283
|
+
</>
|
|
284
|
+
)}
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
</div>
|
|
288
|
+
);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
export default OAuthProviderCard;
|