@vendure/dashboard 3.3.2 → 3.3.3

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 (37) hide show
  1. package/dist/plugin/utils/config-loader.d.ts +12 -1
  2. package/dist/plugin/utils/config-loader.js +25 -7
  3. package/dist/plugin/vite-plugin-vendure-dashboard.d.ts +8 -0
  4. package/dist/plugin/vite-plugin-vendure-dashboard.js +5 -1
  5. package/package.json +4 -4
  6. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +1 -4
  7. package/src/app/routes/_authenticated/_channels/channels.tsx +18 -0
  8. package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +1 -5
  9. package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +1 -4
  10. package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +1 -4
  11. package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +1 -4
  12. package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +1 -5
  13. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +56 -74
  14. package/src/app/routes/_authenticated/_products/components/add-product-variant-dialog.tsx +369 -0
  15. package/src/app/routes/_authenticated/_products/components/create-product-options-dialog.tsx +435 -0
  16. package/src/app/routes/_authenticated/_products/components/product-option-select.tsx +117 -0
  17. package/src/app/routes/_authenticated/_products/components/product-variants-table.tsx +4 -2
  18. package/src/app/routes/_authenticated/_products/products_.$id.tsx +17 -3
  19. package/src/app/routes/_authenticated/_profile/profile.tsx +1 -4
  20. package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +1 -4
  21. package/src/lib/components/data-table/data-table-view-options.tsx +12 -2
  22. package/src/lib/components/data-table/data-table.tsx +9 -0
  23. package/src/lib/components/layout/channel-switcher.tsx +1 -2
  24. package/src/lib/components/shared/assigned-facet-values.tsx +13 -14
  25. package/src/lib/components/shared/entity-assets.tsx +140 -70
  26. package/src/lib/components/shared/paginated-list-data-table.tsx +10 -0
  27. package/src/lib/components/ui/button.tsx +1 -1
  28. package/src/lib/framework/form-engine/use-generated-form.tsx +1 -0
  29. package/src/lib/framework/page/list-page.tsx +2 -2
  30. package/src/lib/framework/page/use-detail-page.ts +7 -0
  31. package/src/lib/graphql/api.ts +10 -1
  32. package/src/lib/hooks/use-permissions.ts +4 -4
  33. package/src/lib/providers/auth.tsx +9 -3
  34. package/src/lib/providers/channel-provider.tsx +64 -24
  35. package/src/lib/providers/server-config.tsx +2 -2
  36. package/vite/utils/config-loader.ts +48 -13
  37. package/vite/vite-plugin-vendure-dashboard.ts +14 -4
@@ -14,6 +14,7 @@ export interface ConfigLoaderOptions {
14
14
  tempDir: string;
15
15
  vendureConfigExport?: string;
16
16
  logger?: Logger;
17
+ reportCompilationErrors?: boolean;
17
18
  }
18
19
  export interface LoadVendureConfigResult {
19
20
  vendureConfig: VendureConfig;
@@ -37,5 +38,15 @@ export interface LoadVendureConfigResult {
37
38
  * to handle the compiled JavaScript output.
38
39
  */
39
40
  export declare function loadVendureConfig(options: ConfigLoaderOptions): Promise<LoadVendureConfigResult>;
40
- export declare function compileFile(inputRootDir: string, inputPath: string, outputDir: string, logger?: Logger, compiledFiles?: Set<string>, isRoot?: boolean, pluginInfo?: PluginInfo[]): Promise<PluginInfo[]>;
41
+ type CompileFileOptions = {
42
+ inputRootDir: string;
43
+ inputPath: string;
44
+ outputDir: string;
45
+ logger?: Logger;
46
+ compiledFiles?: Set<string>;
47
+ isRoot?: boolean;
48
+ pluginInfo?: PluginInfo[];
49
+ reportCompilationErrors?: boolean;
50
+ };
51
+ export declare function compileFile({ inputRootDir, inputPath, outputDir, logger, compiledFiles, isRoot, pluginInfo, reportCompilationErrors, }: CompileFileOptions): Promise<PluginInfo[]>;
41
52
  export {};
@@ -38,7 +38,12 @@ export async function loadVendureConfig(options) {
38
38
  const configFileName = path.basename(vendureConfigPath);
39
39
  const inputRootDir = path.dirname(vendureConfigPath);
40
40
  await fs.remove(outputPath);
41
- const pluginInfo = await compileFile(inputRootDir, vendureConfigPath, outputPath, logger);
41
+ const pluginInfo = await compileFile({
42
+ inputRootDir,
43
+ inputPath: vendureConfigPath,
44
+ outputDir: outputPath,
45
+ logger,
46
+ });
42
47
  const compiledConfigFilePath = pathToFileURL(path.join(outputPath, configFileName)).href.replace(/.ts$/, '.js');
43
48
  // create package.json with type commonjs and save it to the output dir
44
49
  await fs.writeFile(path.join(outputPath, 'package.json'), JSON.stringify({ type: 'commonjs' }, null, 2));
@@ -93,7 +98,10 @@ async function findTsConfigPaths(configPath, logger) {
93
98
  // Normalize slashes for consistency, keep relative
94
99
  pattern.replace(/\\/g, '/'));
95
100
  }
96
- logger.debug(`Found tsconfig paths in ${tsConfigPath}: ${JSON.stringify({ baseUrl: tsConfigBaseUrl, paths }, null, 2)}`);
101
+ logger.debug(`Found tsconfig paths in ${tsConfigPath}: ${JSON.stringify({
102
+ baseUrl: tsConfigBaseUrl,
103
+ paths,
104
+ }, null, 2)}`);
97
105
  return { baseUrl: tsConfigBaseUrl, paths };
98
106
  }
99
107
  }
@@ -111,7 +119,7 @@ async function findTsConfigPaths(configPath, logger) {
111
119
  logger.debug(`No tsconfig paths found traversing up from ${configDir}`);
112
120
  return undefined;
113
121
  }
114
- export async function compileFile(inputRootDir, inputPath, outputDir, logger = defaultLogger, compiledFiles = new Set(), isRoot = true, pluginInfo = []) {
122
+ export async function compileFile({ inputRootDir, inputPath, outputDir, logger = defaultLogger, compiledFiles = new Set(), isRoot = true, pluginInfo = [], reportCompilationErrors = false, }) {
115
123
  const absoluteInputPath = path.resolve(inputPath);
116
124
  if (compiledFiles.has(absoluteInputPath)) {
117
125
  return pluginInfo;
@@ -250,7 +258,15 @@ export async function compileFile(inputRootDir, inputPath, outputDir, logger = d
250
258
  // Recursively collect all files that need to be compiled
251
259
  for (const importPath of importPaths) {
252
260
  // Pass rootTsConfigInfo down, but set isRoot to false
253
- await compileFile(inputRootDir, importPath, outputDir, logger, compiledFiles, false, pluginInfo);
261
+ await compileFile({
262
+ inputRootDir,
263
+ inputPath: importPath,
264
+ outputDir,
265
+ logger,
266
+ compiledFiles,
267
+ isRoot: false,
268
+ pluginInfo,
269
+ });
254
270
  }
255
271
  // If this is the root file (the one that started the compilation),
256
272
  // use the TypeScript compiler API to compile all files together
@@ -289,9 +305,11 @@ export async function compileFile(inputRootDir, inputPath, outputDir, logger = d
289
305
  const program = ts.createProgram(allFiles, compilerOptions);
290
306
  logger.info(`Emitting compiled files to ${outputDir}`);
291
307
  const emitResult = program.emit();
292
- const hasEmitErrors = reportDiagnostics(program, emitResult, logger);
293
- if (hasEmitErrors) {
294
- throw new Error('TypeScript compilation failed with errors.');
308
+ if (reportCompilationErrors) {
309
+ const hasEmitErrors = reportDiagnostics(program, emitResult, logger);
310
+ if (hasEmitErrors) {
311
+ throw new Error('TypeScript compilation failed with errors.');
312
+ }
295
313
  }
296
314
  logger.info(`Successfully compiled ${allFiles.length} files to ${outputDir}`);
297
315
  }
@@ -24,6 +24,14 @@ export type VitePluginVendureDashboardOptions = {
24
24
  gqlTadaOutputPath?: string;
25
25
  tempCompilationDir?: string;
26
26
  disableTansStackRouterPlugin?: boolean;
27
+ /**
28
+ * @description
29
+ * If set to `true`, compilation errors during the build process will be reported and
30
+ * the build will fail.
31
+ *
32
+ * @default false
33
+ */
34
+ reportCompilationErrors?: boolean;
27
35
  } & UiConfigPluginOptions & ThemeVariablesPluginOptions;
28
36
  /**
29
37
  * @description
@@ -42,7 +42,11 @@ export function vendureDashboardPlugin(options) {
42
42
  }),
43
43
  themeVariablesPlugin({ theme: options.theme }),
44
44
  tailwindcss(),
45
- configLoaderPlugin({ vendureConfigPath: normalizedVendureConfigPath, tempDir }),
45
+ configLoaderPlugin({
46
+ vendureConfigPath: normalizedVendureConfigPath,
47
+ tempDir,
48
+ reportCompilationErrors: options.reportCompilationErrors,
49
+ }),
46
50
  viteConfigPlugin({ packageRoot }),
47
51
  adminApiSchemaPlugin(),
48
52
  dashboardMetadataPlugin({ rootDir: tempDir }),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vendure/dashboard",
3
3
  "private": false,
4
- "version": "3.3.2",
4
+ "version": "3.3.3",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -86,8 +86,8 @@
86
86
  "@types/react-dom": "^19.0.4",
87
87
  "@types/react-grid-layout": "^1.3.5",
88
88
  "@uidotdev/usehooks": "^2.4.1",
89
- "@vendure/common": "3.3.2",
90
- "@vendure/core": "3.3.2",
89
+ "@vendure/common": "3.3.3",
90
+ "@vendure/core": "3.3.3",
91
91
  "@vitejs/plugin-react": "^4.3.4",
92
92
  "awesome-graphql-client": "^2.1.0",
93
93
  "class-variance-authority": "^0.7.1",
@@ -130,5 +130,5 @@
130
130
  "lightningcss-linux-arm64-musl": "^1.29.3",
131
131
  "lightningcss-linux-x64-musl": "^1.29.1"
132
132
  },
133
- "gitHead": "18a7e1f05da5921cc9478db90066654e502bea41"
133
+ "gitHead": "56068550360311cb70b04a8aba2743a1387787ac"
134
134
  }
@@ -70,9 +70,7 @@ function AdministratorDetailPage() {
70
70
  },
71
71
  params: { id: params.id },
72
72
  onSuccess: async data => {
73
- toast(i18n.t('Successfully updated administrator'), {
74
- position: 'top-right',
75
- });
73
+ toast(i18n.t('Successfully updated administrator'));
76
74
  resetForm();
77
75
  if (creatingNewEntity) {
78
76
  await navigate({ to: `../$id`, params: { id: data.id } });
@@ -80,7 +78,6 @@ function AdministratorDetailPage() {
80
78
  },
81
79
  onError: err => {
82
80
  toast(i18n.t('Failed to update administrator'), {
83
- position: 'top-right',
84
81
  description: err instanceof Error ? err.message : 'Unknown error',
85
82
  });
86
83
  },
@@ -8,6 +8,7 @@ import { Trans } from '@/lib/trans.js';
8
8
  import { createFileRoute, Link } from '@tanstack/react-router';
9
9
  import { PlusIcon } from 'lucide-react';
10
10
  import { channelListQuery, deleteChannelDocument } from './channels.graphql.js';
11
+ import { useLocalFormat } from '@/hooks/use-local-format.js';
11
12
 
12
13
  export const Route = createFileRoute('/_authenticated/_channels/channels')({
13
14
  component: ChannelListPage,
@@ -15,6 +16,7 @@ export const Route = createFileRoute('/_authenticated/_channels/channels')({
15
16
  });
16
17
 
17
18
  function ChannelListPage() {
19
+ const { formatLanguageName } = useLocalFormat();
18
20
  return (
19
21
  <ListPage
20
22
  pageId="channel-list"
@@ -25,6 +27,10 @@ function ChannelListPage() {
25
27
  defaultVisibility={{
26
28
  code: true,
27
29
  token: true,
30
+ availableCurrencyCodes: false,
31
+ availableLanguageCodes: false,
32
+ defaultTaxZone: false,
33
+ defaultShippingZone: false,
28
34
  }}
29
35
  onSearchTermChange={searchTerm => {
30
36
  return {
@@ -43,6 +49,18 @@ function ChannelListPage() {
43
49
  );
44
50
  },
45
51
  },
52
+ seller: {
53
+ header: 'Seller',
54
+ cell: ({ row }) => {
55
+ return row.original.seller?.name;
56
+ }
57
+ },
58
+ defaultLanguageCode: {
59
+ header: 'Default Language',
60
+ cell: ({ row }) => {
61
+ return formatLanguageName(row.original.defaultLanguageCode);
62
+ }
63
+ },
46
64
  }}
47
65
  >
48
66
  <PageActionBarRight>
@@ -76,23 +76,19 @@ function ChannelDetailPage() {
76
76
  params: { id: params.id },
77
77
  onSuccess: async data => {
78
78
  if (data.__typename === 'Channel') {
79
- toast(i18n.t('Successfully updated channel'), {
80
- position: 'top-right',
81
- });
79
+ toast(i18n.t('Successfully updated channel'));
82
80
  resetForm();
83
81
  if (creatingNewEntity) {
84
82
  await navigate({ to: `../$id`, params: { id: data.id } });
85
83
  }
86
84
  } else {
87
85
  toast(i18n.t('Failed to update channel'), {
88
- position: 'top-right',
89
86
  description: data.message,
90
87
  });
91
88
  }
92
89
  },
93
90
  onError: err => {
94
91
  toast(i18n.t('Failed to update channel'), {
95
- position: 'top-right',
96
92
  description: err instanceof Error ? err.message : 'Unknown error',
97
93
  });
98
94
  },
@@ -85,9 +85,7 @@ function CollectionDetailPage() {
85
85
  },
86
86
  params: { id: params.id },
87
87
  onSuccess: async data => {
88
- toast(i18n.t('Successfully updated collection'), {
89
- position: 'top-right',
90
- });
88
+ toast(i18n.t('Successfully updated collection'));
91
89
  resetForm();
92
90
  if (creatingNewEntity) {
93
91
  await navigate({ to: `../$id`, params: { id: data.id } });
@@ -95,7 +93,6 @@ function CollectionDetailPage() {
95
93
  },
96
94
  onError: err => {
97
95
  toast(i18n.t('Failed to update collection'), {
98
- position: 'top-right',
99
96
  description: err instanceof Error ? err.message : 'Unknown error',
100
97
  });
101
98
  },
@@ -57,9 +57,7 @@ function CountryDetailPage() {
57
57
  },
58
58
  params: { id: params.id },
59
59
  onSuccess: async data => {
60
- toast(i18n.t('Successfully updated country'), {
61
- position: 'top-right',
62
- });
60
+ toast(i18n.t('Successfully updated country'));
63
61
  form.reset(form.getValues());
64
62
  if (creatingNewEntity) {
65
63
  await navigate({ to: `../$id`, params: { id: data.id } });
@@ -67,7 +65,6 @@ function CountryDetailPage() {
67
65
  },
68
66
  onError: err => {
69
67
  toast(i18n.t('Failed to update country'), {
70
- position: 'top-right',
71
68
  description: err instanceof Error ? err.message : 'Unknown error',
72
69
  });
73
70
  },
@@ -68,9 +68,7 @@ function FacetDetailPage() {
68
68
  },
69
69
  params: { id: params.id },
70
70
  onSuccess: async data => {
71
- toast(i18n.t('Successfully updated facet'), {
72
- position: 'top-right',
73
- });
71
+ toast(i18n.t('Successfully updated facet'));
74
72
  resetForm();
75
73
  if (creatingNewEntity) {
76
74
  await navigate({ to: `../$id`, params: { id: data.id } });
@@ -78,7 +76,6 @@ function FacetDetailPage() {
78
76
  },
79
77
  onError: err => {
80
78
  toast(i18n.t('Failed to update facet'), {
81
- position: 'top-right',
82
79
  description: err instanceof Error ? err.message : 'Unknown error',
83
80
  });
84
81
  },
@@ -59,23 +59,19 @@ function GlobalSettingsPage() {
59
59
  params: { id: 'undefined' },
60
60
  onSuccess: async data => {
61
61
  if (data.__typename === 'GlobalSettings') {
62
- toast(i18n.t('Successfully updated global settings'), {
63
- position: 'top-right',
64
- });
62
+ toast(i18n.t('Successfully updated global settings'));
65
63
  form.reset(form.getValues());
66
64
  if (creatingNewEntity) {
67
65
  await navigate({ to: `../$id`, params: { id: data.id } });
68
66
  }
69
67
  } else {
70
68
  toast(i18n.t('Failed to update global settings'), {
71
- position: 'top-right',
72
69
  description: data.message,
73
70
  });
74
71
  }
75
72
  },
76
73
  onError: err => {
77
74
  toast(i18n.t('Failed to update global settings'), {
78
- position: 'top-right',
79
75
  description: err instanceof Error ? err.message : 'Unknown error',
80
76
  });
81
77
  },
@@ -7,14 +7,7 @@ import { PermissionGuard } from '@/components/shared/permission-guard.js';
7
7
  import { TaxCategorySelector } from '@/components/shared/tax-category-selector.js';
8
8
  import { TranslatableFormFieldWrapper } from '@/components/shared/translatable-form-field.js';
9
9
  import { Button } from '@/components/ui/button.js';
10
- import {
11
- FormControl,
12
- FormDescription,
13
- FormField,
14
- FormItem,
15
- FormLabel,
16
- FormMessage,
17
- } from '@/components/ui/form.js';
10
+ import { FormControl, FormDescription, FormItem, FormLabel, FormMessage } from '@/components/ui/form.js';
18
11
  import { Input } from '@/components/ui/input.js';
19
12
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select.js';
20
13
  import { Switch } from '@/components/ui/switch.js';
@@ -194,17 +187,15 @@ function ProductVariantDetailPage() {
194
187
  <FormFieldWrapper
195
188
  control={form.control}
196
189
  name={`stockLevels.${index}.stockOnHand`}
190
+ label={<Trans>Stock level</Trans>}
197
191
  render={({ field }) => (
198
- <FormItem>
199
- <FormLabel>
200
- <Trans>Stock level</Trans>
201
- </FormLabel>
202
- <FormControl>
203
- <Input type="number" value={field.value} onChange={e => {
204
- field.onChange(e.target.valueAsNumber);
205
- }} />
206
- </FormControl>
207
- </FormItem>
192
+ <Input
193
+ type="number"
194
+ value={field.value}
195
+ onChange={e => {
196
+ field.onChange(e.target.valueAsNumber);
197
+ }}
198
+ />
208
199
  )}
209
200
  />
210
201
  <div>
@@ -218,77 +209,68 @@ function ProductVariantDetailPage() {
218
209
  </Fragment>
219
210
  ))}
220
211
 
221
- <FormField
212
+ <FormFieldWrapper
222
213
  control={form.control}
223
214
  name="trackInventory"
215
+ label={<Trans>Stock levels</Trans>}
224
216
  render={({ field }) => (
225
- <FormItem>
226
- <FormLabel>
227
- <Trans>Track inventory</Trans>
228
- </FormLabel>
229
- <Select onValueChange={val => {
217
+ <Select
218
+ onValueChange={val => {
230
219
  if (val) {
231
- field.onChange(val)
220
+ field.onChange(val);
232
221
  }
233
- }} value={field.value}>
234
- <FormControl>
235
- <SelectTrigger className="">
236
- <SelectValue placeholder="Track inventory" />
237
- </SelectTrigger>
238
- </FormControl>
239
- <SelectContent>
240
- <SelectItem value="INHERIT">
241
- <Trans>Inherit from global settings</Trans>
242
- </SelectItem>
243
- <SelectItem value="TRUE">
244
- <Trans>Track</Trans>
245
- </SelectItem>
246
- <SelectItem value="FALSE">
247
- <Trans>Do not track</Trans>
248
- </SelectItem>
249
- </SelectContent>
250
- </Select>
251
- </FormItem>
222
+ }}
223
+ value={field.value}
224
+ >
225
+ <FormControl>
226
+ <SelectTrigger className="">
227
+ <SelectValue placeholder="Track inventory" />
228
+ </SelectTrigger>
229
+ </FormControl>
230
+ <SelectContent>
231
+ <SelectItem value="INHERIT">
232
+ <Trans>Inherit from global settings</Trans>
233
+ </SelectItem>
234
+ <SelectItem value="TRUE">
235
+ <Trans>Track</Trans>
236
+ </SelectItem>
237
+ <SelectItem value="FALSE">
238
+ <Trans>Do not track</Trans>
239
+ </SelectItem>
240
+ </SelectContent>
241
+ </Select>
252
242
  )}
253
243
  />
254
- <FormField
244
+ <FormFieldWrapper
255
245
  control={form.control}
256
246
  name="outOfStockThreshold"
247
+ label={<Trans>Out-of-stock threshold</Trans>}
248
+ description={
249
+ <Trans>
250
+ Sets the stock level at which this variant is considered to be out of
251
+ stock. Using a negative value enables backorder support.
252
+ </Trans>
253
+ }
257
254
  render={({ field }) => (
258
- <FormItem>
259
- <FormLabel>
260
- <Trans>Out-of-stock threshold</Trans>
261
- </FormLabel>
262
- <FormControl>
263
- <Input type="number" {...field} />
264
- </FormControl>
265
- <FormDescription>
266
- <Trans>
267
- Sets the stock level at which this variant is considered to be out
268
- of stock. Using a negative value enables backorder support.
269
- </Trans>
270
- </FormDescription>
271
- </FormItem>
255
+ <Input
256
+ type="number"
257
+ value={field.value}
258
+ onChange={e => field.onChange(e.target.valueAsNumber)}
259
+ />
272
260
  )}
273
261
  />
274
- <FormField
262
+ <FormFieldWrapper
275
263
  control={form.control}
276
264
  name="useGlobalOutOfStockThreshold"
265
+ label={<Trans>Use global out-of-stock threshold</Trans>}
266
+ description={
267
+ <Trans>
268
+ Sets the stock level at which this variant is considered to be out of
269
+ stock. Using a negative value enables backorder support.
270
+ </Trans>
271
+ }
277
272
  render={({ field }) => (
278
- <FormItem>
279
- <FormLabel>
280
- <Trans>Use global out-of-stock threshold</Trans>
281
- </FormLabel>
282
- <FormControl>
283
- <Switch checked={field.value} onCheckedChange={field.onChange} />
284
- </FormControl>
285
- <FormDescription>
286
- <Trans>
287
- Sets the stock level at which this variant is considered to be out
288
- of stock. Using a negative value enables backorder support.
289
- </Trans>
290
- </FormDescription>
291
- </FormItem>
273
+ <Switch checked={field.value} onCheckedChange={field.onChange} />
292
274
  )}
293
275
  />
294
276
  </DetailFormGrid>