@oronts/vendure-data-hub-plugin 0.1.0 → 0.1.2
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/CHANGELOG.md +10 -0
- package/dist/dashboard/components/common/ConnectionConfigEditor.tsx +589 -0
- package/dist/dashboard/components/common/HeadersEditor.tsx +90 -0
- package/dist/dashboard/components/common/ValidationFeedback.tsx +17 -0
- package/dist/dashboard/components/common/index.ts +10 -0
- package/dist/dashboard/components/pipelines/PipelineEditor.tsx +504 -0
- package/dist/dashboard/components/pipelines/PipelineExport.tsx +63 -0
- package/dist/dashboard/components/pipelines/PipelineImport.tsx +87 -0
- package/dist/dashboard/components/pipelines/ReactFlowPipelineEditor.tsx +539 -0
- package/dist/dashboard/components/pipelines/shared/NodePropertiesPanel.tsx +146 -0
- package/dist/dashboard/components/pipelines/shared/PipelineNode.tsx +155 -0
- package/dist/dashboard/components/pipelines/shared/PipelineSettingsPanel.tsx +392 -0
- package/dist/dashboard/components/pipelines/shared/StepListItem.tsx +144 -0
- package/dist/dashboard/components/pipelines/shared/index.ts +33 -0
- package/dist/dashboard/components/pipelines/shared/visual-node-config.ts +169 -0
- package/dist/dashboard/components/shared/LoadMoreButton.tsx +18 -0
- package/dist/dashboard/components/shared/entity-selector/EntitySelector.tsx +59 -0
- package/dist/dashboard/components/shared/entity-selector/index.ts +1 -0
- package/dist/dashboard/components/shared/error-boundary/ErrorBoundary.tsx +90 -0
- package/dist/dashboard/components/shared/error-boundary/index.ts +1 -0
- package/dist/dashboard/components/shared/feedback/EmptyState.tsx +36 -0
- package/dist/dashboard/components/shared/feedback/ErrorState.tsx +69 -0
- package/dist/dashboard/components/shared/feedback/LoadingState.tsx +104 -0
- package/dist/dashboard/components/shared/feedback/ValidationErrorDisplay.tsx +29 -0
- package/dist/dashboard/components/shared/feedback/index.ts +4 -0
- package/dist/dashboard/components/shared/file-dropzone/FileDropzone.tsx +167 -0
- package/dist/dashboard/components/shared/file-dropzone/index.ts +1 -0
- package/dist/dashboard/components/shared/filter-conditions-editor/FilterConditionsEditor.tsx +226 -0
- package/dist/dashboard/components/shared/filter-conditions-editor/index.ts +1 -0
- package/dist/dashboard/components/shared/index.ts +45 -0
- package/dist/dashboard/components/shared/schema-form/SchemaFormRenderer.tsx +248 -0
- package/dist/dashboard/components/shared/schema-form/fields/BooleanField.tsx +26 -0
- package/dist/dashboard/components/shared/schema-form/fields/FieldWrapper.tsx +28 -0
- package/dist/dashboard/components/shared/schema-form/fields/FileUploadField.tsx +171 -0
- package/dist/dashboard/components/shared/schema-form/fields/JsonField.tsx +132 -0
- package/dist/dashboard/components/shared/schema-form/fields/NumberField.tsx +33 -0
- package/dist/dashboard/components/shared/schema-form/fields/SelectField.tsx +70 -0
- package/dist/dashboard/components/shared/schema-form/fields/StringField.tsx +36 -0
- package/dist/dashboard/components/shared/schema-form/fields/TextareaField.tsx +31 -0
- package/dist/dashboard/components/shared/schema-form/fields/index.ts +23 -0
- package/dist/dashboard/components/shared/schema-form/index.ts +2 -0
- package/dist/dashboard/components/shared/schema-form/utils.ts +3 -0
- package/dist/dashboard/components/shared/selectable-card/SelectableCard.tsx +65 -0
- package/dist/dashboard/components/shared/selectable-card/index.ts +1 -0
- package/dist/dashboard/components/shared/stat-card/StatCard.tsx +121 -0
- package/dist/dashboard/components/shared/stat-card/index.ts +1 -0
- package/dist/dashboard/components/shared/step-config/AdapterRequiredWarning.tsx +25 -0
- package/dist/dashboard/components/shared/step-config/AdapterSelector.tsx +109 -0
- package/dist/dashboard/components/shared/step-config/AdvancedEditors.tsx +634 -0
- package/dist/dashboard/components/shared/step-config/EnrichConfigComponent.tsx +295 -0
- package/dist/dashboard/components/shared/step-config/ExtractTestResults.tsx +143 -0
- package/dist/dashboard/components/shared/step-config/GateConfigComponent.tsx +127 -0
- package/dist/dashboard/components/shared/step-config/LoadTestResults.tsx +104 -0
- package/dist/dashboard/components/shared/step-config/OperatorCard.tsx +266 -0
- package/dist/dashboard/components/shared/step-config/OperatorCheatSheetButton.tsx +54 -0
- package/dist/dashboard/components/shared/step-config/OperatorFieldInput.tsx +209 -0
- package/dist/dashboard/components/shared/step-config/RetrySettingsComponent.tsx +111 -0
- package/dist/dashboard/components/shared/step-config/RouteConfigComponent.tsx +125 -0
- package/dist/dashboard/components/shared/step-config/StepConfigPanel.tsx +564 -0
- package/dist/dashboard/components/shared/step-config/StepTester.tsx +165 -0
- package/dist/dashboard/components/shared/step-config/TestResultContainer.tsx +57 -0
- package/dist/dashboard/components/shared/step-config/TransformTestResults.tsx +130 -0
- package/dist/dashboard/components/shared/step-config/ValidateConfigComponent.tsx +334 -0
- package/dist/dashboard/components/shared/step-config/index.ts +29 -0
- package/dist/dashboard/components/shared/step-config/step-test-handlers.ts +297 -0
- package/dist/dashboard/components/shared/trigger-config/TriggerForm.tsx +478 -0
- package/dist/dashboard/components/shared/trigger-config/index.ts +1 -0
- package/dist/dashboard/components/shared/triggers-panel/TriggersPanel.tsx +281 -0
- package/dist/dashboard/components/shared/triggers-panel/index.ts +1 -0
- package/dist/dashboard/components/shared/wizard/ConfigurationNameCard.tsx +59 -0
- package/dist/dashboard/components/shared/wizard/SummaryCard.tsx +53 -0
- package/dist/dashboard/components/shared/wizard/WizardFooter.tsx +47 -0
- package/dist/dashboard/components/shared/wizard/WizardProgressBar.tsx +78 -0
- package/dist/dashboard/components/shared/wizard/index.ts +4 -0
- package/dist/dashboard/components/shared/wizard-trigger/TriggerSchemaFields.tsx +128 -0
- package/dist/dashboard/components/shared/wizard-trigger/TriggerSelector.tsx +33 -0
- package/dist/dashboard/components/shared/wizard-trigger/index.ts +2 -0
- package/dist/dashboard/components/templates/TemplateGallery.tsx +210 -0
- package/dist/dashboard/components/templates/TemplatePreview.tsx +214 -0
- package/dist/dashboard/components/templates/index.ts +4 -0
- package/dist/dashboard/components/wizards/export-wizard/DestinationStep.tsx +207 -0
- package/dist/dashboard/components/wizards/export-wizard/ExportWizard.tsx +221 -0
- package/dist/dashboard/components/wizards/export-wizard/FieldsStep.tsx +159 -0
- package/dist/dashboard/components/wizards/export-wizard/FormatStep.tsx +246 -0
- package/dist/dashboard/components/wizards/export-wizard/ReviewStep.tsx +231 -0
- package/dist/dashboard/components/wizards/export-wizard/SourceStep.tsx +154 -0
- package/dist/dashboard/components/wizards/export-wizard/TriggerStep.tsx +234 -0
- package/dist/dashboard/components/wizards/export-wizard/constants.ts +73 -0
- package/dist/dashboard/components/wizards/export-wizard/index.ts +2 -0
- package/dist/dashboard/components/wizards/export-wizard/types.ts +39 -0
- package/dist/dashboard/components/wizards/import-wizard/ImportWizard.tsx +350 -0
- package/dist/dashboard/components/wizards/import-wizard/MappingStep.tsx +286 -0
- package/dist/dashboard/components/wizards/import-wizard/PreviewStep.tsx +79 -0
- package/dist/dashboard/components/wizards/import-wizard/ReviewStep.tsx +266 -0
- package/dist/dashboard/components/wizards/import-wizard/SourceStep.tsx +537 -0
- package/dist/dashboard/components/wizards/import-wizard/StrategyStep.tsx +328 -0
- package/dist/dashboard/components/wizards/import-wizard/TargetStep.tsx +76 -0
- package/dist/dashboard/components/wizards/import-wizard/TemplateStep.tsx +116 -0
- package/dist/dashboard/components/wizards/import-wizard/TransformStep.tsx +666 -0
- package/dist/dashboard/components/wizards/import-wizard/TriggerStep.tsx +51 -0
- package/dist/dashboard/components/wizards/import-wizard/constants.ts +104 -0
- package/dist/dashboard/components/wizards/import-wizard/index.ts +3 -0
- package/dist/dashboard/components/wizards/import-wizard/types.ts +35 -0
- package/dist/dashboard/components/wizards/index.ts +7 -0
- package/dist/dashboard/components/wizards/shared/WizardStepContainer.tsx +27 -0
- package/dist/dashboard/components/wizards/shared/constants.ts +16 -0
- package/dist/dashboard/components/wizards/shared/index.ts +10 -0
- package/dist/dashboard/constants/colors.ts +25 -0
- package/dist/dashboard/constants/connection-defaults.ts +7 -0
- package/dist/dashboard/constants/connection-types.ts +1 -0
- package/dist/dashboard/constants/defaults.ts +18 -0
- package/dist/dashboard/constants/editor.ts +69 -0
- package/dist/dashboard/constants/enum-maps.ts +18 -0
- package/dist/dashboard/constants/fallbacks.ts +44 -0
- package/dist/dashboard/constants/file-format-registry.ts +206 -0
- package/dist/dashboard/constants/index.ts +24 -0
- package/dist/dashboard/constants/navigation.ts +29 -0
- package/dist/dashboard/constants/permissions.ts +41 -0
- package/dist/dashboard/constants/placeholders.ts +77 -0
- package/dist/dashboard/constants/routes.ts +12 -0
- package/dist/dashboard/constants/run-status.ts +1 -0
- package/dist/dashboard/constants/sentinel-values.ts +12 -0
- package/dist/dashboard/constants/step-configs.ts +9 -0
- package/dist/dashboard/constants/step-mappings.ts +170 -0
- package/dist/dashboard/constants/steps.ts +37 -0
- package/dist/dashboard/constants/toast-messages.ts +149 -0
- package/dist/dashboard/constants/triggers.ts +5 -0
- package/dist/dashboard/constants/ui-config.ts +139 -0
- package/dist/dashboard/constants/ui-dimensions.ts +145 -0
- package/dist/dashboard/constants/ui-states.ts +28 -0
- package/dist/dashboard/constants/ui-types.ts +85 -0
- package/dist/dashboard/constants/validation-patterns.ts +26 -0
- package/dist/dashboard/gql/gql.ts +370 -0
- package/dist/dashboard/gql/graphql.ts +10378 -0
- package/dist/dashboard/gql/index.ts +1 -0
- package/dist/dashboard/hooks/api/index.ts +115 -0
- package/dist/dashboard/hooks/api/mutation-helpers.ts +34 -0
- package/dist/dashboard/hooks/api/use-adapters.ts +92 -0
- package/dist/dashboard/hooks/api/use-config-options.ts +513 -0
- package/dist/dashboard/hooks/api/use-connections.ts +84 -0
- package/dist/dashboard/hooks/api/use-entity-field-schemas.ts +99 -0
- package/dist/dashboard/hooks/api/use-entity-loaders.ts +45 -0
- package/dist/dashboard/hooks/api/use-hooks.ts +68 -0
- package/dist/dashboard/hooks/api/use-logs.ts +102 -0
- package/dist/dashboard/hooks/api/use-pipeline-runs.ts +221 -0
- package/dist/dashboard/hooks/api/use-pipelines.ts +279 -0
- package/dist/dashboard/hooks/api/use-queues.ts +141 -0
- package/dist/dashboard/hooks/api/use-secrets.ts +75 -0
- package/dist/dashboard/hooks/api/use-settings.ts +55 -0
- package/dist/dashboard/hooks/api/use-step-tester.ts +79 -0
- package/dist/dashboard/hooks/index.ts +13 -0
- package/dist/dashboard/hooks/use-adapter-catalog.ts +253 -0
- package/dist/dashboard/hooks/use-export-templates.ts +80 -0
- package/dist/dashboard/hooks/use-import-templates.ts +139 -0
- package/dist/dashboard/hooks/use-load-more.ts +29 -0
- package/dist/dashboard/hooks/use-stable-keys.ts +54 -0
- package/dist/dashboard/hooks/use-trigger-types.ts +100 -0
- package/dist/dashboard/hooks/use-wizard-navigation.ts +128 -0
- package/dist/dashboard/index.tsx +55 -0
- package/dist/dashboard/routes/adapters/AdapterCard.tsx +102 -0
- package/dist/dashboard/routes/adapters/AdapterConstants.tsx +20 -0
- package/dist/dashboard/routes/adapters/AdapterDetail.tsx +208 -0
- package/dist/dashboard/routes/adapters/AdapterTypeSection.tsx +105 -0
- package/dist/dashboard/routes/adapters/AdaptersPage.tsx +276 -0
- package/dist/dashboard/routes/adapters/AdaptersTable.tsx +107 -0
- package/dist/dashboard/routes/adapters/index.ts +1 -0
- package/dist/dashboard/routes/connections/ConnectionDetail.tsx +218 -0
- package/dist/dashboard/routes/connections/ConnectionsList.tsx +34 -0
- package/dist/dashboard/routes/connections/index.ts +2 -0
- package/dist/dashboard/routes/hooks/Hooks.tsx +425 -0
- package/dist/dashboard/routes/hooks/hook-stages.ts +52 -0
- package/dist/dashboard/routes/hooks/index.ts +1 -0
- package/dist/dashboard/routes/index.ts +8 -0
- package/dist/dashboard/routes/logs/Logs.tsx +93 -0
- package/dist/dashboard/routes/logs/components/LogDetailDrawer.tsx +118 -0
- package/dist/dashboard/routes/logs/components/LogExplorerTab.tsx +367 -0
- package/dist/dashboard/routes/logs/components/LogLevelBadge.tsx +34 -0
- package/dist/dashboard/routes/logs/components/LogTableRow.tsx +70 -0
- package/dist/dashboard/routes/logs/components/LogsOverviewTab.tsx +178 -0
- package/dist/dashboard/routes/logs/components/RealtimeLogTab.tsx +122 -0
- package/dist/dashboard/routes/logs/index.ts +1 -0
- package/dist/dashboard/routes/pipelines/ErrorAuditList.tsx +39 -0
- package/dist/dashboard/routes/pipelines/ExportWizardPage.tsx +96 -0
- package/dist/dashboard/routes/pipelines/ImportWizardPage.tsx +104 -0
- package/dist/dashboard/routes/pipelines/PipelineDetail.tsx +211 -0
- package/dist/dashboard/routes/pipelines/PipelineRunsBlock.tsx +377 -0
- package/dist/dashboard/routes/pipelines/PipelinesList.tsx +87 -0
- package/dist/dashboard/routes/pipelines/RetryPatchHelper.tsx +51 -0
- package/dist/dashboard/routes/pipelines/RunDetailsPanel.tsx +238 -0
- package/dist/dashboard/routes/pipelines/RunErrorsList.tsx +116 -0
- package/dist/dashboard/routes/pipelines/StepCounters.tsx +24 -0
- package/dist/dashboard/routes/pipelines/StepSummaryTable.tsx +36 -0
- package/dist/dashboard/routes/pipelines/components/DryRunDialog.tsx +341 -0
- package/dist/dashboard/routes/pipelines/components/PipelineActionButtons.tsx +201 -0
- package/dist/dashboard/routes/pipelines/components/PipelineEditorToggle.tsx +116 -0
- package/dist/dashboard/routes/pipelines/components/PipelineFormFields.tsx +156 -0
- package/dist/dashboard/routes/pipelines/components/PipelineWebhookInfo.tsx +111 -0
- package/dist/dashboard/routes/pipelines/components/ReviewActionsPanel.tsx +342 -0
- package/dist/dashboard/routes/pipelines/components/ValidationPanel.tsx +121 -0
- package/dist/dashboard/routes/pipelines/components/VersionHistoryDialog.tsx +131 -0
- package/dist/dashboard/routes/pipelines/components/index.ts +25 -0
- package/dist/dashboard/routes/pipelines/hooks/index.ts +1 -0
- package/dist/dashboard/routes/pipelines/hooks/use-pipeline-validation.ts +114 -0
- package/dist/dashboard/routes/pipelines/index.ts +4 -0
- package/dist/dashboard/routes/pipelines/utils/index.ts +1 -0
- package/dist/dashboard/routes/pipelines/utils/pipeline-conversion.ts +261 -0
- package/dist/dashboard/routes/queues/ConsumersTable.tsx +134 -0
- package/dist/dashboard/routes/queues/DeadLettersTable.tsx +118 -0
- package/dist/dashboard/routes/queues/FailedRunsTable.tsx +74 -0
- package/dist/dashboard/routes/queues/QueuesPage.tsx +290 -0
- package/dist/dashboard/routes/queues/index.ts +1 -0
- package/dist/dashboard/routes/queues/types.ts +22 -0
- package/dist/dashboard/routes/secrets/SecretDetail.tsx +278 -0
- package/dist/dashboard/routes/secrets/SecretsList.tsx +34 -0
- package/dist/dashboard/routes/secrets/index.ts +2 -0
- package/dist/dashboard/routes/settings/Settings.tsx +343 -0
- package/dist/dashboard/routes/settings/index.ts +1 -0
- package/dist/dashboard/types/index.ts +89 -0
- package/dist/dashboard/types/pipeline.ts +51 -0
- package/dist/dashboard/types/ui-types.ts +400 -0
- package/dist/dashboard/types/wizard.ts +235 -0
- package/dist/dashboard/utils/adapter-grouping.ts +43 -0
- package/dist/dashboard/utils/column-analysis.ts +11 -0
- package/dist/dashboard/utils/field-preparation.ts +31 -0
- package/dist/dashboard/utils/form-validation.ts +373 -0
- package/dist/dashboard/utils/formatters.ts +92 -0
- package/dist/dashboard/utils/icon-resolver.ts +35 -0
- package/dist/dashboard/utils/index.ts +60 -0
- package/dist/dashboard/utils/query-key-factory.ts +54 -0
- package/dist/dashboard/utils/step-helpers.ts +32 -0
- package/dist/dashboard/utils/string-helpers.ts +4 -0
- package/dist/dashboard/utils/template-helpers.ts +26 -0
- package/dist/dashboard/utils/trigger-sync.ts +138 -0
- package/dist/dashboard/utils/wizard-to-pipeline.ts +569 -0
- package/package.json +4 -4
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import type { TriggerType, TriggerSelectorProps } from '../../../types';
|
|
4
|
+
import { useTriggerIconResolver } from '../../../hooks';
|
|
5
|
+
import { SelectableCard, SelectableCardGrid } from '../selectable-card';
|
|
6
|
+
|
|
7
|
+
function TriggerSelectorComponent({
|
|
8
|
+
options,
|
|
9
|
+
value,
|
|
10
|
+
onChange,
|
|
11
|
+
columns = 4,
|
|
12
|
+
}: TriggerSelectorProps) {
|
|
13
|
+
const resolveTriggerIcon = useTriggerIconResolver();
|
|
14
|
+
return (
|
|
15
|
+
<SelectableCardGrid columns={columns}>
|
|
16
|
+
{options.map(option => {
|
|
17
|
+
const Icon = resolveTriggerIcon(option.id as TriggerType);
|
|
18
|
+
return (
|
|
19
|
+
<SelectableCard
|
|
20
|
+
key={option.id}
|
|
21
|
+
icon={Icon}
|
|
22
|
+
title={option.label}
|
|
23
|
+
description={option.desc}
|
|
24
|
+
selected={value === option.id}
|
|
25
|
+
onClick={() => onChange(option.id)}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
})}
|
|
29
|
+
</SelectableCardGrid>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const TriggerSelector = memo(TriggerSelectorComponent);
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Gallery Component
|
|
3
|
+
*
|
|
4
|
+
* Displays available import templates with filtering and search capabilities.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import { memo } from 'react';
|
|
9
|
+
import { CheckCircle, Package, Search } from 'lucide-react';
|
|
10
|
+
import { Badge, Input } from '@vendure/dashboard';
|
|
11
|
+
import type { TemplateCategory } from '../../types';
|
|
12
|
+
import type { ImportTemplate, CategoryInfo } from '../../hooks/use-import-templates';
|
|
13
|
+
import { resolveIconName } from '../../utils';
|
|
14
|
+
import { filterTemplates } from '../../utils/template-helpers';
|
|
15
|
+
|
|
16
|
+
export interface TemplateGalleryProps {
|
|
17
|
+
templates: ImportTemplate[];
|
|
18
|
+
categories: CategoryInfo[];
|
|
19
|
+
selectedTemplate?: ImportTemplate | null;
|
|
20
|
+
onSelectTemplate: (template: ImportTemplate) => void;
|
|
21
|
+
onUseTemplate?: (template: ImportTemplate) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function TemplateCardComponent({
|
|
25
|
+
template,
|
|
26
|
+
selected,
|
|
27
|
+
onSelect,
|
|
28
|
+
}: {
|
|
29
|
+
template: ImportTemplate;
|
|
30
|
+
selected: boolean;
|
|
31
|
+
onSelect: () => void;
|
|
32
|
+
}) {
|
|
33
|
+
const IconComponent = resolveIconName(template.icon) ?? Package;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={onSelect}
|
|
39
|
+
className={`
|
|
40
|
+
relative p-4 rounded-lg border text-left transition-all w-full
|
|
41
|
+
${selected
|
|
42
|
+
? 'border-primary bg-primary/5 ring-2 ring-primary/20'
|
|
43
|
+
: 'border-border hover:border-primary/50 hover:bg-muted/50'
|
|
44
|
+
}
|
|
45
|
+
`}
|
|
46
|
+
>
|
|
47
|
+
<div className="flex items-start gap-3">
|
|
48
|
+
<div className={`p-2 rounded-md ${selected ? 'bg-primary/10' : 'bg-muted'}`}>
|
|
49
|
+
<IconComponent className={`h-5 w-5 ${selected ? 'text-primary' : 'text-muted-foreground'}`} />
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div className="flex-1 min-w-0">
|
|
53
|
+
<h4 className="font-medium text-sm truncate">{template.name}</h4>
|
|
54
|
+
<p className="text-xs text-muted-foreground mt-1 line-clamp-2">
|
|
55
|
+
{template.description}
|
|
56
|
+
</p>
|
|
57
|
+
|
|
58
|
+
<div className="flex items-center gap-2 mt-3 flex-wrap">
|
|
59
|
+
{template.formats?.map(format => (
|
|
60
|
+
<Badge key={format} variant="outline" className="text-xs">
|
|
61
|
+
{format.toUpperCase()}
|
|
62
|
+
</Badge>
|
|
63
|
+
))}
|
|
64
|
+
{template.tags?.map(tag => (
|
|
65
|
+
<Badge key={tag} variant="secondary" className="text-xs">
|
|
66
|
+
{tag}
|
|
67
|
+
</Badge>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{selected && (
|
|
74
|
+
<div className="absolute bottom-2 right-2">
|
|
75
|
+
<CheckCircle className="h-4 w-4 text-primary" />
|
|
76
|
+
</div>
|
|
77
|
+
)}
|
|
78
|
+
</button>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const TemplateCard = memo(TemplateCardComponent);
|
|
83
|
+
|
|
84
|
+
function CategoryTabComponent({
|
|
85
|
+
category,
|
|
86
|
+
selected,
|
|
87
|
+
onSelect,
|
|
88
|
+
}: {
|
|
89
|
+
category: CategoryInfo;
|
|
90
|
+
selected: boolean;
|
|
91
|
+
onSelect: () => void;
|
|
92
|
+
}) {
|
|
93
|
+
const IconComponent = resolveIconName(category.icon) ?? Package;
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
onClick={onSelect}
|
|
99
|
+
className={`
|
|
100
|
+
flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors whitespace-nowrap
|
|
101
|
+
${selected
|
|
102
|
+
? 'bg-primary text-primary-foreground'
|
|
103
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
104
|
+
}
|
|
105
|
+
`}
|
|
106
|
+
>
|
|
107
|
+
<IconComponent className="h-4 w-4" />
|
|
108
|
+
<span>{category.label}</span>
|
|
109
|
+
<Badge variant="secondary" className="ml-1 text-xs">
|
|
110
|
+
{category.count}
|
|
111
|
+
</Badge>
|
|
112
|
+
</button>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const CategoryTab = memo(CategoryTabComponent);
|
|
117
|
+
|
|
118
|
+
function TemplateGalleryComponent({
|
|
119
|
+
templates,
|
|
120
|
+
categories,
|
|
121
|
+
selectedTemplate,
|
|
122
|
+
onSelectTemplate,
|
|
123
|
+
}: TemplateGalleryProps) {
|
|
124
|
+
const [searchQuery, setSearchQuery] = React.useState('');
|
|
125
|
+
const [selectedCategory, setSelectedCategory] = React.useState<TemplateCategory | 'all'>('all');
|
|
126
|
+
|
|
127
|
+
const filteredTemplates = React.useMemo(() => {
|
|
128
|
+
let filtered = templates;
|
|
129
|
+
|
|
130
|
+
// Filter by category
|
|
131
|
+
if (selectedCategory !== 'all') {
|
|
132
|
+
filtered = filtered.filter(t => t.category === selectedCategory);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Filter by search query
|
|
136
|
+
return filterTemplates(filtered, searchQuery);
|
|
137
|
+
}, [templates, selectedCategory, searchQuery]);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div className="flex flex-col h-full">
|
|
141
|
+
{/* Search Bar */}
|
|
142
|
+
<div className="mb-4">
|
|
143
|
+
<div className="relative">
|
|
144
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
145
|
+
<Input
|
|
146
|
+
type="text"
|
|
147
|
+
placeholder="Search templates..."
|
|
148
|
+
value={searchQuery}
|
|
149
|
+
onChange={e => setSearchQuery(e.target.value)}
|
|
150
|
+
className="pl-9"
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* Category Tabs */}
|
|
156
|
+
<div className="flex gap-2 mb-4 overflow-x-auto pb-2">
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
onClick={() => setSelectedCategory('all')}
|
|
160
|
+
className={`
|
|
161
|
+
flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors whitespace-nowrap
|
|
162
|
+
${selectedCategory === 'all'
|
|
163
|
+
? 'bg-primary text-primary-foreground'
|
|
164
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
165
|
+
}
|
|
166
|
+
`}
|
|
167
|
+
>
|
|
168
|
+
All Templates
|
|
169
|
+
<Badge variant="secondary" className="ml-1 text-xs">
|
|
170
|
+
{templates.length}
|
|
171
|
+
</Badge>
|
|
172
|
+
</button>
|
|
173
|
+
{categories.map(cat => (
|
|
174
|
+
<CategoryTab
|
|
175
|
+
key={cat.category}
|
|
176
|
+
category={cat}
|
|
177
|
+
selected={selectedCategory === cat.category}
|
|
178
|
+
onSelect={() => setSelectedCategory(cat.category)}
|
|
179
|
+
/>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
{/* Template Grid */}
|
|
184
|
+
<div className="flex-1 overflow-y-auto">
|
|
185
|
+
{filteredTemplates.length === 0 ? (
|
|
186
|
+
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
187
|
+
<Package className="h-12 w-12 text-muted-foreground/50 mb-4" />
|
|
188
|
+
<h3 className="text-lg font-medium">No templates found</h3>
|
|
189
|
+
<p className="text-sm text-muted-foreground mt-1">
|
|
190
|
+
Try adjusting your search or category filter
|
|
191
|
+
</p>
|
|
192
|
+
</div>
|
|
193
|
+
) : (
|
|
194
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
195
|
+
{filteredTemplates.map(template => (
|
|
196
|
+
<TemplateCard
|
|
197
|
+
key={template.id}
|
|
198
|
+
template={template}
|
|
199
|
+
selected={selectedTemplate?.id === template.id}
|
|
200
|
+
onSelect={() => onSelectTemplate(template)}
|
|
201
|
+
/>
|
|
202
|
+
))}
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const TemplateGallery = memo(TemplateGalleryComponent);
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Preview Component
|
|
3
|
+
*
|
|
4
|
+
* Shows detailed information about a selected template including
|
|
5
|
+
* required fields, sample data, and configuration preview.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as React from 'react';
|
|
9
|
+
import { memo } from 'react';
|
|
10
|
+
import {
|
|
11
|
+
FileText,
|
|
12
|
+
CheckCircle,
|
|
13
|
+
Circle,
|
|
14
|
+
Tag,
|
|
15
|
+
Download,
|
|
16
|
+
Copy,
|
|
17
|
+
ChevronDown,
|
|
18
|
+
ChevronRight,
|
|
19
|
+
} from 'lucide-react';
|
|
20
|
+
import { Badge, Button } from '@vendure/dashboard';
|
|
21
|
+
import { toast } from 'sonner';
|
|
22
|
+
import { TOAST_TEMPLATE } from '../../constants';
|
|
23
|
+
import type { ImportTemplate } from '../../hooks/use-import-templates';
|
|
24
|
+
|
|
25
|
+
export interface TemplatePreviewProps {
|
|
26
|
+
template: ImportTemplate;
|
|
27
|
+
onUseTemplate: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function TemplatePreviewComponent({ template, onUseTemplate }: TemplatePreviewProps) {
|
|
31
|
+
const [showSampleData, setShowSampleData] = React.useState(false);
|
|
32
|
+
|
|
33
|
+
const handleCopySampleData = React.useCallback(() => {
|
|
34
|
+
if (template.sampleData) {
|
|
35
|
+
const csvHeader = [...template.requiredFields, ...template.optionalFields].join(',');
|
|
36
|
+
const csvRows = template.sampleData.map(row =>
|
|
37
|
+
[...template.requiredFields, ...template.optionalFields]
|
|
38
|
+
.map(field => row[field] ?? '')
|
|
39
|
+
.join(','),
|
|
40
|
+
);
|
|
41
|
+
const csv = [csvHeader, ...csvRows].join('\n');
|
|
42
|
+
navigator.clipboard.writeText(csv).catch(() => {
|
|
43
|
+
// Silently fail if clipboard API is not available
|
|
44
|
+
});
|
|
45
|
+
toast.success(TOAST_TEMPLATE.SAMPLE_COPIED);
|
|
46
|
+
}
|
|
47
|
+
}, [template]);
|
|
48
|
+
|
|
49
|
+
const handleDownloadSample = React.useCallback(() => {
|
|
50
|
+
if (template.sampleData) {
|
|
51
|
+
const csvHeader = [...template.requiredFields, ...template.optionalFields].join(',');
|
|
52
|
+
const csvRows = template.sampleData.map(row =>
|
|
53
|
+
[...template.requiredFields, ...template.optionalFields]
|
|
54
|
+
.map(field => {
|
|
55
|
+
const value = row[field] ?? '';
|
|
56
|
+
// Escape commas and quotes in CSV
|
|
57
|
+
const strValue = String(value);
|
|
58
|
+
if (strValue.includes(',') || strValue.includes('"')) {
|
|
59
|
+
return `"${strValue.replace(/"/g, '""')}"`;
|
|
60
|
+
}
|
|
61
|
+
return strValue;
|
|
62
|
+
})
|
|
63
|
+
.join(','),
|
|
64
|
+
);
|
|
65
|
+
const csv = [csvHeader, ...csvRows].join('\n');
|
|
66
|
+
|
|
67
|
+
const blob = new Blob([csv], { type: 'text/csv' });
|
|
68
|
+
const url = URL.createObjectURL(blob);
|
|
69
|
+
const a = document.createElement('a');
|
|
70
|
+
a.href = url;
|
|
71
|
+
a.download = `${template.id}-sample.csv`;
|
|
72
|
+
document.body.appendChild(a);
|
|
73
|
+
a.click();
|
|
74
|
+
document.body.removeChild(a);
|
|
75
|
+
URL.revokeObjectURL(url);
|
|
76
|
+
|
|
77
|
+
toast.success(TOAST_TEMPLATE.SAMPLE_DOWNLOADED);
|
|
78
|
+
}
|
|
79
|
+
}, [template]);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div className="border rounded-lg bg-card">
|
|
83
|
+
{/* Header */}
|
|
84
|
+
<div className="p-4 border-b">
|
|
85
|
+
<div className="flex items-start justify-between">
|
|
86
|
+
<div>
|
|
87
|
+
<h3 className="text-lg font-semibold">{template.name}</h3>
|
|
88
|
+
<p className="text-sm text-muted-foreground mt-1">{template.description}</p>
|
|
89
|
+
</div>
|
|
90
|
+
<Button onClick={onUseTemplate}>
|
|
91
|
+
Use Template
|
|
92
|
+
</Button>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{/* Format badges */}
|
|
96
|
+
<div className="flex items-center gap-2 mt-3 flex-wrap">
|
|
97
|
+
{template.formats?.map(format => (
|
|
98
|
+
<Badge key={format} variant="outline" className="text-xs">
|
|
99
|
+
{format.toUpperCase()}
|
|
100
|
+
</Badge>
|
|
101
|
+
))}
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
{/* Tags */}
|
|
105
|
+
{template.tags && template.tags.length > 0 && (
|
|
106
|
+
<div className="flex items-center gap-1.5 mt-3">
|
|
107
|
+
<Tag className="h-3.5 w-3.5 text-muted-foreground" />
|
|
108
|
+
{template.tags.map(tag => (
|
|
109
|
+
<Badge key={tag} variant="secondary" className="text-xs">
|
|
110
|
+
{tag}
|
|
111
|
+
</Badge>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
{/* Required Fields */}
|
|
118
|
+
<div className="p-4 border-b">
|
|
119
|
+
<h4 className="text-sm font-medium mb-3 flex items-center gap-2">
|
|
120
|
+
<FileText className="h-4 w-4" />
|
|
121
|
+
Required Fields
|
|
122
|
+
</h4>
|
|
123
|
+
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
|
124
|
+
{template.requiredFields.map(field => (
|
|
125
|
+
<div
|
|
126
|
+
key={field}
|
|
127
|
+
className="flex items-center gap-2 text-sm"
|
|
128
|
+
>
|
|
129
|
+
<CheckCircle className="h-4 w-4 text-green-500" />
|
|
130
|
+
<code className="text-xs bg-muted px-1.5 py-0.5 rounded">{field}</code>
|
|
131
|
+
</div>
|
|
132
|
+
))}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{/* Optional Fields */}
|
|
137
|
+
{template.optionalFields.length > 0 && (
|
|
138
|
+
<div className="p-4 border-b">
|
|
139
|
+
<h4 className="text-sm font-medium mb-3">Optional Fields</h4>
|
|
140
|
+
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
|
141
|
+
{template.optionalFields.map(field => (
|
|
142
|
+
<div
|
|
143
|
+
key={field}
|
|
144
|
+
className="flex items-center gap-2 text-sm"
|
|
145
|
+
>
|
|
146
|
+
<Circle className="h-4 w-4 text-muted-foreground" />
|
|
147
|
+
<code className="text-xs bg-muted px-1.5 py-0.5 rounded">{field}</code>
|
|
148
|
+
</div>
|
|
149
|
+
))}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
{/* Sample Data */}
|
|
155
|
+
{template.sampleData && template.sampleData.length > 0 && (
|
|
156
|
+
<div className="p-4">
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
onClick={() => setShowSampleData(!showSampleData)}
|
|
160
|
+
className="flex items-center gap-2 text-sm font-medium w-full text-left"
|
|
161
|
+
>
|
|
162
|
+
{showSampleData ? (
|
|
163
|
+
<ChevronDown className="h-4 w-4" />
|
|
164
|
+
) : (
|
|
165
|
+
<ChevronRight className="h-4 w-4" />
|
|
166
|
+
)}
|
|
167
|
+
Sample Data ({template.sampleData.length} rows)
|
|
168
|
+
</button>
|
|
169
|
+
|
|
170
|
+
{showSampleData && (
|
|
171
|
+
<div className="mt-3">
|
|
172
|
+
<div className="flex justify-end gap-2 mb-2">
|
|
173
|
+
<Button variant="ghost" size="sm" onClick={handleCopySampleData}>
|
|
174
|
+
<Copy className="h-3.5 w-3.5 mr-1" />
|
|
175
|
+
Copy CSV
|
|
176
|
+
</Button>
|
|
177
|
+
<Button variant="ghost" size="sm" onClick={handleDownloadSample}>
|
|
178
|
+
<Download className="h-3.5 w-3.5 mr-1" />
|
|
179
|
+
Download
|
|
180
|
+
</Button>
|
|
181
|
+
</div>
|
|
182
|
+
<div className="overflow-x-auto border rounded">
|
|
183
|
+
<table className="w-full text-xs">
|
|
184
|
+
<thead className="bg-muted">
|
|
185
|
+
<tr>
|
|
186
|
+
{[...template.requiredFields, ...template.optionalFields].map(field => (
|
|
187
|
+
<th key={field} className="px-3 py-2 text-left font-medium">
|
|
188
|
+
{field}
|
|
189
|
+
</th>
|
|
190
|
+
))}
|
|
191
|
+
</tr>
|
|
192
|
+
</thead>
|
|
193
|
+
<tbody>
|
|
194
|
+
{template.sampleData.map((row, idx) => (
|
|
195
|
+
<tr key={idx} className="border-t">
|
|
196
|
+
{[...template.requiredFields, ...template.optionalFields].map(field => (
|
|
197
|
+
<td key={field} className="px-3 py-2">
|
|
198
|
+
{String(row[field] ?? '')}
|
|
199
|
+
</td>
|
|
200
|
+
))}
|
|
201
|
+
</tr>
|
|
202
|
+
))}
|
|
203
|
+
</tbody>
|
|
204
|
+
</table>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
208
|
+
</div>
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export const TemplatePreview = memo(TemplatePreviewComponent);
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useCallback, memo, useMemo } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardTitle,
|
|
8
|
+
} from '@vendure/dashboard';
|
|
9
|
+
import { FolderOpen } from 'lucide-react';
|
|
10
|
+
import { useSecrets } from '../../../hooks/api';
|
|
11
|
+
import { useOptionValues, useDestinationSchemas, type ConfigOptionValue, type DestinationSchema, type ConnectionSchemaField } from '../../../hooks/api/use-config-options';
|
|
12
|
+
import { resolveIconName } from '../../../utils';
|
|
13
|
+
import { WizardStepContainer } from '../shared';
|
|
14
|
+
import { SelectableCard, SelectableCardGrid } from '../../shared/selectable-card';
|
|
15
|
+
import { SchemaFormRenderer } from '../../shared/schema-form';
|
|
16
|
+
import { STEP_CONTENT } from './constants';
|
|
17
|
+
import type { ExportConfiguration, DestinationType } from './types';
|
|
18
|
+
import type { AdapterSchema, AdapterSchemaField, SchemaFieldType } from '../../../../shared/types';
|
|
19
|
+
|
|
20
|
+
interface DestinationStepProps {
|
|
21
|
+
config: Partial<ExportConfiguration>;
|
|
22
|
+
updateConfig: (updates: Partial<ExportConfiguration>) => void;
|
|
23
|
+
errors?: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function DestinationStep({ config, updateConfig, errors = {} }: DestinationStepProps) {
|
|
27
|
+
const destination = config.destination ?? { type: 'FILE' };
|
|
28
|
+
|
|
29
|
+
const { data: secretsData } = useSecrets();
|
|
30
|
+
const secretCodes = useMemo(
|
|
31
|
+
() => (secretsData?.items ?? []).map(s => s.code),
|
|
32
|
+
[secretsData],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const { options: destinationTypeOptions } = useOptionValues('destinationTypes');
|
|
36
|
+
const { schemas: destinationSchemas } = useDestinationSchemas();
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<WizardStepContainer
|
|
40
|
+
title={STEP_CONTENT.destination.title}
|
|
41
|
+
description={STEP_CONTENT.destination.description}
|
|
42
|
+
>
|
|
43
|
+
<DestinationTypeSelection destination={destination} updateConfig={updateConfig} options={destinationTypeOptions} />
|
|
44
|
+
|
|
45
|
+
<DestinationConfigPanel
|
|
46
|
+
destination={destination}
|
|
47
|
+
updateConfig={updateConfig}
|
|
48
|
+
secretCodes={secretCodes}
|
|
49
|
+
schemas={destinationSchemas}
|
|
50
|
+
/>
|
|
51
|
+
</WizardStepContainer>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface DestinationConfigPanelProps {
|
|
56
|
+
destination: ExportConfiguration['destination'];
|
|
57
|
+
updateConfig: (updates: Partial<ExportConfiguration>) => void;
|
|
58
|
+
secretCodes: string[];
|
|
59
|
+
schemas: DestinationSchema[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function DestinationConfigPanel({
|
|
63
|
+
destination,
|
|
64
|
+
updateConfig,
|
|
65
|
+
secretCodes,
|
|
66
|
+
schemas,
|
|
67
|
+
}: DestinationConfigPanelProps) {
|
|
68
|
+
const schema = schemas.find(s => s.type === destination.type);
|
|
69
|
+
|
|
70
|
+
// DOWNLOAD: static message, no form
|
|
71
|
+
if (schema?.message) {
|
|
72
|
+
return (
|
|
73
|
+
<div className="p-4 rounded-lg bg-muted/50 text-sm text-muted-foreground">
|
|
74
|
+
<p>{schema.message}</p>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Schema found with fields: render schema-driven editor
|
|
80
|
+
if (schema && schema.fields.length > 0) {
|
|
81
|
+
return (
|
|
82
|
+
<SchemaDestinationEditor
|
|
83
|
+
schema={schema}
|
|
84
|
+
destination={destination}
|
|
85
|
+
updateConfig={updateConfig}
|
|
86
|
+
secretCodes={secretCodes}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// No schema found: generic fallback
|
|
92
|
+
return <GenericDestinationConfig destinationType={destination.type} />;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface DestinationTypeSelectionProps {
|
|
96
|
+
destination: ExportConfiguration['destination'];
|
|
97
|
+
updateConfig: (updates: Partial<ExportConfiguration>) => void;
|
|
98
|
+
options: ConfigOptionValue[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function DestinationTypeSelection({ destination, updateConfig, options }: DestinationTypeSelectionProps) {
|
|
102
|
+
return (
|
|
103
|
+
<SelectableCardGrid columns={3}>
|
|
104
|
+
{options.map(type => (
|
|
105
|
+
<DestinationTypeCard
|
|
106
|
+
key={type.value}
|
|
107
|
+
type={type}
|
|
108
|
+
destination={destination}
|
|
109
|
+
updateConfig={updateConfig}
|
|
110
|
+
/>
|
|
111
|
+
))}
|
|
112
|
+
</SelectableCardGrid>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface DestinationTypeCardProps {
|
|
117
|
+
type: ConfigOptionValue;
|
|
118
|
+
destination: ExportConfiguration['destination'];
|
|
119
|
+
updateConfig: (updates: Partial<ExportConfiguration>) => void;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const DestinationTypeCard = memo(function DestinationTypeCard({
|
|
123
|
+
type,
|
|
124
|
+
destination,
|
|
125
|
+
updateConfig,
|
|
126
|
+
}: DestinationTypeCardProps) {
|
|
127
|
+
const handleClick = useCallback(() => {
|
|
128
|
+
updateConfig({ destination: { type: type.value as DestinationType } });
|
|
129
|
+
}, [type.value, updateConfig]);
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<SelectableCard
|
|
133
|
+
icon={resolveIconName(type.icon) ?? FolderOpen}
|
|
134
|
+
title={type.label}
|
|
135
|
+
selected={destination.type === type.value}
|
|
136
|
+
onClick={handleClick}
|
|
137
|
+
data-testid={`datahub-export-destination-${type.value}-btn`}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// --- Schema-driven destination editor using SchemaFormRenderer ---
|
|
143
|
+
|
|
144
|
+
/** Convert ConnectionSchemaField[] to AdapterSchema for SchemaFormRenderer. */
|
|
145
|
+
function toAdapterSchema(fields: ConnectionSchemaField[]): AdapterSchema {
|
|
146
|
+
return {
|
|
147
|
+
fields: fields.map((f): AdapterSchemaField => ({
|
|
148
|
+
key: f.key,
|
|
149
|
+
label: f.label,
|
|
150
|
+
description: f.description ?? undefined,
|
|
151
|
+
type: (f.type || 'string') as SchemaFieldType,
|
|
152
|
+
required: f.required ?? undefined,
|
|
153
|
+
default: f.defaultValue,
|
|
154
|
+
placeholder: f.placeholder ?? undefined,
|
|
155
|
+
options: f.options?.map(o => ({ value: o.value, label: o.label })),
|
|
156
|
+
})),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface SchemaDestinationEditorProps {
|
|
161
|
+
schema: DestinationSchema;
|
|
162
|
+
destination: ExportConfiguration['destination'];
|
|
163
|
+
updateConfig: (updates: Partial<ExportConfiguration>) => void;
|
|
164
|
+
secretCodes: string[];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function SchemaDestinationEditor({ schema, destination, updateConfig, secretCodes }: SchemaDestinationEditorProps) {
|
|
168
|
+
const configKey = schema.configKey as keyof typeof destination;
|
|
169
|
+
const currentConfig = (destination[configKey] as Record<string, unknown> | undefined) ?? {};
|
|
170
|
+
|
|
171
|
+
const adapterSchema = useMemo(() => toAdapterSchema(schema.fields), [schema.fields]);
|
|
172
|
+
|
|
173
|
+
const handleChange = useCallback((values: Record<string, unknown>) => {
|
|
174
|
+
updateConfig({
|
|
175
|
+
destination: {
|
|
176
|
+
...destination,
|
|
177
|
+
[configKey]: values,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}, [destination, configKey, updateConfig]);
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<Card>
|
|
184
|
+
<CardHeader>
|
|
185
|
+
<CardTitle>{schema.label}</CardTitle>
|
|
186
|
+
</CardHeader>
|
|
187
|
+
<CardContent>
|
|
188
|
+
<SchemaFormRenderer
|
|
189
|
+
schema={adapterSchema}
|
|
190
|
+
values={currentConfig}
|
|
191
|
+
onChange={handleChange}
|
|
192
|
+
secretCodes={secretCodes}
|
|
193
|
+
/>
|
|
194
|
+
</CardContent>
|
|
195
|
+
</Card>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function GenericDestinationConfig({ destinationType }: { destinationType: string }) {
|
|
200
|
+
return (
|
|
201
|
+
<div className="p-4 rounded-lg bg-muted/50 text-sm text-muted-foreground">
|
|
202
|
+
<p>
|
|
203
|
+
No additional configuration is needed for the "{destinationType}" destination type.
|
|
204
|
+
</p>
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
}
|