datajunction-ui 0.0.15 → 0.0.17
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 +1 -1
- package/src/app/components/__tests__/NodeMaterializationDelete.test.jsx +263 -0
- package/src/app/components/__tests__/QueryInfo.test.jsx +174 -46
- package/src/app/components/__tests__/Search.test.jsx +300 -56
- package/src/app/pages/AddEditNodePage/FormikSelect.jsx +15 -2
- package/src/app/pages/NamespacePage/Explorer.jsx +192 -21
- package/src/app/pages/NamespacePage/__tests__/AddNamespacePopover.test.jsx +283 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +74 -41
- package/src/app/pages/NamespacePage/index.jsx +13 -7
- package/src/app/pages/NodePage/AddComplexDimensionLinkPopover.jsx +367 -0
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +1 -1
- package/src/app/pages/NodePage/ManageDimensionLinksDialog.jsx +526 -0
- package/src/app/pages/NodePage/NodeColumnTab.jsx +223 -58
- package/src/app/pages/NodePage/__tests__/AddComplexDimensionLinkPopover.test.jsx +459 -0
- package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +2 -6
- package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +19 -48
- package/src/app/pages/NodePage/__tests__/ManageDimensionLinksDialog.test.jsx +390 -0
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +22 -12
- package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +4 -2
- package/src/app/services/DJService.js +46 -6
- package/src/app/services/__tests__/DJService.test.jsx +551 -5
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { useContext, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import DJClientContext from '../../providers/djclient';
|
|
4
|
+
import { ErrorMessage, Field, Form, Formik } from 'formik';
|
|
5
|
+
import { FormikSelect } from '../AddEditNodePage/FormikSelect';
|
|
6
|
+
import EditIcon from '../../icons/EditIcon';
|
|
7
|
+
import { displayMessageAfterSubmit } from '../../../utils/form';
|
|
8
|
+
import LoadingIcon from '../../icons/LoadingIcon';
|
|
9
|
+
|
|
10
|
+
export default function ManageDimensionLinksDialog({
|
|
11
|
+
column,
|
|
12
|
+
node,
|
|
13
|
+
dimensions,
|
|
14
|
+
fkLinks,
|
|
15
|
+
referenceLink,
|
|
16
|
+
onSubmit,
|
|
17
|
+
}) {
|
|
18
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
19
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
20
|
+
const [activeTab, setActiveTab] = useState('fk');
|
|
21
|
+
const ref = useRef(null);
|
|
22
|
+
const [dimensionColumns, setDimensionColumns] = useState([]);
|
|
23
|
+
const [selectedDimension, setSelectedDimension] = useState(
|
|
24
|
+
referenceLink?.dimension || '',
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const handleClickOutside = event => {
|
|
29
|
+
if (ref.current && !ref.current.contains(event.target)) {
|
|
30
|
+
setIsOpen(false);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
document.addEventListener('click', handleClickOutside, true);
|
|
34
|
+
return () => {
|
|
35
|
+
document.removeEventListener('click', handleClickOutside, true);
|
|
36
|
+
};
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
// Fetch columns for the selected dimension
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const fetchDimensionColumns = async () => {
|
|
42
|
+
if (selectedDimension) {
|
|
43
|
+
try {
|
|
44
|
+
const dimensionNode = await djClient.node(selectedDimension);
|
|
45
|
+
const columns = dimensionNode.columns.map(col => ({
|
|
46
|
+
value: col.name,
|
|
47
|
+
label: col.display_name || col.name,
|
|
48
|
+
}));
|
|
49
|
+
setDimensionColumns(columns);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Failed to fetch dimension columns:', error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
fetchDimensionColumns();
|
|
56
|
+
}, [selectedDimension, djClient]);
|
|
57
|
+
|
|
58
|
+
const handleFKSubmit = async (values, { setSubmitting, setStatus }) => {
|
|
59
|
+
try {
|
|
60
|
+
// Add FK links that are not already present
|
|
61
|
+
const existingDims = new Set(fkLinks);
|
|
62
|
+
const newDims = new Set(values.fkDimensions);
|
|
63
|
+
|
|
64
|
+
// Links to add
|
|
65
|
+
const toAdd = Array.from(newDims).filter(dim => !existingDims.has(dim));
|
|
66
|
+
// Links to remove
|
|
67
|
+
const toRemove = Array.from(existingDims).filter(
|
|
68
|
+
dim => !newDims.has(dim),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const addPromises = toAdd.map(dim =>
|
|
72
|
+
djClient.linkDimension(node.name, column.name, dim),
|
|
73
|
+
);
|
|
74
|
+
const removePromises = toRemove.map(dim =>
|
|
75
|
+
djClient.unlinkDimension(node.name, column.name, dim),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
await Promise.all([...addPromises, ...removePromises]);
|
|
79
|
+
|
|
80
|
+
setStatus({ success: 'FK links updated successfully!' });
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
onSubmit();
|
|
83
|
+
}, 500);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
setStatus({ failure: error.message });
|
|
86
|
+
} finally {
|
|
87
|
+
setSubmitting(false);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleReferenceSubmit = async (
|
|
92
|
+
values,
|
|
93
|
+
{ setSubmitting, setStatus },
|
|
94
|
+
) => {
|
|
95
|
+
try {
|
|
96
|
+
const response = await djClient.addReferenceDimensionLink(
|
|
97
|
+
node.name,
|
|
98
|
+
column.name,
|
|
99
|
+
values.dimensionNode,
|
|
100
|
+
values.dimensionColumn,
|
|
101
|
+
values.role || null,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (response.status === 200 || response.status === 201) {
|
|
105
|
+
setStatus({ success: 'Reference link updated successfully!' });
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
onSubmit();
|
|
108
|
+
}, 500);
|
|
109
|
+
} else {
|
|
110
|
+
setStatus({ failure: response.json?.message || 'Failed to add link' });
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
setStatus({ failure: error.message });
|
|
114
|
+
} finally {
|
|
115
|
+
setSubmitting(false);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const handleRemoveReference = async () => {
|
|
120
|
+
if (
|
|
121
|
+
!window.confirm('Are you sure you want to remove this reference link?')
|
|
122
|
+
) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const response = await djClient.removeReferenceDimensionLink(
|
|
128
|
+
node.name,
|
|
129
|
+
column.name,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (response.status === 200 || response.status === 201) {
|
|
133
|
+
alert('Reference dimension link removed successfully!');
|
|
134
|
+
window.location.reload();
|
|
135
|
+
} else {
|
|
136
|
+
alert(response.json?.message || 'Failed to remove link');
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
alert(error.message);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<>
|
|
145
|
+
<button
|
|
146
|
+
className="edit_button dimension-link-edit"
|
|
147
|
+
aria-label="ManageDimensionLinksToggle"
|
|
148
|
+
tabIndex="0"
|
|
149
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
150
|
+
title="Manage dimension links for this column"
|
|
151
|
+
style={{
|
|
152
|
+
marginLeft: '0.35rem',
|
|
153
|
+
padding: '0',
|
|
154
|
+
opacity: 0.5,
|
|
155
|
+
transition: 'opacity 0.2s ease',
|
|
156
|
+
background: 'transparent',
|
|
157
|
+
border: 'none',
|
|
158
|
+
cursor: 'pointer',
|
|
159
|
+
fontSize: '12px',
|
|
160
|
+
color: '#999',
|
|
161
|
+
display: 'inline-flex',
|
|
162
|
+
alignItems: 'center',
|
|
163
|
+
}}
|
|
164
|
+
>
|
|
165
|
+
<EditIcon />
|
|
166
|
+
</button>
|
|
167
|
+
{isOpen && (
|
|
168
|
+
<>
|
|
169
|
+
{/* Backdrop overlay */}
|
|
170
|
+
<div
|
|
171
|
+
style={{
|
|
172
|
+
position: 'fixed',
|
|
173
|
+
top: 0,
|
|
174
|
+
left: 0,
|
|
175
|
+
right: 0,
|
|
176
|
+
bottom: 0,
|
|
177
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
178
|
+
zIndex: 1000,
|
|
179
|
+
display: 'flex',
|
|
180
|
+
alignItems: 'center',
|
|
181
|
+
justifyContent: 'center',
|
|
182
|
+
padding: '2rem',
|
|
183
|
+
}}
|
|
184
|
+
onClick={() => setIsOpen(false)}
|
|
185
|
+
>
|
|
186
|
+
{/* Modal */}
|
|
187
|
+
<div
|
|
188
|
+
role="dialog"
|
|
189
|
+
aria-label="ManageDimensionLinksDialog"
|
|
190
|
+
ref={ref}
|
|
191
|
+
onClick={e => e.stopPropagation()}
|
|
192
|
+
style={{
|
|
193
|
+
backgroundColor: 'white',
|
|
194
|
+
borderRadius: '8px',
|
|
195
|
+
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.15)',
|
|
196
|
+
width: '550px',
|
|
197
|
+
maxWidth: '100%',
|
|
198
|
+
maxHeight: '100%',
|
|
199
|
+
overflow: 'visible',
|
|
200
|
+
padding: '1.5rem',
|
|
201
|
+
textTransform: 'none',
|
|
202
|
+
fontWeight: 'normal',
|
|
203
|
+
position: 'relative',
|
|
204
|
+
zIndex: 1000,
|
|
205
|
+
}}
|
|
206
|
+
>
|
|
207
|
+
<div
|
|
208
|
+
style={{ maxHeight: 'calc(85vh - 4rem)', overflowY: 'auto' }}
|
|
209
|
+
>
|
|
210
|
+
<div style={{ display: 'flex', marginBottom: '1rem' }}>
|
|
211
|
+
<h3
|
|
212
|
+
style={{
|
|
213
|
+
margin: '0 1em 0 0',
|
|
214
|
+
fontSize: '1.25rem',
|
|
215
|
+
fontWeight: '600',
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
Manage Dimension Links
|
|
219
|
+
</h3>
|
|
220
|
+
<span
|
|
221
|
+
style={{
|
|
222
|
+
display: 'flex',
|
|
223
|
+
alignItems: 'center',
|
|
224
|
+
gap: '0.5rem',
|
|
225
|
+
}}
|
|
226
|
+
>
|
|
227
|
+
<span
|
|
228
|
+
style={{
|
|
229
|
+
fontSize: '1rem',
|
|
230
|
+
fontWeight: '500',
|
|
231
|
+
color: '#333',
|
|
232
|
+
}}
|
|
233
|
+
>
|
|
234
|
+
{column.name}
|
|
235
|
+
</span>
|
|
236
|
+
<span
|
|
237
|
+
className="rounded-pill badge bg-secondary-soft"
|
|
238
|
+
style={{ fontSize: '0.75rem', fontWeight: 'normal' }}
|
|
239
|
+
>
|
|
240
|
+
{column.type}
|
|
241
|
+
</span>
|
|
242
|
+
</span>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
{/* Tab Navigation */}
|
|
246
|
+
<div
|
|
247
|
+
style={{
|
|
248
|
+
display: 'flex',
|
|
249
|
+
borderBottom: '2px solid #e0e0e0',
|
|
250
|
+
marginBottom: '1rem',
|
|
251
|
+
}}
|
|
252
|
+
>
|
|
253
|
+
<button
|
|
254
|
+
type="button"
|
|
255
|
+
onClick={() => setActiveTab('fk')}
|
|
256
|
+
style={{
|
|
257
|
+
flex: 1,
|
|
258
|
+
padding: '0.5rem 1rem',
|
|
259
|
+
border: 'none',
|
|
260
|
+
background: 'transparent',
|
|
261
|
+
borderBottom:
|
|
262
|
+
activeTab === 'fk' ? '3px solid #007bff' : 'none',
|
|
263
|
+
color: activeTab === 'fk' ? '#007bff' : '#6c757d',
|
|
264
|
+
fontWeight: activeTab === 'fk' ? 'bold' : 'normal',
|
|
265
|
+
cursor: 'pointer',
|
|
266
|
+
}}
|
|
267
|
+
>
|
|
268
|
+
FK Links
|
|
269
|
+
</button>
|
|
270
|
+
<button
|
|
271
|
+
type="button"
|
|
272
|
+
onClick={() => setActiveTab('reference')}
|
|
273
|
+
style={{
|
|
274
|
+
flex: 1,
|
|
275
|
+
padding: '0.5rem 1rem',
|
|
276
|
+
border: 'none',
|
|
277
|
+
background: 'transparent',
|
|
278
|
+
borderBottom:
|
|
279
|
+
activeTab === 'reference'
|
|
280
|
+
? '3px solid #007bff'
|
|
281
|
+
: 'none',
|
|
282
|
+
color: activeTab === 'reference' ? '#007bff' : '#6c757d',
|
|
283
|
+
fontWeight: activeTab === 'reference' ? 'bold' : 'normal',
|
|
284
|
+
cursor: 'pointer',
|
|
285
|
+
}}
|
|
286
|
+
>
|
|
287
|
+
Reference Links
|
|
288
|
+
</button>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
{/* FK Links Tab */}
|
|
292
|
+
{activeTab === 'fk' && (
|
|
293
|
+
<div>
|
|
294
|
+
<p
|
|
295
|
+
style={{
|
|
296
|
+
fontSize: '0.875rem',
|
|
297
|
+
color: '#6c757d',
|
|
298
|
+
marginBottom: '1rem',
|
|
299
|
+
}}
|
|
300
|
+
>
|
|
301
|
+
FK Links automatically join via the dimension's primary
|
|
302
|
+
key. Select one or more dimensions to link to this column.
|
|
303
|
+
</p>
|
|
304
|
+
<Formik
|
|
305
|
+
initialValues={{
|
|
306
|
+
fkDimensions: fkLinks,
|
|
307
|
+
}}
|
|
308
|
+
onSubmit={handleFKSubmit}
|
|
309
|
+
>
|
|
310
|
+
{function Render({ isSubmitting, status }) {
|
|
311
|
+
return (
|
|
312
|
+
<Form>
|
|
313
|
+
{displayMessageAfterSubmit(status)}
|
|
314
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
315
|
+
<label htmlFor="fkDimensions">
|
|
316
|
+
Select Dimensions
|
|
317
|
+
</label>
|
|
318
|
+
<FormikSelect
|
|
319
|
+
selectOptions={dimensions}
|
|
320
|
+
formikFieldName="fkDimensions"
|
|
321
|
+
placeholder="Select dimensions"
|
|
322
|
+
isMulti={true}
|
|
323
|
+
defaultValue={fkLinks.map(dim => ({
|
|
324
|
+
value: dim,
|
|
325
|
+
label: dim,
|
|
326
|
+
}))}
|
|
327
|
+
menuPosition="fixed"
|
|
328
|
+
/>
|
|
329
|
+
</div>
|
|
330
|
+
<button
|
|
331
|
+
className="add_node"
|
|
332
|
+
type="submit"
|
|
333
|
+
disabled={isSubmitting}
|
|
334
|
+
style={{ marginLeft: '0' }}
|
|
335
|
+
>
|
|
336
|
+
{isSubmitting ? <LoadingIcon /> : 'Save'}
|
|
337
|
+
</button>
|
|
338
|
+
</Form>
|
|
339
|
+
);
|
|
340
|
+
}}
|
|
341
|
+
</Formik>
|
|
342
|
+
</div>
|
|
343
|
+
)}
|
|
344
|
+
|
|
345
|
+
{/* Reference Links Tab */}
|
|
346
|
+
{activeTab === 'reference' && (
|
|
347
|
+
<div>
|
|
348
|
+
<p
|
|
349
|
+
style={{
|
|
350
|
+
fontSize: '0.875rem',
|
|
351
|
+
color: '#6c757d',
|
|
352
|
+
marginBottom: '1rem',
|
|
353
|
+
}}
|
|
354
|
+
>
|
|
355
|
+
Reference Links explicitly map this column to a specific
|
|
356
|
+
dimension attribute. Use when the relationship is not
|
|
357
|
+
through a primary key.
|
|
358
|
+
</p>
|
|
359
|
+
<Formik
|
|
360
|
+
initialValues={{
|
|
361
|
+
dimensionNode: referenceLink?.dimension || '',
|
|
362
|
+
dimensionColumn: referenceLink?.dimension_column || '',
|
|
363
|
+
role: referenceLink?.role || '',
|
|
364
|
+
}}
|
|
365
|
+
onSubmit={handleReferenceSubmit}
|
|
366
|
+
validate={values => {
|
|
367
|
+
const errors = {};
|
|
368
|
+
if (!values.dimensionNode) {
|
|
369
|
+
errors.dimensionNode = 'Required';
|
|
370
|
+
}
|
|
371
|
+
if (!values.dimensionColumn) {
|
|
372
|
+
errors.dimensionColumn = 'Required';
|
|
373
|
+
}
|
|
374
|
+
return errors;
|
|
375
|
+
}}
|
|
376
|
+
>
|
|
377
|
+
{function Render({
|
|
378
|
+
isSubmitting,
|
|
379
|
+
status,
|
|
380
|
+
setFieldValue,
|
|
381
|
+
}) {
|
|
382
|
+
return (
|
|
383
|
+
<Form>
|
|
384
|
+
{displayMessageAfterSubmit(status)}
|
|
385
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
386
|
+
<ErrorMessage
|
|
387
|
+
name="dimensionNode"
|
|
388
|
+
component="span"
|
|
389
|
+
/>
|
|
390
|
+
<label htmlFor="dimensionNode">
|
|
391
|
+
Dimension Node *
|
|
392
|
+
</label>
|
|
393
|
+
<FormikSelect
|
|
394
|
+
selectOptions={dimensions}
|
|
395
|
+
formikFieldName="dimensionNode"
|
|
396
|
+
placeholder="Select dimension"
|
|
397
|
+
defaultValue={
|
|
398
|
+
referenceLink
|
|
399
|
+
? {
|
|
400
|
+
value: referenceLink.dimension,
|
|
401
|
+
label: referenceLink.dimension,
|
|
402
|
+
}
|
|
403
|
+
: null
|
|
404
|
+
}
|
|
405
|
+
onChange={option => {
|
|
406
|
+
setFieldValue(
|
|
407
|
+
'dimensionNode',
|
|
408
|
+
option?.value || '',
|
|
409
|
+
);
|
|
410
|
+
setSelectedDimension(option?.value || '');
|
|
411
|
+
setFieldValue('dimensionColumn', '');
|
|
412
|
+
}}
|
|
413
|
+
/>
|
|
414
|
+
</div>
|
|
415
|
+
|
|
416
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
417
|
+
<ErrorMessage
|
|
418
|
+
name="dimensionColumn"
|
|
419
|
+
component="span"
|
|
420
|
+
/>
|
|
421
|
+
<label htmlFor="dimensionColumn">
|
|
422
|
+
Dimension Column *
|
|
423
|
+
</label>
|
|
424
|
+
{dimensionColumns.length > 0 ? (
|
|
425
|
+
<FormikSelect
|
|
426
|
+
selectOptions={dimensionColumns}
|
|
427
|
+
formikFieldName="dimensionColumn"
|
|
428
|
+
placeholder="Select column"
|
|
429
|
+
defaultValue={
|
|
430
|
+
referenceLink
|
|
431
|
+
? {
|
|
432
|
+
value: referenceLink.dimension_column,
|
|
433
|
+
label: referenceLink.dimension_column,
|
|
434
|
+
}
|
|
435
|
+
: null
|
|
436
|
+
}
|
|
437
|
+
/>
|
|
438
|
+
) : (
|
|
439
|
+
<Field
|
|
440
|
+
type="text"
|
|
441
|
+
name="dimensionColumn"
|
|
442
|
+
id="dimensionColumn"
|
|
443
|
+
placeholder="Enter dimension column name"
|
|
444
|
+
style={{
|
|
445
|
+
width: '100%',
|
|
446
|
+
padding: '0.5rem',
|
|
447
|
+
}}
|
|
448
|
+
/>
|
|
449
|
+
)}
|
|
450
|
+
</div>
|
|
451
|
+
|
|
452
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
453
|
+
<label htmlFor="role">Role (Optional)</label>
|
|
454
|
+
<Field
|
|
455
|
+
type="text"
|
|
456
|
+
name="role"
|
|
457
|
+
id="role"
|
|
458
|
+
placeholder="e.g., birth_date, registration_date"
|
|
459
|
+
style={{
|
|
460
|
+
width: '100%',
|
|
461
|
+
padding: '0.5rem',
|
|
462
|
+
}}
|
|
463
|
+
/>
|
|
464
|
+
<small
|
|
465
|
+
style={{
|
|
466
|
+
color: '#6c757d',
|
|
467
|
+
fontSize: '0.75rem',
|
|
468
|
+
}}
|
|
469
|
+
>
|
|
470
|
+
Optional role if linking the same dimension
|
|
471
|
+
multiple times
|
|
472
|
+
</small>
|
|
473
|
+
</div>
|
|
474
|
+
|
|
475
|
+
<div
|
|
476
|
+
style={{
|
|
477
|
+
display: 'flex',
|
|
478
|
+
gap: '0.5rem',
|
|
479
|
+
marginTop: '1rem',
|
|
480
|
+
}}
|
|
481
|
+
>
|
|
482
|
+
<button
|
|
483
|
+
className="add_node"
|
|
484
|
+
type="submit"
|
|
485
|
+
disabled={isSubmitting}
|
|
486
|
+
>
|
|
487
|
+
{isSubmitting ? (
|
|
488
|
+
<LoadingIcon />
|
|
489
|
+
) : referenceLink ? (
|
|
490
|
+
'Update Link'
|
|
491
|
+
) : (
|
|
492
|
+
'Add Link'
|
|
493
|
+
)}
|
|
494
|
+
</button>
|
|
495
|
+
{referenceLink && (
|
|
496
|
+
<button
|
|
497
|
+
type="button"
|
|
498
|
+
onClick={handleRemoveReference}
|
|
499
|
+
style={{
|
|
500
|
+
padding: '0.5rem 1rem',
|
|
501
|
+
background: '#dc3545',
|
|
502
|
+
color: 'white',
|
|
503
|
+
border: 'none',
|
|
504
|
+
borderRadius: '4px',
|
|
505
|
+
cursor: 'pointer',
|
|
506
|
+
textTransform: 'none',
|
|
507
|
+
}}
|
|
508
|
+
>
|
|
509
|
+
Remove Link
|
|
510
|
+
</button>
|
|
511
|
+
)}
|
|
512
|
+
</div>
|
|
513
|
+
</Form>
|
|
514
|
+
);
|
|
515
|
+
}}
|
|
516
|
+
</Formik>
|
|
517
|
+
</div>
|
|
518
|
+
)}
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
</div>
|
|
522
|
+
</>
|
|
523
|
+
)}
|
|
524
|
+
</>
|
|
525
|
+
);
|
|
526
|
+
}
|