@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.
@@ -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-202511070232",
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-202511070232",
159
- "@vendure/core": "^3.5.1-master-202511070232",
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": "f1d4de23a9b4896cfad004a458d8901ebfb40256"
176
+ "gitHead": "61db4f11fe2040c5457e312abfd02b59116a302c"
177
177
  }
@@ -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-danger text-danger-foreground';
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') {
@@ -79,7 +79,7 @@ export type PageBlockPosition = { blockId: string; order: 'before' | 'after' | '
79
79
  export type PageBlockLocation = {
80
80
  pageId: string;
81
81
  position: PageBlockPosition;
82
- column: 'main' | 'side';
82
+ column: 'main' | 'side' | 'full';
83
83
  };
84
84
 
85
85
  /**
@@ -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-danger text-danger-foreground';
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
- if (extensionBlock) {
240
- let extensionBlockShouldRender = true;
241
- if (typeof extensionBlock?.shouldRender === 'function') {
242
- extensionBlockShouldRender = extensionBlock.shouldRender(page);
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
- const ExtensionBlock =
245
- extensionBlock.component && extensionBlockShouldRender ? (
246
- <PageBlock
247
- key={childBlock.key}
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">{children}</div>
318
+ <div className="space-y-4">{finalChildArray}</div>
290
319
  )}
291
320
  </div>
292
321
  );