@vendure/dashboard 3.5.1-master-202511070232 → 3.5.1-master-202511110232
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-dashboard-metadata.js +2 -1
- package/package.json +4 -4
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +1 -1
- package/src/lib/framework/extension-api/types/layout.ts +1 -1
- package/src/lib/framework/history-entry/history-entry.tsx +1 -1
- package/src/lib/framework/layout-engine/page-layout.spec.tsx +138 -0
- package/src/lib/framework/layout-engine/page-layout.tsx +56 -27
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
2
3
|
import { getConfigLoaderApi } from './vite-plugin-config-loader.js';
|
|
3
4
|
const virtualModuleId = 'virtual:dashboard-extensions';
|
|
4
5
|
const resolvedVirtualModuleId = `\0${virtualModuleId}`;
|
|
@@ -50,7 +51,7 @@ export function dashboardMetadataPlugin() {
|
|
|
50
51
|
export async function runDashboardExtensions() {
|
|
51
52
|
${pluginsWithExtensions
|
|
52
53
|
.map(extension => {
|
|
53
|
-
return `await import(\`${extension}\`);`;
|
|
54
|
+
return `await import(\`${pathToFileURL(extension)}\`);`;
|
|
54
55
|
})
|
|
55
56
|
.join('\n')}
|
|
56
57
|
}`;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.5.1-master-
|
|
4
|
+
"version": "3.5.1-master-202511110232",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -155,8 +155,8 @@
|
|
|
155
155
|
"@storybook/addon-vitest": "^10.0.0-beta.9",
|
|
156
156
|
"@storybook/react-vite": "^10.0.0-beta.9",
|
|
157
157
|
"@types/node": "^22.13.4",
|
|
158
|
-
"@vendure/common": "^3.5.1-master-
|
|
159
|
-
"@vendure/core": "^3.5.1-master-
|
|
158
|
+
"@vendure/common": "^3.5.1-master-202511110232",
|
|
159
|
+
"@vendure/core": "^3.5.1-master-202511110232",
|
|
160
160
|
"@vitest/browser": "^3.2.4",
|
|
161
161
|
"@vitest/coverage-v8": "^3.2.4",
|
|
162
162
|
"eslint": "^9.19.0",
|
|
@@ -173,5 +173,5 @@
|
|
|
173
173
|
"lightningcss-linux-arm64-musl": "^1.29.3",
|
|
174
174
|
"lightningcss-linux-x64-musl": "^1.29.1"
|
|
175
175
|
},
|
|
176
|
-
"gitHead": "
|
|
176
|
+
"gitHead": "61db4f11fe2040c5457e312abfd02b59116a302c"
|
|
177
177
|
}
|
package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx
CHANGED
|
@@ -105,7 +105,7 @@ export function orderHistoryUtils(order: OrderHistoryOrderDetail) {
|
|
|
105
105
|
|
|
106
106
|
const getIconColor = ({ type, data }: HistoryEntryItem) => {
|
|
107
107
|
const success = 'bg-success text-success-foreground';
|
|
108
|
-
const destructive = 'bg-
|
|
108
|
+
const destructive = 'bg-destructive text-destructive-foreground';
|
|
109
109
|
const regular = 'bg-muted text-muted-foreground';
|
|
110
110
|
|
|
111
111
|
if (type === 'ORDER_PAYMENT_TRANSITION' && data.to === 'Settled') {
|
|
@@ -34,7 +34,7 @@ export interface HistoryEntryProps {
|
|
|
34
34
|
*
|
|
35
35
|
* ```ts
|
|
36
36
|
* const success = 'bg-success text-success-foreground';
|
|
37
|
-
* const destructive = 'bg-
|
|
37
|
+
* const destructive = 'bg-destructive text-destructive-foreground';
|
|
38
38
|
* ```
|
|
39
39
|
*/
|
|
40
40
|
timelineIconClassName?: string;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { PageBlock, PageLayout } from './page-layout.js';
|
|
6
|
+
import { registerDashboardPageBlock } from './layout-extensions.js';
|
|
7
|
+
import { PageContext } from './page-provider.js';
|
|
8
|
+
import { globalRegistry } from '../registry/global-registry.js';
|
|
9
|
+
import { UserSettingsContext, type UserSettingsContextType } from '../../providers/user-settings.js';
|
|
10
|
+
|
|
11
|
+
const useMediaQueryMock = vi.hoisted(() => vi.fn());
|
|
12
|
+
const useCopyToClipboardMock = vi.hoisted(() => vi.fn(() => [null, vi.fn()]));
|
|
13
|
+
|
|
14
|
+
vi.mock('@uidotdev/usehooks', () => ({
|
|
15
|
+
useMediaQuery: useMediaQueryMock,
|
|
16
|
+
useCopyToClipboard: useCopyToClipboardMock,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
function registerBlock(
|
|
20
|
+
id: string,
|
|
21
|
+
order: 'before' | 'after' | 'replace',
|
|
22
|
+
pageId = 'customer-list',
|
|
23
|
+
): void {
|
|
24
|
+
registerDashboardPageBlock({
|
|
25
|
+
id,
|
|
26
|
+
title: id,
|
|
27
|
+
location: {
|
|
28
|
+
pageId,
|
|
29
|
+
column: 'main',
|
|
30
|
+
position: { blockId: 'list-table', order },
|
|
31
|
+
},
|
|
32
|
+
component: ({ context }) => <div data-testid={`page-block-${id}`}>{context.pageId}</div>,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function renderPageLayout(children: React.ReactNode, { isDesktop = true } = {}) {
|
|
37
|
+
useMediaQueryMock.mockReturnValue(isDesktop);
|
|
38
|
+
const noop = () => undefined;
|
|
39
|
+
const contextValue = {
|
|
40
|
+
settings: {
|
|
41
|
+
displayLanguage: 'en',
|
|
42
|
+
contentLanguage: 'en',
|
|
43
|
+
theme: 'system',
|
|
44
|
+
displayUiExtensionPoints: false,
|
|
45
|
+
mainNavExpanded: true,
|
|
46
|
+
activeChannelId: '',
|
|
47
|
+
devMode: false,
|
|
48
|
+
hasSeenOnboarding: false,
|
|
49
|
+
tableSettings: {},
|
|
50
|
+
},
|
|
51
|
+
settingsStoreIsAvailable: true,
|
|
52
|
+
setDisplayLanguage: noop,
|
|
53
|
+
setDisplayLocale: noop,
|
|
54
|
+
setContentLanguage: noop,
|
|
55
|
+
setTheme: noop,
|
|
56
|
+
setDisplayUiExtensionPoints: noop,
|
|
57
|
+
setMainNavExpanded: noop,
|
|
58
|
+
setActiveChannelId: noop,
|
|
59
|
+
setDevMode: noop,
|
|
60
|
+
setHasSeenOnboarding: noop,
|
|
61
|
+
setTableSettings: () => undefined,
|
|
62
|
+
setWidgetLayout: noop,
|
|
63
|
+
} as UserSettingsContextType;
|
|
64
|
+
|
|
65
|
+
return renderToStaticMarkup(
|
|
66
|
+
<UserSettingsContext.Provider value={contextValue}>
|
|
67
|
+
<PageContext.Provider value={{ pageId: 'customer-list' }}>
|
|
68
|
+
<PageLayout>{children}</PageLayout>
|
|
69
|
+
</PageContext.Provider>
|
|
70
|
+
</UserSettingsContext.Provider>,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getRenderedBlockIds(markup: string) {
|
|
75
|
+
return Array.from(markup.matchAll(/data-testid="(page-block-[^"]+)"/g)).map(match => match[1]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe('PageLayout', () => {
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
useMediaQueryMock.mockReset();
|
|
81
|
+
useCopyToClipboardMock.mockReset();
|
|
82
|
+
useCopyToClipboardMock.mockReturnValue([null, vi.fn()]);
|
|
83
|
+
const pageBlockRegistry = globalRegistry.get('dashboardPageBlockRegistry');
|
|
84
|
+
pageBlockRegistry.clear();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('renders multiple before/after extension blocks in registration order', () => {
|
|
88
|
+
registerBlock('before-1', 'before');
|
|
89
|
+
registerBlock('before-2', 'before');
|
|
90
|
+
registerBlock('after-1', 'after');
|
|
91
|
+
|
|
92
|
+
const markup = renderPageLayout(
|
|
93
|
+
<PageBlock column="main" blockId="list-table">
|
|
94
|
+
<div data-testid="page-block-original">original</div>
|
|
95
|
+
</PageBlock>,
|
|
96
|
+
{ isDesktop: true },
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(getRenderedBlockIds(markup)).toEqual([
|
|
100
|
+
'page-block-before-1',
|
|
101
|
+
'page-block-before-2',
|
|
102
|
+
'page-block-original',
|
|
103
|
+
'page-block-after-1',
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('replaces original block when replacement extensions are registered', () => {
|
|
108
|
+
registerBlock('replacement-1', 'replace');
|
|
109
|
+
registerBlock('replacement-2', 'replace');
|
|
110
|
+
|
|
111
|
+
const markup = renderPageLayout(
|
|
112
|
+
<PageBlock column="main" blockId="list-table">
|
|
113
|
+
<div data-testid="page-block-original">original</div>
|
|
114
|
+
</PageBlock>,
|
|
115
|
+
{ isDesktop: true },
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(getRenderedBlockIds(markup)).toEqual(['page-block-replacement-1', 'page-block-replacement-2']);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('renders extension blocks in mobile layout', () => {
|
|
122
|
+
registerBlock('before-mobile', 'before');
|
|
123
|
+
registerBlock('after-mobile', 'after');
|
|
124
|
+
|
|
125
|
+
const markup = renderPageLayout(
|
|
126
|
+
<PageBlock column="main" blockId="list-table">
|
|
127
|
+
<div data-testid="page-block-original">original</div>
|
|
128
|
+
</PageBlock>,
|
|
129
|
+
{ isDesktop: false },
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(getRenderedBlockIds(markup)).toEqual([
|
|
133
|
+
'page-block-before-mobile',
|
|
134
|
+
'page-block-original',
|
|
135
|
+
'page-block-after-mobile',
|
|
136
|
+
]);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -234,34 +234,63 @@ export function PageLayout({ children, className }: Readonly<PageLayoutProps>) {
|
|
|
234
234
|
const blockId =
|
|
235
235
|
childBlock.props.blockId ??
|
|
236
236
|
(isOfType(childBlock, CustomFieldsPageBlock) ? 'custom-fields' : undefined);
|
|
237
|
-
const extensionBlock = extensionBlocks.find(block => block.location.position.blockId === blockId);
|
|
238
237
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
238
|
+
// Get all extension blocks with the same position blockId
|
|
239
|
+
const matchingExtensionBlocks = extensionBlocks.filter(
|
|
240
|
+
block => block.location.position.blockId === blockId,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// sort the blocks to make sure we have the correct order
|
|
244
|
+
const arrangedExtensionBlocks = matchingExtensionBlocks.sort((a, b) => {
|
|
245
|
+
const orderPriority = { before: 1, replace: 2, after: 3 };
|
|
246
|
+
return orderPriority[a.location.position.order] - orderPriority[b.location.position.order];
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const replacementBlockExists = arrangedExtensionBlocks.some(
|
|
250
|
+
block => block.location.position.order === 'replace',
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
let childBlockInserted = false;
|
|
254
|
+
if (matchingExtensionBlocks.length > 0) {
|
|
255
|
+
for (const extensionBlock of arrangedExtensionBlocks) {
|
|
256
|
+
let extensionBlockShouldRender = true;
|
|
257
|
+
if (typeof extensionBlock?.shouldRender === 'function') {
|
|
258
|
+
extensionBlockShouldRender = extensionBlock.shouldRender(page);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Insert child block before the first non-"before" block
|
|
262
|
+
if (
|
|
263
|
+
!childBlockInserted &&
|
|
264
|
+
!replacementBlockExists &&
|
|
265
|
+
extensionBlock.location.position.order !== 'before'
|
|
266
|
+
) {
|
|
267
|
+
finalChildArray.push(childBlock);
|
|
268
|
+
childBlockInserted = true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const isFullWidth = extensionBlock.location.column === 'full';
|
|
272
|
+
const BlockComponent = isFullWidth ? FullWidthPageBlock : PageBlock;
|
|
273
|
+
|
|
274
|
+
const ExtensionBlock =
|
|
275
|
+
extensionBlock.component && extensionBlockShouldRender ? (
|
|
276
|
+
<BlockComponent
|
|
277
|
+
key={extensionBlock.id}
|
|
278
|
+
column={extensionBlock.location.column}
|
|
279
|
+
blockId={extensionBlock.id}
|
|
280
|
+
title={extensionBlock.title}
|
|
281
|
+
>
|
|
282
|
+
{<extensionBlock.component context={page} />}
|
|
283
|
+
</BlockComponent>
|
|
284
|
+
) : undefined;
|
|
285
|
+
|
|
286
|
+
if (extensionBlockShouldRender && ExtensionBlock) {
|
|
287
|
+
finalChildArray.push(ExtensionBlock);
|
|
288
|
+
}
|
|
243
289
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
column={extensionBlock.location.column}
|
|
249
|
-
blockId={extensionBlock.id}
|
|
250
|
-
title={extensionBlock.title}
|
|
251
|
-
>
|
|
252
|
-
{<extensionBlock.component context={page} />}
|
|
253
|
-
</PageBlock>
|
|
254
|
-
) : undefined;
|
|
255
|
-
if (extensionBlock.location.position.order === 'before') {
|
|
256
|
-
finalChildArray.push(...[ExtensionBlock, childBlock].filter(x => !!x));
|
|
257
|
-
} else if (extensionBlock.location.position.order === 'after') {
|
|
258
|
-
finalChildArray.push(...[childBlock, ExtensionBlock].filter(x => !!x));
|
|
259
|
-
} else if (
|
|
260
|
-
extensionBlock.location.position.order === 'replace' &&
|
|
261
|
-
extensionBlockShouldRender &&
|
|
262
|
-
ExtensionBlock
|
|
263
|
-
) {
|
|
264
|
-
finalChildArray.push(ExtensionBlock);
|
|
290
|
+
|
|
291
|
+
// If all blocks were "before", insert child block at the end
|
|
292
|
+
if (!childBlockInserted && !replacementBlockExists) {
|
|
293
|
+
finalChildArray.push(childBlock);
|
|
265
294
|
}
|
|
266
295
|
} else {
|
|
267
296
|
finalChildArray.push(childBlock);
|
|
@@ -286,7 +315,7 @@ export function PageLayout({ children, className }: Readonly<PageLayoutProps>) {
|
|
|
286
315
|
<div className="@3xl/layout:col-span-1 space-y-4">{sideBlocks}</div>
|
|
287
316
|
</div>
|
|
288
317
|
) : (
|
|
289
|
-
<div className="space-y-4">{
|
|
318
|
+
<div className="space-y-4">{finalChildArray}</div>
|
|
290
319
|
)}
|
|
291
320
|
</div>
|
|
292
321
|
);
|