@vendure/dashboard 3.2.3 → 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 (123) hide show
  1. package/dist/plugin/utils/ast-utils.d.ts +10 -0
  2. package/dist/plugin/utils/ast-utils.js +96 -0
  3. package/dist/plugin/utils/ast-utils.spec.d.ts +1 -0
  4. package/dist/plugin/utils/ast-utils.spec.js +120 -0
  5. package/dist/plugin/{config-loader.d.ts → utils/config-loader.d.ts} +22 -8
  6. package/dist/plugin/utils/config-loader.js +325 -0
  7. package/dist/plugin/{schema-generator.d.ts → utils/schema-generator.d.ts} +5 -0
  8. package/dist/plugin/{schema-generator.js → utils/schema-generator.js} +7 -1
  9. package/dist/plugin/{ui-config.js → utils/ui-config.js} +2 -3
  10. package/dist/plugin/vite-plugin-admin-api-schema.js +2 -2
  11. package/dist/plugin/vite-plugin-config-loader.d.ts +2 -3
  12. package/dist/plugin/vite-plugin-config-loader.js +18 -9
  13. package/dist/plugin/vite-plugin-config.js +4 -6
  14. package/dist/plugin/vite-plugin-dashboard-metadata.js +12 -14
  15. package/dist/plugin/vite-plugin-gql-tada.js +2 -2
  16. package/dist/plugin/vite-plugin-ui-config.js +3 -2
  17. package/package.json +16 -11
  18. package/src/app/app-providers.tsx +9 -9
  19. package/src/app/main.tsx +1 -1
  20. package/src/app/routes/_authenticated/_assets/assets.graphql.ts +26 -0
  21. package/src/app/routes/_authenticated/_assets/assets.tsx +2 -2
  22. package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +156 -0
  23. package/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx +104 -0
  24. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +228 -0
  25. package/src/app/routes/_authenticated/_orders/components/money-gross-net.tsx +18 -0
  26. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +2 -1
  27. package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +38 -0
  28. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +53 -0
  29. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +8 -49
  30. package/src/app/routes/_authenticated/_orders/components/shipping-method-selector.tsx +65 -0
  31. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +187 -2
  32. package/src/app/routes/_authenticated/_orders/orders.tsx +39 -18
  33. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +31 -9
  34. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +418 -0
  35. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +8 -2
  36. package/src/app/routes/_authenticated/_products/products.tsx +1 -1
  37. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +6 -0
  38. package/src/app/routes/_authenticated/_system/job-queue.tsx +7 -8
  39. package/src/app/routes/_authenticated/_system/scheduled-tasks.tsx +241 -0
  40. package/src/app/routes/_authenticated.tsx +12 -1
  41. package/src/app/styles.css +15 -0
  42. package/src/lib/components/data-table/add-filter-menu.tsx +61 -0
  43. package/src/lib/components/data-table/data-table-column-header.tsx +0 -13
  44. package/src/lib/components/data-table/data-table-filter-badge.tsx +75 -0
  45. package/src/lib/components/data-table/data-table-filter-dialog.tsx +27 -28
  46. package/src/lib/components/data-table/data-table-types.ts +1 -0
  47. package/src/lib/components/data-table/data-table-view-options.tsx +73 -24
  48. package/src/lib/components/data-table/data-table.tsx +49 -44
  49. package/src/lib/components/data-table/filters/data-table-boolean-filter.tsx +57 -0
  50. package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +93 -0
  51. package/src/lib/components/data-table/filters/data-table-id-filter.tsx +58 -0
  52. package/src/lib/components/data-table/filters/data-table-number-filter.tsx +119 -0
  53. package/src/lib/components/data-table/filters/data-table-string-filter.tsx +62 -0
  54. package/src/lib/components/data-table/human-readable-operator.tsx +65 -0
  55. package/src/lib/components/data-table/refresh-button.tsx +25 -0
  56. package/src/lib/components/layout/nav-user.tsx +20 -15
  57. package/src/lib/components/layout/prerelease-popup.tsx +1 -5
  58. package/src/lib/components/shared/alerts.tsx +19 -1
  59. package/src/lib/components/shared/asset/asset-focal-point-editor.tsx +93 -0
  60. package/src/lib/components/shared/{asset-gallery.tsx → asset/asset-gallery.tsx} +51 -20
  61. package/src/lib/components/shared/{asset-picker-dialog.tsx → asset/asset-picker-dialog.tsx} +1 -1
  62. package/src/lib/components/shared/{asset-preview-dialog.tsx → asset/asset-preview-dialog.tsx} +1 -7
  63. package/src/lib/components/shared/asset/asset-preview-selector.tsx +34 -0
  64. package/src/lib/components/shared/asset/asset-preview.tsx +128 -0
  65. package/src/lib/components/shared/asset/asset-properties.tsx +46 -0
  66. package/src/lib/components/shared/{focal-point-control.tsx → asset/focal-point-control.tsx} +1 -1
  67. package/src/lib/components/shared/custom-fields-form.tsx +4 -3
  68. package/src/lib/components/shared/customer-selector.tsx +13 -14
  69. package/src/lib/components/shared/detail-page-button.tsx +2 -2
  70. package/src/lib/components/shared/entity-assets.tsx +3 -3
  71. package/src/lib/components/shared/error-page.tsx +2 -2
  72. package/src/lib/components/shared/navigation-confirmation.tsx +49 -0
  73. package/src/lib/components/shared/paginated-list-data-table.tsx +10 -1
  74. package/src/lib/components/shared/product-variant-selector.tsx +111 -0
  75. package/src/lib/components/shared/vendure-image.tsx +1 -1
  76. package/src/lib/components/ui/calendar.tsx +508 -63
  77. package/src/lib/framework/alert/alert-extensions.tsx +31 -0
  78. package/src/lib/framework/alert/alert-item.tsx +47 -0
  79. package/src/lib/framework/alert/alerts-indicator.tsx +23 -0
  80. package/src/lib/framework/alert/types.ts +13 -0
  81. package/src/lib/framework/dashboard-widget/base-widget.tsx +1 -0
  82. package/src/lib/framework/defaults.ts +34 -0
  83. package/src/lib/framework/document-introspection/get-document-structure.spec.ts +113 -3
  84. package/src/lib/framework/document-introspection/get-document-structure.ts +71 -13
  85. package/src/lib/framework/extension-api/define-dashboard-extension.ts +15 -5
  86. package/src/lib/framework/extension-api/extension-api-types.ts +81 -12
  87. package/src/lib/framework/form-engine/use-generated-form.tsx +8 -7
  88. package/src/lib/framework/layout-engine/layout-extensions.ts +3 -3
  89. package/src/lib/framework/layout-engine/page-layout.tsx +196 -35
  90. package/src/lib/framework/layout-engine/page-provider.tsx +10 -0
  91. package/src/lib/framework/page/detail-page.tsx +62 -9
  92. package/src/lib/framework/page/list-page.tsx +42 -4
  93. package/src/lib/framework/page/page-api.ts +1 -1
  94. package/src/lib/framework/page/use-detail-page.ts +82 -0
  95. package/src/lib/framework/registry/registry-types.ts +6 -2
  96. package/src/lib/graphql/fragments.tsx +8 -0
  97. package/src/lib/graphql/graphql-env.d.ts +25 -9
  98. package/src/lib/hooks/use-auth.tsx +13 -1
  99. package/src/lib/hooks/use-channel.ts +13 -0
  100. package/src/lib/hooks/use-local-format.ts +28 -1
  101. package/src/lib/hooks/use-page.tsx +2 -3
  102. package/src/lib/hooks/use-permissions.ts +13 -0
  103. package/src/lib/index.ts +7 -8
  104. package/src/lib/providers/auth.tsx +22 -9
  105. package/src/lib/providers/channel-provider.tsx +9 -1
  106. package/src/lib/providers/server-config.tsx +7 -1
  107. package/src/lib/providers/user-settings.tsx +24 -0
  108. package/vite/utils/ast-utils.spec.ts +128 -0
  109. package/vite/utils/ast-utils.ts +119 -0
  110. package/vite/utils/config-loader.ts +410 -0
  111. package/vite/{schema-generator.ts → utils/schema-generator.ts} +11 -6
  112. package/vite/{ui-config.ts → utils/ui-config.ts} +7 -3
  113. package/vite/vite-plugin-admin-api-schema.ts +2 -12
  114. package/vite/vite-plugin-config-loader.ts +25 -13
  115. package/vite/vite-plugin-config.ts +1 -0
  116. package/vite/vite-plugin-dashboard-metadata.ts +19 -15
  117. package/vite/vite-plugin-gql-tada.ts +2 -2
  118. package/vite/vite-plugin-ui-config.ts +3 -2
  119. package/dist/plugin/config-loader.js +0 -141
  120. package/src/lib/components/shared/asset-preview.tsx +0 -345
  121. package/src/lib/components/ui/avatar.tsx +0 -38
  122. package/vite/config-loader.ts +0 -181
  123. /package/dist/plugin/{ui-config.d.ts → utils/ui-config.d.ts} +0 -0
@@ -2,6 +2,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
2
2
  import { cn } from '@/lib/utils.js';
3
3
  import { Trans } from '@/lib/trans.js';
4
4
  import { PropsWithChildren, useRef, useEffect, useState, createContext, useContext } from 'react';
5
+ import type React from 'react';
5
6
 
6
7
  type WidgetDimensions = {
7
8
  width: number;
@@ -122,6 +122,11 @@ export function registerDefaults() {
122
122
  title: 'Healthchecks',
123
123
  url: '/healthchecks',
124
124
  },
125
+ {
126
+ id: 'scheduled-tasks',
127
+ title: 'Scheduled Tasks',
128
+ url: '/scheduled-tasks',
129
+ },
125
130
  ],
126
131
  },
127
132
  {
@@ -216,4 +221,33 @@ export function registerDefaults() {
216
221
  component: OrdersSummaryWidget,
217
222
  defaultSize: { w: 6, h: 3, x: 6, y: 0 },
218
223
  });
224
+
225
+ // registerAlert<boolean>({
226
+ // id: 'test-alert',
227
+ // title: data => `Test Alert ${String(data)}`,
228
+ // description: 'This is a test alert',
229
+ // severity: 'info',
230
+ // check: () => Promise.resolve(true),
231
+ // actions: [
232
+ // {
233
+ // label: 'Test Action',
234
+ // onClick: () => console.log('Test Action'),
235
+ // },
236
+ // ],
237
+ // });
238
+
239
+ // registerAlert<boolean>({
240
+ // id: 'test-alert-2',
241
+ // title: 'Test Alert 2',
242
+ // description: 'This is a test alert 2',
243
+ // severity: 'info',
244
+ // check: () => Promise.resolve(true),
245
+ // shouldShow: data => data === true,
246
+ // actions: [
247
+ // {
248
+ // label: 'Test Action',
249
+ // onClick: () => console.log('Test Action'),
250
+ // },
251
+ // ],
252
+ // });
219
253
  }
@@ -1,7 +1,7 @@
1
1
  import { graphql } from 'gql.tada';
2
2
  import { describe, expect, it, vi } from 'vitest';
3
3
 
4
- import { getListQueryFields } from './get-document-structure.js';
4
+ import { getListQueryFields, getOperationVariablesFields } from './get-document-structure.js';
5
5
 
6
6
  vi.mock('virtual:admin-api-schema', () => {
7
7
  return {
@@ -12,7 +12,10 @@ vi.mock('virtual:admin-api-schema', () => {
12
12
  product: ['Product', false, false, false],
13
13
  collection: ['Collection', false, false, false],
14
14
  },
15
- Mutation: {},
15
+ Mutation: {
16
+ updateProduct: ['Product', false, false, false],
17
+ adjustDraftOrderLine: ['Order', false, false, false],
18
+ },
16
19
 
17
20
  Collection: {
18
21
  id: ['ID', false, false, false],
@@ -126,8 +129,25 @@ vi.mock('virtual:admin-api-schema', () => {
126
129
  languageCode: ['LanguageCode', false, false, false],
127
130
  name: ['String', false, false, false],
128
131
  },
132
+ Order: {
133
+ id: ['ID', false, false, false],
134
+ lines: ['OrderLine', false, true, false],
135
+ },
136
+ OrderLine: {
137
+ id: ['ID', false, false, false],
138
+ quantity: ['Int', false, false, false],
139
+ },
140
+ },
141
+ inputs: {
142
+ UpdateProductInput: {
143
+ id: ['ID', false, false, false],
144
+ name: ['String', false, false, false],
145
+ },
146
+ AdjustDraftOrderLineInput: {
147
+ orderLineId: ['ID', false, false, false],
148
+ quantity: ['Int', false, false, false],
149
+ },
129
150
  },
130
- inputs: {},
131
151
  scalars: ['ID', 'String', 'Int', 'Boolean', 'Float', 'JSON', 'DateTime', 'Upload', 'Money'],
132
152
  enums: {},
133
153
  },
@@ -308,3 +328,93 @@ describe('getListQueryFields', () => {
308
328
  ]);
309
329
  });
310
330
  });
331
+
332
+ describe('getOperationVariablesFields', () => {
333
+ it('should extract fields from a simple mutation', () => {
334
+ const doc = graphql(`
335
+ mutation UpdateProduct($input: UpdateProductInput!) {
336
+ updateProduct(input: $input) {
337
+ ...ProductDetail
338
+ }
339
+ }
340
+
341
+ fragment ProductDetail on Product {
342
+ id
343
+ name
344
+ }
345
+ `);
346
+
347
+ const fields = getOperationVariablesFields(doc, 'input');
348
+ expect(fields).toEqual([
349
+ {
350
+ isPaginatedList: false,
351
+ isScalar: true,
352
+ list: false,
353
+ name: 'id',
354
+ nullable: false,
355
+ type: 'ID',
356
+ typeInfo: undefined,
357
+ },
358
+ {
359
+ isPaginatedList: false,
360
+ isScalar: true,
361
+ list: false,
362
+ name: 'name',
363
+ nullable: false,
364
+ type: 'String',
365
+ typeInfo: undefined,
366
+ },
367
+ ]);
368
+ });
369
+
370
+ it('should handle a mutation with a nested input', () => {
371
+ const doc = graphql(`
372
+ mutation AdjustDraftOrderLine($orderId: ID!, $input: AdjustDraftOrderLineInput!) {
373
+ adjustDraftOrderLine(orderId: $orderId, input: $input) {
374
+ id
375
+ }
376
+ }
377
+ `);
378
+
379
+ const fields = getOperationVariablesFields(doc, undefined);
380
+ expect(fields).toEqual([
381
+ {
382
+ name: 'orderId',
383
+ isPaginatedList: false,
384
+ isScalar: true,
385
+ list: false,
386
+ nullable: false,
387
+ type: 'ID',
388
+ typeInfo: undefined,
389
+ },
390
+ {
391
+ name: 'input',
392
+ isPaginatedList: false,
393
+ isScalar: false,
394
+ list: false,
395
+ nullable: true,
396
+ type: 'AdjustDraftOrderLineInput',
397
+ typeInfo: [
398
+ {
399
+ name: 'orderLineId',
400
+ isPaginatedList: false,
401
+ isScalar: true,
402
+ list: false,
403
+ nullable: false,
404
+ type: 'ID',
405
+ typeInfo: undefined,
406
+ },
407
+ {
408
+ name: 'quantity',
409
+ isPaginatedList: false,
410
+ isScalar: true,
411
+ list: false,
412
+ nullable: false,
413
+ type: 'Int',
414
+ typeInfo: undefined,
415
+ },
416
+ ],
417
+ },
418
+ ]);
419
+ });
420
+ });
@@ -1,10 +1,11 @@
1
+ import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
2
+ import { VariablesOf } from 'gql.tada';
1
3
  import {
2
4
  DocumentNode,
3
- OperationDefinitionNode,
4
5
  FieldNode,
5
6
  FragmentDefinitionNode,
6
7
  FragmentSpreadNode,
7
- VariableDefinitionNode,
8
+ OperationDefinitionNode,
8
9
  } from 'graphql';
9
10
  import { DefinitionNode, NamedTypeNode, SelectionSetNode, TypeNode } from 'graphql/language/ast.js';
10
11
  import { schemaInfo } from 'virtual:admin-api-schema';
@@ -146,7 +147,10 @@ function findNestedPaginatedLists(
146
147
  *
147
148
  * The operation variables fields are the fields of the `UpdateProductInput` type.
148
149
  */
149
- export function getOperationVariablesFields(documentNode: DocumentNode): FieldInfo[] {
150
+ export function getOperationVariablesFields<T extends TypedDocumentNode<any, any>>(
151
+ documentNode: T,
152
+ varName?: keyof VariablesOf<T>,
153
+ ): FieldInfo[] {
150
154
  const fields: FieldInfo[] = [];
151
155
 
152
156
  const operationDefinition = documentNode.definitions.find(
@@ -154,11 +158,31 @@ export function getOperationVariablesFields(documentNode: DocumentNode): FieldIn
154
158
  );
155
159
 
156
160
  if (operationDefinition?.variableDefinitions) {
157
- operationDefinition.variableDefinitions.forEach(variable => {
158
- const unwrappedType = unwrapVariableDefinitionType(variable.type);
159
- const inputName = unwrappedType.name.value;
160
- const inputFields = getInputTypeInfo(inputName);
161
- fields.push(...inputFields);
161
+ const variableDefinitions = varName
162
+ ? operationDefinition.variableDefinitions.filter(
163
+ variable => variable.variable.name.value === varName,
164
+ )
165
+ : operationDefinition.variableDefinitions;
166
+ variableDefinitions.forEach(variableDef => {
167
+ const unwrappedType = unwrapVariableDefinitionType(variableDef.type);
168
+ const isScalar = isScalarType(unwrappedType.name.value);
169
+ const fieldName = variableDef.variable.name.value;
170
+ const typeName = unwrappedType.name.value;
171
+ const inputTypeInfo = isScalar
172
+ ? {
173
+ name: fieldName,
174
+ type: typeName,
175
+ nullable: false,
176
+ list: false,
177
+ isScalar: true,
178
+ isPaginatedList: false,
179
+ }
180
+ : getInputTypeInfo(fieldName, typeName);
181
+ if (varName && inputTypeInfo?.name === varName) {
182
+ fields.push(...(inputTypeInfo.typeInfo ?? []));
183
+ } else {
184
+ fields.push(inputTypeInfo);
185
+ }
162
186
  });
163
187
  }
164
188
 
@@ -331,7 +355,9 @@ export function getOperationTypeInfo(
331
355
  if (definitionNode.kind === 'OperationDefinition') {
332
356
  const firstSelection = definitionNode?.selectionSet.selections[0];
333
357
  if (firstSelection?.kind === 'Field') {
334
- return getQueryInfo(firstSelection.name.value);
358
+ return definitionNode.operation === 'query'
359
+ ? getQueryInfo(firstSelection.name.value)
360
+ : getMutationInfo(firstSelection.name.value);
335
361
  }
336
362
  }
337
363
  if (definitionNode.kind === 'Field' && parentTypeName) {
@@ -349,7 +375,7 @@ export function getTypeFieldInfo(typeName: string): FieldInfo[] {
349
375
  }
350
376
  return fieldInfo;
351
377
  })
352
- .filter(x => x != null) as FieldInfo[];
378
+ .filter(x => x != null);
353
379
  }
354
380
 
355
381
  function getQueryInfo(name: string): FieldInfo {
@@ -364,8 +390,40 @@ function getQueryInfo(name: string): FieldInfo {
364
390
  };
365
391
  }
366
392
 
367
- function getInputTypeInfo(name: string): FieldInfo[] {
368
- return Object.entries(schemaInfo.inputs[name]).map(([fieldName, fieldInfo]: [string, any]) => {
393
+ function getMutationInfo(name: string): FieldInfo {
394
+ const fieldInfo = schemaInfo.types.Mutation[name];
395
+ return {
396
+ name,
397
+ type: fieldInfo[0],
398
+ nullable: fieldInfo[1],
399
+ list: fieldInfo[2],
400
+ isPaginatedList: fieldInfo[3],
401
+ isScalar: schemaInfo.scalars.includes(fieldInfo[0]),
402
+ };
403
+ }
404
+
405
+ function getInputTypeInfo(name: string, type: string): FieldInfo {
406
+ const fieldInfo = schemaInfo.inputs[type];
407
+ if (!fieldInfo) {
408
+ throw new Error(`Input type ${type} not found`);
409
+ }
410
+ return {
411
+ name,
412
+ type,
413
+ nullable: true,
414
+ list: false,
415
+ isPaginatedList: false,
416
+ isScalar: false,
417
+ typeInfo: getInputTypeFields(type),
418
+ };
419
+ }
420
+
421
+ function getInputTypeFields(name: string): FieldInfo[] {
422
+ const inputType = schemaInfo.inputs[name];
423
+ if (!inputType) {
424
+ throw new Error(`Input type ${name} not found`);
425
+ }
426
+ return Object.entries(inputType).map(([fieldName, fieldInfo]: [string, any]) => {
369
427
  const type = fieldInfo[0];
370
428
  const isScalar = isScalarType(type);
371
429
  const isEnum = isEnumType(type);
@@ -376,7 +434,7 @@ function getInputTypeInfo(name: string): FieldInfo[] {
376
434
  list: fieldInfo[2],
377
435
  isPaginatedList: fieldInfo[3],
378
436
  isScalar,
379
- typeInfo: !isScalar && !isEnum ? getInputTypeInfo(type) : undefined,
437
+ typeInfo: !isScalar && !isEnum ? getInputTypeFields(type) : undefined,
380
438
  };
381
439
  });
382
440
  }
@@ -1,14 +1,14 @@
1
- import { registerDashboardWidget } from '@/framework/dashboard-widget/widget-extensions.js';
2
- import { DashboardExtension } from '@/framework/extension-api/extension-api-types.js';
3
- import { addNavMenuItem, NavMenuItem } from '@/framework/nav-menu/nav-menu-extensions.js';
4
- import { registerRoute } from '@/framework/page/page-api.js';
5
-
1
+ import { registerDashboardWidget } from '../dashboard-widget/widget-extensions.js';
6
2
  import {
7
3
  registerDashboardActionBarItem,
8
4
  registerDashboardPageBlock,
9
5
  } from '../layout-engine/layout-extensions.js';
6
+ import { addNavMenuItem, NavMenuItem } from '../nav-menu/nav-menu-extensions.js';
7
+ import { registerRoute } from '../page/page-api.js';
10
8
  import { globalRegistry } from '../registry/global-registry.js';
11
9
 
10
+ import { DashboardExtension } from './extension-api-types.js';
11
+
12
12
  globalRegistry.register('extensionSourceChangeCallbacks', new Set<() => void>());
13
13
  globalRegistry.register('registerDashboardExtensionCallbacks', new Set<() => void>());
14
14
 
@@ -22,6 +22,16 @@ export function executeDashboardExtensionCallbacks() {
22
22
  }
23
23
  }
24
24
 
25
+ /**
26
+ * @description
27
+ * **Status: Developer Preview**
28
+ *
29
+ * The main entry point for extensions to the React-based dashboard.
30
+ *
31
+ *
32
+ * @docsCategory extensions
33
+ * @since 3.3.0
34
+ */
25
35
  export function defineDashboardExtension(extension: DashboardExtension) {
26
36
  globalRegistry.get('registerDashboardExtensionCallbacks').add(() => {
27
37
  if (extension.routes) {
@@ -1,9 +1,10 @@
1
- import { NavMenuItem } from '@/framework/nav-menu/nav-menu-extensions.js';
1
+ import { PageContextValue } from '@/framework/layout-engine/page-provider.js';
2
2
  import { AnyRoute, RouteOptions } from '@tanstack/react-router';
3
- import React from 'react';
3
+ import type React from 'react';
4
4
 
5
+ import { DashboardAlertDefinition } from '../alert/types.js';
5
6
  import { DashboardWidgetDefinition } from '../dashboard-widget/types.js';
6
- import { PageContext } from '../layout-engine/page-layout.js';
7
+ import { NavMenuItem } from '../nav-menu/nav-menu-extensions.js';
7
8
 
8
9
  export interface DashboardRouteDefinition {
9
10
  component: (route: AnyRoute) => React.ReactNode;
@@ -17,42 +18,110 @@ export interface ActionBarButtonState {
17
18
  visible: boolean;
18
19
  }
19
20
 
21
+ /**
22
+ * @description
23
+ * **Status: Developer Preview**
24
+ *
25
+ * Allows you to define custom action bar items for any page in the dashboard.
26
+ *
27
+ * @docsCategory extensions
28
+ * @since 3.3.0
29
+ */
20
30
  export interface DashboardActionBarItem {
21
- locationId: string;
22
- component: React.FunctionComponent<{ context: PageContext }>;
31
+ /**
32
+ * @description
33
+ * The ID of the page where the action bar item should be displayed.
34
+ */
35
+ pageId: string;
36
+ /**
37
+ * @description
38
+ * A React component that will be rendered in the action bar.
39
+ */
40
+ component: React.FunctionComponent<{ context: PageContextValue }>;
41
+ /**
42
+ * @description
43
+ * Any permissions that are required to display this action bar item.
44
+ */
23
45
  requiresPermission?: string | string[];
24
46
  }
25
47
 
26
48
  export interface DashboardActionBarDropdownMenuItem {
27
49
  locationId: string;
28
- component: React.FunctionComponent<{ context: PageContext }>;
50
+ component: React.FunctionComponent<{ context: PageContextValue }>;
29
51
  requiresPermission?: string | string[];
30
52
  }
31
53
 
32
54
  export type PageBlockPosition = { blockId: string; order: 'before' | 'after' | 'replace' };
33
55
 
56
+ /**
57
+ * @description
58
+ * **Status: Developer Preview**
59
+ *
60
+ * The location of a page block in the dashboard. The location can be found by turning on
61
+ * "developer mode" in the dashboard user menu (bottom left corner) and then
62
+ * clicking the `< />` icon when hovering over a page block.
63
+ *
64
+ * @docsCategory extensions
65
+ * @since 3.3.0
66
+ */
34
67
  export type PageBlockLocation = {
35
68
  pageId: string;
36
69
  position: PageBlockPosition;
37
70
  column: 'main' | 'side';
38
71
  };
39
72
 
73
+ /**
74
+ * @description
75
+ * **Status: Developer Preview**
76
+ *
77
+ * This allows you to insert a custom component into a specific location
78
+ * on any page in the dashboard.
79
+ *
80
+ * @docsCategory extensions
81
+ * @since 3.3.0
82
+ */
40
83
  export interface DashboardPageBlockDefinition {
41
84
  id: string;
42
85
  title?: React.ReactNode;
43
86
  location: PageBlockLocation;
44
- component: React.FunctionComponent<{ context: PageContext }>;
87
+ component: React.FunctionComponent<{ context: PageContextValue }>;
45
88
  requiresPermission?: string | string[];
46
89
  }
47
90
 
48
91
  /**
49
92
  * @description
50
- * The main entry point for a dashboard extension.
93
+ * **Status: Developer Preview**
94
+ *
51
95
  * This is used to define the routes, widgets, etc. that will be displayed in the dashboard.
96
+ *
97
+ * @docsCategory extensions
98
+ * @since 3.3.0
52
99
  */
53
100
  export interface DashboardExtension {
54
- routes: DashboardRouteDefinition[];
55
- widgets: DashboardWidgetDefinition[];
56
- actionBarItems: DashboardActionBarItem[];
57
- pageBlocks: DashboardPageBlockDefinition[];
101
+ /**
102
+ * @description
103
+ * Allows you to define custom routes such as list or detail views.
104
+ */
105
+ routes?: DashboardRouteDefinition[];
106
+ /**
107
+ * @description
108
+ * Allows you to define custom page blocks for any page in the dashboard.
109
+ */
110
+ pageBlocks?: DashboardPageBlockDefinition[];
111
+ /**
112
+ * @description
113
+ * Allows you to define custom action bar items for any page in the dashboard.
114
+ */
115
+ actionBarItems?: DashboardActionBarItem[];
116
+ /**
117
+ * @description
118
+ * Not yet implemented
119
+ */
120
+ alerts?: DashboardAlertDefinition[];
121
+ /**
122
+ * @description
123
+ * Allows you to define custom routes for the dashboard, which will render the
124
+ * given components and optionally also add a nav menu item.
125
+ */
126
+ widgets?: DashboardWidgetDefinition[];
58
127
  }
@@ -13,13 +13,14 @@ import { useForm } from 'react-hook-form';
13
13
 
14
14
  export interface GeneratedFormOptions<
15
15
  T extends TypedDocumentNode<any, any>,
16
- VarName extends keyof VariablesOf<T> = 'input',
16
+ VarName extends (keyof VariablesOf<T>) | undefined = 'input',
17
17
  E extends Record<string, any> = Record<string, any>,
18
18
  > {
19
19
  document?: T;
20
+ varName?: VarName;
20
21
  entity: E | null | undefined;
21
- setValues: (entity: NonNullable<E>) => VariablesOf<T>[VarName];
22
- onSubmit?: (values: VariablesOf<T>[VarName]) => void;
22
+ setValues: (entity: NonNullable<E>) => VarName extends keyof VariablesOf<T> ? VariablesOf<T>[VarName] : VariablesOf<T>;
23
+ onSubmit?: (values: VarName extends keyof VariablesOf<T> ? VariablesOf<T>[VarName] : VariablesOf<T>) => void;
23
24
  }
24
25
 
25
26
  /**
@@ -31,13 +32,13 @@ export interface GeneratedFormOptions<
31
32
  */
32
33
  export function useGeneratedForm<
33
34
  T extends TypedDocumentNode<any, any>,
34
- VarName extends keyof VariablesOf<T> = 'input',
35
+ VarName extends keyof VariablesOf<T> | undefined,
35
36
  E extends Record<string, any> = Record<string, any>,
36
37
  >(options: GeneratedFormOptions<T, VarName, E>) {
37
- const { document, entity, setValues, onSubmit } = options;
38
+ const { document, entity, setValues, onSubmit, varName } = options;
38
39
  const { activeChannel } = useChannel();
39
40
  const availableLanguages = useServerConfig()?.availableLanguages || [];
40
- const updateFields = document ? getOperationVariablesFields(document) : [];
41
+ const updateFields = document ? getOperationVariablesFields(document, varName) : [];
41
42
  const schema = createFormSchemaFromFields(updateFields);
42
43
  const defaultValues = getDefaultValuesFromFields(updateFields, activeChannel?.defaultLanguageCode);
43
44
  const processedEntity = ensureTranslationsForAllLanguages(entity, availableLanguages);
@@ -58,7 +59,7 @@ export function useGeneratedForm<
58
59
  };
59
60
  if (onSubmit) {
60
61
  submitHandler = (event: FormEvent) => {
61
- form.handleSubmit(onSubmit)(event);
62
+ form.handleSubmit(onSubmit as any)(event);
62
63
  };
63
64
  }
64
65
 
@@ -9,13 +9,13 @@ globalRegistry.register('dashboardPageBlockRegistry', new Map<string, DashboardP
9
9
 
10
10
  export function registerDashboardActionBarItem(item: DashboardActionBarItem) {
11
11
  globalRegistry.set('dashboardActionBarItemRegistry', map => {
12
- map.set(item.locationId, [...(map.get(item.locationId) ?? []), item]);
12
+ map.set(item.pageId, [...(map.get(item.pageId) ?? []), item]);
13
13
  return map;
14
14
  });
15
15
  }
16
16
 
17
- export function getDashboardActionBarItems(locationId: string) {
18
- return globalRegistry.get('dashboardActionBarItemRegistry').get(locationId) ?? [];
17
+ export function getDashboardActionBarItems(pageId: string) {
18
+ return globalRegistry.get('dashboardActionBarItemRegistry').get(pageId) ?? [];
19
19
  }
20
20
 
21
21
  export function registerDashboardPageBlock(block: DashboardPageBlockDefinition) {