@vendure/dashboard 3.3.8-master-202507231129 → 3.3.8-master-202507240240
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/plugin-discovery.d.ts +1 -1
- package/dist/plugin/utils/plugin-discovery.js +61 -21
- package/dist/plugin/vite-plugin-tailwind-source.js +13 -1
- package/dist/plugin/vite-plugin-vendure-dashboard.d.ts +1 -1
- package/dist/plugin/vite-plugin-vendure-dashboard.js +2 -2
- package/package.json +4 -4
- package/src/app/routes/_authenticated/_facets/components/add-facet-value-dialog.tsx +146 -0
- package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +84 -62
- package/src/app/routes/_authenticated/_facets/facets.graphql.ts +9 -0
- package/src/lib/components/data-table/use-generated-columns.tsx +20 -5
- package/src/lib/index.ts +0 -1
- package/vite/utils/plugin-discovery.ts +67 -18
- package/vite/vite-plugin-tailwind-source.ts +20 -4
- package/vite/vite-plugin-vendure-dashboard.ts +3 -3
- package/src/lib/components/ui/avatar.tsx +0 -53
|
@@ -12,7 +12,7 @@ export declare function discoverPlugins({ vendureConfigPath, transformTsConfigPa
|
|
|
12
12
|
* 1. Local Vendure plugins
|
|
13
13
|
* 2. All non-local package imports that could contain plugins
|
|
14
14
|
*/
|
|
15
|
-
export declare function analyzeSourceFiles(vendureConfigPath: string, logger: Logger, transformTsConfigPathMappings: TransformTsConfigPathMappingsFn): Promise<{
|
|
15
|
+
export declare function analyzeSourceFiles(vendureConfigPath: string, nodeModulesRoot: string, logger: Logger, transformTsConfigPathMappings: TransformTsConfigPathMappingsFn): Promise<{
|
|
16
16
|
localPluginLocations: Map<string, string>;
|
|
17
17
|
packageImports: string[];
|
|
18
18
|
}>;
|
|
@@ -8,9 +8,10 @@ import * as ts from 'typescript';
|
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { findTsConfigPaths } from './tsconfig-utils.js';
|
|
10
10
|
export async function discoverPlugins({ vendureConfigPath, transformTsConfigPathMappings, logger, outputPath, pluginPackageScanner, }) {
|
|
11
|
+
var _a;
|
|
11
12
|
const plugins = [];
|
|
12
13
|
// Analyze source files to find local plugins and package imports
|
|
13
|
-
const { localPluginLocations, packageImports } = await analyzeSourceFiles(vendureConfigPath, logger, transformTsConfigPathMappings);
|
|
14
|
+
const { localPluginLocations, packageImports } = await analyzeSourceFiles(vendureConfigPath, (_a = pluginPackageScanner === null || pluginPackageScanner === void 0 ? void 0 : pluginPackageScanner.nodeModulesRoot) !== null && _a !== void 0 ? _a : guessNodeModulesRoot(vendureConfigPath, logger), logger, transformTsConfigPathMappings);
|
|
14
15
|
logger.debug(`[discoverPlugins] Found ${localPluginLocations.size} local plugins: ${JSON.stringify([...localPluginLocations.entries()], null, 2)}`);
|
|
15
16
|
logger.debug(`[discoverPlugins] Found ${packageImports.length} package imports: ${JSON.stringify(packageImports, null, 2)}`);
|
|
16
17
|
const filePaths = await findVendurePluginFiles({
|
|
@@ -92,12 +93,32 @@ function getDecoratorObjectProps(decorator) {
|
|
|
92
93
|
}
|
|
93
94
|
return [];
|
|
94
95
|
}
|
|
96
|
+
async function isSymlinkedLocalPackage(packageName, nodeModulesRoot) {
|
|
97
|
+
try {
|
|
98
|
+
const packagePath = path.join(nodeModulesRoot, packageName);
|
|
99
|
+
const stats = await fs.lstat(packagePath);
|
|
100
|
+
if (stats.isSymbolicLink()) {
|
|
101
|
+
// Get the real path that the symlink points to
|
|
102
|
+
const realPath = await fs.realpath(packagePath);
|
|
103
|
+
// If the real path is within the project directory (i.e. not in some other node_modules),
|
|
104
|
+
// then it's a local package
|
|
105
|
+
if (!realPath.includes('node_modules')) {
|
|
106
|
+
return realPath;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
// Package doesn't exist or other error - not a local package
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
95
116
|
/**
|
|
96
117
|
* Analyzes TypeScript source files starting from the config file to discover:
|
|
97
118
|
* 1. Local Vendure plugins
|
|
98
119
|
* 2. All non-local package imports that could contain plugins
|
|
99
120
|
*/
|
|
100
|
-
export async function analyzeSourceFiles(vendureConfigPath, logger, transformTsConfigPathMappings) {
|
|
121
|
+
export async function analyzeSourceFiles(vendureConfigPath, nodeModulesRoot, logger, transformTsConfigPathMappings) {
|
|
101
122
|
const localPluginLocations = new Map();
|
|
102
123
|
const visitedFiles = new Set();
|
|
103
124
|
const packageImportsSet = new Set();
|
|
@@ -123,7 +144,7 @@ export async function analyzeSourceFiles(vendureConfigPath, logger, transformTsC
|
|
|
123
144
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
124
145
|
// Track imports to follow
|
|
125
146
|
const importsToFollow = [];
|
|
126
|
-
function visit(node) {
|
|
147
|
+
async function visit(node) {
|
|
127
148
|
// Look for VendurePlugin decorator
|
|
128
149
|
const vendurePluginClassName = getVendurePluginClassName(node);
|
|
129
150
|
if (vendurePluginClassName) {
|
|
@@ -139,7 +160,16 @@ export async function analyzeSourceFiles(vendureConfigPath, logger, transformTsC
|
|
|
139
160
|
// Track non-local imports (packages)
|
|
140
161
|
const npmPackageName = getNpmPackageNameFromImport(importPath);
|
|
141
162
|
if (npmPackageName) {
|
|
142
|
-
|
|
163
|
+
// Check if this is actually a symlinked local package
|
|
164
|
+
const localPackagePath = await isSymlinkedLocalPackage(npmPackageName, nodeModulesRoot);
|
|
165
|
+
if (localPackagePath) {
|
|
166
|
+
// If it is local, follow it like a local import
|
|
167
|
+
importsToFollow.push(localPackagePath);
|
|
168
|
+
logger.debug(`Found symlinked local package "${npmPackageName}" at ${localPackagePath}`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
packageImportsSet.add(npmPackageName);
|
|
172
|
+
}
|
|
143
173
|
}
|
|
144
174
|
// Handle path aliases and local imports
|
|
145
175
|
const pathAliasImports = getPotentialPathAliasImportPaths(importPath, tsConfigInfo);
|
|
@@ -153,9 +183,14 @@ export async function analyzeSourceFiles(vendureConfigPath, logger, transformTsC
|
|
|
153
183
|
}
|
|
154
184
|
}
|
|
155
185
|
}
|
|
156
|
-
|
|
186
|
+
// Visit children
|
|
187
|
+
const promises = [];
|
|
188
|
+
ts.forEachChild(node, child => {
|
|
189
|
+
promises.push(visit(child));
|
|
190
|
+
});
|
|
191
|
+
await Promise.all(promises);
|
|
157
192
|
}
|
|
158
|
-
visit(sourceFile);
|
|
193
|
+
await visit(sourceFile);
|
|
159
194
|
// Follow imports
|
|
160
195
|
for (const importPath of importsToFollow) {
|
|
161
196
|
// Try all possible file paths
|
|
@@ -265,27 +300,14 @@ export async function findVendurePluginFiles({ outputPath, vendureConfigPath, lo
|
|
|
265
300
|
let nodeModulesRoot = providedNodeModulesRoot;
|
|
266
301
|
const readStart = Date.now();
|
|
267
302
|
if (!nodeModulesRoot) {
|
|
268
|
-
|
|
269
|
-
// specified, we will try to guess it by resolving the
|
|
270
|
-
// `@vendure/core` package.
|
|
271
|
-
try {
|
|
272
|
-
const coreUrl = import.meta.resolve('@vendure/core');
|
|
273
|
-
logger.debug(`Found core URL: ${coreUrl}`);
|
|
274
|
-
const corePath = fileURLToPath(coreUrl);
|
|
275
|
-
logger.debug(`Found core path: ${corePath}`);
|
|
276
|
-
nodeModulesRoot = path.join(path.dirname(corePath), '..', '..');
|
|
277
|
-
}
|
|
278
|
-
catch (e) {
|
|
279
|
-
logger.warn(`Failed to resolve @vendure/core: ${e instanceof Error ? e.message : String(e)}`);
|
|
280
|
-
nodeModulesRoot = path.dirname(vendureConfigPath);
|
|
281
|
-
}
|
|
303
|
+
nodeModulesRoot = guessNodeModulesRoot(vendureConfigPath, logger);
|
|
282
304
|
}
|
|
283
305
|
const patterns = [
|
|
284
306
|
// Local compiled plugins in temp dir
|
|
285
307
|
path.join(outputPath, '**/*.js'),
|
|
286
308
|
// Node modules patterns
|
|
287
309
|
...packageGlobs.map(pattern => path.join(nodeModulesRoot, pattern)),
|
|
288
|
-
];
|
|
310
|
+
].map(p => p.replace(/\\/g, '/'));
|
|
289
311
|
logger.debug(`Finding Vendure plugins using patterns: ${patterns.join('\n')}`);
|
|
290
312
|
const globStart = Date.now();
|
|
291
313
|
const files = await glob(patterns, {
|
|
@@ -345,3 +367,21 @@ export async function findVendurePluginFiles({ outputPath, vendureConfigPath, lo
|
|
|
345
367
|
`(scanned ${files.length} files)`);
|
|
346
368
|
return potentialPluginFiles;
|
|
347
369
|
}
|
|
370
|
+
function guessNodeModulesRoot(vendureConfigPath, logger) {
|
|
371
|
+
let nodeModulesRoot;
|
|
372
|
+
// If the node_modules root path has not been explicitly
|
|
373
|
+
// specified, we will try to guess it by resolving the
|
|
374
|
+
// `@vendure/core` package.
|
|
375
|
+
try {
|
|
376
|
+
const coreUrl = import.meta.resolve('@vendure/core');
|
|
377
|
+
logger.debug(`Found core URL: ${coreUrl}`);
|
|
378
|
+
const corePath = fileURLToPath(coreUrl);
|
|
379
|
+
logger.debug(`Found core path: ${corePath}`);
|
|
380
|
+
nodeModulesRoot = path.join(path.dirname(corePath), '..', '..');
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
383
|
+
logger.warn(`Failed to resolve @vendure/core: ${e instanceof Error ? e.message : String(e)}`);
|
|
384
|
+
nodeModulesRoot = path.dirname(vendureConfigPath);
|
|
385
|
+
}
|
|
386
|
+
return nodeModulesRoot;
|
|
387
|
+
}
|
|
@@ -22,7 +22,19 @@ export function dashboardTailwindSourcePlugin() {
|
|
|
22
22
|
loadVendureConfigResult = await configLoaderApi.getVendureConfig();
|
|
23
23
|
}
|
|
24
24
|
const { pluginInfo } = loadVendureConfigResult;
|
|
25
|
-
const dashboardExtensionDirs = (_a = pluginInfo === null || pluginInfo === void 0 ? void 0 : pluginInfo.
|
|
25
|
+
const dashboardExtensionDirs = (_a = pluginInfo === null || pluginInfo === void 0 ? void 0 : pluginInfo.flatMap(({ dashboardEntryPath, sourcePluginPath, pluginPath }) => {
|
|
26
|
+
if (!dashboardEntryPath) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const sourcePaths = [];
|
|
30
|
+
if (sourcePluginPath) {
|
|
31
|
+
sourcePaths.push(path.join(path.dirname(sourcePluginPath), path.dirname(dashboardEntryPath)));
|
|
32
|
+
}
|
|
33
|
+
if (pluginPath) {
|
|
34
|
+
sourcePaths.push(path.join(path.dirname(pluginPath), path.dirname(dashboardEntryPath)));
|
|
35
|
+
}
|
|
36
|
+
return sourcePaths;
|
|
37
|
+
}).filter(x => x != null)) !== null && _a !== void 0 ? _a : [];
|
|
26
38
|
const sources = dashboardExtensionDirs
|
|
27
39
|
.map(extension => {
|
|
28
40
|
return `@source '${extension}';`;
|
|
@@ -63,7 +63,7 @@ export type VitePluginVendureDashboardOptions = {
|
|
|
63
63
|
* @description
|
|
64
64
|
* The path to the directory where the generated GraphQL Tada files will be output.
|
|
65
65
|
*/
|
|
66
|
-
|
|
66
|
+
gqlOutputPath?: string;
|
|
67
67
|
tempCompilationDir?: string;
|
|
68
68
|
disableTansStackRouterPlugin?: boolean;
|
|
69
69
|
/**
|
|
@@ -55,8 +55,8 @@ export function vendureDashboardPlugin(options) {
|
|
|
55
55
|
adminApiSchemaPlugin(),
|
|
56
56
|
dashboardMetadataPlugin(),
|
|
57
57
|
uiConfigPlugin({ adminUiConfig: options.adminUiConfig }),
|
|
58
|
-
...(options.
|
|
59
|
-
? [gqlTadaPlugin({ gqlTadaOutputPath: options.
|
|
58
|
+
...(options.gqlOutputPath
|
|
59
|
+
? [gqlTadaPlugin({ gqlTadaOutputPath: options.gqlOutputPath, tempDir, packageRoot })]
|
|
60
60
|
: []),
|
|
61
61
|
transformIndexHtmlPlugin(),
|
|
62
62
|
];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.3.8-master-
|
|
4
|
+
"version": "3.3.8-master-202507240240",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -95,8 +95,8 @@
|
|
|
95
95
|
"@types/react-dom": "^19.0.4",
|
|
96
96
|
"@types/react-grid-layout": "^1.3.5",
|
|
97
97
|
"@uidotdev/usehooks": "^2.4.1",
|
|
98
|
-
"@vendure/common": "^3.3.8-master-
|
|
99
|
-
"@vendure/core": "^3.3.8-master-
|
|
98
|
+
"@vendure/common": "^3.3.8-master-202507240240",
|
|
99
|
+
"@vendure/core": "^3.3.8-master-202507240240",
|
|
100
100
|
"@vitejs/plugin-react": "^4.3.4",
|
|
101
101
|
"acorn": "^8.11.3",
|
|
102
102
|
"acorn-walk": "^8.3.2",
|
|
@@ -146,5 +146,5 @@
|
|
|
146
146
|
"lightningcss-linux-arm64-musl": "^1.29.3",
|
|
147
147
|
"lightningcss-linux-x64-musl": "^1.29.1"
|
|
148
148
|
},
|
|
149
|
-
"gitHead": "
|
|
149
|
+
"gitHead": "5a2448f30b88b5c0fd038adc2966a1147c9a0406"
|
|
150
150
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js';
|
|
2
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
3
|
+
import {
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogFooter,
|
|
7
|
+
DialogHeader,
|
|
8
|
+
DialogTitle,
|
|
9
|
+
DialogTrigger,
|
|
10
|
+
} from '@/vdb/components/ui/dialog.js';
|
|
11
|
+
import { Form } from '@/vdb/components/ui/form.js';
|
|
12
|
+
import { Input } from '@/vdb/components/ui/input.js';
|
|
13
|
+
import { api } from '@/vdb/graphql/api.js';
|
|
14
|
+
import { graphql } from '@/vdb/graphql/graphql.js';
|
|
15
|
+
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
16
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
17
|
+
import { useMutation } from '@tanstack/react-query';
|
|
18
|
+
import { Plus } from 'lucide-react';
|
|
19
|
+
import { useCallback, useState } from 'react';
|
|
20
|
+
import { useForm } from 'react-hook-form';
|
|
21
|
+
import { toast } from 'sonner';
|
|
22
|
+
import * as z from 'zod';
|
|
23
|
+
|
|
24
|
+
const createFacetValuesDocument = graphql(`
|
|
25
|
+
mutation CreateFacetValues($input: [CreateFacetValueInput!]!) {
|
|
26
|
+
createFacetValues(input: $input) {
|
|
27
|
+
id
|
|
28
|
+
name
|
|
29
|
+
code
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
`);
|
|
33
|
+
|
|
34
|
+
const formSchema = z.object({
|
|
35
|
+
name: z.string().min(1, 'Name is required'),
|
|
36
|
+
code: z.string().min(1, 'Code is required'),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
type FormValues = z.infer<typeof formSchema>;
|
|
40
|
+
|
|
41
|
+
export function AddFacetValueDialog({
|
|
42
|
+
facetId,
|
|
43
|
+
onSuccess,
|
|
44
|
+
}: Readonly<{
|
|
45
|
+
facetId: string;
|
|
46
|
+
onSuccess?: () => void;
|
|
47
|
+
}>) {
|
|
48
|
+
const [open, setOpen] = useState(false);
|
|
49
|
+
const { i18n } = useLingui();
|
|
50
|
+
|
|
51
|
+
const form = useForm<FormValues>({
|
|
52
|
+
resolver: zodResolver(formSchema),
|
|
53
|
+
defaultValues: {
|
|
54
|
+
name: '',
|
|
55
|
+
code: '',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const createFacetValueMutation = useMutation({
|
|
60
|
+
mutationFn: api.mutate(createFacetValuesDocument),
|
|
61
|
+
onSuccess: () => {
|
|
62
|
+
toast.success(i18n.t('Successfully created facet value'));
|
|
63
|
+
setOpen(false);
|
|
64
|
+
form.reset();
|
|
65
|
+
onSuccess?.();
|
|
66
|
+
},
|
|
67
|
+
onError: error => {
|
|
68
|
+
toast.error(i18n.t('Failed to create facet value'), {
|
|
69
|
+
description: error instanceof Error ? error.message : i18n.t('Unknown error'),
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const onSubmit = useCallback(
|
|
75
|
+
(values: FormValues) => {
|
|
76
|
+
createFacetValueMutation.mutate({
|
|
77
|
+
input: [
|
|
78
|
+
{
|
|
79
|
+
facetId,
|
|
80
|
+
code: values.code,
|
|
81
|
+
translations: [
|
|
82
|
+
{
|
|
83
|
+
languageCode: 'en',
|
|
84
|
+
name: values.name,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
[createFacetValueMutation, facetId],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
96
|
+
<DialogTrigger asChild>
|
|
97
|
+
<Button variant="outline">
|
|
98
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
99
|
+
<Trans>Add facet value</Trans>
|
|
100
|
+
</Button>
|
|
101
|
+
</DialogTrigger>
|
|
102
|
+
<DialogContent>
|
|
103
|
+
<DialogHeader>
|
|
104
|
+
<DialogTitle>
|
|
105
|
+
<Trans>Add facet value</Trans>
|
|
106
|
+
</DialogTitle>
|
|
107
|
+
</DialogHeader>
|
|
108
|
+
<Form {...form}>
|
|
109
|
+
<form
|
|
110
|
+
onSubmit={e => {
|
|
111
|
+
e.stopPropagation();
|
|
112
|
+
form.handleSubmit(onSubmit)(e);
|
|
113
|
+
}}
|
|
114
|
+
className="space-y-4"
|
|
115
|
+
>
|
|
116
|
+
<FormFieldWrapper
|
|
117
|
+
control={form.control}
|
|
118
|
+
name="name"
|
|
119
|
+
label={<Trans>Name</Trans>}
|
|
120
|
+
render={({ field }) => <Input {...field} />}
|
|
121
|
+
/>
|
|
122
|
+
<FormFieldWrapper
|
|
123
|
+
control={form.control}
|
|
124
|
+
name="code"
|
|
125
|
+
label={<Trans>Code</Trans>}
|
|
126
|
+
render={({ field }) => <Input {...field} />}
|
|
127
|
+
/>
|
|
128
|
+
<DialogFooter>
|
|
129
|
+
<Button
|
|
130
|
+
type="submit"
|
|
131
|
+
disabled={
|
|
132
|
+
createFacetValueMutation.isPending ||
|
|
133
|
+
!form.formState.isValid ||
|
|
134
|
+
!form.watch('name').trim() ||
|
|
135
|
+
!form.watch('code').trim()
|
|
136
|
+
}
|
|
137
|
+
>
|
|
138
|
+
<Trans>Create facet value</Trans>
|
|
139
|
+
</Button>
|
|
140
|
+
</DialogFooter>
|
|
141
|
+
</form>
|
|
142
|
+
</Form>
|
|
143
|
+
</DialogContent>
|
|
144
|
+
</Dialog>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -5,8 +5,10 @@ import { addCustomFields } from '@/vdb/framework/document-introspection/add-cust
|
|
|
5
5
|
import { graphql } from '@/vdb/graphql/graphql.js';
|
|
6
6
|
import { Trans } from '@/vdb/lib/trans.js';
|
|
7
7
|
import { ColumnFiltersState, SortingState } from '@tanstack/react-table';
|
|
8
|
-
import { useState } from 'react';
|
|
8
|
+
import { useRef, useState } from 'react';
|
|
9
|
+
import { AddFacetValueDialog } from './add-facet-value-dialog.js';
|
|
9
10
|
import { EditFacetValue } from './edit-facet-value.js';
|
|
11
|
+
import { deleteFacetValuesDocument } from '../facets.graphql.js';
|
|
10
12
|
|
|
11
13
|
export const facetValueListDocument = graphql(`
|
|
12
14
|
query FacetValueList($options: FacetValueListOptions) {
|
|
@@ -26,76 +28,96 @@ export const facetValueListDocument = graphql(`
|
|
|
26
28
|
|
|
27
29
|
export interface FacetValuesTableProps {
|
|
28
30
|
facetId: string;
|
|
31
|
+
registerRefresher?: (refresher: () => void) => void;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
export function FacetValuesTable({ facetId }: Readonly<FacetValuesTableProps>) {
|
|
34
|
+
export function FacetValuesTable({ facetId, registerRefresher }: Readonly<FacetValuesTableProps>) {
|
|
32
35
|
const [sorting, setSorting] = useState<SortingState>([]);
|
|
33
36
|
const [page, setPage] = useState(1);
|
|
34
37
|
const [pageSize, setPageSize] = useState(10);
|
|
35
38
|
const [filters, setFilters] = useState<ColumnFiltersState>([]);
|
|
39
|
+
const refreshRef = useRef<() => void>(() => {});
|
|
36
40
|
|
|
37
41
|
return (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
42
|
+
<>
|
|
43
|
+
<PaginatedListDataTable
|
|
44
|
+
listQuery={addCustomFields(facetValueListDocument)}
|
|
45
|
+
deleteMutation={deleteFacetValuesDocument}
|
|
46
|
+
page={page}
|
|
47
|
+
itemsPerPage={pageSize}
|
|
48
|
+
sorting={sorting}
|
|
49
|
+
columnFilters={filters}
|
|
50
|
+
onPageChange={(_, page, perPage) => {
|
|
51
|
+
setPage(page);
|
|
52
|
+
setPageSize(perPage);
|
|
53
|
+
}}
|
|
54
|
+
onSortChange={(_, sorting) => {
|
|
55
|
+
setSorting(sorting);
|
|
56
|
+
}}
|
|
57
|
+
onFilterChange={(_, filters) => {
|
|
58
|
+
setFilters(filters);
|
|
59
|
+
}}
|
|
60
|
+
registerRefresher={refresher => {
|
|
61
|
+
refreshRef.current = refresher;
|
|
62
|
+
registerRefresher?.(refresher);
|
|
63
|
+
}}
|
|
64
|
+
transformVariables={variables => {
|
|
65
|
+
const filter = variables.options?.filter ?? {};
|
|
66
|
+
return {
|
|
67
|
+
options: {
|
|
68
|
+
filter: {
|
|
69
|
+
...filter,
|
|
70
|
+
facetId: { eq: facetId },
|
|
71
|
+
},
|
|
72
|
+
sort: variables.options?.sort,
|
|
73
|
+
take: pageSize,
|
|
74
|
+
skip: (page - 1) * pageSize,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}}
|
|
78
|
+
onSearchTermChange={searchTerm => {
|
|
79
|
+
return {
|
|
80
|
+
name: {
|
|
81
|
+
contains: searchTerm,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}}
|
|
85
|
+
additionalColumns={{
|
|
86
|
+
actions: {
|
|
87
|
+
header: 'Actions',
|
|
88
|
+
cell: ({ row }) => {
|
|
89
|
+
const [open, setOpen] = useState(false);
|
|
90
|
+
const facetValue = row.original;
|
|
91
|
+
return (
|
|
92
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
93
|
+
<PopoverTrigger asChild>
|
|
94
|
+
<Button type="button" variant="outline" size="sm">
|
|
95
|
+
<Trans>Edit</Trans>
|
|
96
|
+
</Button>
|
|
97
|
+
</PopoverTrigger>
|
|
98
|
+
<PopoverContent className="w-80">
|
|
99
|
+
<EditFacetValue
|
|
100
|
+
facetValueId={facetValue.id}
|
|
101
|
+
onSuccess={() => {
|
|
102
|
+
setOpen(false);
|
|
103
|
+
refreshRef.current?.();
|
|
104
|
+
}}
|
|
105
|
+
/>
|
|
106
|
+
</PopoverContent>
|
|
107
|
+
</Popover>
|
|
108
|
+
);
|
|
61
109
|
},
|
|
62
|
-
sort: variables.options?.sort,
|
|
63
|
-
take: pageSize,
|
|
64
|
-
skip: (page - 1) * pageSize,
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
}}
|
|
68
|
-
onSearchTermChange={searchTerm => {
|
|
69
|
-
return {
|
|
70
|
-
name: {
|
|
71
|
-
contains: searchTerm,
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
}}
|
|
75
|
-
additionalColumns={{
|
|
76
|
-
actions: {
|
|
77
|
-
header: 'Actions',
|
|
78
|
-
cell: ({ row }) => {
|
|
79
|
-
const [open, setOpen] = useState(false);
|
|
80
|
-
const facetValue = row.original;
|
|
81
|
-
return (
|
|
82
|
-
<Popover open={open} onOpenChange={setOpen}>
|
|
83
|
-
<PopoverTrigger asChild>
|
|
84
|
-
<Button type="button" variant="outline" size="sm">
|
|
85
|
-
<Trans>Edit</Trans>
|
|
86
|
-
</Button>
|
|
87
|
-
</PopoverTrigger>
|
|
88
|
-
<PopoverContent className="w-80">
|
|
89
|
-
<EditFacetValue
|
|
90
|
-
facetValueId={facetValue.id}
|
|
91
|
-
onSuccess={() => setOpen(false)}
|
|
92
|
-
/>
|
|
93
|
-
</PopoverContent>
|
|
94
|
-
</Popover>
|
|
95
|
-
);
|
|
96
110
|
},
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
<div className="mt-4">
|
|
114
|
+
<AddFacetValueDialog
|
|
115
|
+
facetId={facetId}
|
|
116
|
+
onSuccess={() => {
|
|
117
|
+
refreshRef.current?.();
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
</>
|
|
100
122
|
);
|
|
101
123
|
}
|
|
@@ -132,3 +132,12 @@ export const deleteFacetsDocument = graphql(`
|
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
`);
|
|
135
|
+
|
|
136
|
+
export const deleteFacetValuesDocument = graphql(`
|
|
137
|
+
mutation DeleteFacetValues($ids: [ID!]!) {
|
|
138
|
+
deleteFacetValues(ids: $ids) {
|
|
139
|
+
result
|
|
140
|
+
message
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DisplayComponent } from '@/vdb/framework/component-registry/dynamic-component.js';
|
|
2
|
-
import { FieldInfo, getTypeFieldInfo } from '@/vdb/framework/document-introspection/get-document-structure.js';
|
|
2
|
+
import { FieldInfo, getTypeFieldInfo, getOperationVariablesFields } from '@/vdb/framework/document-introspection/get-document-structure.js';
|
|
3
3
|
import { api } from '@/vdb/graphql/api.js';
|
|
4
4
|
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
5
5
|
import { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
@@ -250,16 +250,23 @@ function DeleteMutationRowAction({
|
|
|
250
250
|
}>) {
|
|
251
251
|
const { refetchPaginatedList } = usePaginatedList();
|
|
252
252
|
const { i18n } = useLingui();
|
|
253
|
+
|
|
254
|
+
// Inspect the mutation variables to determine if it expects 'id' or 'ids'
|
|
255
|
+
const mutationVariables = getOperationVariablesFields(deleteMutation);
|
|
256
|
+
const hasIdsParameter = mutationVariables.some(field => field.name === 'ids');
|
|
257
|
+
|
|
253
258
|
const { mutate: deleteMutationFn } = useMutation({
|
|
254
259
|
mutationFn: api.mutate(deleteMutation),
|
|
255
|
-
onSuccess: (result: { [key: string]: { result: 'DELETED' | 'NOT_DELETED'; message: string } }) => {
|
|
260
|
+
onSuccess: (result: { [key: string]: { result: 'DELETED' | 'NOT_DELETED'; message: string } | { result: 'DELETED' | 'NOT_DELETED'; message: string }[] }) => {
|
|
256
261
|
const unwrappedResult = Object.values(result)[0];
|
|
257
|
-
|
|
262
|
+
// Handle both single result and array of results
|
|
263
|
+
const resultToCheck = Array.isArray(unwrappedResult) ? unwrappedResult[0] : unwrappedResult;
|
|
264
|
+
if (resultToCheck.result === 'DELETED') {
|
|
258
265
|
refetchPaginatedList();
|
|
259
266
|
toast.success(i18n.t('Deleted successfully'));
|
|
260
267
|
} else {
|
|
261
268
|
toast.error(i18n.t('Failed to delete'), {
|
|
262
|
-
description:
|
|
269
|
+
description: resultToCheck.message,
|
|
263
270
|
});
|
|
264
271
|
}
|
|
265
272
|
},
|
|
@@ -295,7 +302,15 @@ function DeleteMutationRowAction({
|
|
|
295
302
|
<Trans>Cancel</Trans>
|
|
296
303
|
</AlertDialogCancel>
|
|
297
304
|
<AlertDialogAction
|
|
298
|
-
onClick={() =>
|
|
305
|
+
onClick={() => {
|
|
306
|
+
// Pass variables based on what the mutation expects
|
|
307
|
+
if (hasIdsParameter) {
|
|
308
|
+
deleteMutationFn({ ids: [row.original.id] });
|
|
309
|
+
} else {
|
|
310
|
+
// Fallback to single id if we can't determine the format
|
|
311
|
+
deleteMutationFn({ id: row.original.id });
|
|
312
|
+
}
|
|
313
|
+
}}
|
|
299
314
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
300
315
|
>
|
|
301
316
|
<Trans>Delete</Trans>
|
package/src/lib/index.ts
CHANGED
|
@@ -105,7 +105,6 @@ export * from './components/ui/accordion.js';
|
|
|
105
105
|
export * from './components/ui/alert-dialog.js';
|
|
106
106
|
export * from './components/ui/alert.js';
|
|
107
107
|
export * from './components/ui/aspect-ratio.js';
|
|
108
|
-
export * from './components/ui/avatar.js';
|
|
109
108
|
export * from './components/ui/badge.js';
|
|
110
109
|
export * from './components/ui/breadcrumb.js';
|
|
111
110
|
export * from './components/ui/button.js';
|
|
@@ -30,6 +30,7 @@ export async function discoverPlugins({
|
|
|
30
30
|
// Analyze source files to find local plugins and package imports
|
|
31
31
|
const { localPluginLocations, packageImports } = await analyzeSourceFiles(
|
|
32
32
|
vendureConfigPath,
|
|
33
|
+
pluginPackageScanner?.nodeModulesRoot ?? guessNodeModulesRoot(vendureConfigPath, logger),
|
|
33
34
|
logger,
|
|
34
35
|
transformTsConfigPathMappings,
|
|
35
36
|
);
|
|
@@ -137,6 +138,29 @@ function getDecoratorObjectProps(decorator: any): any[] {
|
|
|
137
138
|
return [];
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
async function isSymlinkedLocalPackage(
|
|
142
|
+
packageName: string,
|
|
143
|
+
nodeModulesRoot: string,
|
|
144
|
+
): Promise<string | undefined> {
|
|
145
|
+
try {
|
|
146
|
+
const packagePath = path.join(nodeModulesRoot, packageName);
|
|
147
|
+
const stats = await fs.lstat(packagePath);
|
|
148
|
+
if (stats.isSymbolicLink()) {
|
|
149
|
+
// Get the real path that the symlink points to
|
|
150
|
+
const realPath = await fs.realpath(packagePath);
|
|
151
|
+
// If the real path is within the project directory (i.e. not in some other node_modules),
|
|
152
|
+
// then it's a local package
|
|
153
|
+
if (!realPath.includes('node_modules')) {
|
|
154
|
+
return realPath;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
// Package doesn't exist or other error - not a local package
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
140
164
|
/**
|
|
141
165
|
* Analyzes TypeScript source files starting from the config file to discover:
|
|
142
166
|
* 1. Local Vendure plugins
|
|
@@ -144,6 +168,7 @@ function getDecoratorObjectProps(decorator: any): any[] {
|
|
|
144
168
|
*/
|
|
145
169
|
export async function analyzeSourceFiles(
|
|
146
170
|
vendureConfigPath: string,
|
|
171
|
+
nodeModulesRoot: string,
|
|
147
172
|
logger: Logger,
|
|
148
173
|
transformTsConfigPathMappings: TransformTsConfigPathMappingsFn,
|
|
149
174
|
): Promise<{
|
|
@@ -186,7 +211,7 @@ export async function analyzeSourceFiles(
|
|
|
186
211
|
// Track imports to follow
|
|
187
212
|
const importsToFollow: string[] = [];
|
|
188
213
|
|
|
189
|
-
function visit(node: ts.Node) {
|
|
214
|
+
async function visit(node: ts.Node) {
|
|
190
215
|
// Look for VendurePlugin decorator
|
|
191
216
|
const vendurePluginClassName = getVendurePluginClassName(node);
|
|
192
217
|
if (vendurePluginClassName) {
|
|
@@ -204,7 +229,20 @@ export async function analyzeSourceFiles(
|
|
|
204
229
|
// Track non-local imports (packages)
|
|
205
230
|
const npmPackageName = getNpmPackageNameFromImport(importPath);
|
|
206
231
|
if (npmPackageName) {
|
|
207
|
-
|
|
232
|
+
// Check if this is actually a symlinked local package
|
|
233
|
+
const localPackagePath = await isSymlinkedLocalPackage(
|
|
234
|
+
npmPackageName,
|
|
235
|
+
nodeModulesRoot,
|
|
236
|
+
);
|
|
237
|
+
if (localPackagePath) {
|
|
238
|
+
// If it is local, follow it like a local import
|
|
239
|
+
importsToFollow.push(localPackagePath);
|
|
240
|
+
logger.debug(
|
|
241
|
+
`Found symlinked local package "${npmPackageName}" at ${localPackagePath}`,
|
|
242
|
+
);
|
|
243
|
+
} else {
|
|
244
|
+
packageImportsSet.add(npmPackageName);
|
|
245
|
+
}
|
|
208
246
|
}
|
|
209
247
|
// Handle path aliases and local imports
|
|
210
248
|
const pathAliasImports = getPotentialPathAliasImportPaths(importPath, tsConfigInfo);
|
|
@@ -219,10 +257,15 @@ export async function analyzeSourceFiles(
|
|
|
219
257
|
}
|
|
220
258
|
}
|
|
221
259
|
|
|
222
|
-
|
|
260
|
+
// Visit children
|
|
261
|
+
const promises: Array<Promise<void>> = [];
|
|
262
|
+
ts.forEachChild(node, child => {
|
|
263
|
+
promises.push(visit(child));
|
|
264
|
+
});
|
|
265
|
+
await Promise.all(promises);
|
|
223
266
|
}
|
|
224
267
|
|
|
225
|
-
visit(sourceFile);
|
|
268
|
+
await visit(sourceFile);
|
|
226
269
|
|
|
227
270
|
// Follow imports
|
|
228
271
|
for (const importPath of importsToFollow) {
|
|
@@ -352,19 +395,7 @@ export async function findVendurePluginFiles({
|
|
|
352
395
|
let nodeModulesRoot = providedNodeModulesRoot;
|
|
353
396
|
const readStart = Date.now();
|
|
354
397
|
if (!nodeModulesRoot) {
|
|
355
|
-
|
|
356
|
-
// specified, we will try to guess it by resolving the
|
|
357
|
-
// `@vendure/core` package.
|
|
358
|
-
try {
|
|
359
|
-
const coreUrl = import.meta.resolve('@vendure/core');
|
|
360
|
-
logger.debug(`Found core URL: ${coreUrl}`);
|
|
361
|
-
const corePath = fileURLToPath(coreUrl);
|
|
362
|
-
logger.debug(`Found core path: ${corePath}`);
|
|
363
|
-
nodeModulesRoot = path.join(path.dirname(corePath), '..', '..');
|
|
364
|
-
} catch (e) {
|
|
365
|
-
logger.warn(`Failed to resolve @vendure/core: ${e instanceof Error ? e.message : String(e)}`);
|
|
366
|
-
nodeModulesRoot = path.dirname(vendureConfigPath);
|
|
367
|
-
}
|
|
398
|
+
nodeModulesRoot = guessNodeModulesRoot(vendureConfigPath, logger);
|
|
368
399
|
}
|
|
369
400
|
|
|
370
401
|
const patterns = [
|
|
@@ -372,7 +403,7 @@ export async function findVendurePluginFiles({
|
|
|
372
403
|
path.join(outputPath, '**/*.js'),
|
|
373
404
|
// Node modules patterns
|
|
374
405
|
...packageGlobs.map(pattern => path.join(nodeModulesRoot, pattern)),
|
|
375
|
-
];
|
|
406
|
+
].map(p => p.replace(/\\/g, '/'));
|
|
376
407
|
|
|
377
408
|
logger.debug(`Finding Vendure plugins using patterns: ${patterns.join('\n')}`);
|
|
378
409
|
|
|
@@ -443,3 +474,21 @@ export async function findVendurePluginFiles({
|
|
|
443
474
|
|
|
444
475
|
return potentialPluginFiles;
|
|
445
476
|
}
|
|
477
|
+
|
|
478
|
+
function guessNodeModulesRoot(vendureConfigPath: string, logger: Logger): string {
|
|
479
|
+
let nodeModulesRoot: string;
|
|
480
|
+
// If the node_modules root path has not been explicitly
|
|
481
|
+
// specified, we will try to guess it by resolving the
|
|
482
|
+
// `@vendure/core` package.
|
|
483
|
+
try {
|
|
484
|
+
const coreUrl = import.meta.resolve('@vendure/core');
|
|
485
|
+
logger.debug(`Found core URL: ${coreUrl}`);
|
|
486
|
+
const corePath = fileURLToPath(coreUrl);
|
|
487
|
+
logger.debug(`Found core path: ${corePath}`);
|
|
488
|
+
nodeModulesRoot = path.join(path.dirname(corePath), '..', '..');
|
|
489
|
+
} catch (e) {
|
|
490
|
+
logger.warn(`Failed to resolve @vendure/core: ${e instanceof Error ? e.message : String(e)}`);
|
|
491
|
+
nodeModulesRoot = path.dirname(vendureConfigPath);
|
|
492
|
+
}
|
|
493
|
+
return nodeModulesRoot;
|
|
494
|
+
}
|
|
@@ -27,10 +27,26 @@ export function dashboardTailwindSourcePlugin(): Plugin {
|
|
|
27
27
|
const { pluginInfo } = loadVendureConfigResult;
|
|
28
28
|
const dashboardExtensionDirs =
|
|
29
29
|
pluginInfo
|
|
30
|
-
?.
|
|
31
|
-
(
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
?.flatMap(({ dashboardEntryPath, sourcePluginPath, pluginPath }) => {
|
|
31
|
+
if (!dashboardEntryPath) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
const sourcePaths = [];
|
|
35
|
+
if (sourcePluginPath) {
|
|
36
|
+
sourcePaths.push(
|
|
37
|
+
path.join(
|
|
38
|
+
path.dirname(sourcePluginPath),
|
|
39
|
+
path.dirname(dashboardEntryPath),
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (pluginPath) {
|
|
44
|
+
sourcePaths.push(
|
|
45
|
+
path.join(path.dirname(pluginPath), path.dirname(dashboardEntryPath)),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return sourcePaths;
|
|
49
|
+
})
|
|
34
50
|
.filter(x => x != null) ?? [];
|
|
35
51
|
const sources = dashboardExtensionDirs
|
|
36
52
|
.map(extension => {
|
|
@@ -76,7 +76,7 @@ export type VitePluginVendureDashboardOptions = {
|
|
|
76
76
|
* @description
|
|
77
77
|
* The path to the directory where the generated GraphQL Tada files will be output.
|
|
78
78
|
*/
|
|
79
|
-
|
|
79
|
+
gqlOutputPath?: string;
|
|
80
80
|
tempCompilationDir?: string;
|
|
81
81
|
disableTansStackRouterPlugin?: boolean;
|
|
82
82
|
/**
|
|
@@ -134,8 +134,8 @@ export function vendureDashboardPlugin(options: VitePluginVendureDashboardOption
|
|
|
134
134
|
adminApiSchemaPlugin(),
|
|
135
135
|
dashboardMetadataPlugin(),
|
|
136
136
|
uiConfigPlugin({ adminUiConfig: options.adminUiConfig }),
|
|
137
|
-
...(options.
|
|
138
|
-
? [gqlTadaPlugin({ gqlTadaOutputPath: options.
|
|
137
|
+
...(options.gqlOutputPath
|
|
138
|
+
? [gqlTadaPlugin({ gqlTadaOutputPath: options.gqlOutputPath, tempDir, packageRoot })]
|
|
139
139
|
: []),
|
|
140
140
|
transformIndexHtmlPlugin(),
|
|
141
141
|
];
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
|
5
|
-
|
|
6
|
-
import { cn } from "@/vdb/lib/utils"
|
|
7
|
-
|
|
8
|
-
function Avatar({
|
|
9
|
-
className,
|
|
10
|
-
...props
|
|
11
|
-
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
|
12
|
-
return (
|
|
13
|
-
<AvatarPrimitive.Root
|
|
14
|
-
data-slot="avatar"
|
|
15
|
-
className={cn(
|
|
16
|
-
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
|
17
|
-
className
|
|
18
|
-
)}
|
|
19
|
-
{...props}
|
|
20
|
-
/>
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function AvatarImage({
|
|
25
|
-
className,
|
|
26
|
-
...props
|
|
27
|
-
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
28
|
-
return (
|
|
29
|
-
<AvatarPrimitive.Image
|
|
30
|
-
data-slot="avatar-image"
|
|
31
|
-
className={cn("aspect-square size-full", className)}
|
|
32
|
-
{...props}
|
|
33
|
-
/>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function AvatarFallback({
|
|
38
|
-
className,
|
|
39
|
-
...props
|
|
40
|
-
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
|
41
|
-
return (
|
|
42
|
-
<AvatarPrimitive.Fallback
|
|
43
|
-
data-slot="avatar-fallback"
|
|
44
|
-
className={cn(
|
|
45
|
-
"bg-muted flex size-full items-center justify-center rounded-full",
|
|
46
|
-
className
|
|
47
|
-
)}
|
|
48
|
-
{...props}
|
|
49
|
-
/>
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export { Avatar, AvatarImage, AvatarFallback }
|