@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.
- package/dist/plugin/utils/config-loader.d.ts +12 -1
- package/dist/plugin/utils/config-loader.js +25 -7
- package/dist/plugin/vite-plugin-vendure-dashboard.d.ts +8 -0
- package/dist/plugin/vite-plugin-vendure-dashboard.js +5 -1
- package/package.json +4 -4
- package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +1 -4
- package/src/app/routes/_authenticated/_channels/channels.tsx +18 -0
- package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +1 -5
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +1 -4
- package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +1 -4
- package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +1 -4
- package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +1 -5
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +56 -74
- package/src/app/routes/_authenticated/_products/components/add-product-variant-dialog.tsx +369 -0
- package/src/app/routes/_authenticated/_products/components/create-product-options-dialog.tsx +435 -0
- package/src/app/routes/_authenticated/_products/components/product-option-select.tsx +117 -0
- package/src/app/routes/_authenticated/_products/components/product-variants-table.tsx +4 -2
- package/src/app/routes/_authenticated/_products/products_.$id.tsx +17 -3
- package/src/app/routes/_authenticated/_profile/profile.tsx +1 -4
- package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +1 -4
- package/src/lib/components/data-table/data-table-view-options.tsx +12 -2
- package/src/lib/components/data-table/data-table.tsx +9 -0
- package/src/lib/components/layout/channel-switcher.tsx +1 -2
- package/src/lib/components/shared/assigned-facet-values.tsx +13 -14
- package/src/lib/components/shared/entity-assets.tsx +140 -70
- package/src/lib/components/shared/paginated-list-data-table.tsx +10 -0
- package/src/lib/components/ui/button.tsx +1 -1
- package/src/lib/framework/form-engine/use-generated-form.tsx +1 -0
- package/src/lib/framework/page/list-page.tsx +2 -2
- package/src/lib/framework/page/use-detail-page.ts +7 -0
- package/src/lib/graphql/api.ts +10 -1
- package/src/lib/hooks/use-permissions.ts +4 -4
- package/src/lib/providers/auth.tsx +9 -3
- package/src/lib/providers/channel-provider.tsx +64 -24
- package/src/lib/providers/server-config.tsx +2 -2
- package/vite/utils/config-loader.ts +48 -13
- package/vite/vite-plugin-vendure-dashboard.ts +14 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { api } from '@/graphql/api.js';
|
|
2
2
|
import { ResultOf, graphql } from '@/graphql/graphql.js';
|
|
3
|
-
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
4
3
|
import { useAuth } from '@/hooks/use-auth.js';
|
|
4
|
+
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
5
5
|
import * as React from 'react';
|
|
6
6
|
|
|
7
7
|
// Define the channel fragment for reuse
|
|
@@ -63,10 +63,11 @@ export const ChannelContext = React.createContext<ChannelContext | undefined>(un
|
|
|
63
63
|
|
|
64
64
|
// Local storage key for the selected channel
|
|
65
65
|
const SELECTED_CHANNEL_KEY = 'vendure-selected-channel';
|
|
66
|
+
const SELECTED_CHANNEL_TOKEN_KEY = 'vendure-selected-channel-token';
|
|
66
67
|
|
|
67
68
|
export function ChannelProvider({ children }: { children: React.ReactNode }) {
|
|
68
|
-
const { isAuthenticated } = useAuth();
|
|
69
69
|
const queryClient = useQueryClient();
|
|
70
|
+
const { channels: userChannels, isAuthenticated } = useAuth();
|
|
70
71
|
const [selectedChannelId, setSelectedChannelId] = React.useState<string | undefined>(() => {
|
|
71
72
|
// Initialize from localStorage if available
|
|
72
73
|
try {
|
|
@@ -79,44 +80,83 @@ export function ChannelProvider({ children }: { children: React.ReactNode }) {
|
|
|
79
80
|
});
|
|
80
81
|
|
|
81
82
|
// Fetch all available channels
|
|
82
|
-
const { data: channelsData, isLoading: isChannelsLoading
|
|
83
|
-
queryKey: ['channels'],
|
|
83
|
+
const { data: channelsData, isLoading: isChannelsLoading } = useQuery({
|
|
84
|
+
queryKey: ['channels', isAuthenticated],
|
|
84
85
|
queryFn: () => api.query(ChannelsQuery),
|
|
85
86
|
retry: false,
|
|
87
|
+
enabled: isAuthenticated,
|
|
86
88
|
});
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
// Filter channels based on user permissions
|
|
91
|
+
const channels = React.useMemo(() => {
|
|
92
|
+
// If user has specific channels assigned (non-superadmin), use those
|
|
93
|
+
if (userChannels && userChannels.length > 0) {
|
|
94
|
+
// 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
|
+
}));
|
|
92
106
|
}
|
|
93
|
-
|
|
107
|
+
// Otherwise use all channels (superadmin)
|
|
108
|
+
return channelsData?.channels.items || [];
|
|
109
|
+
}, [userChannels, channelsData?.channels.items]);
|
|
94
110
|
|
|
95
111
|
// Set the selected channel and update localStorage
|
|
96
|
-
const setSelectedChannel = React.useCallback(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
const setSelectedChannel = React.useCallback(
|
|
113
|
+
(channelId: string) => {
|
|
114
|
+
try {
|
|
115
|
+
// Find the channel to get its token
|
|
116
|
+
const channel = channels.find(c => c.id === channelId);
|
|
117
|
+
if (channel) {
|
|
118
|
+
// Store channel ID and token in localStorage
|
|
119
|
+
localStorage.setItem(SELECTED_CHANNEL_KEY, channelId);
|
|
120
|
+
localStorage.setItem(SELECTED_CHANNEL_TOKEN_KEY, channel.token);
|
|
121
|
+
setSelectedChannelId(channelId);
|
|
122
|
+
queryClient.invalidateQueries();
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.error('Failed to set selected channel', e);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
[queryClient, channels],
|
|
129
|
+
);
|
|
106
130
|
|
|
107
131
|
// If no selected channel is set but we have an active channel, use that
|
|
132
|
+
// Also validate that the selected channel is accessible to the user
|
|
108
133
|
React.useEffect(() => {
|
|
109
|
-
|
|
110
|
-
|
|
134
|
+
const validChannelIds = channels.map(c => c.id);
|
|
135
|
+
|
|
136
|
+
// If selected channel is not valid for this user, reset it
|
|
137
|
+
if (selectedChannelId && !validChannelIds.includes(selectedChannelId)) {
|
|
138
|
+
setSelectedChannelId(undefined);
|
|
139
|
+
try {
|
|
140
|
+
localStorage.removeItem(SELECTED_CHANNEL_KEY);
|
|
141
|
+
localStorage.removeItem(SELECTED_CHANNEL_TOKEN_KEY);
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.error('Failed to remove selected channel from localStorage', e);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// If no selected channel is set, use the first available channel
|
|
148
|
+
if (!selectedChannelId && channels.length > 0) {
|
|
149
|
+
const defaultChannel = channels[0];
|
|
150
|
+
setSelectedChannelId(defaultChannel.id);
|
|
111
151
|
try {
|
|
112
|
-
localStorage.setItem(SELECTED_CHANNEL_KEY,
|
|
152
|
+
localStorage.setItem(SELECTED_CHANNEL_KEY, defaultChannel.id);
|
|
153
|
+
localStorage.setItem(SELECTED_CHANNEL_TOKEN_KEY, defaultChannel.token);
|
|
113
154
|
} catch (e) {
|
|
114
155
|
console.error('Failed to store selected channel in localStorage', e);
|
|
115
156
|
}
|
|
116
157
|
}
|
|
117
|
-
}, [selectedChannelId,
|
|
158
|
+
}, [selectedChannelId, channels]);
|
|
118
159
|
|
|
119
|
-
const channels = channelsData?.channels.items || [];
|
|
120
160
|
const activeChannel = channelsData?.activeChannel;
|
|
121
161
|
const isLoading = isChannelsLoading;
|
|
122
162
|
|
|
@@ -270,14 +270,14 @@ export const ServerConfigProvider = ({ children }: { children: React.ReactNode }
|
|
|
270
270
|
enabled: !!user?.id,
|
|
271
271
|
staleTime: 1000,
|
|
272
272
|
});
|
|
273
|
-
const value: ServerConfig = {
|
|
273
|
+
const value: ServerConfig | null = data?.globalSettings ? {
|
|
274
274
|
availableLanguages: data?.globalSettings.availableLanguages ?? [],
|
|
275
275
|
moneyStrategyPrecision: data?.globalSettings.serverConfig.moneyStrategyPrecision ?? 2,
|
|
276
276
|
orderProcess: data?.globalSettings.serverConfig.orderProcess ?? [],
|
|
277
277
|
permittedAssetTypes: data?.globalSettings.serverConfig.permittedAssetTypes ?? [],
|
|
278
278
|
permissions: data?.globalSettings.serverConfig.permissions ?? [],
|
|
279
279
|
entityCustomFields: data?.globalSettings.serverConfig.entityCustomFields ?? [],
|
|
280
|
-
};
|
|
280
|
+
} : null;
|
|
281
281
|
|
|
282
282
|
return <ServerConfigContext.Provider value={value}>{children}</ServerConfigContext.Provider>;
|
|
283
283
|
};
|
|
@@ -36,6 +36,7 @@ export interface ConfigLoaderOptions {
|
|
|
36
36
|
tempDir: string;
|
|
37
37
|
vendureConfigExport?: string;
|
|
38
38
|
logger?: Logger;
|
|
39
|
+
reportCompilationErrors?: boolean;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
export interface LoadVendureConfigResult {
|
|
@@ -67,7 +68,12 @@ export async function loadVendureConfig(options: ConfigLoaderOptions): Promise<L
|
|
|
67
68
|
const configFileName = path.basename(vendureConfigPath);
|
|
68
69
|
const inputRootDir = path.dirname(vendureConfigPath);
|
|
69
70
|
await fs.remove(outputPath);
|
|
70
|
-
const pluginInfo = await compileFile(
|
|
71
|
+
const pluginInfo = await compileFile({
|
|
72
|
+
inputRootDir,
|
|
73
|
+
inputPath: vendureConfigPath,
|
|
74
|
+
outputDir: outputPath,
|
|
75
|
+
logger,
|
|
76
|
+
});
|
|
71
77
|
const compiledConfigFilePath = pathToFileURL(path.join(outputPath, configFileName)).href.replace(
|
|
72
78
|
/.ts$/,
|
|
73
79
|
'.js',
|
|
@@ -147,7 +153,14 @@ async function findTsConfigPaths(
|
|
|
147
153
|
);
|
|
148
154
|
}
|
|
149
155
|
logger.debug(
|
|
150
|
-
`Found tsconfig paths in ${tsConfigPath}: ${JSON.stringify(
|
|
156
|
+
`Found tsconfig paths in ${tsConfigPath}: ${JSON.stringify(
|
|
157
|
+
{
|
|
158
|
+
baseUrl: tsConfigBaseUrl,
|
|
159
|
+
paths,
|
|
160
|
+
},
|
|
161
|
+
null,
|
|
162
|
+
2,
|
|
163
|
+
)}`,
|
|
151
164
|
);
|
|
152
165
|
return { baseUrl: tsConfigBaseUrl, paths };
|
|
153
166
|
}
|
|
@@ -167,15 +180,27 @@ async function findTsConfigPaths(
|
|
|
167
180
|
return undefined;
|
|
168
181
|
}
|
|
169
182
|
|
|
170
|
-
|
|
171
|
-
inputRootDir: string
|
|
172
|
-
inputPath: string
|
|
173
|
-
outputDir: string
|
|
174
|
-
logger
|
|
183
|
+
type CompileFileOptions = {
|
|
184
|
+
inputRootDir: string;
|
|
185
|
+
inputPath: string;
|
|
186
|
+
outputDir: string;
|
|
187
|
+
logger?: Logger;
|
|
188
|
+
compiledFiles?: Set<string>;
|
|
189
|
+
isRoot?: boolean;
|
|
190
|
+
pluginInfo?: PluginInfo[];
|
|
191
|
+
reportCompilationErrors?: boolean;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export async function compileFile({
|
|
195
|
+
inputRootDir,
|
|
196
|
+
inputPath,
|
|
197
|
+
outputDir,
|
|
198
|
+
logger = defaultLogger,
|
|
175
199
|
compiledFiles = new Set<string>(),
|
|
176
200
|
isRoot = true,
|
|
177
|
-
pluginInfo
|
|
178
|
-
|
|
201
|
+
pluginInfo = [],
|
|
202
|
+
reportCompilationErrors = false,
|
|
203
|
+
}: CompileFileOptions): Promise<PluginInfo[]> {
|
|
179
204
|
const absoluteInputPath = path.resolve(inputPath);
|
|
180
205
|
if (compiledFiles.has(absoluteInputPath)) {
|
|
181
206
|
return pluginInfo;
|
|
@@ -325,7 +350,15 @@ export async function compileFile(
|
|
|
325
350
|
// Recursively collect all files that need to be compiled
|
|
326
351
|
for (const importPath of importPaths) {
|
|
327
352
|
// Pass rootTsConfigInfo down, but set isRoot to false
|
|
328
|
-
await compileFile(
|
|
353
|
+
await compileFile({
|
|
354
|
+
inputRootDir,
|
|
355
|
+
inputPath: importPath,
|
|
356
|
+
outputDir,
|
|
357
|
+
logger,
|
|
358
|
+
compiledFiles,
|
|
359
|
+
isRoot: false,
|
|
360
|
+
pluginInfo,
|
|
361
|
+
});
|
|
329
362
|
}
|
|
330
363
|
|
|
331
364
|
// If this is the root file (the one that started the compilation),
|
|
@@ -370,10 +403,12 @@ export async function compileFile(
|
|
|
370
403
|
logger.info(`Emitting compiled files to ${outputDir}`);
|
|
371
404
|
const emitResult = program.emit();
|
|
372
405
|
|
|
373
|
-
|
|
406
|
+
if (reportCompilationErrors) {
|
|
407
|
+
const hasEmitErrors = reportDiagnostics(program, emitResult, logger);
|
|
374
408
|
|
|
375
|
-
|
|
376
|
-
|
|
409
|
+
if (hasEmitErrors) {
|
|
410
|
+
throw new Error('TypeScript compilation failed with errors.');
|
|
411
|
+
}
|
|
377
412
|
}
|
|
378
413
|
|
|
379
414
|
logger.info(`Successfully compiled ${allFiles.length} files to ${outputDir}`);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { lingui } from '@lingui/vite-plugin';
|
|
2
1
|
import tailwindcss from '@tailwindcss/vite';
|
|
3
2
|
import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
|
|
4
3
|
import react from '@vitejs/plugin-react';
|
|
@@ -10,8 +9,7 @@ import { configLoaderPlugin } from './vite-plugin-config-loader.js';
|
|
|
10
9
|
import { viteConfigPlugin } from './vite-plugin-config.js';
|
|
11
10
|
import { dashboardMetadataPlugin } from './vite-plugin-dashboard-metadata.js';
|
|
12
11
|
import { gqlTadaPlugin } from './vite-plugin-gql-tada.js';
|
|
13
|
-
import { themeVariablesPlugin } from './vite-plugin-theme.js';
|
|
14
|
-
import { ThemeVariablesPluginOptions } from './vite-plugin-theme.js';
|
|
12
|
+
import { ThemeVariablesPluginOptions, themeVariablesPlugin } from './vite-plugin-theme.js';
|
|
15
13
|
import { UiConfigPluginOptions, uiConfigPlugin } from './vite-plugin-ui-config.js';
|
|
16
14
|
|
|
17
15
|
/**
|
|
@@ -37,6 +35,14 @@ export type VitePluginVendureDashboardOptions = {
|
|
|
37
35
|
gqlTadaOutputPath?: string;
|
|
38
36
|
tempCompilationDir?: string;
|
|
39
37
|
disableTansStackRouterPlugin?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* @description
|
|
40
|
+
* If set to `true`, compilation errors during the build process will be reported and
|
|
41
|
+
* the build will fail.
|
|
42
|
+
*
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
reportCompilationErrors?: boolean;
|
|
40
46
|
} & UiConfigPluginOptions &
|
|
41
47
|
ThemeVariablesPluginOptions;
|
|
42
48
|
|
|
@@ -74,7 +80,11 @@ export function vendureDashboardPlugin(options: VitePluginVendureDashboardOption
|
|
|
74
80
|
}),
|
|
75
81
|
themeVariablesPlugin({ theme: options.theme }),
|
|
76
82
|
tailwindcss(),
|
|
77
|
-
configLoaderPlugin({
|
|
83
|
+
configLoaderPlugin({
|
|
84
|
+
vendureConfigPath: normalizedVendureConfigPath,
|
|
85
|
+
tempDir,
|
|
86
|
+
reportCompilationErrors: options.reportCompilationErrors,
|
|
87
|
+
}),
|
|
78
88
|
viteConfigPlugin({ packageRoot }),
|
|
79
89
|
adminApiSchemaPlugin(),
|
|
80
90
|
dashboardMetadataPlugin({ rootDir: tempDir }),
|