@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.
Files changed (235) hide show
  1. package/CHANGELOG.md +5 -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 +2 -2
@@ -0,0 +1,104 @@
1
+ import * as React from 'react';
2
+ import { Loader2 } from 'lucide-react';
3
+ import { Card, CardContent } from '@vendure/dashboard';
4
+ import type { LoadingStateProps } from '../../../types';
5
+ import { LOADING_STATE_TYPE, SKELETON_WIDTHS, ICON_SIZES } from '../../../constants';
6
+
7
+ // Deterministic skeleton width based on index
8
+ function getSkeletonWidth(index: number): number {
9
+ return SKELETON_WIDTHS[index % SKELETON_WIDTHS.length];
10
+ }
11
+
12
+ export function LoadingState({
13
+ type = LOADING_STATE_TYPE.SPINNER,
14
+ rows = 3,
15
+ message = 'Loading...',
16
+ className = '',
17
+ }: LoadingStateProps) {
18
+ if (type === LOADING_STATE_TYPE.SPINNER) {
19
+ return (
20
+ <div className={`flex items-center justify-center p-8 ${className}`} data-testid="loading-spinner">
21
+ <Loader2 className={`${ICON_SIZES.XL} animate-spin text-muted-foreground`} />
22
+ {message && <span className="ml-3 text-muted-foreground">{message}</span>}
23
+ </div>
24
+ );
25
+ }
26
+
27
+ if (type === LOADING_STATE_TYPE.TABLE) {
28
+ // Index as key is acceptable for skeleton placeholders - purely decorative, never reordered
29
+ return (
30
+ <div className={`space-y-3 ${className}`} data-testid="loading-table-skeleton">
31
+ <div className="flex gap-4 border-b pb-2">
32
+ {[0, 1, 2, 3].map((colIndex) => (
33
+ <div
34
+ key={`header-${colIndex}`}
35
+ className="h-4 bg-muted rounded animate-pulse"
36
+ style={{ width: getSkeletonWidth(colIndex) }}
37
+ />
38
+ ))}
39
+ </div>
40
+ {Array.from({ length: rows }).map((_, rowIndex) => (
41
+ <div key={`row-${rowIndex}`} className="flex gap-4 py-2">
42
+ {[0, 1, 2, 3].map((colIndex) => (
43
+ <div
44
+ key={`cell-${colIndex}`}
45
+ className="h-4 bg-muted rounded animate-pulse"
46
+ style={{ width: getSkeletonWidth((rowIndex + colIndex) % SKELETON_WIDTHS.length) }}
47
+ />
48
+ ))}
49
+ </div>
50
+ ))}
51
+ </div>
52
+ );
53
+ }
54
+
55
+ if (type === LOADING_STATE_TYPE.FORM) {
56
+ // Index as key is acceptable for skeleton placeholders - purely decorative, never reordered
57
+ return (
58
+ <div className={`space-y-4 ${className}`}>
59
+ {Array.from({ length: rows }).map((_, fieldIndex) => (
60
+ <div key={`field-${fieldIndex}`} className="space-y-2">
61
+ <div className="h-4 w-24 bg-muted rounded animate-pulse" />
62
+ <div className="h-10 bg-muted rounded animate-pulse" />
63
+ </div>
64
+ ))}
65
+ </div>
66
+ );
67
+ }
68
+
69
+ if (type === LOADING_STATE_TYPE.CARD) {
70
+ // Index as key is acceptable for skeleton placeholders - purely decorative, never reordered
71
+ return (
72
+ <div className={`grid gap-4 md:grid-cols-2 lg:grid-cols-3 ${className}`}>
73
+ {Array.from({ length: rows }).map((_, cardIndex) => (
74
+ <Card key={`card-${cardIndex}`}>
75
+ <CardContent className="p-4 space-y-3">
76
+ <div className="h-5 w-3/4 bg-muted rounded animate-pulse" />
77
+ <div className="h-4 w-full bg-muted rounded animate-pulse" />
78
+ <div className="h-4 w-2/3 bg-muted rounded animate-pulse" />
79
+ </CardContent>
80
+ </Card>
81
+ ))}
82
+ </div>
83
+ );
84
+ }
85
+
86
+ if (type === LOADING_STATE_TYPE.LIST) {
87
+ // Index as key is acceptable for skeleton placeholders - purely decorative, never reordered
88
+ return (
89
+ <div className={`space-y-2 ${className}`}>
90
+ {Array.from({ length: rows }).map((_, itemIndex) => (
91
+ <div key={`item-${itemIndex}`} className="flex items-center gap-3 p-2">
92
+ <div className="h-8 w-8 bg-muted rounded-full animate-pulse" />
93
+ <div className="flex-1 space-y-1">
94
+ <div className="h-4 w-1/3 bg-muted rounded animate-pulse" />
95
+ <div className="h-3 w-2/3 bg-muted rounded animate-pulse" />
96
+ </div>
97
+ </div>
98
+ ))}
99
+ </div>
100
+ );
101
+ }
102
+
103
+ return null;
104
+ }
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+ import { memo } from 'react';
3
+ import { AlertCircle } from 'lucide-react';
4
+ import type { ValidationErrorDisplayProps } from '../../../types';
5
+
6
+ export const ValidationErrorDisplay = memo(function ValidationErrorDisplay({
7
+ errors,
8
+ show = true,
9
+ title = 'Please fix the following errors:',
10
+ className = '',
11
+ }: ValidationErrorDisplayProps) {
12
+ if (!show || Object.keys(errors).length === 0) return null;
13
+
14
+ return (
15
+ <div className={`mb-4 p-3 rounded-lg border border-destructive/50 bg-destructive/10 ${className}`}>
16
+ <div className="flex items-start gap-2">
17
+ <AlertCircle className="w-4 h-4 text-destructive mt-0.5" />
18
+ <div>
19
+ <div className="text-sm font-medium text-destructive mb-1">{title}</div>
20
+ <ul className="text-sm text-destructive/90 list-disc pl-4">
21
+ {Object.entries(errors).map(([field, error]) => (
22
+ <li key={field}>{error}</li>
23
+ ))}
24
+ </ul>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ );
29
+ });
@@ -0,0 +1,4 @@
1
+ export { LoadingState } from './LoadingState';
2
+ export { EmptyState } from './EmptyState';
3
+ export { ErrorState } from './ErrorState';
4
+ export { ValidationErrorDisplay } from './ValidationErrorDisplay';
@@ -0,0 +1,167 @@
1
+ import * as React from 'react';
2
+ import { useCallback, memo } from 'react';
3
+ import { Button } from '@vendure/dashboard';
4
+ import { Upload, RefreshCw, CheckCircle2, X, File } from 'lucide-react';
5
+ import type { FileType, FileDropzoneProps } from '../../../types';
6
+ import { formatFileSize } from '../../../utils';
7
+ import { DROPZONE_DIMENSIONS, ICON_SIZES, buildAcceptString } from '../../../constants';
8
+ import { useOptionValues } from '../../../hooks/api/use-config-options';
9
+ import { resolveIconName } from '../../../utils/icon-resolver';
10
+
11
+ function FileDropzoneComponent({
12
+ onFileSelect,
13
+ allowedTypes,
14
+ accept,
15
+ loading = false,
16
+ loadingMessage = 'Parsing file...',
17
+ selectedFile,
18
+ onClear,
19
+ showFileIcons = true,
20
+ compact = false,
21
+ className = '',
22
+ }: FileDropzoneProps) {
23
+ const [dragOver, setDragOver] = React.useState(false);
24
+ const inputRef = React.useRef<HTMLInputElement>(null);
25
+ const { options: fileFormatOptions } = useOptionValues('fileFormats');
26
+
27
+ const handleDrop = useCallback((e: React.DragEvent) => {
28
+ e.preventDefault();
29
+ setDragOver(false);
30
+ const file = e.dataTransfer.files[0];
31
+ if (file) onFileSelect(file);
32
+ }, [onFileSelect]);
33
+
34
+ const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
35
+ const file = e.target.files?.[0];
36
+ if (file) onFileSelect(file);
37
+ }, [onFileSelect]);
38
+
39
+ const handleClick = useCallback(() => {
40
+ if (!selectedFile) {
41
+ inputRef.current?.click();
42
+ }
43
+ }, [selectedFile]);
44
+
45
+ const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
46
+ if ((e.key === 'Enter' || e.key === ' ') && !selectedFile) {
47
+ e.preventDefault();
48
+ inputRef.current?.click();
49
+ }
50
+ }, [selectedFile]);
51
+
52
+ const handleDragOver = useCallback((e: React.DragEvent) => {
53
+ e.preventDefault();
54
+ setDragOver(true);
55
+ }, []);
56
+
57
+ const handleDragLeave = useCallback(() => {
58
+ setDragOver(false);
59
+ }, []);
60
+
61
+ const handleClearClick = useCallback((e: React.MouseEvent) => {
62
+ e.stopPropagation();
63
+ onClear?.();
64
+ }, [onClear]);
65
+
66
+ const handleBrowseClick = useCallback((e: React.MouseEvent) => {
67
+ e.stopPropagation();
68
+ inputRef.current?.click();
69
+ }, []);
70
+
71
+ const acceptString = accept || buildAcceptString(allowedTypes);
72
+ const padding = compact ? DROPZONE_DIMENSIONS.PADDING_COMPACT : DROPZONE_DIMENSIONS.PADDING_DEFAULT;
73
+ const iconSize = compact ? DROPZONE_DIMENSIONS.ICON_COMPACT : DROPZONE_DIMENSIONS.ICON_DEFAULT;
74
+ const fileIconSize = compact ? DROPZONE_DIMENSIONS.FILE_ICON_COMPACT : DROPZONE_DIMENSIONS.FILE_ICON_DEFAULT;
75
+
76
+ const borderClass = selectedFile
77
+ ? 'border-green-500 bg-green-50 dark:bg-green-950/20'
78
+ : dragOver
79
+ ? 'border-primary bg-primary/5'
80
+ : 'border-muted-foreground/25 hover:border-primary/50';
81
+
82
+ return (
83
+ <div
84
+ role="button"
85
+ tabIndex={0}
86
+ aria-label="Upload file"
87
+ className={`border-2 border-dashed rounded-lg ${padding} text-center transition-colors cursor-pointer ${borderClass} ${className}`}
88
+ onDragOver={handleDragOver}
89
+ onDragLeave={handleDragLeave}
90
+ onDrop={handleDrop}
91
+ onClick={handleClick}
92
+ onKeyDown={handleKeyDown}
93
+ data-testid="datahub-filedropzone-dropzone"
94
+ >
95
+ <input
96
+ ref={inputRef}
97
+ type="file"
98
+ accept={acceptString}
99
+ onChange={handleChange}
100
+ className="hidden"
101
+ aria-hidden="true"
102
+ />
103
+
104
+ {loading ? (
105
+ <div className="flex flex-col items-center gap-4">
106
+ <RefreshCw className={`${iconSize} text-primary animate-spin`} />
107
+ <p className="text-lg font-medium">{loadingMessage}</p>
108
+ </div>
109
+ ) : selectedFile ? (
110
+ <div className="flex flex-col items-center gap-2">
111
+ <CheckCircle2 className={`${iconSize} text-green-500`} />
112
+ <p className="font-medium">{selectedFile.name}</p>
113
+ <p className="text-sm text-muted-foreground">
114
+ {formatFileSize(selectedFile.size)}
115
+ </p>
116
+ {onClear && (
117
+ <Button
118
+ variant="outline"
119
+ size="sm"
120
+ className="mt-2"
121
+ onClick={handleClearClick}
122
+ data-testid="datahub-filedropzone-remove"
123
+ >
124
+ <X className={`${ICON_SIZES.SM} mr-2`} />
125
+ Remove
126
+ </Button>
127
+ )}
128
+ </div>
129
+ ) : (
130
+ <>
131
+ {showFileIcons && allowedTypes && allowedTypes.length > 0 && (
132
+ <div className="flex justify-center gap-4 mb-4">
133
+ {allowedTypes.map(type => {
134
+ if (!type) return null;
135
+ const formatOpt = fileFormatOptions.find(f => f.value === type.toUpperCase());
136
+ const Icon = resolveIconName(formatOpt?.icon) ?? File;
137
+ const hexColor = formatOpt?.color ?? undefined;
138
+ return <Icon key={type} className={fileIconSize} style={hexColor ? { color: hexColor } : undefined} />;
139
+ })}
140
+ </div>
141
+ )}
142
+ {!showFileIcons && (
143
+ <Upload className={`${iconSize} mx-auto mb-4 text-muted-foreground`} />
144
+ )}
145
+ <p className={`font-medium ${compact ? 'text-base' : 'text-lg'} mb-2`}>
146
+ Drop your file here or click to browse
147
+ </p>
148
+ {allowedTypes && allowedTypes.length > 0 && (
149
+ <p className="text-sm text-muted-foreground mb-4">
150
+ Supports: {allowedTypes.filter((t): t is FileType => Boolean(t)).map(t => t.toUpperCase()).join(', ')}
151
+ </p>
152
+ )}
153
+ <Button
154
+ variant="outline"
155
+ onClick={handleBrowseClick}
156
+ data-testid="datahub-filedropzone-browse"
157
+ >
158
+ <Upload className={`${ICON_SIZES.SM} mr-2`} />
159
+ Browse Files
160
+ </Button>
161
+ </>
162
+ )}
163
+ </div>
164
+ );
165
+ }
166
+
167
+ export const FileDropzone = memo(FileDropzoneComponent);
@@ -0,0 +1 @@
1
+ export { FileDropzone } from './FileDropzone';
@@ -0,0 +1,226 @@
1
+ import * as React from 'react';
2
+ import { useCallback } from 'react';
3
+ import {
4
+ Button,
5
+ Input,
6
+ Label,
7
+ Select,
8
+ SelectContent,
9
+ SelectItem,
10
+ SelectTrigger,
11
+ SelectValue,
12
+ Badge,
13
+ } from '@vendure/dashboard';
14
+ import { Plus, Trash2 } from 'lucide-react';
15
+ import { COMPONENT_WIDTHS, SENTINEL_VALUES } from '../../../constants';
16
+ import type { FilterCondition, FilterOperator } from '../../../types';
17
+ import { useComparisonOperators } from '../../../hooks/api/use-config-options';
18
+ import type { ComparisonOperatorOption } from '../../../hooks/api/use-config-options';
19
+ import { useStableKeys } from '../../../hooks';
20
+
21
+ export interface FilterConditionsEditorProps {
22
+ /** Array of filter conditions */
23
+ conditions: FilterCondition[];
24
+ /** Called when conditions change */
25
+ onChange: (conditions: FilterCondition[]) => void;
26
+ /** Available fields to filter on */
27
+ fields: string[];
28
+ /** Logic operator between conditions (AND/OR) */
29
+ logic?: 'AND' | 'OR';
30
+ /** Called when logic changes */
31
+ onLogicChange?: (logic: 'AND' | 'OR') => void;
32
+ /** Show logic selector */
33
+ showLogicSelector?: boolean;
34
+ /** Placeholder for field selector */
35
+ fieldPlaceholder?: string;
36
+ /** Placeholder for value input */
37
+ valuePlaceholder?: string;
38
+ /** Empty state message */
39
+ emptyMessage?: string;
40
+ /** Add button label */
41
+ addLabel?: string;
42
+ /** Compact mode */
43
+ compact?: boolean;
44
+ }
45
+
46
+ /**
47
+ * Shared component for editing filter conditions.
48
+ * Used by pipeline editors and wizards for consistent filter UI.
49
+ */
50
+ export function FilterConditionsEditor({
51
+ conditions,
52
+ onChange,
53
+ fields,
54
+ logic = 'AND',
55
+ onLogicChange,
56
+ showLogicSelector = true,
57
+ fieldPlaceholder = 'Select field...',
58
+ valuePlaceholder = 'Value',
59
+ emptyMessage = 'No conditions - all rows pass through',
60
+ addLabel = 'Add Condition',
61
+ compact = false,
62
+ }: FilterConditionsEditorProps) {
63
+ const { operators: comparisonOperators } = useComparisonOperators();
64
+ const conditionKeys = useStableKeys(conditions, 'condition');
65
+
66
+ const addCondition = useCallback(() => {
67
+ onChange([...conditions, { field: '', operator: 'eq' as FilterOperator, value: '' }]);
68
+ }, [conditions, onChange]);
69
+
70
+ const updateCondition = useCallback((index: number, updates: Partial<FilterCondition>) => {
71
+ const newConditions = [...conditions];
72
+ newConditions[index] = { ...newConditions[index], ...updates };
73
+ onChange(newConditions);
74
+ }, [conditions, onChange]);
75
+
76
+ const removeCondition = useCallback((index: number) => {
77
+ onChange(conditions.filter((_, i) => i !== index));
78
+ }, [conditions, onChange]);
79
+
80
+ return (
81
+ <div className={compact ? 'space-y-2' : 'space-y-4'}>
82
+ <div className="flex items-center justify-between">
83
+ <Label className={compact ? 'text-xs' : ''}>Filter Conditions</Label>
84
+ <div className="flex items-center gap-2">
85
+ {showLogicSelector && onLogicChange && (
86
+ <Select
87
+ value={logic}
88
+ onValueChange={(v) => onLogicChange(v as 'AND' | 'OR')}
89
+ >
90
+ <SelectTrigger className={COMPONENT_WIDTHS.LOGIC_SELECT}>
91
+ <SelectValue />
92
+ </SelectTrigger>
93
+ <SelectContent>
94
+ <SelectItem value="AND">AND</SelectItem>
95
+ <SelectItem value="OR">OR</SelectItem>
96
+ </SelectContent>
97
+ </Select>
98
+ )}
99
+ <Button variant="outline" size="sm" onClick={addCondition} data-testid="datahub-filter-conditions-add-button">
100
+ <Plus className="w-4 h-4 mr-2" />
101
+ {addLabel}
102
+ </Button>
103
+ </div>
104
+ </div>
105
+
106
+ {conditions.length === 0 ? (
107
+ <div className={`text-center ${compact ? 'py-4' : 'py-6'} text-muted-foreground border-2 border-dashed rounded-lg`}>
108
+ <p className={compact ? 'text-xs' : 'text-sm'}>{emptyMessage}</p>
109
+ </div>
110
+ ) : (
111
+ <div className={compact ? 'space-y-1.5' : 'space-y-2'}>
112
+ {conditions.map((condition, index) => (
113
+ <FilterConditionRow
114
+ key={conditionKeys[index]}
115
+ condition={condition}
116
+ fields={fields}
117
+ comparisonOperators={comparisonOperators}
118
+ logic={logic}
119
+ showLogicBadge={showLogicSelector && index > 0}
120
+ fieldPlaceholder={fieldPlaceholder}
121
+ valuePlaceholder={valuePlaceholder}
122
+ compact={compact}
123
+ onUpdate={(updates) => updateCondition(index, updates)}
124
+ onRemove={() => removeCondition(index)}
125
+ />
126
+ ))}
127
+ </div>
128
+ )}
129
+ </div>
130
+ );
131
+ }
132
+
133
+ interface FilterConditionRowProps {
134
+ condition: FilterCondition;
135
+ fields: string[];
136
+ comparisonOperators: ComparisonOperatorOption[];
137
+ logic: 'AND' | 'OR';
138
+ showLogicBadge: boolean;
139
+ fieldPlaceholder: string;
140
+ valuePlaceholder: string;
141
+ compact: boolean;
142
+ onUpdate: (updates: Partial<FilterCondition>) => void;
143
+ onRemove: () => void;
144
+ }
145
+
146
+ function FilterConditionRow({
147
+ condition,
148
+ fields,
149
+ comparisonOperators,
150
+ logic,
151
+ showLogicBadge,
152
+ fieldPlaceholder,
153
+ valuePlaceholder,
154
+ compact,
155
+ onUpdate,
156
+ onRemove,
157
+ }: FilterConditionRowProps) {
158
+ const operatorDef = comparisonOperators.find((op) => op.value === condition.operator);
159
+ const showValueInput = !operatorDef?.noValue;
160
+
161
+ return (
162
+ <div className="flex items-center gap-2">
163
+ {showLogicBadge && (
164
+ <Badge variant="outline" className="w-12 justify-center flex-shrink-0">
165
+ {logic}
166
+ </Badge>
167
+ )}
168
+
169
+ {/* Field Selector */}
170
+ <Select
171
+ value={condition.field || SENTINEL_VALUES.NONE}
172
+ onValueChange={(v) => onUpdate({ field: v === SENTINEL_VALUES.NONE ? '' : v })}
173
+ >
174
+ <SelectTrigger className={compact ? 'flex-1 h-8' : 'flex-1'}>
175
+ <SelectValue placeholder={fieldPlaceholder} />
176
+ </SelectTrigger>
177
+ <SelectContent>
178
+ <SelectItem value={SENTINEL_VALUES.NONE}>{fieldPlaceholder}</SelectItem>
179
+ {fields.map((f) => (
180
+ <SelectItem key={f} value={f}>
181
+ {f}
182
+ </SelectItem>
183
+ ))}
184
+ </SelectContent>
185
+ </Select>
186
+
187
+ {/* Operator Selector */}
188
+ <Select
189
+ value={condition.operator}
190
+ onValueChange={(v) => onUpdate({ operator: v as FilterOperator })}
191
+ >
192
+ <SelectTrigger className={compact ? `${COMPONENT_WIDTHS.OPERATOR_SELECT} h-8` : COMPONENT_WIDTHS.OPERATOR_SELECT}>
193
+ <SelectValue />
194
+ </SelectTrigger>
195
+ <SelectContent>
196
+ {comparisonOperators.map((op) => (
197
+ <SelectItem key={op.value} value={op.value}>
198
+ {op.label}
199
+ </SelectItem>
200
+ ))}
201
+ </SelectContent>
202
+ </Select>
203
+
204
+ {/* Value Input (if operator requires it) */}
205
+ {showValueInput && (
206
+ <Input
207
+ value={String(condition.value ?? '')}
208
+ onChange={(e) => onUpdate({ value: e.target.value })}
209
+ placeholder={valuePlaceholder}
210
+ className={compact ? 'flex-1 h-8' : 'flex-1'}
211
+ />
212
+ )}
213
+
214
+ {/* Remove Button */}
215
+ <Button
216
+ variant="ghost"
217
+ size="icon"
218
+ className={compact ? 'h-8 w-8' : ''}
219
+ onClick={onRemove}
220
+ aria-label="Remove condition"
221
+ >
222
+ <Trash2 className={compact ? 'w-3.5 h-3.5 text-destructive' : 'w-4 h-4 text-destructive'} />
223
+ </Button>
224
+ </div>
225
+ );
226
+ }
@@ -0,0 +1 @@
1
+ export { FilterConditionsEditor } from './FilterConditionsEditor';
@@ -0,0 +1,45 @@
1
+ export {
2
+ StepConfigPanel,
3
+ AdapterSelector,
4
+ AdapterRequiredWarning,
5
+ RouteConfigComponent,
6
+ ValidateConfigComponent,
7
+ AdvancedMapEditor,
8
+ AdvancedTemplateEditor,
9
+ AdvancedWhenEditor,
10
+ MultiOperatorEditor,
11
+ StepTester,
12
+ OperatorCheatSheetButton,
13
+ } from './step-config';
14
+ export type { StepConfigData } from './step-config';
15
+
16
+ export { SchemaFormRenderer } from './schema-form';
17
+ export { TriggerForm } from './trigger-config';
18
+ export { TriggersPanel } from './triggers-panel';
19
+
20
+ export { LoadingState, EmptyState, ErrorState, ValidationErrorDisplay } from './feedback';
21
+
22
+ export { StatCard } from './stat-card';
23
+
24
+ export {
25
+ WizardProgressBar,
26
+ WizardFooter,
27
+ ConfigurationNameCard,
28
+ SummaryCard,
29
+ SummaryCardGrid,
30
+ } from './wizard';
31
+ export {
32
+ TriggerSelector,
33
+ TriggerSchemaFields,
34
+ } from './wizard-trigger';
35
+
36
+ export { SelectableCard, SelectableCardGrid } from './selectable-card';
37
+ export { EntitySelector } from './entity-selector';
38
+
39
+ export { FileDropzone } from './file-dropzone';
40
+
41
+ export { FilterConditionsEditor } from './filter-conditions-editor';
42
+
43
+ export { ErrorBoundary } from './error-boundary';
44
+
45
+ export { LoadMoreButton } from './LoadMoreButton';