@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.
- package/README.md +62 -0
- package/dist/plugin/api/api-extensions.d.ts +1 -0
- package/dist/plugin/api/api-extensions.js +38 -0
- package/dist/plugin/api/metrics.resolver.d.ts +8 -0
- package/dist/plugin/api/metrics.resolver.js +40 -0
- package/dist/plugin/config/metrics-strategies.d.ts +39 -0
- package/dist/plugin/config/metrics-strategies.js +74 -0
- package/dist/plugin/constants.d.ts +4 -3
- package/dist/plugin/constants.js +10 -277
- package/dist/plugin/dashboard.plugin.d.ts +95 -0
- package/dist/plugin/dashboard.plugin.js +168 -0
- package/dist/plugin/index.d.ts +2 -1
- package/dist/plugin/index.js +18 -1
- package/dist/plugin/package.json +3 -0
- package/dist/plugin/service/metrics.service.d.ts +15 -0
- package/dist/plugin/service/metrics.service.js +145 -0
- package/dist/plugin/types.d.ts +20 -37
- package/dist/plugin/types.js +13 -1
- package/dist/vite/constants.d.ts +5 -0
- package/dist/vite/constants.js +277 -0
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.js +1 -0
- package/dist/vite/types.d.ts +40 -0
- package/dist/vite/utils/config-loader.js +1 -0
- package/dist/{plugin → vite}/utils/plugin-discovery.js +1 -1
- package/dist/vite/utils/ui-config.d.ts +3 -0
- package/dist/vite/utils/ui-config.js +30 -0
- package/dist/vite/vite-plugin-ui-config.d.ts +123 -0
- package/dist/{plugin → vite}/vite-plugin-ui-config.js +3 -11
- package/dist/{plugin → vite}/vite-plugin-vendure-dashboard.js +1 -1
- package/index.html +1 -1
- package/package.json +16 -7
- package/src/app/app-providers.tsx +1 -1
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +20 -35
- package/src/app/routes/_authenticated/_facets/facets.graphql.ts +40 -0
- package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +147 -0
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history.tsx +380 -33
- package/src/app/routes/_authenticated/_products/components/option-value-input.tsx +1 -1
- package/src/app/routes/_authenticated/_system/healthchecks.tsx +1 -1
- package/src/app/routes/_authenticated/_system/job-queue.tsx +1 -0
- package/src/app/routes/_authenticated/index.tsx +2 -2
- package/src/app/routes/_authenticated.tsx +1 -1
- package/src/lib/components/data-input/rich-text-input.tsx +14 -8
- package/src/lib/components/data-table/data-table-bulk-actions.tsx +17 -4
- package/src/lib/components/layout/app-layout.tsx +2 -7
- package/src/lib/components/layout/channel-switcher.tsx +166 -57
- package/src/lib/components/layout/dev-mode-indicator.tsx +18 -0
- package/src/lib/components/layout/language-dialog.tsx +2 -1
- package/src/lib/components/layout/manage-languages-dialog.tsx +77 -40
- package/src/lib/components/layout/nav-item-wrapper.tsx +107 -0
- package/src/lib/components/layout/nav-main.tsx +196 -107
- package/src/lib/components/login/login-form.tsx +80 -45
- package/src/lib/components/shared/asset/asset-bulk-actions.tsx +19 -4
- package/src/lib/components/shared/asset/asset-gallery.tsx +2 -2
- package/src/lib/components/shared/detail-page-button.tsx +42 -0
- package/src/lib/components/shared/history-timeline/history-entry-date.tsx +37 -0
- package/src/lib/components/shared/history-timeline/history-entry.tsx +135 -65
- package/src/lib/components/shared/history-timeline/history-note-input.tsx +4 -4
- package/src/lib/components/shared/history-timeline/history-timeline.tsx +7 -54
- package/src/lib/components/shared/translatable-form-field.tsx +16 -2
- package/src/lib/framework/defaults.ts +4 -10
- package/src/lib/framework/extension-api/define-dashboard-extension.ts +4 -0
- package/src/lib/framework/extension-api/extension-api-types.ts +11 -2
- package/src/lib/framework/extension-api/logic/index.ts +1 -0
- package/src/lib/framework/extension-api/logic/login.ts +17 -0
- package/src/lib/framework/extension-api/logic/navigation.ts +1 -0
- package/src/lib/framework/extension-api/types/data-table.ts +12 -3
- package/src/lib/framework/extension-api/types/detail-forms.ts +13 -0
- package/src/lib/framework/extension-api/types/form-components.ts +11 -0
- package/src/lib/framework/extension-api/types/index.ts +1 -0
- package/src/lib/framework/extension-api/types/layout.ts +3 -6
- package/src/lib/framework/extension-api/types/login.ts +96 -0
- package/src/lib/framework/extension-api/types/navigation.ts +57 -0
- package/src/lib/framework/extension-api/types/widgets.ts +0 -4
- package/src/lib/framework/extension-api/use-login-extensions.ts +26 -0
- package/src/lib/framework/layout-engine/dev-mode-button.tsx +24 -0
- package/src/lib/framework/layout-engine/location-wrapper.tsx +5 -12
- package/src/lib/framework/registry/global-registry.ts +4 -0
- package/src/lib/framework/registry/registry-types.ts +2 -0
- package/src/lib/graphql/api.ts +25 -3
- package/src/lib/graphql/graphql-env.d.ts +28 -28
- package/src/lib/graphql/settings-store-operations.ts +17 -0
- package/src/lib/hooks/use-floating-bulk-actions.ts +82 -0
- package/src/lib/hooks/use-local-format.ts +20 -5
- package/src/lib/index.ts +2 -1
- package/src/lib/providers/channel-provider.tsx +13 -11
- package/src/lib/providers/user-settings.tsx +78 -3
- package/src/lib/virtual.d.ts +26 -2
- package/src/vite-env.d.ts +2 -0
- package/vite/utils/plugin-discovery.ts +1 -1
- package/vite/utils/ui-config.ts +30 -42
- package/vite/vite-plugin-ui-config.ts +119 -17
- package/vite/vite-plugin-vendure-dashboard.ts +1 -1
- package/dist/plugin/utils/ui-config.d.ts +0 -3
- package/dist/plugin/utils/ui-config.js +0 -34
- package/dist/plugin/vite-plugin-ui-config.d.ts +0 -15
- package/src/app/routes/_authenticated/_facets/components/add-facet-value-dialog.tsx +0 -146
- package/src/lib/components/shared/rich-text-editor.tsx +0 -0
- /package/dist/{plugin/utils/ast-utils.spec.d.ts → vite/types.js} +0 -0
- /package/dist/{plugin → vite}/utils/ast-utils.d.ts +0 -0
- /package/dist/{plugin → vite}/utils/ast-utils.js +0 -0
- /package/dist/{plugin/utils/config-loader.d.ts → vite/utils/ast-utils.spec.d.ts} +0 -0
- /package/dist/{plugin → vite}/utils/ast-utils.spec.js +0 -0
- /package/dist/{plugin → vite}/utils/compiler.d.ts +0 -0
- /package/dist/{plugin → vite}/utils/compiler.js +0 -0
- /package/dist/{plugin/utils/config-loader.js → vite/utils/config-loader.d.ts} +0 -0
- /package/dist/{plugin → vite}/utils/logger.d.ts +0 -0
- /package/dist/{plugin → vite}/utils/logger.js +0 -0
- /package/dist/{plugin → vite}/utils/plugin-discovery.d.ts +0 -0
- /package/dist/{plugin → vite}/utils/schema-generator.d.ts +0 -0
- /package/dist/{plugin → vite}/utils/schema-generator.js +0 -0
- /package/dist/{plugin → vite}/utils/tsconfig-utils.d.ts +0 -0
- /package/dist/{plugin → vite}/utils/tsconfig-utils.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-admin-api-schema.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-admin-api-schema.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-config-loader.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-config-loader.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-config.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-config.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-dashboard-metadata.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-dashboard-metadata.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-gql-tada.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-gql-tada.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-tailwind-source.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-tailwind-source.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-theme.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-theme.js +0 -0
- /package/dist/{plugin → vite}/vite-plugin-transform-index.d.ts +0 -0
- /package/dist/{plugin → vite}/vite-plugin-transform-index.js +0 -0
- /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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
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
|
-
|
|
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 }));
|
package/src/lib/virtual.d.ts
CHANGED
|
@@ -7,6 +7,30 @@ declare module 'virtual:dashboard-extensions' {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
declare module 'virtual:vendure-ui-config' {
|
|
10
|
-
import {
|
|
11
|
-
|
|
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
|
}
|
|
@@ -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);
|
package/vite/utils/ui-config.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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 {
|
|
2
|
-
import path from 'path';
|
|
1
|
+
import { LanguageCode, VendureConfig } from '@vendure/core';
|
|
3
2
|
import { Plugin } from 'vite';
|
|
4
3
|
|
|
5
|
-
import {
|
|
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
|
|
10
|
+
export interface ApiConfig {
|
|
12
11
|
/**
|
|
13
12
|
* @description
|
|
14
|
-
* The admin UI
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
136
|
+
uiConfigPlugin(options),
|
|
137
137
|
...(options.gqlOutputPath
|
|
138
138
|
? [gqlTadaPlugin({ gqlTadaOutputPath: options.gqlOutputPath, tempDir, packageRoot })]
|
|
139
139
|
: []),
|
|
@@ -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
|
-
}
|