@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.
Files changed (235) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/dashboard/components/common/ConnectionConfigEditor.tsx +589 -0
  3. package/dist/dashboard/components/common/HeadersEditor.tsx +90 -0
  4. package/dist/dashboard/components/common/ValidationFeedback.tsx +17 -0
  5. package/dist/dashboard/components/common/index.ts +10 -0
  6. package/dist/dashboard/components/pipelines/PipelineEditor.tsx +504 -0
  7. package/dist/dashboard/components/pipelines/PipelineExport.tsx +63 -0
  8. package/dist/dashboard/components/pipelines/PipelineImport.tsx +87 -0
  9. package/dist/dashboard/components/pipelines/ReactFlowPipelineEditor.tsx +539 -0
  10. package/dist/dashboard/components/pipelines/shared/NodePropertiesPanel.tsx +146 -0
  11. package/dist/dashboard/components/pipelines/shared/PipelineNode.tsx +155 -0
  12. package/dist/dashboard/components/pipelines/shared/PipelineSettingsPanel.tsx +392 -0
  13. package/dist/dashboard/components/pipelines/shared/StepListItem.tsx +144 -0
  14. package/dist/dashboard/components/pipelines/shared/index.ts +33 -0
  15. package/dist/dashboard/components/pipelines/shared/visual-node-config.ts +169 -0
  16. package/dist/dashboard/components/shared/LoadMoreButton.tsx +18 -0
  17. package/dist/dashboard/components/shared/entity-selector/EntitySelector.tsx +59 -0
  18. package/dist/dashboard/components/shared/entity-selector/index.ts +1 -0
  19. package/dist/dashboard/components/shared/error-boundary/ErrorBoundary.tsx +90 -0
  20. package/dist/dashboard/components/shared/error-boundary/index.ts +1 -0
  21. package/dist/dashboard/components/shared/feedback/EmptyState.tsx +36 -0
  22. package/dist/dashboard/components/shared/feedback/ErrorState.tsx +69 -0
  23. package/dist/dashboard/components/shared/feedback/LoadingState.tsx +104 -0
  24. package/dist/dashboard/components/shared/feedback/ValidationErrorDisplay.tsx +29 -0
  25. package/dist/dashboard/components/shared/feedback/index.ts +4 -0
  26. package/dist/dashboard/components/shared/file-dropzone/FileDropzone.tsx +167 -0
  27. package/dist/dashboard/components/shared/file-dropzone/index.ts +1 -0
  28. package/dist/dashboard/components/shared/filter-conditions-editor/FilterConditionsEditor.tsx +226 -0
  29. package/dist/dashboard/components/shared/filter-conditions-editor/index.ts +1 -0
  30. package/dist/dashboard/components/shared/index.ts +45 -0
  31. package/dist/dashboard/components/shared/schema-form/SchemaFormRenderer.tsx +248 -0
  32. package/dist/dashboard/components/shared/schema-form/fields/BooleanField.tsx +26 -0
  33. package/dist/dashboard/components/shared/schema-form/fields/FieldWrapper.tsx +28 -0
  34. package/dist/dashboard/components/shared/schema-form/fields/FileUploadField.tsx +171 -0
  35. package/dist/dashboard/components/shared/schema-form/fields/JsonField.tsx +132 -0
  36. package/dist/dashboard/components/shared/schema-form/fields/NumberField.tsx +33 -0
  37. package/dist/dashboard/components/shared/schema-form/fields/SelectField.tsx +70 -0
  38. package/dist/dashboard/components/shared/schema-form/fields/StringField.tsx +36 -0
  39. package/dist/dashboard/components/shared/schema-form/fields/TextareaField.tsx +31 -0
  40. package/dist/dashboard/components/shared/schema-form/fields/index.ts +23 -0
  41. package/dist/dashboard/components/shared/schema-form/index.ts +2 -0
  42. package/dist/dashboard/components/shared/schema-form/utils.ts +3 -0
  43. package/dist/dashboard/components/shared/selectable-card/SelectableCard.tsx +65 -0
  44. package/dist/dashboard/components/shared/selectable-card/index.ts +1 -0
  45. package/dist/dashboard/components/shared/stat-card/StatCard.tsx +121 -0
  46. package/dist/dashboard/components/shared/stat-card/index.ts +1 -0
  47. package/dist/dashboard/components/shared/step-config/AdapterRequiredWarning.tsx +25 -0
  48. package/dist/dashboard/components/shared/step-config/AdapterSelector.tsx +109 -0
  49. package/dist/dashboard/components/shared/step-config/AdvancedEditors.tsx +634 -0
  50. package/dist/dashboard/components/shared/step-config/EnrichConfigComponent.tsx +295 -0
  51. package/dist/dashboard/components/shared/step-config/ExtractTestResults.tsx +143 -0
  52. package/dist/dashboard/components/shared/step-config/GateConfigComponent.tsx +127 -0
  53. package/dist/dashboard/components/shared/step-config/LoadTestResults.tsx +104 -0
  54. package/dist/dashboard/components/shared/step-config/OperatorCard.tsx +266 -0
  55. package/dist/dashboard/components/shared/step-config/OperatorCheatSheetButton.tsx +54 -0
  56. package/dist/dashboard/components/shared/step-config/OperatorFieldInput.tsx +209 -0
  57. package/dist/dashboard/components/shared/step-config/RetrySettingsComponent.tsx +111 -0
  58. package/dist/dashboard/components/shared/step-config/RouteConfigComponent.tsx +125 -0
  59. package/dist/dashboard/components/shared/step-config/StepConfigPanel.tsx +564 -0
  60. package/dist/dashboard/components/shared/step-config/StepTester.tsx +165 -0
  61. package/dist/dashboard/components/shared/step-config/TestResultContainer.tsx +57 -0
  62. package/dist/dashboard/components/shared/step-config/TransformTestResults.tsx +130 -0
  63. package/dist/dashboard/components/shared/step-config/ValidateConfigComponent.tsx +334 -0
  64. package/dist/dashboard/components/shared/step-config/index.ts +29 -0
  65. package/dist/dashboard/components/shared/step-config/step-test-handlers.ts +297 -0
  66. package/dist/dashboard/components/shared/trigger-config/TriggerForm.tsx +478 -0
  67. package/dist/dashboard/components/shared/trigger-config/index.ts +1 -0
  68. package/dist/dashboard/components/shared/triggers-panel/TriggersPanel.tsx +281 -0
  69. package/dist/dashboard/components/shared/triggers-panel/index.ts +1 -0
  70. package/dist/dashboard/components/shared/wizard/ConfigurationNameCard.tsx +59 -0
  71. package/dist/dashboard/components/shared/wizard/SummaryCard.tsx +53 -0
  72. package/dist/dashboard/components/shared/wizard/WizardFooter.tsx +47 -0
  73. package/dist/dashboard/components/shared/wizard/WizardProgressBar.tsx +78 -0
  74. package/dist/dashboard/components/shared/wizard/index.ts +4 -0
  75. package/dist/dashboard/components/shared/wizard-trigger/TriggerSchemaFields.tsx +128 -0
  76. package/dist/dashboard/components/shared/wizard-trigger/TriggerSelector.tsx +33 -0
  77. package/dist/dashboard/components/shared/wizard-trigger/index.ts +2 -0
  78. package/dist/dashboard/components/templates/TemplateGallery.tsx +210 -0
  79. package/dist/dashboard/components/templates/TemplatePreview.tsx +214 -0
  80. package/dist/dashboard/components/templates/index.ts +4 -0
  81. package/dist/dashboard/components/wizards/export-wizard/DestinationStep.tsx +207 -0
  82. package/dist/dashboard/components/wizards/export-wizard/ExportWizard.tsx +221 -0
  83. package/dist/dashboard/components/wizards/export-wizard/FieldsStep.tsx +159 -0
  84. package/dist/dashboard/components/wizards/export-wizard/FormatStep.tsx +246 -0
  85. package/dist/dashboard/components/wizards/export-wizard/ReviewStep.tsx +231 -0
  86. package/dist/dashboard/components/wizards/export-wizard/SourceStep.tsx +154 -0
  87. package/dist/dashboard/components/wizards/export-wizard/TriggerStep.tsx +234 -0
  88. package/dist/dashboard/components/wizards/export-wizard/constants.ts +73 -0
  89. package/dist/dashboard/components/wizards/export-wizard/index.ts +2 -0
  90. package/dist/dashboard/components/wizards/export-wizard/types.ts +39 -0
  91. package/dist/dashboard/components/wizards/import-wizard/ImportWizard.tsx +350 -0
  92. package/dist/dashboard/components/wizards/import-wizard/MappingStep.tsx +286 -0
  93. package/dist/dashboard/components/wizards/import-wizard/PreviewStep.tsx +79 -0
  94. package/dist/dashboard/components/wizards/import-wizard/ReviewStep.tsx +266 -0
  95. package/dist/dashboard/components/wizards/import-wizard/SourceStep.tsx +537 -0
  96. package/dist/dashboard/components/wizards/import-wizard/StrategyStep.tsx +328 -0
  97. package/dist/dashboard/components/wizards/import-wizard/TargetStep.tsx +76 -0
  98. package/dist/dashboard/components/wizards/import-wizard/TemplateStep.tsx +116 -0
  99. package/dist/dashboard/components/wizards/import-wizard/TransformStep.tsx +666 -0
  100. package/dist/dashboard/components/wizards/import-wizard/TriggerStep.tsx +51 -0
  101. package/dist/dashboard/components/wizards/import-wizard/constants.ts +104 -0
  102. package/dist/dashboard/components/wizards/import-wizard/index.ts +3 -0
  103. package/dist/dashboard/components/wizards/import-wizard/types.ts +35 -0
  104. package/dist/dashboard/components/wizards/index.ts +7 -0
  105. package/dist/dashboard/components/wizards/shared/WizardStepContainer.tsx +27 -0
  106. package/dist/dashboard/components/wizards/shared/constants.ts +16 -0
  107. package/dist/dashboard/components/wizards/shared/index.ts +10 -0
  108. package/dist/dashboard/constants/colors.ts +25 -0
  109. package/dist/dashboard/constants/connection-defaults.ts +7 -0
  110. package/dist/dashboard/constants/connection-types.ts +1 -0
  111. package/dist/dashboard/constants/defaults.ts +18 -0
  112. package/dist/dashboard/constants/editor.ts +69 -0
  113. package/dist/dashboard/constants/enum-maps.ts +18 -0
  114. package/dist/dashboard/constants/fallbacks.ts +44 -0
  115. package/dist/dashboard/constants/file-format-registry.ts +206 -0
  116. package/dist/dashboard/constants/index.ts +24 -0
  117. package/dist/dashboard/constants/navigation.ts +29 -0
  118. package/dist/dashboard/constants/permissions.ts +41 -0
  119. package/dist/dashboard/constants/placeholders.ts +77 -0
  120. package/dist/dashboard/constants/routes.ts +12 -0
  121. package/dist/dashboard/constants/run-status.ts +1 -0
  122. package/dist/dashboard/constants/sentinel-values.ts +12 -0
  123. package/dist/dashboard/constants/step-configs.ts +9 -0
  124. package/dist/dashboard/constants/step-mappings.ts +170 -0
  125. package/dist/dashboard/constants/steps.ts +37 -0
  126. package/dist/dashboard/constants/toast-messages.ts +149 -0
  127. package/dist/dashboard/constants/triggers.ts +5 -0
  128. package/dist/dashboard/constants/ui-config.ts +139 -0
  129. package/dist/dashboard/constants/ui-dimensions.ts +145 -0
  130. package/dist/dashboard/constants/ui-states.ts +28 -0
  131. package/dist/dashboard/constants/ui-types.ts +85 -0
  132. package/dist/dashboard/constants/validation-patterns.ts +26 -0
  133. package/dist/dashboard/gql/gql.ts +370 -0
  134. package/dist/dashboard/gql/graphql.ts +10378 -0
  135. package/dist/dashboard/gql/index.ts +1 -0
  136. package/dist/dashboard/hooks/api/index.ts +115 -0
  137. package/dist/dashboard/hooks/api/mutation-helpers.ts +34 -0
  138. package/dist/dashboard/hooks/api/use-adapters.ts +92 -0
  139. package/dist/dashboard/hooks/api/use-config-options.ts +513 -0
  140. package/dist/dashboard/hooks/api/use-connections.ts +84 -0
  141. package/dist/dashboard/hooks/api/use-entity-field-schemas.ts +99 -0
  142. package/dist/dashboard/hooks/api/use-entity-loaders.ts +45 -0
  143. package/dist/dashboard/hooks/api/use-hooks.ts +68 -0
  144. package/dist/dashboard/hooks/api/use-logs.ts +102 -0
  145. package/dist/dashboard/hooks/api/use-pipeline-runs.ts +221 -0
  146. package/dist/dashboard/hooks/api/use-pipelines.ts +279 -0
  147. package/dist/dashboard/hooks/api/use-queues.ts +141 -0
  148. package/dist/dashboard/hooks/api/use-secrets.ts +75 -0
  149. package/dist/dashboard/hooks/api/use-settings.ts +55 -0
  150. package/dist/dashboard/hooks/api/use-step-tester.ts +79 -0
  151. package/dist/dashboard/hooks/index.ts +13 -0
  152. package/dist/dashboard/hooks/use-adapter-catalog.ts +253 -0
  153. package/dist/dashboard/hooks/use-export-templates.ts +80 -0
  154. package/dist/dashboard/hooks/use-import-templates.ts +139 -0
  155. package/dist/dashboard/hooks/use-load-more.ts +29 -0
  156. package/dist/dashboard/hooks/use-stable-keys.ts +54 -0
  157. package/dist/dashboard/hooks/use-trigger-types.ts +100 -0
  158. package/dist/dashboard/hooks/use-wizard-navigation.ts +128 -0
  159. package/dist/dashboard/index.tsx +55 -0
  160. package/dist/dashboard/routes/adapters/AdapterCard.tsx +102 -0
  161. package/dist/dashboard/routes/adapters/AdapterConstants.tsx +20 -0
  162. package/dist/dashboard/routes/adapters/AdapterDetail.tsx +208 -0
  163. package/dist/dashboard/routes/adapters/AdapterTypeSection.tsx +105 -0
  164. package/dist/dashboard/routes/adapters/AdaptersPage.tsx +276 -0
  165. package/dist/dashboard/routes/adapters/AdaptersTable.tsx +107 -0
  166. package/dist/dashboard/routes/adapters/index.ts +1 -0
  167. package/dist/dashboard/routes/connections/ConnectionDetail.tsx +218 -0
  168. package/dist/dashboard/routes/connections/ConnectionsList.tsx +34 -0
  169. package/dist/dashboard/routes/connections/index.ts +2 -0
  170. package/dist/dashboard/routes/hooks/Hooks.tsx +425 -0
  171. package/dist/dashboard/routes/hooks/hook-stages.ts +52 -0
  172. package/dist/dashboard/routes/hooks/index.ts +1 -0
  173. package/dist/dashboard/routes/index.ts +8 -0
  174. package/dist/dashboard/routes/logs/Logs.tsx +93 -0
  175. package/dist/dashboard/routes/logs/components/LogDetailDrawer.tsx +118 -0
  176. package/dist/dashboard/routes/logs/components/LogExplorerTab.tsx +367 -0
  177. package/dist/dashboard/routes/logs/components/LogLevelBadge.tsx +34 -0
  178. package/dist/dashboard/routes/logs/components/LogTableRow.tsx +70 -0
  179. package/dist/dashboard/routes/logs/components/LogsOverviewTab.tsx +178 -0
  180. package/dist/dashboard/routes/logs/components/RealtimeLogTab.tsx +122 -0
  181. package/dist/dashboard/routes/logs/index.ts +1 -0
  182. package/dist/dashboard/routes/pipelines/ErrorAuditList.tsx +39 -0
  183. package/dist/dashboard/routes/pipelines/ExportWizardPage.tsx +96 -0
  184. package/dist/dashboard/routes/pipelines/ImportWizardPage.tsx +104 -0
  185. package/dist/dashboard/routes/pipelines/PipelineDetail.tsx +211 -0
  186. package/dist/dashboard/routes/pipelines/PipelineRunsBlock.tsx +377 -0
  187. package/dist/dashboard/routes/pipelines/PipelinesList.tsx +87 -0
  188. package/dist/dashboard/routes/pipelines/RetryPatchHelper.tsx +51 -0
  189. package/dist/dashboard/routes/pipelines/RunDetailsPanel.tsx +238 -0
  190. package/dist/dashboard/routes/pipelines/RunErrorsList.tsx +116 -0
  191. package/dist/dashboard/routes/pipelines/StepCounters.tsx +24 -0
  192. package/dist/dashboard/routes/pipelines/StepSummaryTable.tsx +36 -0
  193. package/dist/dashboard/routes/pipelines/components/DryRunDialog.tsx +341 -0
  194. package/dist/dashboard/routes/pipelines/components/PipelineActionButtons.tsx +201 -0
  195. package/dist/dashboard/routes/pipelines/components/PipelineEditorToggle.tsx +116 -0
  196. package/dist/dashboard/routes/pipelines/components/PipelineFormFields.tsx +156 -0
  197. package/dist/dashboard/routes/pipelines/components/PipelineWebhookInfo.tsx +111 -0
  198. package/dist/dashboard/routes/pipelines/components/ReviewActionsPanel.tsx +342 -0
  199. package/dist/dashboard/routes/pipelines/components/ValidationPanel.tsx +121 -0
  200. package/dist/dashboard/routes/pipelines/components/VersionHistoryDialog.tsx +131 -0
  201. package/dist/dashboard/routes/pipelines/components/index.ts +25 -0
  202. package/dist/dashboard/routes/pipelines/hooks/index.ts +1 -0
  203. package/dist/dashboard/routes/pipelines/hooks/use-pipeline-validation.ts +114 -0
  204. package/dist/dashboard/routes/pipelines/index.ts +4 -0
  205. package/dist/dashboard/routes/pipelines/utils/index.ts +1 -0
  206. package/dist/dashboard/routes/pipelines/utils/pipeline-conversion.ts +261 -0
  207. package/dist/dashboard/routes/queues/ConsumersTable.tsx +134 -0
  208. package/dist/dashboard/routes/queues/DeadLettersTable.tsx +118 -0
  209. package/dist/dashboard/routes/queues/FailedRunsTable.tsx +74 -0
  210. package/dist/dashboard/routes/queues/QueuesPage.tsx +290 -0
  211. package/dist/dashboard/routes/queues/index.ts +1 -0
  212. package/dist/dashboard/routes/queues/types.ts +22 -0
  213. package/dist/dashboard/routes/secrets/SecretDetail.tsx +278 -0
  214. package/dist/dashboard/routes/secrets/SecretsList.tsx +34 -0
  215. package/dist/dashboard/routes/secrets/index.ts +2 -0
  216. package/dist/dashboard/routes/settings/Settings.tsx +343 -0
  217. package/dist/dashboard/routes/settings/index.ts +1 -0
  218. package/dist/dashboard/types/index.ts +89 -0
  219. package/dist/dashboard/types/pipeline.ts +51 -0
  220. package/dist/dashboard/types/ui-types.ts +400 -0
  221. package/dist/dashboard/types/wizard.ts +235 -0
  222. package/dist/dashboard/utils/adapter-grouping.ts +43 -0
  223. package/dist/dashboard/utils/column-analysis.ts +11 -0
  224. package/dist/dashboard/utils/field-preparation.ts +31 -0
  225. package/dist/dashboard/utils/form-validation.ts +373 -0
  226. package/dist/dashboard/utils/formatters.ts +92 -0
  227. package/dist/dashboard/utils/icon-resolver.ts +35 -0
  228. package/dist/dashboard/utils/index.ts +60 -0
  229. package/dist/dashboard/utils/query-key-factory.ts +54 -0
  230. package/dist/dashboard/utils/step-helpers.ts +32 -0
  231. package/dist/dashboard/utils/string-helpers.ts +4 -0
  232. package/dist/dashboard/utils/template-helpers.ts +26 -0
  233. package/dist/dashboard/utils/trigger-sync.ts +138 -0
  234. package/dist/dashboard/utils/wizard-to-pipeline.ts +569 -0
  235. 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,2 @@
1
+ export { TriggerSelector } from './TriggerSelector';
2
+ export { TriggerSchemaFields } from './TriggerSchemaFields';
@@ -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,4 @@
1
+ export { TemplateGallery } from './TemplateGallery';
2
+ export type { TemplateGalleryProps } from './TemplateGallery';
3
+ export { TemplatePreview } from './TemplatePreview';
4
+ export type { TemplatePreviewProps } from './TemplatePreview';
@@ -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
+ }