@vendure/dashboard 3.2.2 → 3.2.4

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 (92) 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} +6 -0
  9. package/dist/plugin/{ui-config.js → utils/ui-config.js} +2 -2
  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-dashboard-metadata.js +12 -14
  14. package/dist/plugin/vite-plugin-gql-tada.js +2 -2
  15. package/dist/plugin/vite-plugin-ui-config.js +3 -2
  16. package/package.json +8 -6
  17. package/src/app/app-providers.tsx +8 -8
  18. package/src/app/main.tsx +1 -1
  19. package/src/app/routes/_authenticated/_assets/assets.graphql.ts +26 -0
  20. package/src/app/routes/_authenticated/_assets/assets.tsx +2 -2
  21. package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +156 -0
  22. package/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx +104 -0
  23. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +228 -0
  24. package/src/app/routes/_authenticated/_orders/components/money-gross-net.tsx +18 -0
  25. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +2 -1
  26. package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +38 -0
  27. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +53 -0
  28. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +8 -49
  29. package/src/app/routes/_authenticated/_orders/components/shipping-method-selector.tsx +65 -0
  30. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +187 -1
  31. package/src/app/routes/_authenticated/_orders/orders.tsx +39 -18
  32. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +31 -9
  33. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +418 -0
  34. package/src/app/routes/_authenticated/_products/products.tsx +1 -1
  35. package/src/app/routes/_authenticated.tsx +12 -1
  36. package/src/lib/components/data-table/add-filter-menu.tsx +61 -0
  37. package/src/lib/components/data-table/data-table-column-header.tsx +0 -13
  38. package/src/lib/components/data-table/data-table-filter-badge.tsx +75 -0
  39. package/src/lib/components/data-table/data-table-filter-dialog.tsx +27 -28
  40. package/src/lib/components/data-table/data-table-types.ts +1 -0
  41. package/src/lib/components/data-table/data-table-view-options.tsx +72 -23
  42. package/src/lib/components/data-table/data-table.tsx +23 -24
  43. package/src/lib/components/data-table/filters/data-table-boolean-filter.tsx +57 -0
  44. package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +93 -0
  45. package/src/lib/components/data-table/filters/data-table-id-filter.tsx +58 -0
  46. package/src/lib/components/data-table/filters/data-table-number-filter.tsx +119 -0
  47. package/src/lib/components/data-table/filters/data-table-string-filter.tsx +62 -0
  48. package/src/lib/components/data-table/human-readable-operator.tsx +65 -0
  49. package/src/lib/components/layout/nav-user.tsx +4 -4
  50. package/src/lib/components/shared/asset/asset-focal-point-editor.tsx +93 -0
  51. package/src/lib/components/shared/{asset-gallery.tsx → asset/asset-gallery.tsx} +51 -20
  52. package/src/lib/components/shared/{asset-picker-dialog.tsx → asset/asset-picker-dialog.tsx} +1 -1
  53. package/src/lib/components/shared/{asset-preview-dialog.tsx → asset/asset-preview-dialog.tsx} +1 -7
  54. package/src/lib/components/shared/asset/asset-preview-selector.tsx +34 -0
  55. package/src/lib/components/shared/asset/asset-preview.tsx +128 -0
  56. package/src/lib/components/shared/asset/asset-properties.tsx +46 -0
  57. package/src/lib/components/shared/{focal-point-control.tsx → asset/focal-point-control.tsx} +1 -1
  58. package/src/lib/components/shared/custom-fields-form.tsx +4 -3
  59. package/src/lib/components/shared/customer-selector.tsx +13 -14
  60. package/src/lib/components/shared/detail-page-button.tsx +2 -2
  61. package/src/lib/components/shared/entity-assets.tsx +3 -3
  62. package/src/lib/components/shared/navigation-confirmation.tsx +39 -0
  63. package/src/lib/components/shared/paginated-list-data-table.tsx +9 -1
  64. package/src/lib/components/shared/product-variant-selector.tsx +111 -0
  65. package/src/lib/components/shared/vendure-image.tsx +1 -1
  66. package/src/lib/components/ui/calendar.tsx +508 -63
  67. package/src/lib/framework/document-introspection/get-document-structure.spec.ts +113 -3
  68. package/src/lib/framework/document-introspection/get-document-structure.ts +70 -11
  69. package/src/lib/framework/form-engine/use-generated-form.tsx +8 -7
  70. package/src/lib/framework/layout-engine/page-layout.tsx +4 -0
  71. package/src/lib/framework/page/list-page.tsx +23 -4
  72. package/src/lib/framework/page/use-detail-page.ts +1 -0
  73. package/src/lib/graphql/fragments.tsx +8 -0
  74. package/src/lib/index.ts +5 -5
  75. package/src/lib/providers/auth.tsx +12 -9
  76. package/src/lib/providers/channel-provider.tsx +1 -0
  77. package/src/lib/providers/server-config.tsx +7 -1
  78. package/src/lib/providers/user-settings.tsx +24 -0
  79. package/vite/utils/ast-utils.spec.ts +128 -0
  80. package/vite/utils/ast-utils.ts +119 -0
  81. package/vite/utils/config-loader.ts +410 -0
  82. package/vite/{schema-generator.ts → utils/schema-generator.ts} +7 -1
  83. package/vite/{ui-config.ts → utils/ui-config.ts} +2 -2
  84. package/vite/vite-plugin-admin-api-schema.ts +2 -2
  85. package/vite/vite-plugin-config-loader.ts +25 -13
  86. package/vite/vite-plugin-dashboard-metadata.ts +19 -15
  87. package/vite/vite-plugin-gql-tada.ts +2 -2
  88. package/vite/vite-plugin-ui-config.ts +3 -2
  89. package/dist/plugin/config-loader.js +0 -141
  90. package/src/lib/components/shared/asset-preview.tsx +0 -345
  91. package/vite/config-loader.ts +0 -181
  92. /package/dist/plugin/{ui-config.d.ts → utils/ui-config.d.ts} +0 -0
@@ -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,3 +1,5 @@
1
+ import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
2
+ import { VariablesOf } from 'gql.tada';
1
3
  import {
2
4
  DocumentNode,
3
5
  OperationDefinitionNode,
@@ -146,7 +148,10 @@ function findNestedPaginatedLists(
146
148
  *
147
149
  * The operation variables fields are the fields of the `UpdateProductInput` type.
148
150
  */
149
- export function getOperationVariablesFields(documentNode: DocumentNode): FieldInfo[] {
151
+ export function getOperationVariablesFields<T extends TypedDocumentNode<any, any>>(
152
+ documentNode: T,
153
+ varName?: keyof VariablesOf<T>,
154
+ ): FieldInfo[] {
150
155
  const fields: FieldInfo[] = [];
151
156
 
152
157
  const operationDefinition = documentNode.definitions.find(
@@ -154,11 +159,31 @@ export function getOperationVariablesFields(documentNode: DocumentNode): FieldIn
154
159
  );
155
160
 
156
161
  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);
162
+ const variableDefinitions = varName
163
+ ? operationDefinition.variableDefinitions.filter(
164
+ variable => variable.variable.name.value === varName,
165
+ )
166
+ : operationDefinition.variableDefinitions;
167
+ variableDefinitions.forEach(variableDef => {
168
+ const unwrappedType = unwrapVariableDefinitionType(variableDef.type);
169
+ const isScalar = isScalarType(unwrappedType.name.value);
170
+ const fieldName = variableDef.variable.name.value;
171
+ const typeName = unwrappedType.name.value;
172
+ const inputTypeInfo = isScalar
173
+ ? {
174
+ name: fieldName,
175
+ type: typeName,
176
+ nullable: false,
177
+ list: false,
178
+ isScalar: true,
179
+ isPaginatedList: false,
180
+ }
181
+ : getInputTypeInfo(fieldName, typeName);
182
+ if (varName && inputTypeInfo?.name === varName) {
183
+ fields.push(...(inputTypeInfo.typeInfo ?? []));
184
+ } else {
185
+ fields.push(inputTypeInfo);
186
+ }
162
187
  });
163
188
  }
164
189
 
@@ -331,7 +356,9 @@ export function getOperationTypeInfo(
331
356
  if (definitionNode.kind === 'OperationDefinition') {
332
357
  const firstSelection = definitionNode?.selectionSet.selections[0];
333
358
  if (firstSelection?.kind === 'Field') {
334
- return getQueryInfo(firstSelection.name.value);
359
+ return definitionNode.operation === 'query'
360
+ ? getQueryInfo(firstSelection.name.value)
361
+ : getMutationInfo(firstSelection.name.value);
335
362
  }
336
363
  }
337
364
  if (definitionNode.kind === 'Field' && parentTypeName) {
@@ -349,7 +376,7 @@ export function getTypeFieldInfo(typeName: string): FieldInfo[] {
349
376
  }
350
377
  return fieldInfo;
351
378
  })
352
- .filter(x => x != null) as FieldInfo[];
379
+ .filter(x => x != null);
353
380
  }
354
381
 
355
382
  function getQueryInfo(name: string): FieldInfo {
@@ -364,8 +391,40 @@ function getQueryInfo(name: string): FieldInfo {
364
391
  };
365
392
  }
366
393
 
367
- function getInputTypeInfo(name: string): FieldInfo[] {
368
- return Object.entries(schemaInfo.inputs[name]).map(([fieldName, fieldInfo]: [string, any]) => {
394
+ function getMutationInfo(name: string): FieldInfo {
395
+ const fieldInfo = schemaInfo.types.Mutation[name];
396
+ return {
397
+ name,
398
+ type: fieldInfo[0],
399
+ nullable: fieldInfo[1],
400
+ list: fieldInfo[2],
401
+ isPaginatedList: fieldInfo[3],
402
+ isScalar: schemaInfo.scalars.includes(fieldInfo[0]),
403
+ };
404
+ }
405
+
406
+ function getInputTypeInfo(name: string, type: string): FieldInfo {
407
+ const fieldInfo = schemaInfo.inputs[type];
408
+ if (!fieldInfo) {
409
+ throw new Error(`Input type ${type} not found`);
410
+ }
411
+ return {
412
+ name,
413
+ type,
414
+ nullable: true,
415
+ list: false,
416
+ isPaginatedList: false,
417
+ isScalar: false,
418
+ typeInfo: getInputTypeFields(type),
419
+ };
420
+ }
421
+
422
+ function getInputTypeFields(name: string): FieldInfo[] {
423
+ const inputType = schemaInfo.inputs[name];
424
+ if (!inputType) {
425
+ throw new Error(`Input type ${name} not found`);
426
+ }
427
+ return Object.entries(inputType).map(([fieldName, fieldInfo]: [string, any]) => {
369
428
  const type = fieldInfo[0];
370
429
  const isScalar = isScalarType(type);
371
430
  const isEnum = isEnumType(type);
@@ -376,7 +435,7 @@ function getInputTypeInfo(name: string): FieldInfo[] {
376
435
  list: fieldInfo[2],
377
436
  isPaginatedList: fieldInfo[3],
378
437
  isScalar,
379
- typeInfo: !isScalar && !isEnum ? getInputTypeInfo(type) : undefined,
438
+ typeInfo: !isScalar && !isEnum ? getInputTypeFields(type) : undefined,
380
439
  };
381
440
  });
382
441
  }
@@ -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
 
@@ -5,10 +5,13 @@ import { Form } from '@/components/ui/form.js';
5
5
  import { useCustomFieldConfig } from '@/hooks/use-custom-field-config.js';
6
6
  import { usePage } from '@/hooks/use-page.js';
7
7
  import { cn } from '@/lib/utils.js';
8
+ import { NavigationConfirmation } from '@/components/shared/navigation-confirmation.js';
8
9
  import { useMediaQuery } from '@uidotdev/usehooks';
9
10
  import React, { ComponentProps, createContext } from 'react';
10
11
  import { Control, UseFormReturn } from 'react-hook-form';
12
+
11
13
  import { DashboardActionBarItem } from '../extension-api/extension-api-types.js';
14
+
12
15
  import { getDashboardActionBarItems, getDashboardPageBlocks } from './layout-extensions.js';
13
16
  import { LocationWrapper } from './location-wrapper.js';
14
17
 
@@ -42,6 +45,7 @@ export function Page({ children, pageId, entity, form, submitHandler, ...props }
42
45
 
43
46
  const pageContentWithOptionalForm = form ? (
44
47
  <Form {...form}>
48
+ <NavigationConfirmation form={form} />
45
49
  <form onSubmit={submitHandler} className="space-y-4">
46
50
  {pageHeader}
47
51
  {pageContent}
@@ -11,7 +11,9 @@ import { TypedDocumentNode } from '@graphql-typed-document-node/core';
11
11
  import { AnyRoute, AnyRouter, useNavigate } from '@tanstack/react-router';
12
12
  import { ColumnFiltersState, SortingState, Table } from '@tanstack/react-table';
13
13
  import { TableOptions } from '@tanstack/table-core';
14
+ import { useUserSettings } from '@/hooks/use-user-settings.js';
14
15
  import { ResultOf } from 'gql.tada';
16
+
15
17
  import { addCustomFields } from '../document-introspection/add-custom-fields.js';
16
18
  import {
17
19
  FullWidthPageBlock,
@@ -81,12 +83,18 @@ export function ListPage<
81
83
  const route = typeof routeOrFn === 'function' ? routeOrFn() : routeOrFn;
82
84
  const routeSearch = route.useSearch();
83
85
  const navigate = useNavigate<AnyRouter>({ from: route.fullPath });
86
+ const { setTableSettings, settings } = useUserSettings();
87
+ const tableSettings = pageId ? settings.tableSettings?.[pageId] : undefined;
84
88
 
85
89
  const pagination = {
86
90
  page: routeSearch.page ? parseInt(routeSearch.page) : 1,
87
- itemsPerPage: routeSearch.perPage ? parseInt(routeSearch.perPage) : 10,
91
+ itemsPerPage: routeSearch.perPage ? parseInt(routeSearch.perPage) : tableSettings?.pageSize ?? 10,
88
92
  };
89
93
 
94
+ const columnVisibility = pageId ? tableSettings?.columnVisibility : defaultVisibility;
95
+ const columnOrder = pageId ? tableSettings?.columnOrder : defaultColumnOrder;
96
+ const columnFilters = pageId ? tableSettings?.columnFilters : routeSearch.filters;
97
+
90
98
  const sorting: SortingState = (routeSearch.sort ?? '')
91
99
  .split(',')
92
100
  .filter((s: string) => s.length)
@@ -138,21 +146,32 @@ export function ListPage<
138
146
  transformVariables={transformVariables}
139
147
  customizeColumns={customizeColumns as any}
140
148
  additionalColumns={additionalColumns as any}
141
- defaultColumnOrder={defaultColumnOrder}
142
- defaultVisibility={defaultVisibility}
149
+ defaultColumnOrder={columnOrder as any}
150
+ defaultVisibility={columnVisibility as any}
143
151
  onSearchTermChange={onSearchTermChange}
144
152
  page={pagination.page}
145
153
  itemsPerPage={pagination.itemsPerPage}
146
154
  sorting={sorting}
147
- columnFilters={routeSearch.filters}
155
+ columnFilters={columnFilters}
148
156
  onPageChange={(table, page, perPage) => {
149
157
  persistListStateToUrl(table, { page, perPage });
158
+ if (pageId) {
159
+ setTableSettings(pageId, 'pageSize', perPage);
160
+ }
150
161
  }}
151
162
  onSortChange={(table, sorting) => {
152
163
  persistListStateToUrl(table, { sort: sorting });
153
164
  }}
154
165
  onFilterChange={(table, filters) => {
155
166
  persistListStateToUrl(table, { filters });
167
+ if (pageId) {
168
+ setTableSettings(pageId, 'columnFilters', filters);
169
+ }
170
+ }}
171
+ onColumnVisibilityChange={(table, columnVisibility) => {
172
+ if (pageId) {
173
+ setTableSettings(pageId, 'columnVisibility', columnVisibility);
174
+ }
156
175
  }}
157
176
  facetedFilters={facetedFilters}
158
177
  rowActions={rowActions}
@@ -195,6 +195,7 @@ export function useDetailPage<
195
195
  const document = isNew ? (createDocument ?? updateDocument) : updateDocument;
196
196
  const { form, submitHandler } = useGeneratedForm({
197
197
  document,
198
+ varName: 'input',
198
199
  entity,
199
200
  setValues: setValuesForUpdate,
200
201
  onSubmit(values: any) {
@@ -51,4 +51,12 @@ export const configurableOperationDefFragment = graphql(`
51
51
  }
52
52
  `);
53
53
 
54
+ export const errorResultFragment = graphql(`
55
+ fragment ErrorResult on ErrorResult {
56
+ errorCode
57
+ message
58
+ }
59
+ `);
60
+
61
+
54
62
  export type ConfigurableOperationDefFragment = ResultOf<typeof configurableOperationDefFragment>;
package/src/lib/index.ts CHANGED
@@ -28,10 +28,10 @@ export * from './components/layout/nav-user.js';
28
28
  export * from './components/login/login-form.js';
29
29
  export * from './components/shared/alerts.js';
30
30
  export * from './components/shared/animated-number.js';
31
- export * from './components/shared/asset-gallery.js';
32
- export * from './components/shared/asset-picker-dialog.js';
33
- export * from './components/shared/asset-preview-dialog.js';
34
- export * from './components/shared/asset-preview.js';
31
+ export * from './components/shared/asset/asset-gallery.js';
32
+ export * from './components/shared/asset/asset-picker-dialog.js';
33
+ export * from './components/shared/asset/asset-preview-dialog.js';
34
+ export * from './components/shared/asset/asset-preview.js';
35
35
  export * from './components/shared/assigned-facet-values.js';
36
36
  export * from './components/shared/channel-code-label.js';
37
37
  export * from './components/shared/channel-selector.js';
@@ -50,7 +50,7 @@ export * from './components/shared/entity-assets.js';
50
50
  export * from './components/shared/error-page.js';
51
51
  export * from './components/shared/facet-value-chip.js';
52
52
  export * from './components/shared/facet-value-selector.js';
53
- export * from './components/shared/focal-point-control.js';
53
+ export * from './components/shared/asset/focal-point-control.js';
54
54
  export * from './components/shared/form-field-wrapper.js';
55
55
  export * from './components/shared/history-timeline/history-entry.js';
56
56
  export * from './components/shared/history-timeline/history-note-checkbox.js';
@@ -69,17 +69,20 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
69
69
  const onLogoutSuccessFn = React.useRef<() => void>(() => {});
70
70
  const isAuthenticated = status === 'authenticated';
71
71
 
72
- const { data: currentUserData, isLoading } = useQuery({
73
- queryKey: ['currentUser'],
72
+ const { data: currentUserData, isLoading , error: currentUserError} = useQuery({
73
+ queryKey: ['currentUser', isAuthenticated],
74
74
  queryFn: () => api.query(CurrentUserQuery),
75
75
  retry: false,
76
+ staleTime: 1000,
76
77
  });
77
78
 
79
+ const currentUser = currentUserError ? undefined : currentUserData;
80
+
78
81
  React.useEffect(() => {
79
- if (!settings.activeChannelId && currentUserData?.me?.channels?.length) {
80
- setActiveChannelId(currentUserData.me.channels[0].id);
82
+ if (!settings.activeChannelId && currentUser?.me?.channels?.length) {
83
+ setActiveChannelId(currentUser.me.channels[0].id);
81
84
  }
82
- }, [settings.activeChannelId, currentUserData?.me?.channels]);
85
+ }, [settings.activeChannelId, currentUser?.me?.channels]);
83
86
 
84
87
  const loginMutationFn = api.mutate(LoginMutation);
85
88
  const loginMutation = useMutation({
@@ -123,7 +126,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
123
126
 
124
127
  React.useEffect(() => {
125
128
  if (!isLoading) {
126
- if (currentUserData?.me?.id) {
129
+ if (currentUser?.me?.id) {
127
130
  setStatus('authenticated');
128
131
  } else {
129
132
  setStatus('unauthenticated');
@@ -131,7 +134,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
131
134
  } else {
132
135
  setStatus('verifying');
133
136
  }
134
- }, [isLoading, currentUserData]);
137
+ }, [isLoading, currentUser]);
135
138
 
136
139
  return (
137
140
  <AuthContext.Provider
@@ -139,8 +142,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
139
142
  isAuthenticated,
140
143
  authenticationError,
141
144
  status,
142
- user: currentUserData?.activeAdministrator,
143
- channels: currentUserData?.me?.channels,
145
+ user: currentUser?.activeAdministrator,
146
+ channels: currentUser?.me?.channels,
144
147
  login,
145
148
  logout,
146
149
  }}
@@ -73,6 +73,7 @@ export function ChannelProvider({ children }: { children: React.ReactNode }) {
73
73
  const { data: channelsData, isLoading: isChannelsLoading } = useQuery({
74
74
  queryKey: ['channels'],
75
75
  queryFn: () => api.query(ChannelsQuery),
76
+ retry: false,
76
77
  });
77
78
 
78
79
  // Set the selected channel and update localStorage
@@ -1,5 +1,6 @@
1
1
  import { api } from '@/graphql/api.js';
2
2
  import { graphql } from '@/graphql/graphql.js';
3
+ import { useAuth } from '@/hooks/use-auth.js';
3
4
  import { useQuery } from '@tanstack/react-query';
4
5
  import { ResultOf } from 'gql.tada';
5
6
  import React from 'react';
@@ -260,9 +261,14 @@ export interface ServerConfig {
260
261
 
261
262
  // create a provider for the global settings
262
263
  export const ServerConfigProvider = ({ children }: { children: React.ReactNode }) => {
264
+ const { user } = useAuth();
265
+ const queryKey = ['getServerConfig', user?.id];
263
266
  const { data } = useQuery({
264
- queryKey: ['getServerConfig'],
267
+ queryKey,
265
268
  queryFn: () => api.query(getServerConfigDocument),
269
+ retry: false,
270
+ enabled: !!user?.id,
271
+ staleTime: 1000,
266
272
  });
267
273
  const value: ServerConfig = {
268
274
  availableLanguages: data?.globalSettings.availableLanguages ?? [],
@@ -1,5 +1,13 @@
1
1
  import React, { createContext, useState, useEffect } from 'react';
2
2
  import { Theme } from './theme-provider.js';
3
+ import { ColumnFiltersState } from '@tanstack/react-table';
4
+
5
+ export interface TableSettings {
6
+ columnVisibility?: Record<string, boolean>;
7
+ columnOrder?: string[];
8
+ columnFilters?: ColumnFiltersState;
9
+ pageSize?: number;
10
+ }
3
11
 
4
12
  export interface UserSettings {
5
13
  displayLanguage: string;
@@ -11,6 +19,7 @@ export interface UserSettings {
11
19
  activeChannelId: string;
12
20
  devMode: boolean;
13
21
  hasSeenOnboarding: boolean;
22
+ tableSettings?: Record<string, TableSettings>;
14
23
  }
15
24
 
16
25
  const defaultSettings: UserSettings = {
@@ -23,6 +32,7 @@ const defaultSettings: UserSettings = {
23
32
  activeChannelId: '',
24
33
  devMode: false,
25
34
  hasSeenOnboarding: false,
35
+ tableSettings: {},
26
36
  };
27
37
 
28
38
  export interface UserSettingsContextType {
@@ -36,6 +46,11 @@ export interface UserSettingsContextType {
36
46
  setActiveChannelId: (channelId: string) => void;
37
47
  setDevMode: (devMode: boolean) => void;
38
48
  setHasSeenOnboarding: (hasSeen: boolean) => void;
49
+ setTableSettings: <K extends keyof TableSettings>(
50
+ tableId: string,
51
+ key: K,
52
+ value: TableSettings[K],
53
+ ) => void;
39
54
  }
40
55
 
41
56
  export const UserSettingsContext = createContext<UserSettingsContextType | undefined>(undefined);
@@ -83,6 +98,15 @@ export const UserSettingsProvider: React.FC<React.PropsWithChildren<{}>> = ({ ch
83
98
  setActiveChannelId: channelId => updateSetting('activeChannelId', channelId),
84
99
  setDevMode: devMode => updateSetting('devMode', devMode),
85
100
  setHasSeenOnboarding: hasSeen => updateSetting('hasSeenOnboarding', hasSeen),
101
+ setTableSettings: (tableId, key, value) => {
102
+ setSettings(prev => ({
103
+ ...prev,
104
+ tableSettings: {
105
+ ...prev.tableSettings,
106
+ [tableId]: { ...(prev.tableSettings?.[tableId] || {}), [key]: value },
107
+ },
108
+ }));
109
+ },
86
110
  };
87
111
 
88
112
  return <UserSettingsContext.Provider value={contextValue}>{children}</UserSettingsContext.Provider>;