@vendure/dashboard 3.4.3-master-202509190229 → 3.4.3-master-202509200226
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/vite/vite-plugin-config.js +1 -0
- package/package.json +4 -4
- package/src/app/routes/_authenticated/_administrators/administrators.tsx +1 -2
- package/src/app/routes/_authenticated/_assets/assets.graphql.ts +39 -0
- package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +18 -7
- package/src/app/routes/_authenticated/_assets/components/asset-tag-filter.tsx +206 -0
- package/src/app/routes/_authenticated/_assets/components/asset-tags-editor.tsx +226 -0
- package/src/app/routes/_authenticated/_assets/components/manage-tags-dialog.tsx +217 -0
- package/src/app/routes/_authenticated/_channels/channels.tsx +1 -2
- package/src/app/routes/_authenticated/_collections/collections.tsx +2 -16
- package/src/app/routes/_authenticated/_countries/countries.tsx +1 -2
- package/src/app/routes/_authenticated/_customer-groups/customer-groups.tsx +1 -2
- package/src/app/routes/_authenticated/_customers/customers.tsx +1 -2
- package/src/app/routes/_authenticated/_facets/facets.tsx +0 -1
- package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +1 -2
- package/src/app/routes/_authenticated/_product-variants/product-variants.tsx +1 -2
- package/src/app/routes/_authenticated/_products/products.tsx +1 -2
- package/src/app/routes/_authenticated/_promotions/promotions.tsx +1 -2
- package/src/app/routes/_authenticated/_roles/roles.tsx +1 -2
- package/src/app/routes/_authenticated/_sellers/sellers.tsx +1 -2
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +1 -2
- package/src/app/routes/_authenticated/_stock-locations/stock-locations.tsx +1 -2
- package/src/app/routes/_authenticated/_tax-categories/tax-categories.tsx +1 -2
- package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +1 -2
- package/src/app/routes/_authenticated/_zones/zones.tsx +1 -2
- package/src/lib/components/data-table/data-table-bulk-actions.tsx +5 -14
- package/src/lib/components/data-table/use-all-bulk-actions.ts +19 -0
- package/src/lib/components/data-table/use-generated-columns.tsx +12 -3
- package/src/lib/components/layout/nav-main.tsx +50 -25
- package/src/lib/components/shared/asset/asset-focal-point-editor.tsx +1 -1
- package/src/lib/components/shared/asset/asset-gallery.tsx +83 -50
- package/src/lib/components/shared/paginated-list-data-table.tsx +1 -0
- package/src/lib/components/shared/vendure-image.tsx +9 -1
- package/src/lib/framework/defaults.ts +24 -0
- package/src/lib/framework/extension-api/types/navigation.ts +8 -0
- package/src/lib/framework/nav-menu/nav-menu-extensions.ts +26 -0
- package/src/lib/framework/page/list-page.tsx +7 -0
- package/src/lib/hooks/use-custom-field-config.ts +19 -2
- package/src/lib/index.ts +0 -1
- package/src/lib/providers/channel-provider.tsx +22 -6
- package/src/lib/providers/server-config.tsx +1 -0
- package/src/app/routes/_authenticated/_collections/components/move-single-collection.tsx +0 -33
- package/src/lib/components/shared/asset/focal-point-control.tsx +0 -57
|
@@ -5,15 +5,41 @@ import { globalRegistry } from '../registry/global-registry.js';
|
|
|
5
5
|
// Define the placement options for navigation sections
|
|
6
6
|
export type NavMenuSectionPlacement = 'top' | 'bottom';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @description
|
|
10
|
+
* The base configuration for navigation items and sections of the main app nav bar.
|
|
11
|
+
*
|
|
12
|
+
* @docsCategory extensions-api
|
|
13
|
+
* @docsPage Navigation
|
|
14
|
+
* @since 3.4.0
|
|
15
|
+
*/
|
|
8
16
|
interface NavMenuBaseItem {
|
|
9
17
|
id: string;
|
|
10
18
|
title: string;
|
|
11
19
|
icon?: LucideIcon;
|
|
12
20
|
order?: number;
|
|
13
21
|
placement?: NavMenuSectionPlacement;
|
|
22
|
+
/**
|
|
23
|
+
* @description
|
|
24
|
+
* This can be used to restrict the menu item to the given
|
|
25
|
+
* permission or permissions.
|
|
26
|
+
*/
|
|
27
|
+
requiresPermission?: string | string[];
|
|
14
28
|
}
|
|
15
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @description
|
|
32
|
+
* Defines an items in the navigation menu.
|
|
33
|
+
*
|
|
34
|
+
* @docsCategory extensions-api
|
|
35
|
+
* @docsPage Navigation
|
|
36
|
+
* @since 3.4.0
|
|
37
|
+
*/
|
|
16
38
|
export interface NavMenuItem extends NavMenuBaseItem {
|
|
39
|
+
/**
|
|
40
|
+
* @description
|
|
41
|
+
* The url of the route which this nav item links to.
|
|
42
|
+
*/
|
|
17
43
|
url: string;
|
|
18
44
|
}
|
|
19
45
|
|
|
@@ -38,6 +38,13 @@ export interface ListPageProps<
|
|
|
38
38
|
route: AnyRoute | (() => AnyRoute);
|
|
39
39
|
title: string | React.ReactElement;
|
|
40
40
|
listQuery: T;
|
|
41
|
+
/**
|
|
42
|
+
* @description
|
|
43
|
+
* Providing the `deleteMutation` will automatically add a "delete" menu item to the
|
|
44
|
+
* actions column dropdown. Note that if this table already has a "delete" bulk action,
|
|
45
|
+
* you don't need to additionally provide a delete mutation, because the bulk action
|
|
46
|
+
* will be added to the action column dropdown already.
|
|
47
|
+
*/
|
|
41
48
|
deleteMutation?: TypedDocumentNode<any, { id: string }>;
|
|
42
49
|
transformVariables?: (variables: V) => V;
|
|
43
50
|
onSearchTermChange?: (searchTerm: string) => NonNullable<V['options']>['filter'];
|
|
@@ -1,10 +1,27 @@
|
|
|
1
|
+
import { usePermissions } from '@/vdb/hooks/use-permissions.js';
|
|
2
|
+
import { CustomFieldConfig } from '@/vdb/providers/server-config.js';
|
|
3
|
+
|
|
1
4
|
import { useServerConfig } from './use-server-config.js';
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
/**
|
|
7
|
+
* @description
|
|
8
|
+
* Returns the custom field config for the given entity type (e.g. 'Product').
|
|
9
|
+
* Also filters out any custom fields that the current active user does not
|
|
10
|
+
* have permissions to access.
|
|
11
|
+
*
|
|
12
|
+
* @docsCategory hooks
|
|
13
|
+
* @since 3.4.0
|
|
14
|
+
*/
|
|
15
|
+
export function useCustomFieldConfig(entityType: string): CustomFieldConfig[] {
|
|
4
16
|
const serverConfig = useServerConfig();
|
|
17
|
+
const { hasPermissions } = usePermissions();
|
|
5
18
|
if (!serverConfig) {
|
|
6
19
|
return [];
|
|
7
20
|
}
|
|
8
21
|
const customFieldConfig = serverConfig.entityCustomFields.find(field => field.entityName === entityType);
|
|
9
|
-
return
|
|
22
|
+
return (
|
|
23
|
+
customFieldConfig?.customFields?.filter(config => {
|
|
24
|
+
return config.requiresPermission ? hasPermissions(config.requiresPermission) : true;
|
|
25
|
+
}) ?? []
|
|
26
|
+
);
|
|
10
27
|
}
|
package/src/lib/index.ts
CHANGED
|
@@ -70,7 +70,6 @@ export * from './components/shared/asset/asset-preview-dialog.js';
|
|
|
70
70
|
export * from './components/shared/asset/asset-preview-selector.js';
|
|
71
71
|
export * from './components/shared/asset/asset-preview.js';
|
|
72
72
|
export * from './components/shared/asset/asset-properties.js';
|
|
73
|
-
export * from './components/shared/asset/focal-point-control.js';
|
|
74
73
|
export * from './components/shared/assign-to-channel-bulk-action.js';
|
|
75
74
|
export * from './components/shared/assign-to-channel-dialog.js';
|
|
76
75
|
export * from './components/shared/assigned-facet-values.js';
|
|
@@ -19,7 +19,7 @@ const channelFragment = graphql(`
|
|
|
19
19
|
`);
|
|
20
20
|
|
|
21
21
|
// Query to get all available channels and the active channel
|
|
22
|
-
const
|
|
22
|
+
const activeChannelDocument = graphql(
|
|
23
23
|
`
|
|
24
24
|
query ChannelInformation {
|
|
25
25
|
activeChannel {
|
|
@@ -28,6 +28,14 @@ const ChannelsQuery = graphql(
|
|
|
28
28
|
id
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
}
|
|
32
|
+
`,
|
|
33
|
+
[channelFragment],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const channelsDocument = graphql(
|
|
37
|
+
`
|
|
38
|
+
query ChannelInformation {
|
|
31
39
|
channels {
|
|
32
40
|
items {
|
|
33
41
|
...ChannelInfo
|
|
@@ -40,7 +48,7 @@ const ChannelsQuery = graphql(
|
|
|
40
48
|
);
|
|
41
49
|
|
|
42
50
|
// Define the type for a channel
|
|
43
|
-
type ActiveChannel = ResultOf<typeof
|
|
51
|
+
type ActiveChannel = ResultOf<typeof activeChannelDocument>['activeChannel'];
|
|
44
52
|
type Channel = ResultOf<typeof channelFragment>;
|
|
45
53
|
|
|
46
54
|
/**
|
|
@@ -106,10 +114,18 @@ export function ChannelProvider({ children }: Readonly<{ children: React.ReactNo
|
|
|
106
114
|
return activeChannelId;
|
|
107
115
|
});
|
|
108
116
|
|
|
117
|
+
// Fetch active channel
|
|
118
|
+
const { data: activeChannelData, isLoading: isActiveChannelLoading } = useQuery({
|
|
119
|
+
queryKey: ['activeChannel', isAuthenticated],
|
|
120
|
+
queryFn: () => api.query(activeChannelDocument),
|
|
121
|
+
retry: false,
|
|
122
|
+
enabled: isAuthenticated,
|
|
123
|
+
});
|
|
124
|
+
|
|
109
125
|
// Fetch all available channels
|
|
110
|
-
const { data: channelsData
|
|
126
|
+
const { data: channelsData } = useQuery({
|
|
111
127
|
queryKey: ['channels', isAuthenticated],
|
|
112
|
-
queryFn: () => api.query(
|
|
128
|
+
queryFn: () => api.query(channelsDocument),
|
|
113
129
|
retry: false,
|
|
114
130
|
enabled: isAuthenticated,
|
|
115
131
|
});
|
|
@@ -168,10 +184,10 @@ export function ChannelProvider({ children }: Readonly<{ children: React.ReactNo
|
|
|
168
184
|
}
|
|
169
185
|
}, [selectedChannelId, channels]);
|
|
170
186
|
|
|
171
|
-
const isLoading =
|
|
187
|
+
const isLoading = isActiveChannelLoading;
|
|
172
188
|
|
|
173
189
|
// Find the selected channel from the list of channels
|
|
174
|
-
const selectedChannel =
|
|
190
|
+
const selectedChannel = activeChannelData?.activeChannel;
|
|
175
191
|
|
|
176
192
|
const refreshChannels = () => {
|
|
177
193
|
refreshCurrentUser();
|
|
@@ -250,6 +250,7 @@ export const getServerConfigDocument = graphql(
|
|
|
250
250
|
);
|
|
251
251
|
|
|
252
252
|
type QueryResult = ResultOf<typeof getServerConfigDocument>['globalSettings']['serverConfig'];
|
|
253
|
+
export type CustomFieldConfig = QueryResult['entityCustomFields'][number]['customFields'][number];
|
|
253
254
|
|
|
254
255
|
export interface ServerConfig {
|
|
255
256
|
availableLanguages: string[];
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { ResultOf } from 'gql.tada';
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
|
|
4
|
-
import { collectionListDocument } from '../collections.graphql.js';
|
|
5
|
-
import { MoveCollectionsDialog } from './move-collections-dialog.js';
|
|
6
|
-
|
|
7
|
-
type Collection = ResultOf<typeof collectionListDocument>['collections']['items'][number];
|
|
8
|
-
|
|
9
|
-
export function useMoveSingleCollection() {
|
|
10
|
-
const [moveDialogOpen, setMoveDialogOpen] = useState(false);
|
|
11
|
-
const [collectionsToMove, setCollectionsToMove] = useState<Collection[]>([]);
|
|
12
|
-
|
|
13
|
-
const handleMoveClick = (collection: Collection) => {
|
|
14
|
-
setCollectionsToMove([collection]);
|
|
15
|
-
setMoveDialogOpen(true);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const MoveDialog = () => (
|
|
19
|
-
<MoveCollectionsDialog
|
|
20
|
-
open={moveDialogOpen}
|
|
21
|
-
onOpenChange={setMoveDialogOpen}
|
|
22
|
-
collectionsToMove={collectionsToMove}
|
|
23
|
-
onSuccess={() => {
|
|
24
|
-
// The dialog will handle invalidating queries internally
|
|
25
|
-
}}
|
|
26
|
-
/>
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
handleMoveClick,
|
|
31
|
-
MoveDialog,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { cn } from '@/vdb/lib/utils.js';
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
|
|
4
|
-
export interface Point {
|
|
5
|
-
x: number;
|
|
6
|
-
y: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface FocalPointControlProps {
|
|
10
|
-
width: number;
|
|
11
|
-
height: number;
|
|
12
|
-
point: Point;
|
|
13
|
-
onChange: (point: Point) => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function FocalPointControl({ width, height, point, onChange }: Readonly<FocalPointControlProps>) {
|
|
17
|
-
const [dragging, setDragging] = useState(false);
|
|
18
|
-
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
if (!dragging) return;
|
|
21
|
-
|
|
22
|
-
const handleMouseMove = (e: MouseEvent) => {
|
|
23
|
-
const rect = (e.target as HTMLDivElement)?.getBoundingClientRect();
|
|
24
|
-
const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
|
|
25
|
-
const y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height));
|
|
26
|
-
onChange({ x, y });
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const handleMouseUp = () => {
|
|
30
|
-
setDragging(false);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
document.addEventListener('mousemove', handleMouseMove);
|
|
34
|
-
document.addEventListener('mouseup', handleMouseUp);
|
|
35
|
-
|
|
36
|
-
return () => {
|
|
37
|
-
document.removeEventListener('mousemove', handleMouseMove);
|
|
38
|
-
document.removeEventListener('mouseup', handleMouseUp);
|
|
39
|
-
};
|
|
40
|
-
}, [dragging, onChange]);
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<div className="absolute inset-0 cursor-crosshair" onMouseDown={() => setDragging(true)}>
|
|
44
|
-
<div
|
|
45
|
-
className={cn(
|
|
46
|
-
'absolute w-6 h-6 border-2 border-white rounded-full -translate-x-1/2 -translate-y-1/2',
|
|
47
|
-
'shadow-[0_0_0_1px_rgba(0,0,0,0.3)]',
|
|
48
|
-
dragging && 'scale-75',
|
|
49
|
-
)}
|
|
50
|
-
style={{
|
|
51
|
-
left: `${point.x * width}px`,
|
|
52
|
-
top: `${point.y * height}px`,
|
|
53
|
-
}}
|
|
54
|
-
/>
|
|
55
|
-
</div>
|
|
56
|
-
);
|
|
57
|
-
}
|