@vendure/dashboard 3.2.4 → 3.3.0

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.
Files changed (53) hide show
  1. package/dist/plugin/utils/ast-utils.spec.js +2 -2
  2. package/dist/plugin/utils/schema-generator.js +1 -1
  3. package/dist/plugin/utils/ui-config.js +2 -3
  4. package/dist/plugin/vite-plugin-config.js +4 -6
  5. package/package.json +13 -9
  6. package/src/app/app-providers.tsx +1 -1
  7. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +0 -1
  8. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +8 -2
  9. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +6 -0
  10. package/src/app/routes/_authenticated/_system/job-queue.tsx +7 -8
  11. package/src/app/routes/_authenticated/_system/scheduled-tasks.tsx +241 -0
  12. package/src/app/styles.css +15 -0
  13. package/src/lib/components/data-table/data-table-view-options.tsx +1 -1
  14. package/src/lib/components/data-table/data-table.tsx +32 -26
  15. package/src/lib/components/data-table/refresh-button.tsx +25 -0
  16. package/src/lib/components/layout/nav-user.tsx +16 -11
  17. package/src/lib/components/layout/prerelease-popup.tsx +1 -5
  18. package/src/lib/components/shared/alerts.tsx +19 -1
  19. package/src/lib/components/shared/error-page.tsx +2 -2
  20. package/src/lib/components/shared/navigation-confirmation.tsx +20 -10
  21. package/src/lib/components/shared/paginated-list-data-table.tsx +1 -0
  22. package/src/lib/framework/alert/alert-extensions.tsx +31 -0
  23. package/src/lib/framework/alert/alert-item.tsx +47 -0
  24. package/src/lib/framework/alert/alerts-indicator.tsx +23 -0
  25. package/src/lib/framework/alert/types.ts +13 -0
  26. package/src/lib/framework/dashboard-widget/base-widget.tsx +1 -0
  27. package/src/lib/framework/defaults.ts +34 -0
  28. package/src/lib/framework/document-introspection/get-document-structure.ts +1 -2
  29. package/src/lib/framework/extension-api/define-dashboard-extension.ts +15 -5
  30. package/src/lib/framework/extension-api/extension-api-types.ts +81 -12
  31. package/src/lib/framework/layout-engine/layout-extensions.ts +3 -3
  32. package/src/lib/framework/layout-engine/page-layout.tsx +192 -35
  33. package/src/lib/framework/layout-engine/page-provider.tsx +10 -0
  34. package/src/lib/framework/page/detail-page.tsx +62 -9
  35. package/src/lib/framework/page/list-page.tsx +19 -0
  36. package/src/lib/framework/page/page-api.ts +1 -1
  37. package/src/lib/framework/page/use-detail-page.ts +81 -0
  38. package/src/lib/framework/registry/registry-types.ts +6 -2
  39. package/src/lib/graphql/graphql-env.d.ts +25 -9
  40. package/src/lib/hooks/use-auth.tsx +13 -1
  41. package/src/lib/hooks/use-channel.ts +13 -0
  42. package/src/lib/hooks/use-local-format.ts +28 -1
  43. package/src/lib/hooks/use-page.tsx +2 -3
  44. package/src/lib/hooks/use-permissions.ts +13 -0
  45. package/src/lib/index.ts +3 -4
  46. package/src/lib/providers/auth.tsx +11 -1
  47. package/src/lib/providers/channel-provider.tsx +8 -1
  48. package/vite/utils/ast-utils.spec.ts +2 -2
  49. package/vite/utils/schema-generator.ts +4 -5
  50. package/vite/utils/ui-config.ts +7 -3
  51. package/vite/vite-plugin-admin-api-schema.ts +0 -10
  52. package/vite/vite-plugin-config.ts +1 -0
  53. package/src/lib/components/ui/avatar.tsx +0 -38
@@ -7,13 +7,14 @@ import { usePage } from '@/hooks/use-page.js';
7
7
  import { cn } from '@/lib/utils.js';
8
8
  import { NavigationConfirmation } from '@/components/shared/navigation-confirmation.js';
9
9
  import { useMediaQuery } from '@uidotdev/usehooks';
10
- import React, { ComponentProps, createContext } from 'react';
10
+ import React, { ComponentProps } from 'react';
11
11
  import { Control, UseFormReturn } from 'react-hook-form';
12
12
 
13
13
  import { DashboardActionBarItem } from '../extension-api/extension-api-types.js';
14
14
 
15
15
  import { getDashboardActionBarItems, getDashboardPageBlocks } from './layout-extensions.js';
16
16
  import { LocationWrapper } from './location-wrapper.js';
17
+ import { PageContext, PageContextValue } from '@/framework/layout-engine/page-provider.js';
17
18
 
18
19
  export interface PageProps extends ComponentProps<'div'> {
19
20
  pageId?: string;
@@ -22,18 +23,34 @@ export interface PageProps extends ComponentProps<'div'> {
22
23
  submitHandler?: any;
23
24
  }
24
25
 
25
- export const PageProvider = createContext<PageContext | undefined>(undefined);
26
-
26
+ /**
27
+ * @description
28
+ * **Status: Developer Preview**
29
+ *
30
+ * This component should be used to wrap _all_ pages in the dashboard. It provides
31
+ * a consistent layout as well as a context for the slot-based PageBlock system.
32
+ *
33
+ * The typical hierarchy of a page is as follows:
34
+ * - `Page`
35
+ * - {@link PageTitle}
36
+ * - {@link PageActionBar}
37
+ * - {@link PageLayout}
38
+ *
39
+ * @docsCategory components
40
+ * @docsPage Page
41
+ * @docsWeight 0
42
+ * @since 3.3.0
43
+ */
27
44
  export function Page({ children, pageId, entity, form, submitHandler, ...props }: PageProps) {
28
45
  const childArray = React.Children.toArray(children);
29
46
 
30
47
  const pageTitle = childArray.find(child => React.isValidElement(child) && child.type === PageTitle);
31
48
  const pageActionBar = childArray.find(
32
- child => React.isValidElement(child) && child.type === PageActionBar,
49
+ child => isOfType(child, PageActionBar),
33
50
  );
34
51
 
35
52
  const pageContent = childArray.filter(
36
- child => React.isValidElement(child) && child.type !== PageTitle && child.type !== PageActionBar,
53
+ child => !isOfType(child, PageTitle) && !isOfType(child, PageActionBar),
37
54
  );
38
55
 
39
56
  const pageHeader = (
@@ -43,7 +60,48 @@ export function Page({ children, pageId, entity, form, submitHandler, ...props }
43
60
  </div>
44
61
  );
45
62
 
46
- const pageContentWithOptionalForm = form ? (
63
+ return (
64
+ <PageContext.Provider value={{ pageId, form, entity }}>
65
+ <PageContent
66
+ pageHeader={pageHeader}
67
+ pageContent={pageContent}
68
+ form={form}
69
+ submitHandler={submitHandler}
70
+ className={props.className}
71
+ {...props}
72
+ />
73
+ </PageContext.Provider>
74
+ );
75
+ }
76
+
77
+ function PageContent({ pageHeader, pageContent, form, submitHandler, ...props }: {
78
+ pageHeader: React.ReactNode;
79
+ pageContent: React.ReactNode;
80
+ form?: UseFormReturn<any>;
81
+ submitHandler?: any;
82
+ className?: string;
83
+ }) {
84
+ return (
85
+ <div className={cn('m-4', props.className)} {...props}>
86
+ <LocationWrapper>
87
+ <PageContentWithOptionalForm
88
+ pageHeader={pageHeader}
89
+ pageContent={pageContent}
90
+ form={form}
91
+ submitHandler={submitHandler}
92
+ />
93
+ </LocationWrapper>
94
+ </div>
95
+ );
96
+ }
97
+
98
+ export function PageContentWithOptionalForm({ form, pageHeader, pageContent, submitHandler }: {
99
+ form?: UseFormReturn<any>;
100
+ pageHeader: React.ReactNode
101
+ pageContent: React.ReactNode;
102
+ submitHandler?: any;
103
+ }) {
104
+ return form ? (
47
105
  <Form {...form}>
48
106
  <NavigationConfirmation form={form} />
49
107
  <form onSubmit={submitHandler} className="space-y-4">
@@ -57,18 +115,16 @@ export function Page({ children, pageId, entity, form, submitHandler, ...props }
57
115
  {pageContent}
58
116
  </div>
59
117
  );
60
-
61
- return (
62
- <PageProvider value={{ pageId, form, entity }}>
63
- <LocationWrapper>
64
- <div className={cn('m-4', props.className)} {...props}>
65
- {pageContentWithOptionalForm}
66
- </div>
67
- </LocationWrapper>
68
- </PageProvider>
69
- );
70
118
  }
71
119
 
120
+ /**
121
+ * @description
122
+ * **Status: Developer Preview**
123
+ *
124
+ * @docsCategory components
125
+ * @docsPage PageLayout
126
+ * @since 3.3.0
127
+ */
72
128
  export type PageLayoutProps = {
73
129
  children: React.ReactNode;
74
130
  className?: string;
@@ -87,6 +143,18 @@ function isPageBlock(child: unknown): child is React.ReactElement<PageBlockProps
87
143
  return hasColumn || hasBlockId;
88
144
  }
89
145
 
146
+ /**
147
+ * @description
148
+ * **Status: Developer Preview**
149
+ *
150
+ * This component governs the layout of the contents of a {@link Page} component.
151
+ * It should contain all the {@link PageBlock} components that are to be displayed on the page.
152
+ *
153
+ * @docsCategory components
154
+ * @docsPage PageLayout
155
+ * @docsWeight 0
156
+ * @since 3.3.0
157
+ */
90
158
  export function PageLayout({ children, className }: PageLayoutProps) {
91
159
  const page = usePage();
92
160
  const isDesktop = useMediaQuery('only screen and (min-width : 769px)');
@@ -112,7 +180,7 @@ export function PageLayout({ children, className }: PageLayoutProps) {
112
180
  if (childBlock) {
113
181
  const blockId =
114
182
  childBlock.props.blockId ??
115
- (childBlock.type === CustomFieldsPageBlock ? 'custom-fields' : undefined);
183
+ (isOfType(childBlock, CustomFieldsPageBlock) ? 'custom-fields' : undefined);
116
184
  const extensionBlock = extensionBlocks.find(block => block.location.position.blockId === blockId);
117
185
  if (extensionBlock) {
118
186
  const ExtensionBlock = (
@@ -138,7 +206,7 @@ export function PageLayout({ children, className }: PageLayoutProps) {
138
206
  }
139
207
 
140
208
  const fullWidthBlocks = finalChildArray.filter(
141
- child => isPageBlock(child) && child.type === FullWidthPageBlock,
209
+ child => isPageBlock(child) && isOfType(child, FullWidthPageBlock),
142
210
  );
143
211
  const mainBlocks = finalChildArray.filter(child => isPageBlock(child) && child.props.column === 'main');
144
212
  const sideBlocks = finalChildArray.filter(child => isPageBlock(child) && child.props.column === 'side');
@@ -164,24 +232,41 @@ export function DetailFormGrid({ children }: { children: React.ReactNode }) {
164
232
  return <div className="md:grid md:grid-cols-2 gap-4 items-start mb-4">{children}</div>;
165
233
  }
166
234
 
167
- export interface PageContext {
168
- pageId?: string;
169
- entity?: any;
170
- form?: UseFormReturn<any>;
171
- }
172
-
235
+ /**
236
+ * @description
237
+ * **Status: Developer Preview**
238
+ *
239
+ * A component for displaying the title of a page. This should be used inside the {@link Page} component.
240
+ *
241
+ * @docsCategory components
242
+ * @docsPage PageTitle
243
+ * @since 3.3.0
244
+ */
173
245
  export function PageTitle({ children }: { children: React.ReactNode }) {
174
246
  return <h1 className="text-2xl font-semibold">{children}</h1>;
175
247
  }
176
248
 
249
+ /**
250
+ * @description
251
+ * **Status: Developer Preview**
252
+ *
253
+ * A component for displaying the main actions for a page. This should be used inside the {@link Page} component.
254
+ * It should be used in conjunction with the {@link PageActionBarLeft} and {@link PageActionBarRight} components
255
+ * as direct children.
256
+ *
257
+ * @docsCategory components
258
+ * @docsPage PageActionBar
259
+ * @docsWeight 0
260
+ * @since 3.3.0
261
+ */
177
262
  export function PageActionBar({ children }: { children: React.ReactNode }) {
178
263
  let childArray = React.Children.toArray(children);
179
264
 
180
265
  const leftContent = childArray.filter(
181
- child => React.isValidElement(child) && child.type === PageActionBarLeft,
266
+ child => isOfType(child, PageActionBarLeft),
182
267
  );
183
268
  const rightContent = childArray.filter(
184
- child => React.isValidElement(child) && child.type === PageActionBarRight,
269
+ child => isOfType(child, PageActionBarRight),
185
270
  );
186
271
 
187
272
  return (
@@ -192,10 +277,26 @@ export function PageActionBar({ children }: { children: React.ReactNode }) {
192
277
  );
193
278
  }
194
279
 
280
+ /**
281
+ * @description
282
+ * **Status: Developer Preview**
283
+ *
284
+ * @docsCategory components
285
+ * @docsPage PageActionBar
286
+ * @since 3.3.0
287
+ */
195
288
  export function PageActionBarLeft({ children }: { children: React.ReactNode }) {
196
289
  return <div className="flex justify-start gap-2">{children}</div>;
197
290
  }
198
291
 
292
+ /**
293
+ * @description
294
+ * **Status: Developer Preview**
295
+ *
296
+ * @docsCategory components
297
+ * @docsPage PageActionBar
298
+ * @since 3.3.0
299
+ */
199
300
  export function PageActionBarRight({ children }: { children: React.ReactNode }) {
200
301
  const page = usePage();
201
302
  const actionBarItems = page.pageId ? getDashboardActionBarItems(page.pageId) : [];
@@ -209,7 +310,7 @@ export function PageActionBarRight({ children }: { children: React.ReactNode })
209
310
  );
210
311
  }
211
312
 
212
- function PageActionBarItem({ item, page }: { item: DashboardActionBarItem; page: PageContext }) {
313
+ function PageActionBarItem({ item, page }: { item: DashboardActionBarItem; page: PageContextValue }) {
213
314
  return (
214
315
  <PermissionGuard requires={item.requiresPermission ?? []}>
215
316
  <item.component context={page} />
@@ -217,6 +318,14 @@ function PageActionBarItem({ item, page }: { item: DashboardActionBarItem; page:
217
318
  );
218
319
  }
219
320
 
321
+ /**
322
+ * @description
323
+ * **Status: Developer Preview**
324
+ *
325
+ * @docsCategory components
326
+ * @docsPage PageBlock
327
+ * @since 3.3.0
328
+ */
220
329
  export type PageBlockProps = {
221
330
  children?: React.ReactNode;
222
331
  /** Which column this block should appear in */
@@ -227,6 +336,19 @@ export type PageBlockProps = {
227
336
  className?: string;
228
337
  };
229
338
 
339
+ /**
340
+ * @description
341
+ * **Status: Developer Preview**
342
+ *
343
+ * A component for displaying a block of content on a page. This should be used inside the {@link PageLayout} component.
344
+ * It should be provided with a `column` prop to determine which column it should appear in, and a `blockId` prop
345
+ * to identify the block.
346
+ *
347
+ * @docsCategory components
348
+ * @docsPage PageBlock
349
+ * @docsWeight 0
350
+ * @since 3.3.0
351
+ */
230
352
  export function PageBlock({ children, title, description, className, blockId }: PageBlockProps) {
231
353
  return (
232
354
  <LocationWrapper blockId={blockId}>
@@ -243,11 +365,22 @@ export function PageBlock({ children, title, description, className, blockId }:
243
365
  );
244
366
  }
245
367
 
368
+ /**
369
+ * @description
370
+ * **Status: Developer Preview**
371
+ *
372
+ * A component for displaying a block of content on a page that takes up the full width of the page.
373
+ * This should be used inside the {@link PageLayout} component.
374
+ *
375
+ * @docsCategory components
376
+ * @docsPage PageBlock
377
+ * @since 3.3.0
378
+ */
246
379
  export function FullWidthPageBlock({
247
- children,
248
- className,
249
- blockId,
250
- }: Pick<PageBlockProps, 'children' | 'className' | 'blockId'>) {
380
+ children,
381
+ className,
382
+ blockId,
383
+ }: Pick<PageBlockProps, 'children' | 'className' | 'blockId'>) {
251
384
  return (
252
385
  <LocationWrapper blockId={blockId}>
253
386
  <div className={cn('w-full', className)}>{children}</div>
@@ -255,11 +388,21 @@ export function FullWidthPageBlock({
255
388
  );
256
389
  }
257
390
 
391
+ /**
392
+ * @description
393
+ * **Status: Developer Preview**
394
+ *
395
+ * A component for displaying an auto-generated form for custom fields on a page.
396
+ *
397
+ * @docsCategory components
398
+ * @docsPage PageBlock
399
+ * @since 3.3.0
400
+ */
258
401
  export function CustomFieldsPageBlock({
259
- column,
260
- entityType,
261
- control,
262
- }: {
402
+ column,
403
+ entityType,
404
+ control,
405
+ }: {
263
406
  column: 'main' | 'side';
264
407
  entityType: string;
265
408
  control: Control<any, any>;
@@ -274,3 +417,17 @@ export function CustomFieldsPageBlock({
274
417
  </PageBlock>
275
418
  );
276
419
  }
420
+
421
+ /**
422
+ * @description
423
+ * This compares the type of a React component to a given type.
424
+ * It is safer than a simple `el === Component` check, as it also works in the context of
425
+ * the Vite build where the component is not the same reference.
426
+ */
427
+ export function isOfType(el: unknown, type: React.FunctionComponent<any>): boolean {
428
+ if (React.isValidElement(el)) {
429
+ const elTypeName = typeof el.type === 'string' ? el.type : (el.type as React.FunctionComponent).name;
430
+ return elTypeName === type.name;
431
+ }
432
+ return false;
433
+ }
@@ -0,0 +1,10 @@
1
+ import { createContext } from 'react';
2
+ import { UseFormReturn } from 'react-hook-form';
3
+
4
+ export interface PageContextValue {
5
+ pageId?: string;
6
+ entity?: any;
7
+ form?: UseFormReturn<any>;
8
+ }
9
+
10
+ export const PageContext = createContext<PageContextValue | undefined>(undefined);
@@ -3,19 +3,19 @@ import { Input } from '@/components/ui/input.js';
3
3
  import { useDetailPage } from '@/framework/page/use-detail-page.js';
4
4
  import { Trans } from '@/lib/trans.js';
5
5
  import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
6
- import { AnyRoute } from '@tanstack/react-router';
6
+ import { AnyRoute, useNavigate } from '@tanstack/react-router';
7
7
  import { ResultOf, VariablesOf } from 'gql.tada';
8
-
9
8
  import { DateTimeInput } from '@/components/data-input/datetime-input.js';
10
9
  import { Button } from '@/components/ui/button.js';
11
10
  import { Checkbox } from '@/components/ui/checkbox.js';
11
+ import { NEW_ENTITY_PATH } from '@/constants.js';
12
12
  import { toast } from 'sonner';
13
13
  import { getOperationVariablesFields } from '../document-introspection/get-document-structure.js';
14
+
14
15
  import {
15
16
  DetailFormGrid,
16
17
  Page,
17
18
  PageActionBar,
18
- PageActionBarLeft,
19
19
  PageActionBarRight,
20
20
  PageBlock,
21
21
  PageLayout,
@@ -23,21 +23,70 @@ import {
23
23
  } from '../layout-engine/page-layout.js';
24
24
  import { DetailEntityPath } from './page-types.js';
25
25
 
26
+ /**
27
+ * @description
28
+ * **Status: Developer Preview**
29
+ *
30
+ * @docsCategory components
31
+ * @docsPage DetailPage
32
+ * @since 3.3.0
33
+ */
26
34
  export interface DetailPageProps<
27
35
  T extends TypedDocumentNode<any, any>,
28
36
  C extends TypedDocumentNode<any, any>,
29
37
  U extends TypedDocumentNode<any, any>,
30
38
  EntityField extends keyof ResultOf<T> = DetailEntityPath<T>,
31
39
  > {
40
+ /**
41
+ * @description
42
+ * A unique identifier for the page.
43
+ */
32
44
  pageId: string;
45
+ /**
46
+ * @description
47
+ * The Tanstack Router route used to navigate to this page.
48
+ */
33
49
  route: AnyRoute;
50
+ /**
51
+ * @description
52
+ * The title of the page.
53
+ */
34
54
  title: (entity: ResultOf<T>[EntityField]) => string;
55
+ /**
56
+ * @description
57
+ * The query document used to fetch the entity.
58
+ */
35
59
  queryDocument: T;
60
+ /**
61
+ * @description
62
+ * The mutation document used to create the entity.
63
+ */
36
64
  createDocument?: C;
65
+ /**
66
+ * @description
67
+ * The mutation document used to update the entity.
68
+ */
37
69
  updateDocument: U;
70
+ /**
71
+ * @description
72
+ * A function that sets the values for the update input type based on the entity.
73
+ */
38
74
  setValuesForUpdate: (entity: ResultOf<T>[EntityField]) => VariablesOf<U>['input'];
39
75
  }
40
76
 
77
+ /**
78
+ * @description
79
+ * **Status: Developer Preview**
80
+ *
81
+ * Auto-generates a detail page with a form based on the provided query and mutation documents.
82
+ *
83
+ * For more control over the layout, you would use the more low-level {@link Page} component.
84
+ *
85
+ * @docsCategory components
86
+ * @docsPage DetailPage
87
+ * @docsWeight 0
88
+ * @since 3.3.0
89
+ */
41
90
  export function DetailPage<
42
91
  T extends TypedDocumentNode<any, any>,
43
92
  C extends TypedDocumentNode<any, any>,
@@ -52,6 +101,8 @@ export function DetailPage<
52
101
  title,
53
102
  }: DetailPageProps<T, C, U>) {
54
103
  const params = route.useParams();
104
+ const creatingNewEntity = params.id === NEW_ENTITY_PATH;
105
+ const navigate = useNavigate();
55
106
 
56
107
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage<any, any, any>({
57
108
  queryDocument,
@@ -59,9 +110,13 @@ export function DetailPage<
59
110
  createDocument,
60
111
  params: { id: params.id },
61
112
  setValuesForUpdate,
62
- onSuccess: () => {
113
+ onSuccess: async (data) => {
63
114
  toast.success('Updated successfully');
64
115
  resetForm();
116
+ const id = (data as any).id;
117
+ if (creatingNewEntity && id) {
118
+ await navigate({ to: `../$id`, params: { id } });
119
+ }
65
120
  },
66
121
  onError: error => {
67
122
  toast.error('Failed to update', {
@@ -70,14 +125,12 @@ export function DetailPage<
70
125
  },
71
126
  });
72
127
 
73
- const updateFields = getOperationVariablesFields(updateDocument);
128
+ const updateFields = getOperationVariablesFields(updateDocument, 'input');
74
129
 
75
130
  return (
76
131
  <Page pageId={pageId} form={form} submitHandler={submitHandler}>
132
+ <PageTitle>{title(entity)}</PageTitle>
77
133
  <PageActionBar>
78
- <PageActionBarLeft>
79
- <PageTitle>{title(entity)}</PageTitle>
80
- </PageActionBarLeft>
81
134
  <PageActionBarRight>
82
135
  <Button
83
136
  type="submit"
@@ -114,7 +167,7 @@ export function DetailPage<
114
167
  case 'DateTime':
115
168
  return <DateTimeInput {...field} />;
116
169
  case 'Boolean':
117
- return <Checkbox {...field} />;
170
+ return <Checkbox value={field.value} onCheckedChange={field.onChange} />;
118
171
  case 'String':
119
172
  default:
120
173
  return <Input {...field} />;
@@ -31,6 +31,14 @@ type ListQueryFields<T extends TypedDocumentNode<any, any>> = {
31
31
  : never;
32
32
  }[keyof ResultOf<T>];
33
33
 
34
+ /**
35
+ * @description
36
+ * **Status: Developer Preview**
37
+ *
38
+ * @docsCategory components
39
+ * @docsPage ListPage
40
+ * @since 3.3.0
41
+ */
34
42
  export interface ListPageProps<
35
43
  T extends TypedDocumentNode<U, V>,
36
44
  U extends ListQueryShape,
@@ -56,6 +64,17 @@ export interface ListPageProps<
56
64
  setTableOptions?: (table: TableOptions<any>) => TableOptions<any>;
57
65
  }
58
66
 
67
+ /**
68
+ * @description
69
+ * **Status: Developer Preview**
70
+ *
71
+ * Auto-generates a list page with columns generated based on the provided query document fields.
72
+ *
73
+ * @docsCategory components
74
+ * @docsPage ListPage
75
+ * @docsWeight 0
76
+ * @since 3.3.0
77
+ */
59
78
  export function ListPage<
60
79
  T extends TypedDocumentNode<U, V>,
61
80
  U extends Record<string, any> = any,
@@ -1,4 +1,4 @@
1
- import { DashboardRouteDefinition } from '@/framework/extension-api/extension-api-types.js';
1
+ import { DashboardRouteDefinition } from '../extension-api/extension-api-types.js';
2
2
 
3
3
  export const extensionRoutes = new Map<string, DashboardRouteDefinition>();
4
4
 
@@ -26,6 +26,14 @@ type RemoveNullFields<T> = {
26
26
  [K in keyof T]: RemoveNull<T[K]>;
27
27
  };
28
28
 
29
+ /**
30
+ * @description
31
+ * **Status: Developer Preview**
32
+ *
33
+ * @docsCategory hooks
34
+ * @docsPage useDetailPage
35
+ * @since 3.3.0
36
+ */
29
37
  export interface DetailPageOptions<
30
38
  T extends TypedDocumentNode<any, any>,
31
39
  C extends TypedDocumentNode<any, any>,
@@ -114,6 +122,14 @@ export type DetailPageEntity<
114
122
  translations: DetailPageTranslations<T, EntityField>;
115
123
  };
116
124
 
125
+ /**
126
+ * @description
127
+ * **Status: Developer Preview**
128
+ *
129
+ * @docsCategory hooks
130
+ * @docsPage useDetailPage
131
+ * @since 3.3.0
132
+ */
117
133
  export interface UseDetailPageResult<
118
134
  T extends TypedDocumentNode<any, any>,
119
135
  C extends TypedDocumentNode<any, any>,
@@ -130,8 +146,72 @@ export interface UseDetailPageResult<
130
146
 
131
147
  /**
132
148
  * @description
149
+ * **Status: Developer Preview**
150
+ *
133
151
  * This hook is used to create an entity detail page which can read
134
152
  * and update an entity.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
157
+ * queryDocument: paymentMethodDetailDocument,
158
+ * createDocument: createPaymentMethodDocument,
159
+ * updateDocument: updatePaymentMethodDocument,
160
+ * setValuesForUpdate: entity => {
161
+ * return {
162
+ * id: entity.id,
163
+ * enabled: entity.enabled,
164
+ * name: entity.name,
165
+ * code: entity.code,
166
+ * description: entity.description,
167
+ * checker: entity.checker?.code
168
+ * ? {
169
+ * code: entity.checker?.code,
170
+ * arguments: entity.checker?.args,
171
+ * }
172
+ * : null,
173
+ * handler: entity.handler?.code
174
+ * ? {
175
+ * code: entity.handler?.code,
176
+ * arguments: entity.handler?.args,
177
+ * }
178
+ * : null,
179
+ * translations: entity.translations.map(translation => ({
180
+ * id: translation.id,
181
+ * languageCode: translation.languageCode,
182
+ * name: translation.name,
183
+ * description: translation.description,
184
+ * })),
185
+ * customFields: entity.customFields,
186
+ * };
187
+ * },
188
+ * transformCreateInput: input => {
189
+ * return {
190
+ * ...input,
191
+ * checker: input.checker?.code ? input.checker : undefined,
192
+ * handler: input.handler,
193
+ * };
194
+ * },
195
+ * params: { id: params.id },
196
+ * onSuccess: async data => {
197
+ * toast.success(i18n.t('Successfully updated payment method'));
198
+ * resetForm();
199
+ * if (creatingNewEntity) {
200
+ * await navigate({ to: `../$id`, params: { id: data.id } });
201
+ * }
202
+ * },
203
+ * onError: err => {
204
+ * toast.error(i18n.t('Failed to update payment method'), {
205
+ * description: err instanceof Error ? err.message : 'Unknown error',
206
+ * });
207
+ * },
208
+ * });
209
+ * ```
210
+ *
211
+ * @docsCategory hooks
212
+ * @docsPage useDetailPage
213
+ * @docsWeight 0
214
+ * @since 3.3.0
135
215
  */
136
216
  export function useDetailPage<
137
217
  T extends TypedDocumentNode<any, any>,
@@ -178,6 +258,7 @@ export function useDetailPage<
178
258
  onSuccess?.((data as any)[createMutationName]);
179
259
  }
180
260
  },
261
+ onError,
181
262
  });
182
263
 
183
264
  const updateMutation = useMutation({
@@ -1,6 +1,9 @@
1
+ import { DashboardAlertDefinition } from '../alert/types.js';
1
2
  import { DashboardWidgetDefinition } from '../dashboard-widget/types.js';
2
- import { DashboardActionBarItem } from '../extension-api/extension-api-types.js';
3
- import { DashboardPageBlockDefinition } from '../extension-api/extension-api-types.js';
3
+ import {
4
+ DashboardActionBarItem,
5
+ DashboardPageBlockDefinition,
6
+ } from '../extension-api/extension-api-types.js';
4
7
  import { NavMenuConfig } from '../nav-menu/nav-menu-extensions.js';
5
8
 
6
9
  export interface GlobalRegistryContents {
@@ -10,6 +13,7 @@ export interface GlobalRegistryContents {
10
13
  dashboardActionBarItemRegistry: Map<string, DashboardActionBarItem[]>;
11
14
  dashboardPageBlockRegistry: Map<string, DashboardPageBlockDefinition[]>;
12
15
  dashboardWidgetRegistry: Map<string, DashboardWidgetDefinition>;
16
+ dashboardAlertRegistry: Map<string, DashboardAlertDefinition>;
13
17
  }
14
18
 
15
19
  export type GlobalRegistryKey = keyof GlobalRegistryContents;