@vendure/dashboard 3.2.4 → 3.3.1

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 +191 -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,47 @@ 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
+ {...props}
71
+ />
72
+ </PageContext.Provider>
73
+ );
74
+ }
75
+
76
+ function PageContent({ pageHeader, pageContent, form, submitHandler, ...props }: {
77
+ pageHeader: React.ReactNode;
78
+ pageContent: React.ReactNode;
79
+ form?: UseFormReturn<any>;
80
+ submitHandler?: any;
81
+ className?: string;
82
+ }) {
83
+ return (
84
+ <div className={cn('m-4', props.className)} {...props}>
85
+ <LocationWrapper>
86
+ <PageContentWithOptionalForm
87
+ pageHeader={pageHeader}
88
+ pageContent={pageContent}
89
+ form={form}
90
+ submitHandler={submitHandler}
91
+ />
92
+ </LocationWrapper>
93
+ </div>
94
+ );
95
+ }
96
+
97
+ export function PageContentWithOptionalForm({ form, pageHeader, pageContent, submitHandler }: {
98
+ form?: UseFormReturn<any>;
99
+ pageHeader: React.ReactNode
100
+ pageContent: React.ReactNode;
101
+ submitHandler?: any;
102
+ }) {
103
+ return form ? (
47
104
  <Form {...form}>
48
105
  <NavigationConfirmation form={form} />
49
106
  <form onSubmit={submitHandler} className="space-y-4">
@@ -57,18 +114,16 @@ export function Page({ children, pageId, entity, form, submitHandler, ...props }
57
114
  {pageContent}
58
115
  </div>
59
116
  );
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
117
  }
71
118
 
119
+ /**
120
+ * @description
121
+ * **Status: Developer Preview**
122
+ *
123
+ * @docsCategory components
124
+ * @docsPage PageLayout
125
+ * @since 3.3.0
126
+ */
72
127
  export type PageLayoutProps = {
73
128
  children: React.ReactNode;
74
129
  className?: string;
@@ -87,6 +142,18 @@ function isPageBlock(child: unknown): child is React.ReactElement<PageBlockProps
87
142
  return hasColumn || hasBlockId;
88
143
  }
89
144
 
145
+ /**
146
+ * @description
147
+ * **Status: Developer Preview**
148
+ *
149
+ * This component governs the layout of the contents of a {@link Page} component.
150
+ * It should contain all the {@link PageBlock} components that are to be displayed on the page.
151
+ *
152
+ * @docsCategory components
153
+ * @docsPage PageLayout
154
+ * @docsWeight 0
155
+ * @since 3.3.0
156
+ */
90
157
  export function PageLayout({ children, className }: PageLayoutProps) {
91
158
  const page = usePage();
92
159
  const isDesktop = useMediaQuery('only screen and (min-width : 769px)');
@@ -112,7 +179,7 @@ export function PageLayout({ children, className }: PageLayoutProps) {
112
179
  if (childBlock) {
113
180
  const blockId =
114
181
  childBlock.props.blockId ??
115
- (childBlock.type === CustomFieldsPageBlock ? 'custom-fields' : undefined);
182
+ (isOfType(childBlock, CustomFieldsPageBlock) ? 'custom-fields' : undefined);
116
183
  const extensionBlock = extensionBlocks.find(block => block.location.position.blockId === blockId);
117
184
  if (extensionBlock) {
118
185
  const ExtensionBlock = (
@@ -138,7 +205,7 @@ export function PageLayout({ children, className }: PageLayoutProps) {
138
205
  }
139
206
 
140
207
  const fullWidthBlocks = finalChildArray.filter(
141
- child => isPageBlock(child) && child.type === FullWidthPageBlock,
208
+ child => isPageBlock(child) && isOfType(child, FullWidthPageBlock),
142
209
  );
143
210
  const mainBlocks = finalChildArray.filter(child => isPageBlock(child) && child.props.column === 'main');
144
211
  const sideBlocks = finalChildArray.filter(child => isPageBlock(child) && child.props.column === 'side');
@@ -164,24 +231,41 @@ export function DetailFormGrid({ children }: { children: React.ReactNode }) {
164
231
  return <div className="md:grid md:grid-cols-2 gap-4 items-start mb-4">{children}</div>;
165
232
  }
166
233
 
167
- export interface PageContext {
168
- pageId?: string;
169
- entity?: any;
170
- form?: UseFormReturn<any>;
171
- }
172
-
234
+ /**
235
+ * @description
236
+ * **Status: Developer Preview**
237
+ *
238
+ * A component for displaying the title of a page. This should be used inside the {@link Page} component.
239
+ *
240
+ * @docsCategory components
241
+ * @docsPage PageTitle
242
+ * @since 3.3.0
243
+ */
173
244
  export function PageTitle({ children }: { children: React.ReactNode }) {
174
245
  return <h1 className="text-2xl font-semibold">{children}</h1>;
175
246
  }
176
247
 
248
+ /**
249
+ * @description
250
+ * **Status: Developer Preview**
251
+ *
252
+ * A component for displaying the main actions for a page. This should be used inside the {@link Page} component.
253
+ * It should be used in conjunction with the {@link PageActionBarLeft} and {@link PageActionBarRight} components
254
+ * as direct children.
255
+ *
256
+ * @docsCategory components
257
+ * @docsPage PageActionBar
258
+ * @docsWeight 0
259
+ * @since 3.3.0
260
+ */
177
261
  export function PageActionBar({ children }: { children: React.ReactNode }) {
178
262
  let childArray = React.Children.toArray(children);
179
263
 
180
264
  const leftContent = childArray.filter(
181
- child => React.isValidElement(child) && child.type === PageActionBarLeft,
265
+ child => isOfType(child, PageActionBarLeft),
182
266
  );
183
267
  const rightContent = childArray.filter(
184
- child => React.isValidElement(child) && child.type === PageActionBarRight,
268
+ child => isOfType(child, PageActionBarRight),
185
269
  );
186
270
 
187
271
  return (
@@ -192,10 +276,26 @@ export function PageActionBar({ children }: { children: React.ReactNode }) {
192
276
  );
193
277
  }
194
278
 
279
+ /**
280
+ * @description
281
+ * **Status: Developer Preview**
282
+ *
283
+ * @docsCategory components
284
+ * @docsPage PageActionBar
285
+ * @since 3.3.0
286
+ */
195
287
  export function PageActionBarLeft({ children }: { children: React.ReactNode }) {
196
288
  return <div className="flex justify-start gap-2">{children}</div>;
197
289
  }
198
290
 
291
+ /**
292
+ * @description
293
+ * **Status: Developer Preview**
294
+ *
295
+ * @docsCategory components
296
+ * @docsPage PageActionBar
297
+ * @since 3.3.0
298
+ */
199
299
  export function PageActionBarRight({ children }: { children: React.ReactNode }) {
200
300
  const page = usePage();
201
301
  const actionBarItems = page.pageId ? getDashboardActionBarItems(page.pageId) : [];
@@ -209,7 +309,7 @@ export function PageActionBarRight({ children }: { children: React.ReactNode })
209
309
  );
210
310
  }
211
311
 
212
- function PageActionBarItem({ item, page }: { item: DashboardActionBarItem; page: PageContext }) {
312
+ function PageActionBarItem({ item, page }: { item: DashboardActionBarItem; page: PageContextValue }) {
213
313
  return (
214
314
  <PermissionGuard requires={item.requiresPermission ?? []}>
215
315
  <item.component context={page} />
@@ -217,6 +317,14 @@ function PageActionBarItem({ item, page }: { item: DashboardActionBarItem; page:
217
317
  );
218
318
  }
219
319
 
320
+ /**
321
+ * @description
322
+ * **Status: Developer Preview**
323
+ *
324
+ * @docsCategory components
325
+ * @docsPage PageBlock
326
+ * @since 3.3.0
327
+ */
220
328
  export type PageBlockProps = {
221
329
  children?: React.ReactNode;
222
330
  /** Which column this block should appear in */
@@ -227,6 +335,19 @@ export type PageBlockProps = {
227
335
  className?: string;
228
336
  };
229
337
 
338
+ /**
339
+ * @description
340
+ * **Status: Developer Preview**
341
+ *
342
+ * A component for displaying a block of content on a page. This should be used inside the {@link PageLayout} component.
343
+ * It should be provided with a `column` prop to determine which column it should appear in, and a `blockId` prop
344
+ * to identify the block.
345
+ *
346
+ * @docsCategory components
347
+ * @docsPage PageBlock
348
+ * @docsWeight 0
349
+ * @since 3.3.0
350
+ */
230
351
  export function PageBlock({ children, title, description, className, blockId }: PageBlockProps) {
231
352
  return (
232
353
  <LocationWrapper blockId={blockId}>
@@ -243,11 +364,22 @@ export function PageBlock({ children, title, description, className, blockId }:
243
364
  );
244
365
  }
245
366
 
367
+ /**
368
+ * @description
369
+ * **Status: Developer Preview**
370
+ *
371
+ * A component for displaying a block of content on a page that takes up the full width of the page.
372
+ * This should be used inside the {@link PageLayout} component.
373
+ *
374
+ * @docsCategory components
375
+ * @docsPage PageBlock
376
+ * @since 3.3.0
377
+ */
246
378
  export function FullWidthPageBlock({
247
- children,
248
- className,
249
- blockId,
250
- }: Pick<PageBlockProps, 'children' | 'className' | 'blockId'>) {
379
+ children,
380
+ className,
381
+ blockId,
382
+ }: Pick<PageBlockProps, 'children' | 'className' | 'blockId'>) {
251
383
  return (
252
384
  <LocationWrapper blockId={blockId}>
253
385
  <div className={cn('w-full', className)}>{children}</div>
@@ -255,11 +387,21 @@ export function FullWidthPageBlock({
255
387
  );
256
388
  }
257
389
 
390
+ /**
391
+ * @description
392
+ * **Status: Developer Preview**
393
+ *
394
+ * A component for displaying an auto-generated form for custom fields on a page.
395
+ *
396
+ * @docsCategory components
397
+ * @docsPage PageBlock
398
+ * @since 3.3.0
399
+ */
258
400
  export function CustomFieldsPageBlock({
259
- column,
260
- entityType,
261
- control,
262
- }: {
401
+ column,
402
+ entityType,
403
+ control,
404
+ }: {
263
405
  column: 'main' | 'side';
264
406
  entityType: string;
265
407
  control: Control<any, any>;
@@ -274,3 +416,17 @@ export function CustomFieldsPageBlock({
274
416
  </PageBlock>
275
417
  );
276
418
  }
419
+
420
+ /**
421
+ * @description
422
+ * This compares the type of a React component to a given type.
423
+ * It is safer than a simple `el === Component` check, as it also works in the context of
424
+ * the Vite build where the component is not the same reference.
425
+ */
426
+ export function isOfType(el: unknown, type: React.FunctionComponent<any>): boolean {
427
+ if (React.isValidElement(el)) {
428
+ const elTypeName = typeof el.type === 'string' ? el.type : (el.type as React.FunctionComponent).name;
429
+ return elTypeName === type.name;
430
+ }
431
+ return false;
432
+ }
@@ -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;