@oronts/vendure-data-hub-plugin 0.1.1 → 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 +5 -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 +2 -2
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { toast } from 'sonner';
|
|
3
|
+
import type { WizardStep } from '../types/wizard';
|
|
4
|
+
import type { FormValidationResult } from '../utils/form-validation';
|
|
5
|
+
|
|
6
|
+
interface UseWizardNavigationOptions<TConfig> {
|
|
7
|
+
steps: WizardStep[];
|
|
8
|
+
initialConfig: TConfig;
|
|
9
|
+
validateStep: (stepId: string, config: TConfig) => FormValidationResult;
|
|
10
|
+
onComplete: (config: TConfig) => void;
|
|
11
|
+
nameRequiredMessage: string;
|
|
12
|
+
isSubmitting?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useWizardNavigation<TConfig extends { name?: string }>({
|
|
16
|
+
steps,
|
|
17
|
+
initialConfig,
|
|
18
|
+
validateStep,
|
|
19
|
+
onComplete,
|
|
20
|
+
nameRequiredMessage,
|
|
21
|
+
isSubmitting,
|
|
22
|
+
}: UseWizardNavigationOptions<TConfig>) {
|
|
23
|
+
const [config, setConfig] = React.useState<TConfig>(initialConfig);
|
|
24
|
+
const [currentStep, setCurrentStep] = React.useState(0);
|
|
25
|
+
const [stepErrors, setStepErrors] = React.useState<Record<string, string>>({});
|
|
26
|
+
const [attemptedNext, setAttemptedNext] = React.useState(false);
|
|
27
|
+
|
|
28
|
+
const updateConfig = React.useCallback((updates: Partial<TConfig>) => {
|
|
29
|
+
setConfig(prev => ({ ...prev, ...updates }));
|
|
30
|
+
setStepErrors({});
|
|
31
|
+
setAttemptedNext(false);
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
const validateCurrentStep = React.useCallback(() => {
|
|
35
|
+
const stepId = steps[currentStep].id;
|
|
36
|
+
return validateStep(stepId, config);
|
|
37
|
+
}, [steps, currentStep, config, validateStep]);
|
|
38
|
+
|
|
39
|
+
const canProceed = React.useMemo(() => {
|
|
40
|
+
const validation = validateCurrentStep();
|
|
41
|
+
return validation.isValid;
|
|
42
|
+
}, [validateCurrentStep]);
|
|
43
|
+
|
|
44
|
+
const handleNext = React.useCallback(() => {
|
|
45
|
+
setAttemptedNext(true);
|
|
46
|
+
const validation = validateCurrentStep();
|
|
47
|
+
|
|
48
|
+
if (!validation.isValid) {
|
|
49
|
+
setStepErrors(validation.errorsByField);
|
|
50
|
+
const firstError = validation.errors[0];
|
|
51
|
+
if (firstError) {
|
|
52
|
+
toast.error(firstError.message);
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (currentStep < steps.length - 1) {
|
|
58
|
+
setAttemptedNext(false);
|
|
59
|
+
setStepErrors({});
|
|
60
|
+
setCurrentStep(prev => prev + 1);
|
|
61
|
+
}
|
|
62
|
+
}, [validateCurrentStep, currentStep, steps]);
|
|
63
|
+
|
|
64
|
+
const handleBack = React.useCallback(() => {
|
|
65
|
+
if (currentStep > 0) {
|
|
66
|
+
setAttemptedNext(false);
|
|
67
|
+
setStepErrors({});
|
|
68
|
+
setCurrentStep(prev => prev - 1);
|
|
69
|
+
}
|
|
70
|
+
}, [currentStep]);
|
|
71
|
+
|
|
72
|
+
const handleStepClick = React.useCallback((index: number) => {
|
|
73
|
+
// Going backward is always allowed; going forward requires current step validation
|
|
74
|
+
if (index > currentStep) {
|
|
75
|
+
const validation = validateCurrentStep();
|
|
76
|
+
if (!validation.isValid) {
|
|
77
|
+
setAttemptedNext(true);
|
|
78
|
+
setStepErrors(validation.errorsByField);
|
|
79
|
+
const firstError = validation.errors[0];
|
|
80
|
+
if (firstError) {
|
|
81
|
+
toast.error(firstError.message);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
setAttemptedNext(false);
|
|
87
|
+
setStepErrors({});
|
|
88
|
+
setCurrentStep(index);
|
|
89
|
+
}, [currentStep, validateCurrentStep]);
|
|
90
|
+
|
|
91
|
+
const handleComplete = React.useCallback(() => {
|
|
92
|
+
if (isSubmitting) return;
|
|
93
|
+
|
|
94
|
+
const validation = validateCurrentStep();
|
|
95
|
+
if (!validation.isValid) {
|
|
96
|
+
setAttemptedNext(true);
|
|
97
|
+
setStepErrors(validation.errorsByField);
|
|
98
|
+
const firstError = validation.errors[0];
|
|
99
|
+
if (firstError) {
|
|
100
|
+
toast.error(firstError.message);
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!config.name) {
|
|
106
|
+
toast.error(nameRequiredMessage);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
onComplete(config);
|
|
110
|
+
}, [config, onComplete, nameRequiredMessage, isSubmitting, validateCurrentStep]);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
config,
|
|
114
|
+
setConfig,
|
|
115
|
+
currentStep,
|
|
116
|
+
setCurrentStep,
|
|
117
|
+
stepErrors,
|
|
118
|
+
setStepErrors,
|
|
119
|
+
attemptedNext,
|
|
120
|
+
setAttemptedNext,
|
|
121
|
+
updateConfig,
|
|
122
|
+
handleNext,
|
|
123
|
+
handleBack,
|
|
124
|
+
handleStepClick,
|
|
125
|
+
handleComplete,
|
|
126
|
+
canProceed,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { defineDashboardExtension, DashboardRouteDefinition } from '@vendure/dashboard';
|
|
2
|
+
import { Boxes } from 'lucide-react';
|
|
3
|
+
import { DATAHUB_NAV_SECTION } from './constants';
|
|
4
|
+
import {
|
|
5
|
+
pipelinesList,
|
|
6
|
+
pipelineDetail,
|
|
7
|
+
importWizardPage,
|
|
8
|
+
exportWizardPage,
|
|
9
|
+
adaptersList,
|
|
10
|
+
secretsList,
|
|
11
|
+
secretDetail,
|
|
12
|
+
connectionsList,
|
|
13
|
+
connectionDetail,
|
|
14
|
+
hooksPage,
|
|
15
|
+
queuesPage,
|
|
16
|
+
settingsPage,
|
|
17
|
+
logsPage,
|
|
18
|
+
} from './routes';
|
|
19
|
+
import { ErrorBoundary } from './components/shared';
|
|
20
|
+
|
|
21
|
+
function wrapWithErrorBoundary(route: DashboardRouteDefinition): DashboardRouteDefinition {
|
|
22
|
+
const originalComponent = route.component;
|
|
23
|
+
if (!originalComponent) return route;
|
|
24
|
+
return {
|
|
25
|
+
...route,
|
|
26
|
+
component: (routeArg) => (
|
|
27
|
+
<ErrorBoundary>
|
|
28
|
+
{originalComponent(routeArg)}
|
|
29
|
+
</ErrorBoundary>
|
|
30
|
+
),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const routes: DashboardRouteDefinition[] = [
|
|
35
|
+
pipelinesList,
|
|
36
|
+
importWizardPage,
|
|
37
|
+
exportWizardPage,
|
|
38
|
+
pipelineDetail,
|
|
39
|
+
adaptersList,
|
|
40
|
+
secretsList,
|
|
41
|
+
secretDetail,
|
|
42
|
+
connectionsList,
|
|
43
|
+
connectionDetail,
|
|
44
|
+
hooksPage,
|
|
45
|
+
queuesPage,
|
|
46
|
+
settingsPage,
|
|
47
|
+
logsPage,
|
|
48
|
+
].map(wrapWithErrorBoundary);
|
|
49
|
+
|
|
50
|
+
export default defineDashboardExtension({
|
|
51
|
+
navSections: [
|
|
52
|
+
{ id: DATAHUB_NAV_SECTION, title: 'Data Hub', icon: Boxes, placement: 'bottom', order: 999 },
|
|
53
|
+
],
|
|
54
|
+
routes,
|
|
55
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Badge } from '@vendure/dashboard';
|
|
3
|
+
import { Puzzle, CheckCircle2, Sparkles } from 'lucide-react';
|
|
4
|
+
import { cn } from '../../utils';
|
|
5
|
+
import { resolveIconName } from '../../utils/icon-resolver';
|
|
6
|
+
import type { DataHubAdapter } from '../../types';
|
|
7
|
+
|
|
8
|
+
export const AdapterCard = React.memo(function AdapterCard({
|
|
9
|
+
adapter,
|
|
10
|
+
onSelect,
|
|
11
|
+
isBuiltIn = false,
|
|
12
|
+
}: Readonly<{
|
|
13
|
+
adapter: DataHubAdapter;
|
|
14
|
+
onSelect: (adapter: DataHubAdapter) => void;
|
|
15
|
+
isBuiltIn?: boolean;
|
|
16
|
+
}>) {
|
|
17
|
+
const handleClick = React.useCallback(() => {
|
|
18
|
+
onSelect(adapter);
|
|
19
|
+
}, [onSelect, adapter]);
|
|
20
|
+
|
|
21
|
+
const handleKeyDown = React.useCallback((e: React.KeyboardEvent) => {
|
|
22
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
onSelect(adapter);
|
|
25
|
+
}
|
|
26
|
+
}, [onSelect, adapter]);
|
|
27
|
+
|
|
28
|
+
const Icon = resolveIconName(adapter.icon);
|
|
29
|
+
const fieldCount = adapter.schema.fields.length;
|
|
30
|
+
const requiredCount = adapter.schema.fields.filter(f => f.required).length;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className={cn(
|
|
35
|
+
'group relative rounded-xl border bg-card p-4 cursor-pointer transition-all duration-200',
|
|
36
|
+
'hover:shadow-lg hover:border-primary/40 hover:-translate-y-0.5',
|
|
37
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',
|
|
38
|
+
)}
|
|
39
|
+
onClick={handleClick}
|
|
40
|
+
onKeyDown={handleKeyDown}
|
|
41
|
+
role="button"
|
|
42
|
+
tabIndex={0}
|
|
43
|
+
aria-label={`View ${adapter.name ?? adapter.code} adapter details`}
|
|
44
|
+
data-testid={`datahub-adapter-card-${adapter.code}`}
|
|
45
|
+
>
|
|
46
|
+
{/* Header: icon + code + badges */}
|
|
47
|
+
<div className="flex items-start gap-3 mb-3">
|
|
48
|
+
<div className={cn(
|
|
49
|
+
'flex-shrink-0 p-2 rounded-lg transition-colors',
|
|
50
|
+
isBuiltIn
|
|
51
|
+
? 'bg-primary/10 text-primary group-hover:bg-primary/15'
|
|
52
|
+
: 'bg-violet-500/10 text-violet-600 dark:text-violet-400 group-hover:bg-violet-500/15',
|
|
53
|
+
)}>
|
|
54
|
+
{Icon ? <Icon className="w-4 h-4" /> : <Puzzle className="w-4 h-4" />}
|
|
55
|
+
</div>
|
|
56
|
+
<div className="min-w-0 flex-1">
|
|
57
|
+
<div className="flex items-center gap-2">
|
|
58
|
+
<code className="text-sm font-semibold truncate">{adapter.code}</code>
|
|
59
|
+
</div>
|
|
60
|
+
{adapter.name && adapter.name !== adapter.code && (
|
|
61
|
+
<p className="text-xs text-muted-foreground truncate">{adapter.name}</p>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
<div className="flex-shrink-0 flex items-center gap-1">
|
|
65
|
+
{isBuiltIn ? (
|
|
66
|
+
<Badge variant="outline" className="text-[10px] px-1.5 py-0 gap-1">
|
|
67
|
+
<CheckCircle2 className="w-3 h-3" />
|
|
68
|
+
Built-in
|
|
69
|
+
</Badge>
|
|
70
|
+
) : (
|
|
71
|
+
<Badge variant="secondary" className="text-[10px] px-1.5 py-0 gap-1">
|
|
72
|
+
<Sparkles className="w-3 h-3" />
|
|
73
|
+
Custom
|
|
74
|
+
</Badge>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{/* Description */}
|
|
80
|
+
<p className="text-xs text-muted-foreground line-clamp-2 mb-3 min-h-[2rem]">
|
|
81
|
+
{adapter.description || 'No description available'}
|
|
82
|
+
</p>
|
|
83
|
+
|
|
84
|
+
{/* Footer: metadata chips */}
|
|
85
|
+
<div className="flex items-center gap-2 text-[11px] text-muted-foreground">
|
|
86
|
+
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-muted/60">
|
|
87
|
+
{fieldCount} field{fieldCount !== 1 ? 's' : ''}
|
|
88
|
+
</span>
|
|
89
|
+
{requiredCount > 0 && (
|
|
90
|
+
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-muted/60">
|
|
91
|
+
{requiredCount} required
|
|
92
|
+
</span>
|
|
93
|
+
)}
|
|
94
|
+
{adapter.pure && (
|
|
95
|
+
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
|
|
96
|
+
Pure
|
|
97
|
+
</span>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const ADAPTERS_TABLE_PAGE_SIZE = 25;
|
|
2
|
+
|
|
3
|
+
/** UI helper: default example values per schema field type for adapter detail display */
|
|
4
|
+
const EXAMPLE_VALUES: Record<string, unknown> = {
|
|
5
|
+
number: 1000,
|
|
6
|
+
boolean: true,
|
|
7
|
+
select: 'value',
|
|
8
|
+
json: {},
|
|
9
|
+
array: [],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function guessExampleValue(
|
|
13
|
+
type: string,
|
|
14
|
+
options?: Array<{ value: string; label: string }> | null,
|
|
15
|
+
): unknown {
|
|
16
|
+
if (options && options.length > 0) {
|
|
17
|
+
return options[0].value;
|
|
18
|
+
}
|
|
19
|
+
return EXAMPLE_VALUES[type] ?? 'value';
|
|
20
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Badge,
|
|
5
|
+
Textarea,
|
|
6
|
+
} from '@vendure/dashboard';
|
|
7
|
+
import {
|
|
8
|
+
Copy,
|
|
9
|
+
CheckCircle2,
|
|
10
|
+
Code2,
|
|
11
|
+
Settings2,
|
|
12
|
+
Puzzle,
|
|
13
|
+
Zap,
|
|
14
|
+
Link2,
|
|
15
|
+
} from 'lucide-react';
|
|
16
|
+
import { toast } from 'sonner';
|
|
17
|
+
import { cn } from '../../utils';
|
|
18
|
+
import { UI_DEFAULTS, TEXTAREA_HEIGHTS, TOAST_ADAPTER } from '../../constants';
|
|
19
|
+
import { resolveIconName } from '../../utils/icon-resolver';
|
|
20
|
+
import { guessExampleValue } from './AdapterConstants';
|
|
21
|
+
import type { DataHubAdapter } from '../../types';
|
|
22
|
+
|
|
23
|
+
export function AdapterDetail({ adapter }: Readonly<{ adapter: DataHubAdapter }>) {
|
|
24
|
+
const [copied, setCopied] = React.useState(false);
|
|
25
|
+
const copyTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
26
|
+
const Icon = resolveIconName(adapter.icon);
|
|
27
|
+
|
|
28
|
+
// Cleanup timeout on unmount
|
|
29
|
+
React.useEffect(() => {
|
|
30
|
+
return () => {
|
|
31
|
+
if (copyTimeoutRef.current) {
|
|
32
|
+
clearTimeout(copyTimeoutRef.current);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const exampleConfig = React.useMemo(() => {
|
|
38
|
+
const config: Record<string, unknown> = { adapterCode: adapter.code };
|
|
39
|
+
for (const field of adapter.schema.fields) {
|
|
40
|
+
if (field.required) {
|
|
41
|
+
config[field.key] = guessExampleValue(field.type, field.options);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return JSON.stringify(config, null, 2);
|
|
45
|
+
}, [adapter]);
|
|
46
|
+
|
|
47
|
+
const copyConfig = async () => {
|
|
48
|
+
try {
|
|
49
|
+
await navigator.clipboard.writeText(exampleConfig);
|
|
50
|
+
setCopied(true);
|
|
51
|
+
toast.success(TOAST_ADAPTER.CONFIG_COPIED);
|
|
52
|
+
if (copyTimeoutRef.current) {
|
|
53
|
+
clearTimeout(copyTimeoutRef.current);
|
|
54
|
+
}
|
|
55
|
+
copyTimeoutRef.current = setTimeout(() => setCopied(false), UI_DEFAULTS.COPY_FEEDBACK_TIMEOUT_MS);
|
|
56
|
+
} catch {
|
|
57
|
+
toast.error(TOAST_ADAPTER.COPY_ERROR);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const requiredFields = adapter.schema.fields.filter(f => f.required);
|
|
62
|
+
const optionalFields = adapter.schema.fields.filter(f => !f.required);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className="p-5 space-y-6">
|
|
66
|
+
{/* Quick info cards */}
|
|
67
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
68
|
+
<div className="flex items-center gap-3 p-3 rounded-xl bg-muted/30 border">
|
|
69
|
+
<div className="p-2 rounded-lg bg-primary/10 text-primary">
|
|
70
|
+
{Icon ? <Icon className="w-4 h-4" /> : <Puzzle className="w-4 h-4" />}
|
|
71
|
+
</div>
|
|
72
|
+
<div>
|
|
73
|
+
<div className="text-[11px] text-muted-foreground uppercase tracking-wider">Type</div>
|
|
74
|
+
<div className="font-medium text-sm">{adapter.type}</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
<div className="flex items-center gap-3 p-3 rounded-xl bg-muted/30 border">
|
|
78
|
+
<div className={cn(
|
|
79
|
+
'p-2 rounded-lg',
|
|
80
|
+
adapter.pure ? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400' : 'bg-muted text-muted-foreground',
|
|
81
|
+
)}>
|
|
82
|
+
<Zap className="w-4 h-4" />
|
|
83
|
+
</div>
|
|
84
|
+
<div>
|
|
85
|
+
<div className="text-[11px] text-muted-foreground uppercase tracking-wider">Pure</div>
|
|
86
|
+
<div className="font-medium text-sm">{adapter.pure ? 'Yes' : 'No'}</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
<div className="flex items-center gap-3 p-3 rounded-xl bg-muted/30 border">
|
|
90
|
+
<div className="p-2 rounded-lg bg-muted text-muted-foreground">
|
|
91
|
+
<Link2 className="w-4 h-4" />
|
|
92
|
+
</div>
|
|
93
|
+
<div>
|
|
94
|
+
<div className="text-[11px] text-muted-foreground uppercase tracking-wider">Deps</div>
|
|
95
|
+
<div className="font-medium text-sm">
|
|
96
|
+
{adapter.requires?.length ? adapter.requires.join(', ') : 'None'}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* Configuration Fields */}
|
|
103
|
+
<div>
|
|
104
|
+
<h4 className="text-sm font-medium mb-3 flex items-center gap-2">
|
|
105
|
+
<Settings2 className="w-4 h-4 text-muted-foreground" />
|
|
106
|
+
Configuration Fields
|
|
107
|
+
<Badge variant="secondary" className="text-xs ml-auto">
|
|
108
|
+
{adapter.schema.fields.length} total
|
|
109
|
+
</Badge>
|
|
110
|
+
</h4>
|
|
111
|
+
<div className="border rounded-xl overflow-hidden">
|
|
112
|
+
<table className="w-full text-sm">
|
|
113
|
+
<thead>
|
|
114
|
+
<tr className="bg-muted/50">
|
|
115
|
+
<th className="text-left px-3 py-2.5 text-xs font-medium text-muted-foreground uppercase tracking-wider">Field</th>
|
|
116
|
+
<th className="text-left px-3 py-2.5 text-xs font-medium text-muted-foreground uppercase tracking-wider">Type</th>
|
|
117
|
+
<th className="text-left px-3 py-2.5 text-xs font-medium text-muted-foreground uppercase tracking-wider">Status</th>
|
|
118
|
+
<th className="text-left px-3 py-2.5 text-xs font-medium text-muted-foreground uppercase tracking-wider">Description</th>
|
|
119
|
+
</tr>
|
|
120
|
+
</thead>
|
|
121
|
+
<tbody>
|
|
122
|
+
{requiredFields.map(field => (
|
|
123
|
+
<tr key={field.key} className="border-t hover:bg-muted/20 transition-colors">
|
|
124
|
+
<td className="px-3 py-2.5">
|
|
125
|
+
<code className="text-xs bg-muted px-1.5 py-0.5 rounded font-medium">
|
|
126
|
+
{field.key}
|
|
127
|
+
</code>
|
|
128
|
+
</td>
|
|
129
|
+
<td className="px-3 py-2.5 text-muted-foreground">
|
|
130
|
+
<span className="text-xs">{field.type}</span>
|
|
131
|
+
{field.options && field.options.length > 0 && (
|
|
132
|
+
<span className="ml-1 text-[11px] text-muted-foreground/60">
|
|
133
|
+
({field.options.map(o => o.value).join(' | ')})
|
|
134
|
+
</span>
|
|
135
|
+
)}
|
|
136
|
+
</td>
|
|
137
|
+
<td className="px-3 py-2.5">
|
|
138
|
+
<Badge variant="destructive" className="text-[10px] px-1.5 py-0">
|
|
139
|
+
Required
|
|
140
|
+
</Badge>
|
|
141
|
+
</td>
|
|
142
|
+
<td className="px-3 py-2.5 text-muted-foreground text-xs">
|
|
143
|
+
{field.description || field.label || '\u2014'}
|
|
144
|
+
</td>
|
|
145
|
+
</tr>
|
|
146
|
+
))}
|
|
147
|
+
{optionalFields.map(field => (
|
|
148
|
+
<tr key={field.key} className="border-t hover:bg-muted/20 transition-colors">
|
|
149
|
+
<td className="px-3 py-2.5">
|
|
150
|
+
<code className="text-xs bg-muted px-1.5 py-0.5 rounded">
|
|
151
|
+
{field.key}
|
|
152
|
+
</code>
|
|
153
|
+
</td>
|
|
154
|
+
<td className="px-3 py-2.5 text-muted-foreground">
|
|
155
|
+
<span className="text-xs">{field.type}</span>
|
|
156
|
+
{field.options && field.options.length > 0 && (
|
|
157
|
+
<span className="ml-1 text-[11px] text-muted-foreground/60">
|
|
158
|
+
({field.options.map(o => o.value).join(' | ')})
|
|
159
|
+
</span>
|
|
160
|
+
)}
|
|
161
|
+
</td>
|
|
162
|
+
<td className="px-3 py-2.5">
|
|
163
|
+
<Badge variant="outline" className="text-[10px] px-1.5 py-0">
|
|
164
|
+
Optional
|
|
165
|
+
</Badge>
|
|
166
|
+
</td>
|
|
167
|
+
<td className="px-3 py-2.5 text-muted-foreground text-xs">
|
|
168
|
+
{field.description || field.label || '\u2014'}
|
|
169
|
+
</td>
|
|
170
|
+
</tr>
|
|
171
|
+
))}
|
|
172
|
+
{adapter.schema.fields.length === 0 && (
|
|
173
|
+
<tr>
|
|
174
|
+
<td colSpan={4} className="px-3 py-6 text-center text-muted-foreground text-sm">
|
|
175
|
+
No configuration fields
|
|
176
|
+
</td>
|
|
177
|
+
</tr>
|
|
178
|
+
)}
|
|
179
|
+
</tbody>
|
|
180
|
+
</table>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
{/* Example Configuration */}
|
|
185
|
+
<div>
|
|
186
|
+
<div className="flex items-center justify-between mb-3">
|
|
187
|
+
<h4 className="text-sm font-medium flex items-center gap-2">
|
|
188
|
+
<Code2 className="w-4 h-4 text-muted-foreground" />
|
|
189
|
+
Example Configuration
|
|
190
|
+
</h4>
|
|
191
|
+
<Button variant="outline" size="sm" onClick={copyConfig} data-testid="datahub-adapter-config-copy-button">
|
|
192
|
+
{copied ? (
|
|
193
|
+
<CheckCircle2 className="w-4 h-4 mr-1 text-emerald-600" />
|
|
194
|
+
) : (
|
|
195
|
+
<Copy className="w-4 h-4 mr-1" />
|
|
196
|
+
)}
|
|
197
|
+
{copied ? 'Copied!' : 'Copy'}
|
|
198
|
+
</Button>
|
|
199
|
+
</div>
|
|
200
|
+
<Textarea
|
|
201
|
+
value={exampleConfig}
|
|
202
|
+
readOnly
|
|
203
|
+
className={`font-mono text-sm ${TEXTAREA_HEIGHTS.ADAPTER_SCHEMA} rounded-xl`}
|
|
204
|
+
/>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { CheckCircle2, Info, Puzzle, Sparkles } from 'lucide-react';
|
|
3
|
+
import { Badge } from '@vendure/dashboard';
|
|
4
|
+
import { AdapterCard } from './AdapterCard';
|
|
5
|
+
import { ITEMS_PER_PAGE } from '../../constants';
|
|
6
|
+
import { useLoadMore } from '../../hooks';
|
|
7
|
+
import { LoadMoreButton } from '../../components/shared';
|
|
8
|
+
import { resolveIconName } from '../../utils/icon-resolver';
|
|
9
|
+
import type { DataHubAdapter } from '../../types';
|
|
10
|
+
|
|
11
|
+
export function AdapterTypeSection({
|
|
12
|
+
type,
|
|
13
|
+
label,
|
|
14
|
+
description,
|
|
15
|
+
icon,
|
|
16
|
+
adapters,
|
|
17
|
+
onSelect,
|
|
18
|
+
isBuiltIn,
|
|
19
|
+
}: Readonly<{
|
|
20
|
+
type: string;
|
|
21
|
+
label: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
icon?: string;
|
|
24
|
+
adapters: DataHubAdapter[];
|
|
25
|
+
onSelect: (adapter: DataHubAdapter) => void;
|
|
26
|
+
isBuiltIn: (code: string) => boolean;
|
|
27
|
+
}>) {
|
|
28
|
+
const Icon = resolveIconName(icon);
|
|
29
|
+
const builtIn = React.useMemo(() => adapters.filter(a => isBuiltIn(a.code)), [adapters, isBuiltIn]);
|
|
30
|
+
const custom = React.useMemo(() => adapters.filter(a => !isBuiltIn(a.code)), [adapters, isBuiltIn]);
|
|
31
|
+
|
|
32
|
+
const { displayed: displayedBuiltIn, hasMore: hasMoreBuiltIn, remaining: remainingBuiltIn, loadMore: loadMoreBuiltIn } = useLoadMore(builtIn, { pageSize: ITEMS_PER_PAGE });
|
|
33
|
+
const { displayed: displayedCustom, hasMore: hasMoreCustom, remaining: remainingCustom, loadMore: loadMoreCustom } = useLoadMore(custom, { pageSize: ITEMS_PER_PAGE });
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="space-y-6">
|
|
37
|
+
{/* Section header */}
|
|
38
|
+
<div className="flex items-center gap-3 pb-4 border-b">
|
|
39
|
+
<div className="p-2.5 rounded-xl bg-primary/10 text-primary">
|
|
40
|
+
{Icon ? <Icon className="w-5 h-5" /> : <Puzzle className="w-5 h-5" />}
|
|
41
|
+
</div>
|
|
42
|
+
<div className="flex-1 min-w-0">
|
|
43
|
+
<div className="flex items-center gap-2">
|
|
44
|
+
<h3 className="font-semibold">{label}</h3>
|
|
45
|
+
<Badge variant="secondary" className="text-xs">
|
|
46
|
+
{adapters.length}
|
|
47
|
+
</Badge>
|
|
48
|
+
</div>
|
|
49
|
+
{description && <p className="text-sm text-muted-foreground mt-0.5">{description}</p>}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
{builtIn.length > 0 && (
|
|
54
|
+
<div>
|
|
55
|
+
<h4 className="text-sm font-medium mb-3 flex items-center gap-2 text-muted-foreground">
|
|
56
|
+
<CheckCircle2 className="w-4 h-4 text-emerald-600 dark:text-emerald-400" />
|
|
57
|
+
Built-in
|
|
58
|
+
<span className="text-xs font-normal">({builtIn.length})</span>
|
|
59
|
+
</h4>
|
|
60
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
61
|
+
{displayedBuiltIn.map(adapter => (
|
|
62
|
+
<AdapterCard
|
|
63
|
+
key={adapter.code}
|
|
64
|
+
adapter={adapter}
|
|
65
|
+
onSelect={onSelect}
|
|
66
|
+
isBuiltIn
|
|
67
|
+
/>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
{hasMoreBuiltIn && <LoadMoreButton remaining={remainingBuiltIn} onClick={loadMoreBuiltIn} data-testid={`datahub-adapters-load-more-builtin-${type}`} />}
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
|
|
74
|
+
{custom.length > 0 && (
|
|
75
|
+
<div>
|
|
76
|
+
<h4 className="text-sm font-medium mb-3 flex items-center gap-2 text-muted-foreground">
|
|
77
|
+
<Sparkles className="w-4 h-4 text-violet-600 dark:text-violet-400" />
|
|
78
|
+
Custom
|
|
79
|
+
<span className="text-xs font-normal">({custom.length})</span>
|
|
80
|
+
</h4>
|
|
81
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
82
|
+
{displayedCustom.map(adapter => (
|
|
83
|
+
<AdapterCard
|
|
84
|
+
key={adapter.code}
|
|
85
|
+
adapter={adapter}
|
|
86
|
+
onSelect={onSelect}
|
|
87
|
+
/>
|
|
88
|
+
))}
|
|
89
|
+
</div>
|
|
90
|
+
{hasMoreCustom && <LoadMoreButton remaining={remainingCustom} onClick={loadMoreCustom} data-testid={`datahub-adapters-load-more-custom-${type}`} />}
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
{adapters.length === 0 && (
|
|
95
|
+
<div className="text-center py-12 text-muted-foreground">
|
|
96
|
+
<div className="mx-auto w-12 h-12 rounded-full bg-muted/50 flex items-center justify-center mb-3">
|
|
97
|
+
<Info className="w-6 h-6 opacity-50" />
|
|
98
|
+
</div>
|
|
99
|
+
<p className="font-medium">No {label.toLowerCase()} registered</p>
|
|
100
|
+
<p className="text-sm mt-1">Register adapters in your plugin configuration.</p>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|