@questpie/admin 3.2.6 → 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 (86) hide show
  1. package/dist/client/components/admin-link.d.mts +2 -2
  2. package/dist/client/components/blocks/block-editor-provider.mjs +13 -0
  3. package/dist/client/components/fields/array-field.mjs +105 -122
  4. package/dist/client/components/fields/asset-preview-field.mjs +1 -1
  5. package/dist/client/components/fields/blocks-field/blocks-field.mjs +1 -1
  6. package/dist/client/components/fields/boolean-field.mjs +1 -1
  7. package/dist/client/components/fields/date-field.mjs +1 -1
  8. package/dist/client/components/fields/datetime-field.mjs +1 -1
  9. package/dist/client/components/fields/email-field.mjs +1 -1
  10. package/dist/client/components/fields/field-wrapper.mjs +44 -15
  11. package/dist/client/components/fields/number-field.mjs +1 -1
  12. package/dist/client/components/fields/object-array-field.mjs +179 -149
  13. package/dist/client/components/fields/object-field.mjs +96 -87
  14. package/dist/client/components/fields/relation-picker.mjs +1 -1
  15. package/dist/client/components/fields/relation-select.mjs +1 -1
  16. package/dist/client/components/fields/rich-text-editor/index.mjs +1 -1
  17. package/dist/client/components/fields/select-field.mjs +1 -1
  18. package/dist/client/components/fields/text-field.mjs +1 -1
  19. package/dist/client/components/fields/textarea-field.mjs +1 -1
  20. package/dist/client/components/fields/time-field.mjs +1 -1
  21. package/dist/client/components/fields/upload-field.mjs +1 -1
  22. package/dist/client/components/history-sidebar.mjs +10 -4
  23. package/dist/client/components/structured-diff.mjs +367 -0
  24. package/dist/client/components/ui/sidebar.mjs +1 -1
  25. package/dist/client/hooks/use-field-options.mjs +34 -15
  26. package/dist/client/hooks/use-transition-stage.mjs +2 -2
  27. package/dist/client/preview/preview-banner.d.mts +2 -2
  28. package/dist/client/preview/preview-field.d.mts +4 -4
  29. package/dist/client/preview/use-collection-preview.mjs +0 -35
  30. package/dist/client/utils/auto-expand-fields.mjs +1 -1
  31. package/dist/client/views/collection/auto-form-fields.mjs +23 -19
  32. package/dist/client/views/collection/cells/complex-cells.mjs +1 -1
  33. package/dist/client/views/collection/columns/build-columns.mjs +1 -1
  34. package/dist/client/views/collection/columns/column-defaults.mjs +17 -4
  35. package/dist/client/views/collection/field-renderer.mjs +19 -7
  36. package/dist/client/views/collection/form-view.mjs +10 -6
  37. package/dist/client/views/collection/table-view.mjs +19 -13
  38. package/dist/client/views/globals/global-form-view.mjs +47 -27
  39. package/dist/client/views/layout/admin-sidebar.mjs +2 -2
  40. package/dist/client/views/pages/login-page.d.mts +2 -2
  41. package/dist/client/views/pages/reset-password-page.d.mts +2 -2
  42. package/dist/client/views/pages/setup-page.d.mts +2 -2
  43. package/dist/client.mjs +1 -1
  44. package/dist/index.mjs +1 -1
  45. package/dist/server/adapters/nextjs.d.mts +0 -1
  46. package/dist/server/i18n/messages/cs.mjs +8 -0
  47. package/dist/server/i18n/messages/de.mjs +8 -0
  48. package/dist/server/i18n/messages/en.mjs +8 -0
  49. package/dist/server/i18n/messages/es.mjs +8 -0
  50. package/dist/server/i18n/messages/fr.mjs +8 -0
  51. package/dist/server/i18n/messages/pl.mjs +8 -0
  52. package/dist/server/i18n/messages/pt.mjs +8 -0
  53. package/dist/server/i18n/messages/sk.mjs +8 -0
  54. package/dist/server/modules/admin/.generated/module.d.mts +0 -1
  55. package/dist/server/modules/admin/collections/account.d.mts +46 -46
  56. package/dist/server/modules/admin/collections/admin-locks.d.mts +54 -54
  57. package/dist/server/modules/admin/collections/admin-preferences.d.mts +39 -39
  58. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +47 -47
  59. package/dist/server/modules/admin/collections/apikey.d.mts +68 -68
  60. package/dist/server/modules/admin/collections/assets.d.mts +34 -34
  61. package/dist/server/modules/admin/collections/session.d.mts +38 -38
  62. package/dist/server/modules/admin/collections/user.d.mts +53 -53
  63. package/dist/server/modules/admin/collections/verification.d.mts +2 -2
  64. package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
  65. package/dist/server/modules/admin/routes/preview.d.mts +13 -8
  66. package/dist/server/modules/admin/routes/preview.mjs +83 -62
  67. package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
  68. package/dist/server/modules/admin/routes/setup.d.mts +7 -7
  69. package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
  70. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +33 -33
  71. package/dist/server/modules/audit/.generated/module.d.mts +6 -6
  72. package/dist/server/modules/audit/collections/audit-log.d.mts +39 -37
  73. package/dist/server/modules/audit/collections/audit-log.mjs +7 -2
  74. package/dist/server/modules/audit/config/localize-title.mjs +67 -0
  75. package/dist/server/modules/audit/index.d.mts +2 -1
  76. package/dist/server/modules/audit/log-audit-entry.d.mts +85 -0
  77. package/dist/server/modules/audit/log-audit-entry.mjs +125 -0
  78. package/dist/server/plugin.mjs +4 -4
  79. package/dist/server.d.mts +2 -3
  80. package/dist/server.mjs +2 -1
  81. package/dist/shared/preview-utils.d.mts +4 -4
  82. package/dist/shared/preview-utils.mjs +5 -7
  83. package/package.json +3 -3
  84. package/dist/client/hooks/use-audit-history.mjs +0 -38
  85. package/dist/server/adapters/index.d.mts +0 -2
  86. package/dist/server/auth-helpers.d.mts +0 -1
@@ -385,20 +385,21 @@ function extractFieldNamesFromFieldItems(fieldItems) {
385
385
  * Supports recursive tabs/sections nesting and auto-generates
386
386
  * field list when no form config is defined.
387
387
  */
388
- function AutoFormFields({ app: _cms, collection, mode = "collection", config, registry, fieldResolver, fieldPrefix, allCollectionsConfig }) {
388
+ function AutoFormFields({ app: _cms, collection, mode = "collection", config, registry, fieldResolver, fieldPrefix, allCollectionsConfig, resolvedFields: providedFields, schema: providedSchema }) {
389
389
  const isActionForm = collection === "__action__";
390
+ const shouldFetchSchema = !providedFields || !providedSchema;
390
391
  const collectionResult = useCollectionFields(mode === "collection" ? collection : "", {
391
392
  fallbackFields: mode === "collection" ? config?.fields : void 0,
392
- schemaQueryOptions: { enabled: mode === "collection" && !isActionForm }
393
+ schemaQueryOptions: { enabled: mode === "collection" && !isActionForm && shouldFetchSchema }
393
394
  });
394
- const globalResult = useGlobalFields(mode === "global" ? collection : "", { schemaQueryOptions: { enabled: mode === "global" } });
395
+ const globalResult = useGlobalFields(mode === "global" ? collection : "", { schemaQueryOptions: { enabled: mode === "global" && shouldFetchSchema } });
395
396
  const { data: collectionMeta } = useCollectionMeta(collection, { enabled: mode === "collection" && !isActionForm });
396
397
  const { data: globalMeta } = useGlobalMeta(collection, { enabled: mode === "global" });
397
398
  const resolvedFields = mode === "global" ? {
398
- ...globalResult.fields,
399
+ ...providedFields ?? globalResult.fields,
399
400
  ...config?.fields
400
- } : collectionResult.fields;
401
- const schema = mode === "global" ? globalResult.schema : collectionResult.schema;
401
+ } : providedFields ?? collectionResult.fields;
402
+ const schema = providedSchema ?? (mode === "global" ? globalResult.schema : collectionResult.schema);
402
403
  const entityMeta = mode === "global" ? globalMeta : collectionMeta;
403
404
  const form = useFormContext();
404
405
  const resolveText = useResolveText();
@@ -476,20 +477,23 @@ function AutoFormFields({ app: _cms, collection, mode = "collection", config, re
476
477
  className: "qa-form-fields__main min-w-0 flex-1",
477
478
  children: mainContent
478
479
  }), /* @__PURE__ */ jsx("aside", {
479
- className: cn("qa-form-fields__sidebar", "w-full @max-2xl:pb-2 @2xl:max-w-[18rem] @2xl:pl-1"),
480
+ className: cn("qa-form-fields__sidebar", "w-full @max-2xl:pb-2 @2xl:sticky @2xl:top-4 @2xl:h-[calc(100svh-7rem)] @2xl:min-h-0 @2xl:max-w-[18rem] @2xl:shrink-0 @2xl:self-start @2xl:pl-1"),
480
481
  children: /* @__PURE__ */ jsx("div", {
481
- className: "bg-surface-low/45 space-y-4 rounded-md px-3 py-3 @2xl:sticky @2xl:top-4 @2xl:h-auto",
482
- children: /* @__PURE__ */ jsx(SidebarRenderer, {
483
- sidebar: formConfig.sidebar,
484
- fields,
485
- collection,
486
- mode,
487
- registry,
488
- fieldPrefix,
489
- allCollectionsConfig,
490
- entityMeta,
491
- formValues,
492
- resolveText
482
+ className: "bg-surface-low/45 flex min-h-0 flex-col rounded-md @2xl:h-full",
483
+ children: /* @__PURE__ */ jsx("div", {
484
+ className: "scrollbar-thin min-h-0 space-y-4 overflow-y-auto px-3 py-3 @2xl:flex-1",
485
+ children: /* @__PURE__ */ jsx(SidebarRenderer, {
486
+ sidebar: formConfig.sidebar,
487
+ fields,
488
+ collection,
489
+ mode,
490
+ registry,
491
+ fieldPrefix,
492
+ allCollectionsConfig,
493
+ entityMeta,
494
+ formValues,
495
+ resolveText
496
+ })
493
497
  })
494
498
  })
495
499
  })]
@@ -1,7 +1,7 @@
1
1
  import { useResolveText, useTranslation } from "../../../i18n/hooks.mjs";
2
2
  import { cn } from "../../../lib/utils.mjs";
3
- import { Badge } from "../../../components/ui/badge.mjs";
4
3
  import { isBlockContent } from "../../../blocks/types.mjs";
4
+ import { Badge } from "../../../components/ui/badge.mjs";
5
5
  import { Tooltip, TooltipContent, TooltipTrigger } from "../../../components/ui/tooltip.mjs";
6
6
  import { useAdminConfig } from "../../../hooks/use-admin-config.mjs";
7
7
  import { formatFieldLabel, formatPrimitiveValue, getFieldLabel, getItemLabel, summarizeValue } from "./shared/cell-helpers.mjs";
@@ -1,8 +1,8 @@
1
1
  import { useResolveText } from "../../../i18n/hooks.mjs";
2
2
  import { useSafeContentLocales } from "../../../runtime/content-locales-provider.mjs";
3
3
  import { useScopedLocale } from "../../../runtime/locale-scope.mjs";
4
- import { DefaultCell, TextCell } from "../cells/primitive-cells.mjs";
5
4
  import { LocaleSwitcher } from "../../../components/locale-switcher.mjs";
5
+ import { DefaultCell, TextCell } from "../cells/primitive-cells.mjs";
6
6
  import { normalizeColumnConfig } from "../../../builder/types/collection-types.mjs";
7
7
  import { computeDefaultColumns, formatHeader } from "./column-defaults.mjs";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -12,6 +12,18 @@ const SYSTEM_FIELDS = new Set([
12
12
  "createdBy",
13
13
  "updatedBy"
14
14
  ]);
15
+ const DEFAULT_CONTENT_COLUMN_LIMIT = 4;
16
+ const HEAVY_DEFAULT_FIELD_TYPES = new Set([
17
+ "relation",
18
+ "reverseRelation",
19
+ "upload",
20
+ "uploadMany",
21
+ "blocks",
22
+ "json",
23
+ "object",
24
+ "array",
25
+ "richText"
26
+ ]);
15
27
  /**
16
28
  * Compute sane default visible columns for a collection
17
29
  *
@@ -39,13 +51,14 @@ function computeDefaultColumns(fields, options) {
39
51
  const defaultCols = [titleColumn];
40
52
  if (!fields || Object.keys(fields).length === 0) return defaultCols;
41
53
  const contentFields = [];
42
- const systemFields = [];
43
54
  for (const key of Object.keys(fields)) {
44
55
  if (useTitleField && key === titleFieldName) continue;
45
- if (SYSTEM_FIELDS.has(key)) systemFields.push(key);
46
- else contentFields.push(key);
56
+ if (SYSTEM_FIELDS.has(key)) continue;
57
+ const fieldType = fields[key]?.name ?? "text";
58
+ if (HEAVY_DEFAULT_FIELD_TYPES.has(fieldType)) continue;
59
+ contentFields.push(key);
47
60
  }
48
- defaultCols.push(...contentFields);
61
+ defaultCols.push(...contentFields.slice(0, DEFAULT_CONTENT_COLUMN_LIMIT));
49
62
  if ((options?.meta?.timestamps ?? !!fields.createdAt) && !defaultCols.includes("createdAt")) defaultCols.push("createdAt");
50
63
  return defaultCols;
51
64
  }
@@ -95,6 +95,14 @@ function renderDefinitionComponent({ context, componentProps, blocks }) {
95
95
  });
96
96
  return /* @__PURE__ */ jsx(Component, { ...componentProps });
97
97
  }
98
+ function BlocksDefinitionComponent({ context, componentProps }) {
99
+ const { data: adminConfig } = useAdminConfig();
100
+ return renderDefinitionComponent({
101
+ context,
102
+ componentProps,
103
+ blocks: adminConfig?.blocks
104
+ });
105
+ }
98
106
  /**
99
107
  * Render embedded collection field
100
108
  *
@@ -133,7 +141,6 @@ function FieldRenderer({ fieldName, fieldDef, collection, mode = "collection", r
133
141
  const form = useFormContext();
134
142
  const { locale } = useScopedLocale();
135
143
  const resolveText = useResolveText();
136
- const { data: adminConfig } = useAdminConfig();
137
144
  const fullFieldName = getFullFieldName(fieldName, fieldPrefix);
138
145
  const fieldOptions = React.useMemo(() => getFieldOptions(fieldDef), [fieldDef]);
139
146
  const dynamicDependencyPaths = React.useMemo(() => computeDynamicDependencyPaths({
@@ -222,14 +229,19 @@ function FieldRenderer({ fieldName, fieldDef, collection, mode = "collection", r
222
229
  renderEmbeddedFields
223
230
  });
224
231
  if (!content && componentLoading) content = /* @__PURE__ */ jsx(FieldComponentSkeleton, { type: context.type });
225
- if (!content && resolvedComponent) content = renderDefinitionComponent({
226
- context: {
232
+ if (!content && resolvedComponent) {
233
+ const componentContext = {
227
234
  ...context,
228
235
  component: resolvedComponent
229
- },
230
- componentProps,
231
- blocks: adminConfig?.blocks
232
- });
236
+ };
237
+ content = context.type === "blocks" ? /* @__PURE__ */ jsx(BlocksDefinitionComponent, {
238
+ context: componentContext,
239
+ componentProps
240
+ }) : renderDefinitionComponent({
241
+ context: componentContext,
242
+ componentProps
243
+ });
244
+ }
233
245
  if (!content) content = renderConfigError(`No component registered for field type "${context.type}" (field: "${context.fieldName}").`);
234
246
  if (className) return /* @__PURE__ */ jsx("div", {
235
247
  className,
@@ -4,11 +4,11 @@ import { useSafeContentLocales } from "../../runtime/content-locales-provider.mj
4
4
  import { useScopedLocale } from "../../runtime/locale-scope.mjs";
5
5
  import { resolveIconElement } from "../../components/component-renderer.mjs";
6
6
  import { Button } from "../../components/ui/button.mjs";
7
- import { Badge } from "../../components/ui/badge.mjs";
8
- import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../../components/ui/dialog.mjs";
9
7
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "../../components/ui/dropdown-menu.mjs";
10
8
  import { LocaleSwitcher } from "../../components/locale-switcher.mjs";
11
9
  import { Label } from "../../components/ui/label.mjs";
10
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../../components/ui/dialog.mjs";
11
+ import { Badge } from "../../components/ui/badge.mjs";
12
12
  import { RenderProfiler } from "../../lib/render-profiler.mjs";
13
13
  import { scrollFieldIntoView } from "../../contexts/focus-context.mjs";
14
14
  import { Checkbox } from "../../components/ui/checkbox.mjs";
@@ -144,7 +144,7 @@ function ReactiveFieldsManager({ collection, mode = "collection", reactiveConfig
144
144
  });
145
145
  return null;
146
146
  }
147
- const FormFieldsContent = React.memo(function FormFieldsContent$1({ collection, config, registry, allCollectionsConfig }) {
147
+ const FormFieldsContent = React.memo(function FormFieldsContent$1({ collection, config, registry, allCollectionsConfig, resolvedFields, schema }) {
148
148
  return /* @__PURE__ */ jsx(RenderProfiler, {
149
149
  id: `form.fields.${collection}`,
150
150
  minDurationMs: 10,
@@ -152,7 +152,9 @@ const FormFieldsContent = React.memo(function FormFieldsContent$1({ collection,
152
152
  collection,
153
153
  config,
154
154
  registry,
155
- allCollectionsConfig
155
+ allCollectionsConfig,
156
+ resolvedFields,
157
+ schema
156
158
  })
157
159
  });
158
160
  });
@@ -930,7 +932,7 @@ function FormView({ collection, id, config, viewConfig, navigate, basePath = "/a
930
932
  if (handler.body) body = JSON.stringify(handler.body(actionContext));
931
933
  setActionLoading(true);
932
934
  const apiPromise = async () => {
933
- const url = `${storeBasePath}/${collection}/${endpoint}`;
935
+ const url = `${client.getBasePath?.() ?? storeBasePath}/${collection}/${endpoint}`;
934
936
  const response = await fetch(url, {
935
937
  method,
936
938
  headers: { "Content-Type": "application/json" },
@@ -1402,7 +1404,9 @@ function FormView({ collection, id, config, viewConfig, navigate, basePath = "/a
1402
1404
  collection,
1403
1405
  config: formConfigBridge,
1404
1406
  registry,
1405
- allCollectionsConfig
1407
+ allCollectionsConfig,
1408
+ resolvedFields,
1409
+ schema
1406
1410
  })
1407
1411
  ]
1408
1412
  })
@@ -7,10 +7,10 @@ import { flattenOptions } from "../../components/primitives/types.mjs";
7
7
  import { resolveOptionLabelForValue } from "../../components/primitives/option-label.mjs";
8
8
  import { Button } from "../../components/ui/button.mjs";
9
9
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../components/ui/select.mjs";
10
+ import { LocaleSwitcher } from "../../components/locale-switcher.mjs";
10
11
  import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "../../components/ui/sheet.mjs";
11
12
  import { sanitizeFilename } from "../../components/fields/field-utils.mjs";
12
13
  import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/tooltip.mjs";
13
- import { LocaleSwitcher } from "../../components/locale-switcher.mjs";
14
14
  import { Checkbox } from "../../components/ui/checkbox.mjs";
15
15
  import { createActionRegistryProxy } from "../../builder/types/action-registry.mjs";
16
16
  import { ActionButton } from "../../components/actions/action-button.mjs";
@@ -52,7 +52,7 @@ import { DndContext, DragOverlay, KeyboardSensor, PointerSensor, closestCenter,
52
52
  import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
53
53
  import { CSS } from "@dnd-kit/utilities";
54
54
  import { toast } from "sonner";
55
- import { flexRender, getCoreRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
55
+ import { flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
56
56
 
57
57
  //#region src/client/views/collection/table-view.tsx
58
58
  /**
@@ -422,15 +422,6 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
422
422
  resolvedListConfig,
423
423
  collectionMeta
424
424
  ]);
425
- const expandedFields = useMemo(() => autoExpandFields({
426
- fields: resolvedFields,
427
- list: resolvedListConfig,
428
- relations: collectionMeta?.relations
429
- }), [
430
- resolvedFields,
431
- resolvedListConfig,
432
- collectionMeta?.relations
433
- ]);
434
425
  const [isSheetOpen, setIsSheetOpen] = useSidebarSearchParam("view-options", { legacyKey: "viewOptions" });
435
426
  const [searchTerm, setSearchTerm] = useState("");
436
427
  const [isSearchPanelOpen, setIsSearchPanelOpen] = useState(false);
@@ -479,6 +470,18 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
479
470
  groupBy: defaultGroupBy
480
471
  }, collection, user?.id);
481
472
  const effectiveRealtime = viewState.config.realtime ?? resolvedRealtime;
473
+ const visibleColumnsForExpansion = useMemo(() => viewState.config.visibleColumns.length > 0 ? viewState.config.visibleColumns : defaultColumns, [viewState.config.visibleColumns, defaultColumns]);
474
+ const expandedFields = useMemo(() => autoExpandFields({
475
+ fields: resolvedFields,
476
+ list: resolvedListConfig,
477
+ visibleColumns: visibleColumnsForExpansion,
478
+ relations: collectionMeta?.relations
479
+ }), [
480
+ resolvedFields,
481
+ resolvedListConfig,
482
+ visibleColumnsForExpansion,
483
+ collectionMeta?.relations
484
+ ]);
482
485
  const isKnownSortField = React.useCallback((field) => !!field && (field === "_title" || !!resolvedFields?.[field]), [resolvedFields]);
483
486
  const hasOrderField = isKnownSortField(orderField);
484
487
  const canUseOrderableSort = isOrderableEnabled && hasOrderField;
@@ -489,7 +492,10 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
489
492
  field: orderField,
490
493
  direction: orderDirection
491
494
  };
492
- return null;
495
+ return {
496
+ field: "createdAt",
497
+ direction: "desc"
498
+ };
493
499
  }, [
494
500
  viewState.config.sortConfig,
495
501
  resolvedListConfig?.defaultSort,
@@ -875,7 +881,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
875
881
  data: filteredItems,
876
882
  columns: visibleColumnDefs,
877
883
  getCoreRowModel: getCoreRowModel(),
878
- getSortedRowModel: getSortedRowModel(),
884
+ manualSorting: true,
879
885
  onSortingChange: handleSortingChange,
880
886
  enableRowSelection: true,
881
887
  onRowSelectionChange: setRowSelection,
@@ -2,11 +2,11 @@ import { useResolveText, useTranslation } from "../../i18n/hooks.mjs";
2
2
  import { useSafeContentLocales } from "../../runtime/content-locales-provider.mjs";
3
3
  import { useScopedLocale } from "../../runtime/locale-scope.mjs";
4
4
  import { Button } from "../../components/ui/button.mjs";
5
- import { Badge } from "../../components/ui/badge.mjs";
6
- import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../../components/ui/dialog.mjs";
7
5
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../../components/ui/dropdown-menu.mjs";
8
6
  import { LocaleSwitcher } from "../../components/locale-switcher.mjs";
9
7
  import { Label } from "../../components/ui/label.mjs";
8
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../../components/ui/dialog.mjs";
9
+ import { Badge } from "../../components/ui/badge.mjs";
10
10
  import { Skeleton } from "../../components/ui/skeleton.mjs";
11
11
  import { Checkbox } from "../../components/ui/checkbox.mjs";
12
12
  import { DateTimeInput } from "../../components/primitives/date-input.mjs";
@@ -23,11 +23,10 @@ import { useTransitionStage } from "../../hooks/use-transition-stage.mjs";
23
23
  import { detectManyToManyRelations, hasManyToManyRelations } from "../../utils/detect-relations.mjs";
24
24
  import { shouldHandleAdminShortcut } from "../../utils/keyboard-shortcuts.mjs";
25
25
  import { AdminViewHeader } from "../layout/admin-view-layout.mjs";
26
- import { useGlobalAuditHistory } from "../../hooks/use-audit-history.mjs";
27
26
  import { Icon } from "@iconify/react";
28
27
  import * as React from "react";
29
28
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
30
- import { FormProvider, useForm } from "react-hook-form";
29
+ import { FormProvider, useForm, useFormState } from "react-hook-form";
31
30
  import { toast } from "sonner";
32
31
  import { QuestpieClientError } from "questpie/client";
33
32
 
@@ -97,6 +96,31 @@ function GlobalFormViewSkeleton() {
97
96
  ]
98
97
  });
99
98
  }
99
+ const GlobalFormDirtyRefBridge = React.memo(function GlobalFormDirtyRefBridge$1({ control, onDirtyChange }) {
100
+ const { isDirty } = useFormState({ control });
101
+ React.useEffect(() => {
102
+ onDirtyChange(isDirty);
103
+ }, [isDirty, onDirtyChange]);
104
+ return null;
105
+ });
106
+ const GlobalSaveButton = React.memo(function GlobalSaveButton$1({ control, isMutationPending, t }) {
107
+ const { isSubmitting } = useFormState({ control });
108
+ const isSubmittingNow = isMutationPending || isSubmitting;
109
+ return /* @__PURE__ */ jsx(Button, {
110
+ type: "submit",
111
+ size: "sm",
112
+ disabled: isSubmittingNow,
113
+ className: "gap-2",
114
+ children: isSubmittingNow ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Icon, {
115
+ icon: "ph:spinner-gap",
116
+ className: "size-4 animate-spin"
117
+ }), t("common.loading")] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Icon, {
118
+ icon: "ph:check",
119
+ width: 16,
120
+ height: 16
121
+ }), t("common.save")] })
122
+ });
123
+ });
100
124
  /**
101
125
  * GlobalFormView - Default form-based edit view for globals
102
126
  */
@@ -136,7 +160,6 @@ function GlobalFormView({ global: globalName, config, viewConfig, registry, show
136
160
  id: globalData?.id,
137
161
  limit: 50
138
162
  }, { enabled: isHistoryOpen && !!globalSchema?.options?.versioning });
139
- const { data: auditData, isLoading: auditLoading } = useGlobalAuditHistory(globalName, { limit: 50 }, { enabled: isHistoryOpen });
140
163
  const workflowConfig = globalSchema?.options?.workflow;
141
164
  const workflowEnabled = !!workflowConfig?.enabled;
142
165
  /** Lightweight versions query (limit: 1) to read the current stage. */
@@ -167,6 +190,10 @@ function GlobalFormView({ global: globalName, config, viewConfig, registry, show
167
190
  defaultValues: globalData ?? {},
168
191
  resolver
169
192
  });
193
+ const formIsDirtyRef = React.useRef(false);
194
+ const handleFormDirtyChange = React.useCallback((isDirty) => {
195
+ formIsDirtyRef.current = isDirty;
196
+ }, []);
170
197
  /** Execute the confirmed workflow transition (immediate or scheduled). */
171
198
  const confirmTransition = React.useCallback(() => {
172
199
  if (!transitionTarget) return;
@@ -215,7 +242,7 @@ function GlobalFormView({ global: globalName, config, viewConfig, registry, show
215
242
  });
216
243
  const handleContentLocaleChange = React.useCallback((nextLocale) => {
217
244
  if (nextLocale === prevLocaleRef.current) return;
218
- if (form.formState.isDirty && !localeChangeDialog.open) {
245
+ if (formIsDirtyRef.current && !localeChangeDialog.open) {
219
246
  skipResetRef.current = true;
220
247
  localeSnapshotRef.current = form.getValues();
221
248
  setLocaleChangeDialog({
@@ -229,7 +256,6 @@ function GlobalFormView({ global: globalName, config, viewConfig, registry, show
229
256
  setContentLocale(nextLocale);
230
257
  }, [
231
258
  form,
232
- form.formState.isDirty,
233
259
  localeChangeDialog.open,
234
260
  setContentLocale
235
261
  ]);
@@ -316,7 +342,6 @@ function GlobalFormView({ global: globalName, config, viewConfig, registry, show
316
342
  document.addEventListener("keydown", handleKeyDown);
317
343
  return () => document.removeEventListener("keydown", handleKeyDown);
318
344
  }, [form, onSubmit]);
319
- const isSubmitting = updateMutation.isPending || form.formState.isSubmitting;
320
345
  const confirmRevertVersion = React.useCallback(async () => {
321
346
  if (!pendingRevertVersion) return;
322
347
  const payload = {};
@@ -361,10 +386,14 @@ function GlobalFormView({ global: globalName, config, viewConfig, registry, show
361
386
  })
362
387
  });
363
388
  if (dataLoading) return /* @__PURE__ */ jsx(GlobalFormViewSkeleton, {});
364
- const globalLabel = resolveText(resolvedConfig?.label ?? schemaFields?._globalLabel, globalName);
389
+ const globalLabel = resolveText(resolvedConfig?.label ?? schemaFields["_globalLabel"], globalName);
365
390
  return /* @__PURE__ */ jsxs(FormProvider, {
366
391
  ...form,
367
392
  children: [
393
+ /* @__PURE__ */ jsx(GlobalFormDirtyRefBridge, {
394
+ control: form.control,
395
+ onDirtyChange: handleFormDirtyChange
396
+ }),
368
397
  /* @__PURE__ */ jsxs("form", {
369
398
  onSubmit: form.handleSubmit(onSubmit),
370
399
  className: "qa-global-form w-full space-y-4",
@@ -414,7 +443,7 @@ function GlobalFormView({ global: globalName, config, viewConfig, registry, show
414
443
  }), stage.label || stage.name]
415
444
  }, stage.name))
416
445
  })] }),
417
- /* @__PURE__ */ jsxs(Button, {
446
+ globalSchema?.options?.versioning && /* @__PURE__ */ jsxs(Button, {
418
447
  type: "button",
419
448
  variant: "outline",
420
449
  size: "icon-sm",
@@ -428,33 +457,24 @@ function GlobalFormView({ global: globalName, config, viewConfig, registry, show
428
457
  children: t("history.title")
429
458
  })]
430
459
  }),
431
- /* @__PURE__ */ jsx(Button, {
432
- type: "submit",
433
- size: "sm",
434
- disabled: isSubmitting,
435
- className: "gap-2",
436
- children: isSubmitting ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Icon, {
437
- icon: "ph:spinner-gap",
438
- className: "size-4 animate-spin"
439
- }), t("common.loading")] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Icon, {
440
- icon: "ph:check",
441
- width: 16,
442
- height: 16
443
- }), t("common.save")] })
460
+ /* @__PURE__ */ jsx(GlobalSaveButton, {
461
+ control: form.control,
462
+ isMutationPending: updateMutation.isPending,
463
+ t
444
464
  })
445
465
  ] })
446
466
  }), /* @__PURE__ */ jsx(AutoFormFields, {
447
467
  collection: globalName,
448
468
  mode: "global",
449
469
  config: resolvedConfig,
450
- registry
470
+ registry,
471
+ resolvedFields: schemaFields,
472
+ schema: globalSchema
451
473
  })]
452
474
  }),
453
- /* @__PURE__ */ jsx(HistorySidebar, {
475
+ isHistoryOpen && /* @__PURE__ */ jsx(HistorySidebar, {
454
476
  open: isHistoryOpen,
455
477
  onOpenChange: setIsHistoryOpen,
456
- auditEntries: auditData ?? [],
457
- isLoadingAudit: auditLoading,
458
478
  versions: versionsData ?? [],
459
479
  fields: globalSchema?.fields,
460
480
  isLoadingVersions: versionsLoading,
@@ -4,10 +4,10 @@ import { selectAdmin, selectBasePath, selectContentLocale, selectNavigate, selec
4
4
  import { useSafeContentLocales } from "../../runtime/content-locales-provider.mjs";
5
5
  import { ComponentRenderer } from "../../components/component-renderer.mjs";
6
6
  import { Button } from "../../components/ui/button.mjs";
7
- import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/tooltip.mjs";
8
- import { useAdminConfig } from "../../hooks/use-admin-config.mjs";
9
7
  import { getFlagUrl } from "../../utils/locale-to-flag.mjs";
10
8
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "../../components/ui/dropdown-menu.mjs";
9
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/tooltip.mjs";
10
+ import { useAdminConfig } from "../../hooks/use-admin-config.mjs";
11
11
  import { useLazyComponent } from "../../utils/use-lazy-component.mjs";
12
12
  import { Skeleton } from "../../components/ui/skeleton.mjs";
13
13
  import { useAuthClientSafe } from "../../hooks/use-auth.mjs";
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import * as react_jsx_runtime13 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime11 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/client/views/pages/login-page.d.ts
5
5
 
@@ -64,6 +64,6 @@ declare function LoginPage({
64
64
  signUpPath,
65
65
  showForgotPassword,
66
66
  showSignUp
67
- }: LoginPageProps): react_jsx_runtime13.JSX.Element;
67
+ }: LoginPageProps): react_jsx_runtime11.JSX.Element;
68
68
  //#endregion
69
69
  export { LoginPage };
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import * as react_jsx_runtime14 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime12 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/client/views/pages/reset-password-page.d.ts
5
5
 
@@ -58,6 +58,6 @@ declare function ResetPasswordPage({
58
58
  loginPath,
59
59
  minPasswordLength,
60
60
  getToken
61
- }: ResetPasswordPageProps): react_jsx_runtime14.JSX.Element;
61
+ }: ResetPasswordPageProps): react_jsx_runtime12.JSX.Element;
62
62
  //#endregion
63
63
  export { ResetPasswordPage };
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import * as react_jsx_runtime15 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime13 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/client/views/pages/setup-page.d.ts
5
5
 
@@ -56,6 +56,6 @@ declare function SetupPage({
56
56
  redirectTo,
57
57
  loginPath,
58
58
  showLoginLink
59
- }: SetupPageProps): react_jsx_runtime15.JSX.Element;
59
+ }: SetupPageProps): react_jsx_runtime13.JSX.Element;
60
60
  //#endregion
61
61
  export { SetupPage, SetupPageProps };
package/dist/client.mjs CHANGED
@@ -4,8 +4,8 @@ import { AdminProvider, selectAdmin, selectBasePath, selectClient, selectNavigat
4
4
  import { useShallow } from "./client/runtime/index.mjs";
5
5
  import { Badge, ComponentRenderer, IconifyIcon, createIconRenderer, isComponentReference, resolveIconElement } from "./client/components/component-renderer.mjs";
6
6
  import { configureField, field } from "./client/builder/field/field.mjs";
7
- import { EMPTY_BLOCK_CONTENT, isBlockContent } from "./client/blocks/types.mjs";
8
7
  import { getCountryCode, getFlagConfig, getFlagUrl } from "./client/utils/locale-to-flag.mjs";
8
+ import { EMPTY_BLOCK_CONTENT, isBlockContent } from "./client/blocks/types.mjs";
9
9
  import { FocusProvider, parsePreviewFieldPath, scrollFieldIntoView, useFocus, useFocusOptional, useIsBlockFocused, useIsFieldFocused } from "./client/contexts/focus-context.mjs";
10
10
  import { useIsDesktop, useIsMobile, useMediaQuery } from "./client/hooks/use-media-query.mjs";
11
11
  import { buildValidationSchema, buildZodFromIntrospection, createFormSchema } from "./client/builder/validation.mjs";
package/dist/index.mjs CHANGED
@@ -4,8 +4,8 @@ import { AdminProvider, selectAdmin, selectBasePath, selectClient, selectNavigat
4
4
  import { useShallow } from "./client/runtime/index.mjs";
5
5
  import { Badge, ComponentRenderer, IconifyIcon, createIconRenderer, isComponentReference, resolveIconElement } from "./client/components/component-renderer.mjs";
6
6
  import { configureField, field } from "./client/builder/field/field.mjs";
7
- import { EMPTY_BLOCK_CONTENT, isBlockContent } from "./client/blocks/types.mjs";
8
7
  import { getCountryCode, getFlagConfig, getFlagUrl } from "./client/utils/locale-to-flag.mjs";
8
+ import { EMPTY_BLOCK_CONTENT, isBlockContent } from "./client/blocks/types.mjs";
9
9
  import { FocusProvider, parsePreviewFieldPath, scrollFieldIntoView, useFocus, useFocusOptional, useIsBlockFocused, useIsFieldFocused } from "./client/contexts/focus-context.mjs";
10
10
  import { useIsDesktop, useIsMobile, useMediaQuery } from "./client/hooks/use-media-query.mjs";
11
11
  import { buildValidationSchema, buildZodFromIntrospection, createFormSchema } from "./client/builder/validation.mjs";
@@ -1,5 +1,4 @@
1
1
  import { AuthSession } from "../modules/admin/auth-helpers.mjs";
2
- import "../auth-helpers.mjs";
3
2
  import { Questpie } from "questpie";
4
3
 
5
4
  //#region src/server/adapters/nextjs.d.ts
@@ -749,6 +749,14 @@ var cs_default = {
749
749
  "audit.sections.changes": "Změny",
750
750
  "audit.widget.recentActivity.title": "Nedávná aktivita",
751
751
  "audit.widget.recentActivity.empty": "Nebyla zaznamenána žádná nedávná aktivita.",
752
+ "audit.action.create": "vytvořil(a)",
753
+ "audit.action.update": "upravil(a)",
754
+ "audit.action.delete": "smazal(a)",
755
+ "audit.action.transition": "změnil(a) stav",
756
+ "audit.resourceType.collection": "kolekci",
757
+ "audit.resourceType.global": "globální nastavení",
758
+ "audit.unnamed": "(bez názvu)",
759
+ "audit.title.template": "{{userName}} {{action}} {{resourceType}} {{resourceLabel}}",
752
760
  "history.title": "Historie",
753
761
  "history.description": "Změny a aktivita pro tento záznam",
754
762
  "history.versionDescription": "Procházejte snímky verzí a kontrolujte rozdíly na úrovni polí.",
@@ -747,6 +747,14 @@ var de_default = {
747
747
  "audit.sections.changes": "Änderungen",
748
748
  "audit.widget.recentActivity.title": "Letzte Aktivität",
749
749
  "audit.widget.recentActivity.empty": "Keine aktuelle Aktivität aufgezeichnet.",
750
+ "audit.action.create": "erstellte",
751
+ "audit.action.update": "aktualisierte",
752
+ "audit.action.delete": "löschte",
753
+ "audit.action.transition": "änderte den Status von",
754
+ "audit.resourceType.collection": "Sammlung",
755
+ "audit.resourceType.global": "globale Einstellung",
756
+ "audit.unnamed": "(unbenannt)",
757
+ "audit.title.template": "{{userName}} {{action}} {{resourceType}} {{resourceLabel}}",
750
758
  "history.title": "Verlauf",
751
759
  "history.description": "Änderungen und Aktivität für diesen Datensatz",
752
760
  "history.versionDescription": "Versions-Snapshots durchsuchen und Unterschiede auf Feldebene prüfen.",
@@ -774,6 +774,14 @@ var en_default = {
774
774
  "audit.sections.changes": "Changes",
775
775
  "audit.widget.recentActivity.title": "Recent Activity",
776
776
  "audit.widget.recentActivity.empty": "No recent activity recorded.",
777
+ "audit.action.create": "created",
778
+ "audit.action.update": "updated",
779
+ "audit.action.delete": "deleted",
780
+ "audit.action.transition": "changed status of",
781
+ "audit.resourceType.collection": "collection",
782
+ "audit.resourceType.global": "global",
783
+ "audit.unnamed": "(unnamed)",
784
+ "audit.title.template": "{{userName}} {{action}} {{resourceType}} {{resourceLabel}}",
777
785
  "history.title": "History",
778
786
  "history.description": "Changes and activity for this record",
779
787
  "history.versionDescription": "Browse version snapshots and inspect field-level diffs.",
@@ -539,6 +539,14 @@ var es_default = {
539
539
  "audit.sections.user": "Usuario",
540
540
  "audit.widget.recentActivity.empty": "No hay actividad reciente registrada.",
541
541
  "audit.widget.recentActivity.title": "Actividad reciente",
542
+ "audit.action.create": "creó",
543
+ "audit.action.update": "actualizó",
544
+ "audit.action.delete": "eliminó",
545
+ "audit.action.transition": "cambió el estado de",
546
+ "audit.resourceType.collection": "colección",
547
+ "audit.resourceType.global": "global",
548
+ "audit.unnamed": "(sin nombre)",
549
+ "audit.title.template": "{{userName}} {{action}} {{resourceType}} {{resourceLabel}}",
542
550
  "auth.backToLogin": "Volver al inicio de sesión",
543
551
  "auth.checkYourEmail": "Revise su correo",
544
552
  "auth.completeRegistration": "Completar registro",
@@ -539,6 +539,14 @@ var fr_default = {
539
539
  "audit.sections.user": "Utilisateur",
540
540
  "audit.widget.recentActivity.empty": "Aucune activité récente enregistrée.",
541
541
  "audit.widget.recentActivity.title": "Activité récente",
542
+ "audit.action.create": "a créé",
543
+ "audit.action.update": "a mis à jour",
544
+ "audit.action.delete": "a supprimé",
545
+ "audit.action.transition": "a changé le statut de",
546
+ "audit.resourceType.collection": "collection",
547
+ "audit.resourceType.global": "global",
548
+ "audit.unnamed": "(sans nom)",
549
+ "audit.title.template": "{{userName}} {{action}} {{resourceType}} {{resourceLabel}}",
542
550
  "auth.backToLogin": "Retour à la connexion",
543
551
  "auth.checkYourEmail": "Consultez votre e-mail",
544
552
  "auth.completeRegistration": "Terminer l'inscription",