@vendure/dashboard 3.3.8 → 3.4.0

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 (131) hide show
  1. package/README.md +62 -0
  2. package/dist/plugin/api/api-extensions.d.ts +1 -0
  3. package/dist/plugin/api/api-extensions.js +38 -0
  4. package/dist/plugin/api/metrics.resolver.d.ts +8 -0
  5. package/dist/plugin/api/metrics.resolver.js +40 -0
  6. package/dist/plugin/config/metrics-strategies.d.ts +39 -0
  7. package/dist/plugin/config/metrics-strategies.js +74 -0
  8. package/dist/plugin/constants.d.ts +4 -3
  9. package/dist/plugin/constants.js +10 -277
  10. package/dist/plugin/dashboard.plugin.d.ts +95 -0
  11. package/dist/plugin/dashboard.plugin.js +168 -0
  12. package/dist/plugin/index.d.ts +2 -1
  13. package/dist/plugin/index.js +18 -1
  14. package/dist/plugin/package.json +3 -0
  15. package/dist/plugin/service/metrics.service.d.ts +15 -0
  16. package/dist/plugin/service/metrics.service.js +145 -0
  17. package/dist/plugin/types.d.ts +20 -37
  18. package/dist/plugin/types.js +13 -1
  19. package/dist/vite/constants.d.ts +5 -0
  20. package/dist/vite/constants.js +277 -0
  21. package/dist/vite/index.d.ts +1 -0
  22. package/dist/vite/index.js +1 -0
  23. package/dist/vite/types.d.ts +40 -0
  24. package/dist/vite/utils/config-loader.js +1 -0
  25. package/dist/{plugin → vite}/utils/plugin-discovery.js +1 -1
  26. package/dist/vite/utils/ui-config.d.ts +3 -0
  27. package/dist/vite/utils/ui-config.js +30 -0
  28. package/dist/vite/vite-plugin-ui-config.d.ts +123 -0
  29. package/dist/{plugin → vite}/vite-plugin-ui-config.js +3 -11
  30. package/dist/{plugin → vite}/vite-plugin-vendure-dashboard.js +1 -1
  31. package/index.html +1 -1
  32. package/package.json +16 -7
  33. package/src/app/app-providers.tsx +1 -1
  34. package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +1 -1
  35. package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +20 -35
  36. package/src/app/routes/_authenticated/_facets/facets.graphql.ts +40 -0
  37. package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +147 -0
  38. package/src/app/routes/_authenticated/_orders/components/order-history/order-history.tsx +380 -33
  39. package/src/app/routes/_authenticated/_products/components/option-value-input.tsx +1 -1
  40. package/src/app/routes/_authenticated/_system/healthchecks.tsx +1 -1
  41. package/src/app/routes/_authenticated/_system/job-queue.tsx +1 -0
  42. package/src/app/routes/_authenticated/index.tsx +2 -2
  43. package/src/app/routes/_authenticated.tsx +1 -1
  44. package/src/lib/components/data-input/rich-text-input.tsx +14 -8
  45. package/src/lib/components/data-table/data-table-bulk-actions.tsx +17 -4
  46. package/src/lib/components/layout/app-layout.tsx +2 -7
  47. package/src/lib/components/layout/channel-switcher.tsx +166 -57
  48. package/src/lib/components/layout/dev-mode-indicator.tsx +18 -0
  49. package/src/lib/components/layout/language-dialog.tsx +2 -1
  50. package/src/lib/components/layout/manage-languages-dialog.tsx +77 -40
  51. package/src/lib/components/layout/nav-item-wrapper.tsx +107 -0
  52. package/src/lib/components/layout/nav-main.tsx +196 -107
  53. package/src/lib/components/login/login-form.tsx +80 -45
  54. package/src/lib/components/shared/asset/asset-bulk-actions.tsx +19 -4
  55. package/src/lib/components/shared/asset/asset-gallery.tsx +2 -2
  56. package/src/lib/components/shared/detail-page-button.tsx +42 -0
  57. package/src/lib/components/shared/history-timeline/history-entry-date.tsx +37 -0
  58. package/src/lib/components/shared/history-timeline/history-entry.tsx +135 -65
  59. package/src/lib/components/shared/history-timeline/history-note-input.tsx +4 -4
  60. package/src/lib/components/shared/history-timeline/history-timeline.tsx +7 -54
  61. package/src/lib/components/shared/translatable-form-field.tsx +16 -2
  62. package/src/lib/framework/defaults.ts +4 -10
  63. package/src/lib/framework/extension-api/define-dashboard-extension.ts +4 -0
  64. package/src/lib/framework/extension-api/extension-api-types.ts +11 -2
  65. package/src/lib/framework/extension-api/logic/index.ts +1 -0
  66. package/src/lib/framework/extension-api/logic/login.ts +17 -0
  67. package/src/lib/framework/extension-api/logic/navigation.ts +1 -0
  68. package/src/lib/framework/extension-api/types/data-table.ts +12 -3
  69. package/src/lib/framework/extension-api/types/detail-forms.ts +13 -0
  70. package/src/lib/framework/extension-api/types/form-components.ts +11 -0
  71. package/src/lib/framework/extension-api/types/index.ts +1 -0
  72. package/src/lib/framework/extension-api/types/layout.ts +3 -6
  73. package/src/lib/framework/extension-api/types/login.ts +96 -0
  74. package/src/lib/framework/extension-api/types/navigation.ts +57 -0
  75. package/src/lib/framework/extension-api/types/widgets.ts +0 -4
  76. package/src/lib/framework/extension-api/use-login-extensions.ts +26 -0
  77. package/src/lib/framework/layout-engine/dev-mode-button.tsx +24 -0
  78. package/src/lib/framework/layout-engine/location-wrapper.tsx +5 -12
  79. package/src/lib/framework/registry/global-registry.ts +4 -0
  80. package/src/lib/framework/registry/registry-types.ts +2 -0
  81. package/src/lib/graphql/api.ts +25 -3
  82. package/src/lib/graphql/graphql-env.d.ts +28 -28
  83. package/src/lib/graphql/settings-store-operations.ts +17 -0
  84. package/src/lib/hooks/use-floating-bulk-actions.ts +82 -0
  85. package/src/lib/hooks/use-local-format.ts +20 -5
  86. package/src/lib/index.ts +2 -1
  87. package/src/lib/providers/channel-provider.tsx +13 -11
  88. package/src/lib/providers/user-settings.tsx +78 -3
  89. package/src/lib/virtual.d.ts +26 -2
  90. package/src/vite-env.d.ts +2 -0
  91. package/vite/utils/plugin-discovery.ts +1 -1
  92. package/vite/utils/ui-config.ts +30 -42
  93. package/vite/vite-plugin-ui-config.ts +119 -17
  94. package/vite/vite-plugin-vendure-dashboard.ts +1 -1
  95. package/dist/plugin/utils/ui-config.d.ts +0 -3
  96. package/dist/plugin/utils/ui-config.js +0 -34
  97. package/dist/plugin/vite-plugin-ui-config.d.ts +0 -15
  98. package/src/app/routes/_authenticated/_facets/components/add-facet-value-dialog.tsx +0 -146
  99. package/src/lib/components/shared/rich-text-editor.tsx +0 -0
  100. /package/dist/{plugin/utils/ast-utils.spec.d.ts → vite/types.js} +0 -0
  101. /package/dist/{plugin → vite}/utils/ast-utils.d.ts +0 -0
  102. /package/dist/{plugin → vite}/utils/ast-utils.js +0 -0
  103. /package/dist/{plugin/utils/config-loader.d.ts → vite/utils/ast-utils.spec.d.ts} +0 -0
  104. /package/dist/{plugin → vite}/utils/ast-utils.spec.js +0 -0
  105. /package/dist/{plugin → vite}/utils/compiler.d.ts +0 -0
  106. /package/dist/{plugin → vite}/utils/compiler.js +0 -0
  107. /package/dist/{plugin/utils/config-loader.js → vite/utils/config-loader.d.ts} +0 -0
  108. /package/dist/{plugin → vite}/utils/logger.d.ts +0 -0
  109. /package/dist/{plugin → vite}/utils/logger.js +0 -0
  110. /package/dist/{plugin → vite}/utils/plugin-discovery.d.ts +0 -0
  111. /package/dist/{plugin → vite}/utils/schema-generator.d.ts +0 -0
  112. /package/dist/{plugin → vite}/utils/schema-generator.js +0 -0
  113. /package/dist/{plugin → vite}/utils/tsconfig-utils.d.ts +0 -0
  114. /package/dist/{plugin → vite}/utils/tsconfig-utils.js +0 -0
  115. /package/dist/{plugin → vite}/vite-plugin-admin-api-schema.d.ts +0 -0
  116. /package/dist/{plugin → vite}/vite-plugin-admin-api-schema.js +0 -0
  117. /package/dist/{plugin → vite}/vite-plugin-config-loader.d.ts +0 -0
  118. /package/dist/{plugin → vite}/vite-plugin-config-loader.js +0 -0
  119. /package/dist/{plugin → vite}/vite-plugin-config.d.ts +0 -0
  120. /package/dist/{plugin → vite}/vite-plugin-config.js +0 -0
  121. /package/dist/{plugin → vite}/vite-plugin-dashboard-metadata.d.ts +0 -0
  122. /package/dist/{plugin → vite}/vite-plugin-dashboard-metadata.js +0 -0
  123. /package/dist/{plugin → vite}/vite-plugin-gql-tada.d.ts +0 -0
  124. /package/dist/{plugin → vite}/vite-plugin-gql-tada.js +0 -0
  125. /package/dist/{plugin → vite}/vite-plugin-tailwind-source.d.ts +0 -0
  126. /package/dist/{plugin → vite}/vite-plugin-tailwind-source.js +0 -0
  127. /package/dist/{plugin → vite}/vite-plugin-theme.d.ts +0 -0
  128. /package/dist/{plugin → vite}/vite-plugin-theme.js +0 -0
  129. /package/dist/{plugin → vite}/vite-plugin-transform-index.d.ts +0 -0
  130. /package/dist/{plugin → vite}/vite-plugin-transform-index.js +0 -0
  131. /package/dist/{plugin → vite}/vite-plugin-vendure-dashboard.d.ts +0 -0
@@ -0,0 +1,17 @@
1
+ import { graphql } from './graphql.js';
2
+
3
+ export const getSettingsStoreValueDocument = graphql(`
4
+ query GetSettingsStoreValue($key: String!) {
5
+ getSettingsStoreValue(key: $key)
6
+ }
7
+ `);
8
+
9
+ export const setSettingsStoreValueDocument = graphql(`
10
+ mutation SetSettingsStoreValue($input: SettingsStoreInput!) {
11
+ setSettingsStoreValue(input: $input) {
12
+ key
13
+ result
14
+ error
15
+ }
16
+ }
17
+ `);
@@ -0,0 +1,82 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ interface FloatingPosition {
4
+ bottom: string;
5
+ left: string;
6
+ }
7
+
8
+ interface UseFloatingBulkActionsOptions {
9
+ selectionCount: number;
10
+ containerSelector: string;
11
+ bufferDistance?: number;
12
+ bottomOffset?: number;
13
+ }
14
+
15
+ /**
16
+ * Common logic used to power floating the bulk action component used in
17
+ * data tables and in the asset gallery.
18
+ */
19
+ export function useFloatingBulkActions({
20
+ selectionCount,
21
+ containerSelector,
22
+ bufferDistance = 80,
23
+ bottomOffset = 10,
24
+ }: Readonly<UseFloatingBulkActionsOptions>) {
25
+ const [position, setPosition] = useState<FloatingPosition>({ bottom: '2.5rem', left: '50%' });
26
+ const [isPositioned, setIsPositioned] = useState(false);
27
+
28
+ useEffect(() => {
29
+ if (selectionCount === 0) {
30
+ setIsPositioned(false);
31
+ return;
32
+ }
33
+
34
+ const updatePosition = () => {
35
+ const container = document.querySelector(containerSelector)?.closest('div') as HTMLElement;
36
+ if (!container) return;
37
+
38
+ const containerRect = container.getBoundingClientRect();
39
+ const viewportHeight = window.innerHeight;
40
+
41
+ // Check if container bottom is visible in viewport
42
+ const containerBottom = containerRect.bottom;
43
+ const isContainerFullyVisible = containerBottom <= viewportHeight - bufferDistance;
44
+
45
+ // Calculate horizontal center
46
+ const containerLeft = containerRect.left;
47
+ const containerWidth = containerRect.width;
48
+ const centerX = containerLeft + containerWidth / 2;
49
+
50
+ if (isContainerFullyVisible) {
51
+ // Position relative to container bottom
52
+ setPosition({
53
+ bottom: `${viewportHeight - containerBottom + bottomOffset}px`,
54
+ left: `${centerX}px`,
55
+ });
56
+ } else {
57
+ // Position relative to viewport bottom, centered in container
58
+ setPosition({
59
+ bottom: '2.5rem',
60
+ left: `${centerX}px`,
61
+ });
62
+ }
63
+
64
+ setIsPositioned(true);
65
+ };
66
+
67
+ updatePosition();
68
+ window.addEventListener('scroll', updatePosition);
69
+ window.addEventListener('resize', updatePosition);
70
+
71
+ return () => {
72
+ window.removeEventListener('scroll', updatePosition);
73
+ window.removeEventListener('resize', updatePosition);
74
+ };
75
+ }, [selectionCount, containerSelector, bufferDistance]);
76
+
77
+ return {
78
+ position,
79
+ isPositioned,
80
+ shouldShow: selectionCount > 0 && isPositioned,
81
+ };
82
+ }
@@ -77,15 +77,30 @@ export function useLocalFormat() {
77
77
  if (diffSeconds < 60) {
78
78
  return new Intl.RelativeTimeFormat(locale, options).format(diffSeconds * -1, 'seconds');
79
79
  } else if (diffSeconds < 3600) {
80
- return new Intl.RelativeTimeFormat(locale, options).format(diffSeconds * -1, 'minutes');
80
+ return new Intl.RelativeTimeFormat(locale, options).format(
81
+ Math.floor((diffSeconds / 60) * -1),
82
+ 'minutes',
83
+ );
81
84
  } else if (diffSeconds < 86400) {
82
- return new Intl.RelativeTimeFormat(locale, options).format(diffSeconds * -1, 'hours');
85
+ return new Intl.RelativeTimeFormat(locale, options).format(
86
+ Math.floor((diffSeconds / 3600) * -1),
87
+ 'hours',
88
+ );
83
89
  } else if (diffSeconds < 2592000) {
84
- return new Intl.RelativeTimeFormat(locale, options).format(diffSeconds * -1, 'days');
90
+ return new Intl.RelativeTimeFormat(locale, options).format(
91
+ Math.floor((diffSeconds / 86400) * -1),
92
+ 'days',
93
+ );
85
94
  } else if (diffSeconds < 31536000) {
86
- return new Intl.RelativeTimeFormat(locale, options).format(diffSeconds * -1, 'months');
95
+ return new Intl.RelativeTimeFormat(locale, options).format(
96
+ Math.floor((diffSeconds / 2592000) * -1),
97
+ 'months',
98
+ );
87
99
  } else {
88
- return new Intl.RelativeTimeFormat(locale, options).format(diffSeconds * -1, 'years');
100
+ return new Intl.RelativeTimeFormat(locale, options).format(
101
+ Math.floor((diffSeconds / 31536000) * -1),
102
+ 'years',
103
+ );
89
104
  }
90
105
  },
91
106
  [locale],
package/src/lib/index.ts CHANGED
@@ -92,7 +92,6 @@ export * from './components/shared/paginated-list-data-table.js';
92
92
  export * from './components/shared/permission-guard.js';
93
93
  export * from './components/shared/product-variant-selector.js';
94
94
  export * from './components/shared/remove-from-channel-bulk-action.js';
95
- export * from './components/shared/rich-text-editor.js';
96
95
  export * from './components/shared/role-code-label.js';
97
96
  export * from './components/shared/role-selector.js';
98
97
  export * from './components/shared/seller-selector.js';
@@ -180,9 +179,11 @@ export * from './framework/extension-api/types/data-table.js';
180
179
  export * from './framework/extension-api/types/detail-forms.js';
181
180
  export * from './framework/extension-api/types/form-components.js';
182
181
  export * from './framework/extension-api/types/layout.js';
182
+ export * from './framework/extension-api/types/login.js';
183
183
  export * from './framework/extension-api/types/navigation.js';
184
184
  export * from './framework/extension-api/types/widgets.js';
185
185
  export * from './framework/extension-api/use-dashboard-extensions.js';
186
+ export * from './framework/extension-api/use-login-extensions.js';
186
187
  export * from './framework/form-engine/custom-form-component-extensions.js';
187
188
  export * from './framework/form-engine/custom-form-component.js';
188
189
  export * from './framework/form-engine/form-schema-tools.js';
@@ -13,6 +13,7 @@ const channelFragment = graphql(`
13
13
  defaultLanguageCode
14
14
  defaultCurrencyCode
15
15
  pricesIncludeTax
16
+ availableLanguageCodes
16
17
  }
17
18
  `);
18
19
 
@@ -92,17 +93,18 @@ export function ChannelProvider({ children }: Readonly<{ children: React.ReactNo
92
93
  // If user has specific channels assigned (non-superadmin), use those
93
94
  if (userChannels && userChannels.length > 0) {
94
95
  // Map user channels to match the Channel type structure
95
- return userChannels.map(ch => ({
96
- id: ch.id,
97
- code: ch.code,
98
- token: ch.token,
99
- defaultLanguageCode:
100
- channelsData?.channels.items.find(c => c.id === ch.id)?.defaultLanguageCode || 'en',
101
- defaultCurrencyCode:
102
- channelsData?.channels.items.find(c => c.id === ch.id)?.defaultCurrencyCode || 'USD',
103
- pricesIncludeTax:
104
- channelsData?.channels.items.find(c => c.id === ch.id)?.pricesIncludeTax || false,
105
- }));
96
+ return userChannels.map(ch => {
97
+ const fullChannelData = channelsData?.channels.items.find(c => c.id === ch.id);
98
+ return {
99
+ id: ch.id,
100
+ code: ch.code,
101
+ token: ch.token,
102
+ defaultLanguageCode: fullChannelData?.defaultLanguageCode || 'en',
103
+ defaultCurrencyCode: fullChannelData?.defaultCurrencyCode || 'USD',
104
+ pricesIncludeTax: fullChannelData?.pricesIncludeTax || false,
105
+ availableLanguageCodes: fullChannelData?.availableLanguageCodes || ['en'],
106
+ };
107
+ });
106
108
  }
107
109
  // Otherwise use all channels (superadmin)
108
110
  return channelsData?.channels.items || [];
@@ -1,6 +1,12 @@
1
- import React, { createContext, useState, useEffect } from 'react';
2
- import { Theme } from './theme-provider.js';
1
+ import { QueryClient, useMutation, useQuery } from '@tanstack/react-query';
3
2
  import { ColumnFiltersState } from '@tanstack/react-table';
3
+ import React, { createContext, useEffect, useRef, useState } from 'react';
4
+ import { api } from '../graphql/api.js';
5
+ import {
6
+ getSettingsStoreValueDocument,
7
+ setSettingsStoreValueDocument,
8
+ } from '../graphql/settings-store-operations.js';
9
+ import { Theme } from './theme-provider.js';
4
10
 
5
11
  export interface TableSettings {
6
12
  columnVisibility?: Record<string, boolean>;
@@ -56,8 +62,14 @@ export interface UserSettingsContextType {
56
62
  export const UserSettingsContext = createContext<UserSettingsContextType | undefined>(undefined);
57
63
 
58
64
  const STORAGE_KEY = 'vendure-user-settings';
65
+ const SETTINGS_STORE_KEY = 'vendure.dashboard.userSettings';
59
66
 
60
- export const UserSettingsProvider: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
67
+ interface UserSettingsProviderProps {
68
+ queryClient?: QueryClient;
69
+ children: React.ReactNode;
70
+ }
71
+
72
+ export const UserSettingsProvider: React.FC<UserSettingsProviderProps> = ({ queryClient, children }) => {
61
73
  // Load settings from localStorage or use defaults
62
74
  const loadSettings = (): UserSettings => {
63
75
  try {
@@ -72,6 +84,49 @@ export const UserSettingsProvider: React.FC<React.PropsWithChildren<{}>> = ({ ch
72
84
  };
73
85
 
74
86
  const [settings, setSettings] = useState<UserSettings>(loadSettings);
87
+ const [serverSettings, setServerSettings] = useState<UserSettings | null>(null);
88
+ const [isReady, setIsReady] = useState(false);
89
+ const previousContentLanguage = useRef(settings.contentLanguage);
90
+
91
+ // Load settings from server on mount
92
+ const { data: serverSettingsResponse, isSuccess: serverSettingsLoaded } = useQuery({
93
+ queryKey: ['user-settings', SETTINGS_STORE_KEY],
94
+ queryFn: () => api.query(getSettingsStoreValueDocument, { key: SETTINGS_STORE_KEY }),
95
+ retry: false,
96
+ staleTime: 0,
97
+ });
98
+
99
+ // Mutation to save settings to server
100
+ const saveToServerMutation = useMutation({
101
+ mutationFn: (settingsToSave: UserSettings) =>
102
+ api.mutate(setSettingsStoreValueDocument, {
103
+ input: { key: SETTINGS_STORE_KEY, value: settingsToSave },
104
+ }),
105
+ onError: error => {
106
+ console.error('Failed to save user settings to server:', error);
107
+ },
108
+ });
109
+
110
+ // Initialize settings from server if available
111
+ useEffect(() => {
112
+ if (serverSettingsLoaded && !isReady) {
113
+ try {
114
+ const serverSettingsData =
115
+ serverSettingsResponse?.getSettingsStoreValue as UserSettings | null;
116
+ if (serverSettingsData) {
117
+ // Server has settings, use them
118
+ const mergedSettings = { ...defaultSettings, ...serverSettingsData };
119
+ setSettings(mergedSettings);
120
+ setServerSettings(mergedSettings);
121
+ setIsReady(true);
122
+ }
123
+ } catch (e) {
124
+ console.error('Failed to parse server settings:', e);
125
+ setServerSettings(settings);
126
+ setIsReady(true);
127
+ }
128
+ }
129
+ }, [serverSettingsLoaded, serverSettingsResponse, settings, isReady]);
75
130
 
76
131
  // Save settings to localStorage whenever they change
77
132
  useEffect(() => {
@@ -82,6 +137,26 @@ export const UserSettingsProvider: React.FC<React.PropsWithChildren<{}>> = ({ ch
82
137
  }
83
138
  }, [settings]);
84
139
 
140
+ // Save to server when settings differ from server state
141
+ useEffect(() => {
142
+ if (isReady && serverSettings) {
143
+ const serverDiffers = JSON.stringify(serverSettings) !== JSON.stringify(settings);
144
+
145
+ if (serverDiffers) {
146
+ saveToServerMutation.mutate(settings);
147
+ setServerSettings(settings);
148
+ }
149
+ }
150
+ }, [settings, serverSettings, isReady, saveToServerMutation]);
151
+
152
+ // Invalidate all queries when content language changes
153
+ useEffect(() => {
154
+ if (queryClient && settings.contentLanguage !== previousContentLanguage.current) {
155
+ void queryClient.invalidateQueries();
156
+ previousContentLanguage.current = settings.contentLanguage;
157
+ }
158
+ }, [settings.contentLanguage, queryClient]);
159
+
85
160
  // Settings updaters
86
161
  const updateSetting = <K extends keyof UserSettings>(key: K, value: UserSettings[K]) => {
87
162
  setSettings(prev => ({ ...prev, [key]: value }));
@@ -7,6 +7,30 @@ declare module 'virtual:dashboard-extensions' {
7
7
  }
8
8
 
9
9
  declare module 'virtual:vendure-ui-config' {
10
- import { AdminUiConfig } from '@vendure/core';
11
- export const uiConfig: AdminUiConfig;
10
+ import { LanguageCode } from '@vendure/core';
11
+
12
+ // TODO: Find a better way to share types between vite plugin and virtual module declaration
13
+ // Currently we have duplicated type definitions here and in vite-plugin-ui-config.ts
14
+ interface ResolvedApiConfig {
15
+ host: string | 'auto';
16
+ port: number | 'auto';
17
+ adminApiPath: string;
18
+ tokenMethod: 'cookie' | 'bearer';
19
+ authTokenHeaderKey: string;
20
+ channelTokenKey: string;
21
+ }
22
+
23
+ interface ResolvedI18nConfig {
24
+ defaultLanguage: LanguageCode;
25
+ defaultLocale: string | undefined;
26
+ availableLanguages: LanguageCode[];
27
+ availableLocales: string[];
28
+ }
29
+
30
+ interface ResolvedUiConfig {
31
+ api: ResolvedApiConfig;
32
+ i18n: ResolvedI18nConfig;
33
+ }
34
+
35
+ export const uiConfig: ResolvedUiConfig;
12
36
  }
@@ -0,0 +1,2 @@
1
+ /// <reference types="vite/client" />
2
+ /// <reference path="./lib/virtual.d.ts" />
@@ -485,7 +485,7 @@ function guessNodeModulesRoot(vendureConfigPath: string, logger: Logger): string
485
485
  logger.debug(`Found core URL: ${coreUrl}`);
486
486
  const corePath = fileURLToPath(coreUrl);
487
487
  logger.debug(`Found core path: ${corePath}`);
488
- nodeModulesRoot = path.join(path.dirname(corePath), '..', '..');
488
+ nodeModulesRoot = path.join(path.dirname(corePath), '..', '..', '..');
489
489
  } catch (e) {
490
490
  logger.warn(`Failed to resolve @vendure/core: ${e instanceof Error ? e.message : String(e)}`);
491
491
  nodeModulesRoot = path.dirname(vendureConfigPath);
@@ -3,7 +3,6 @@ import {
3
3
  DEFAULT_AUTH_TOKEN_HEADER_KEY,
4
4
  DEFAULT_CHANNEL_TOKEN_KEY,
5
5
  } from '@vendure/common/lib/shared-constants';
6
- import { AdminUiConfig } from '@vendure/common/lib/shared-types';
7
6
  import { VendureConfig } from '@vendure/core';
8
7
 
9
8
  import {
@@ -12,53 +11,42 @@ import {
12
11
  defaultLanguage,
13
12
  defaultLocale,
14
13
  } from '../constants.js';
14
+ import { ResolvedUiConfig, UiConfigPluginOptions } from '../vite-plugin-ui-config.js';
15
15
 
16
- export function getAdminUiConfig(
17
- config: VendureConfig,
18
- adminUiConfig?: Partial<AdminUiConfig>,
19
- ): AdminUiConfig {
16
+ export function getUiConfig(config: VendureConfig, pluginOptions: UiConfigPluginOptions): ResolvedUiConfig {
20
17
  const { authOptions, apiOptions } = config;
21
18
 
22
- const propOrDefault = <Prop extends keyof AdminUiConfig>(
23
- prop: Prop,
24
- defaultVal: AdminUiConfig[Prop],
25
- isArray: boolean = false,
26
- ): AdminUiConfig[Prop] => {
27
- if (isArray) {
28
- const isValidArray = !!adminUiConfig
29
- ? !!((adminUiConfig as AdminUiConfig)[prop] as any[])?.length
30
- : false;
19
+ // Merge API configuration with defaults
20
+ const api = {
21
+ adminApiPath: pluginOptions.api?.adminApiPath ?? apiOptions.adminApiPath ?? ADMIN_API_PATH,
22
+ host: pluginOptions.api?.host ?? 'auto',
23
+ port: pluginOptions.api?.port ?? 'auto',
24
+ tokenMethod:
25
+ pluginOptions.api?.tokenMethod ?? (authOptions.tokenMethod === 'bearer' ? 'bearer' : 'cookie'),
26
+ authTokenHeaderKey:
27
+ pluginOptions.api?.authTokenHeaderKey ??
28
+ authOptions.authTokenHeaderKey ??
29
+ DEFAULT_AUTH_TOKEN_HEADER_KEY,
30
+ channelTokenKey:
31
+ pluginOptions.api?.channelTokenKey ?? apiOptions.channelTokenKey ?? DEFAULT_CHANNEL_TOKEN_KEY,
32
+ };
31
33
 
32
- return !!adminUiConfig && isValidArray ? (adminUiConfig as AdminUiConfig)[prop] : defaultVal;
33
- } else {
34
- return adminUiConfig ? (adminUiConfig as AdminUiConfig)[prop] || defaultVal : defaultVal;
35
- }
34
+ // Merge i18n configuration with defaults
35
+ const i18n = {
36
+ defaultLanguage: pluginOptions.i18n?.defaultLanguage ?? defaultLanguage,
37
+ defaultLocale: pluginOptions.i18n?.defaultLocale ?? defaultLocale,
38
+ availableLanguages:
39
+ pluginOptions.i18n?.availableLanguages && pluginOptions.i18n.availableLanguages.length > 0
40
+ ? pluginOptions.i18n.availableLanguages
41
+ : defaultAvailableLanguages,
42
+ availableLocales:
43
+ pluginOptions.i18n?.availableLocales && pluginOptions.i18n.availableLocales.length > 0
44
+ ? pluginOptions.i18n.availableLocales
45
+ : defaultAvailableLocales,
36
46
  };
37
47
 
38
48
  return {
39
- adminApiPath: propOrDefault('adminApiPath', apiOptions.adminApiPath || ADMIN_API_PATH),
40
- apiHost: propOrDefault('apiHost', 'auto'),
41
- apiPort: propOrDefault('apiPort', 'auto'),
42
- tokenMethod: propOrDefault('tokenMethod', authOptions.tokenMethod === 'bearer' ? 'bearer' : 'cookie'),
43
- authTokenHeaderKey: propOrDefault(
44
- 'authTokenHeaderKey',
45
- authOptions.authTokenHeaderKey || DEFAULT_AUTH_TOKEN_HEADER_KEY,
46
- ),
47
- channelTokenKey: propOrDefault(
48
- 'channelTokenKey',
49
- apiOptions.channelTokenKey || DEFAULT_CHANNEL_TOKEN_KEY,
50
- ),
51
- defaultLanguage: propOrDefault('defaultLanguage', defaultLanguage),
52
- defaultLocale: propOrDefault('defaultLocale', defaultLocale),
53
- availableLanguages: propOrDefault('availableLanguages', defaultAvailableLanguages, true),
54
- availableLocales: propOrDefault('availableLocales', defaultAvailableLocales, true),
55
- brand: adminUiConfig?.brand,
56
- hideVendureBranding: propOrDefault(
57
- 'hideVendureBranding',
58
- adminUiConfig?.hideVendureBranding || false,
59
- ),
60
- hideVersion: propOrDefault('hideVersion', adminUiConfig?.hideVersion || false),
61
- loginImageUrl: adminUiConfig?.loginImageUrl,
62
- cancellationReasons: propOrDefault('cancellationReasons', undefined),
49
+ api,
50
+ i18n,
63
51
  };
64
52
  }
@@ -1,27 +1,137 @@
1
- import { AdminUiConfig, VendureConfig } from '@vendure/core';
2
- import path from 'path';
1
+ import { LanguageCode, VendureConfig } from '@vendure/core';
3
2
  import { Plugin } from 'vite';
4
3
 
5
- import { getAdminUiConfig } from './utils/ui-config.js';
4
+ import { getUiConfig } from './utils/ui-config.js';
6
5
  import { ConfigLoaderApi, getConfigLoaderApi } from './vite-plugin-config-loader.js';
7
6
 
8
7
  const virtualModuleId = 'virtual:vendure-ui-config';
9
8
  const resolvedVirtualModuleId = `\0${virtualModuleId}`;
10
9
 
11
- export type UiConfigPluginOptions = {
10
+ export interface ApiConfig {
12
11
  /**
13
12
  * @description
14
- * The admin UI config to be passed to the Vendure Dashboard.
13
+ * The hostname of the Vendure server which the admin UI will be making API calls
14
+ * to. If set to "auto", the Admin UI app will determine the hostname from the
15
+ * current location (i.e. `window.location.hostname`).
16
+ *
17
+ * @default 'auto'
15
18
  */
16
- adminUiConfig?: Partial<AdminUiConfig>;
17
- };
19
+ host?: string | 'auto';
20
+ /**
21
+ * @description
22
+ * The port of the Vendure server which the admin UI will be making API calls
23
+ * to. If set to "auto", the Admin UI app will determine the port from the
24
+ * current location (i.e. `window.location.port`).
25
+ *
26
+ * @default 'auto'
27
+ */
28
+ port?: number | 'auto';
29
+ /**
30
+ * @description
31
+ * The path to the GraphQL Admin API.
32
+ *
33
+ * @default 'admin-api'
34
+ */
35
+ adminApiPath?: string;
36
+ /**
37
+ * @description
38
+ * Whether to use cookies or bearer tokens to track sessions.
39
+ * Should match the setting of in the server's `tokenMethod` config
40
+ * option.
41
+ *
42
+ * @default 'cookie'
43
+ */
44
+ tokenMethod?: 'cookie' | 'bearer';
45
+ /**
46
+ * @description
47
+ * The header used when using the 'bearer' auth method. Should match the
48
+ * setting of the server's `authOptions.authTokenHeaderKey` config option.
49
+ *
50
+ * @default 'vendure-auth-token'
51
+ */
52
+ authTokenHeaderKey?: string;
53
+ /**
54
+ * @description
55
+ * The name of the header which contains the channel token. Should match the
56
+ * setting of the server's `apiOptions.channelTokenKey` config option.
57
+ *
58
+ * @default 'vendure-token'
59
+ */
60
+ channelTokenKey?: string;
61
+ }
62
+
63
+ export interface I18nConfig {
64
+ /**
65
+ * @description
66
+ * The default language for the Admin UI. Must be one of the
67
+ * items specified in the `availableLanguages` property.
68
+ *
69
+ * @default LanguageCode.en
70
+ */
71
+ defaultLanguage?: LanguageCode;
72
+ /**
73
+ * @description
74
+ * The default locale for the Admin UI. The locale affects the formatting of
75
+ * currencies & dates. Must be one of the items specified
76
+ * in the `availableLocales` property.
77
+ *
78
+ * If not set, the browser default locale will be used.
79
+ *
80
+ * @since 2.2.0
81
+ */
82
+ defaultLocale?: string;
83
+ /**
84
+ * @description
85
+ * An array of languages for which translations exist for the Admin UI.
86
+ */
87
+ availableLanguages?: LanguageCode[];
88
+ /**
89
+ * @description
90
+ * An array of locales to be used on Admin UI.
91
+ *
92
+ * @since 2.2.0
93
+ */
94
+ availableLocales?: string[];
95
+ }
96
+
97
+ export interface UiConfigPluginOptions {
98
+ /**
99
+ * @description
100
+ * Configuration for API connection settings
101
+ */
102
+ api?: ApiConfig;
103
+ /**
104
+ * @description
105
+ * Configuration for internationalization settings
106
+ */
107
+ i18n?: I18nConfig;
108
+ }
109
+
110
+ /**
111
+ * @description
112
+ * The resolved UI configuration with all defaults applied.
113
+ * This is the type of the configuration object available at runtime.
114
+ */
115
+ export interface ResolvedUiConfig {
116
+ /**
117
+ * @description
118
+ * API connection settings with all defaults applied
119
+ */
120
+ api: Required<ApiConfig>;
121
+ /**
122
+ * @description
123
+ * Internationalization settings with all defaults applied.
124
+ * Note: defaultLocale remains optional as it can be undefined.
125
+ */
126
+ i18n: Required<Omit<I18nConfig, 'defaultLocale'>> & Pick<I18nConfig, 'defaultLocale'>;
127
+ }
18
128
 
19
129
  /**
20
130
  * This Vite plugin scans the configured plugins for any dashboard extensions and dynamically
21
131
  * generates an import statement for each one, wrapped up in a `runDashboardExtensions()`
22
132
  * function which can then be imported and executed in the Dashboard app.
23
133
  */
24
- export function uiConfigPlugin({ adminUiConfig }: UiConfigPluginOptions): Plugin {
134
+ export function uiConfigPlugin(options: UiConfigPluginOptions = {}): Plugin {
25
135
  let configLoaderApi: ConfigLoaderApi;
26
136
  let vendureConfig: VendureConfig;
27
137
 
@@ -42,7 +152,7 @@ export function uiConfigPlugin({ adminUiConfig }: UiConfigPluginOptions): Plugin
42
152
  vendureConfig = result.vendureConfig;
43
153
  }
44
154
 
45
- const config = getAdminUiConfig(vendureConfig, adminUiConfig);
155
+ const config = getUiConfig(vendureConfig, options);
46
156
 
47
157
  return `
48
158
  export const uiConfig = ${JSON.stringify(config)}
@@ -51,11 +161,3 @@ export function uiConfigPlugin({ adminUiConfig }: UiConfigPluginOptions): Plugin
51
161
  },
52
162
  };
53
163
  }
54
-
55
- /**
56
- * Converts an import path to a normalized path relative to the rootDir.
57
- */
58
- function normalizeImportPath(rootDir: string, importPath: string): string {
59
- const relativePath = path.relative(rootDir, importPath).replace(/\\/g, '/');
60
- return relativePath.replace(/\.tsx?$/, '.js');
61
- }
@@ -133,7 +133,7 @@ export function vendureDashboardPlugin(options: VitePluginVendureDashboardOption
133
133
  viteConfigPlugin({ packageRoot }),
134
134
  adminApiSchemaPlugin(),
135
135
  dashboardMetadataPlugin(),
136
- uiConfigPlugin({ adminUiConfig: options.adminUiConfig }),
136
+ uiConfigPlugin(options),
137
137
  ...(options.gqlOutputPath
138
138
  ? [gqlTadaPlugin({ gqlTadaOutputPath: options.gqlOutputPath, tempDir, packageRoot })]
139
139
  : []),
@@ -1,3 +0,0 @@
1
- import { AdminUiConfig } from '@vendure/common/lib/shared-types';
2
- import { VendureConfig } from '@vendure/core';
3
- export declare function getAdminUiConfig(config: VendureConfig, adminUiConfig?: Partial<AdminUiConfig>): AdminUiConfig;
@@ -1,34 +0,0 @@
1
- import { ADMIN_API_PATH, DEFAULT_AUTH_TOKEN_HEADER_KEY, DEFAULT_CHANNEL_TOKEN_KEY, } from '@vendure/common/lib/shared-constants';
2
- import { defaultAvailableLanguages, defaultAvailableLocales, defaultLanguage, defaultLocale, } from '../constants.js';
3
- export function getAdminUiConfig(config, adminUiConfig) {
4
- const { authOptions, apiOptions } = config;
5
- const propOrDefault = (prop, defaultVal, isArray = false) => {
6
- var _a;
7
- if (isArray) {
8
- const isValidArray = !!adminUiConfig
9
- ? !!((_a = adminUiConfig[prop]) === null || _a === void 0 ? void 0 : _a.length)
10
- : false;
11
- return !!adminUiConfig && isValidArray ? adminUiConfig[prop] : defaultVal;
12
- }
13
- else {
14
- return adminUiConfig ? adminUiConfig[prop] || defaultVal : defaultVal;
15
- }
16
- };
17
- return {
18
- adminApiPath: propOrDefault('adminApiPath', apiOptions.adminApiPath || ADMIN_API_PATH),
19
- apiHost: propOrDefault('apiHost', 'auto'),
20
- apiPort: propOrDefault('apiPort', 'auto'),
21
- tokenMethod: propOrDefault('tokenMethod', authOptions.tokenMethod === 'bearer' ? 'bearer' : 'cookie'),
22
- authTokenHeaderKey: propOrDefault('authTokenHeaderKey', authOptions.authTokenHeaderKey || DEFAULT_AUTH_TOKEN_HEADER_KEY),
23
- channelTokenKey: propOrDefault('channelTokenKey', apiOptions.channelTokenKey || DEFAULT_CHANNEL_TOKEN_KEY),
24
- defaultLanguage: propOrDefault('defaultLanguage', defaultLanguage),
25
- defaultLocale: propOrDefault('defaultLocale', defaultLocale),
26
- availableLanguages: propOrDefault('availableLanguages', defaultAvailableLanguages, true),
27
- availableLocales: propOrDefault('availableLocales', defaultAvailableLocales, true),
28
- brand: adminUiConfig === null || adminUiConfig === void 0 ? void 0 : adminUiConfig.brand,
29
- hideVendureBranding: propOrDefault('hideVendureBranding', (adminUiConfig === null || adminUiConfig === void 0 ? void 0 : adminUiConfig.hideVendureBranding) || false),
30
- hideVersion: propOrDefault('hideVersion', (adminUiConfig === null || adminUiConfig === void 0 ? void 0 : adminUiConfig.hideVersion) || false),
31
- loginImageUrl: adminUiConfig === null || adminUiConfig === void 0 ? void 0 : adminUiConfig.loginImageUrl,
32
- cancellationReasons: propOrDefault('cancellationReasons', undefined),
33
- };
34
- }