@questpie/admin 3.0.3 → 3.0.5

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 (253) hide show
  1. package/README.md +34 -5
  2. package/dist/client/blocks/block-renderer.d.mts +2 -2
  3. package/dist/client/blocks/block-renderer.mjs +4 -1
  4. package/dist/client/builder/types/action-types.d.mts +31 -3
  5. package/dist/client/builder/types/collection-types.d.mts +140 -0
  6. package/dist/client/builder/types/ui-config.d.mts +16 -2
  7. package/dist/client/builder/types/views.d.mts +57 -0
  8. package/dist/client/builder/types/widget-types.d.mts +5 -0
  9. package/dist/client/components/actions/action-button.mjs +137 -199
  10. package/dist/client/components/actions/action-dialog.mjs +198 -156
  11. package/dist/client/components/actions/confirmation-dialog.mjs +2 -2
  12. package/dist/client/components/actions/header-actions.mjs +52 -53
  13. package/dist/client/components/admin-link.d.mts +2 -2
  14. package/dist/client/components/auth/auth-loading.mjs +41 -18
  15. package/dist/client/components/blocks/block-editor-layout.mjs +2 -2
  16. package/dist/client/components/blocks/block-fields-renderer.mjs +64 -28
  17. package/dist/client/components/blocks/block-insert-button.mjs +4 -4
  18. package/dist/client/components/blocks/block-item.mjs +2 -2
  19. package/dist/client/components/blocks/block-library-sidebar.mjs +91 -63
  20. package/dist/client/components/component-renderer.mjs +1 -1
  21. package/dist/client/components/fields/array-field.mjs +14 -14
  22. package/dist/client/components/fields/asset-preview-field.mjs +1 -1
  23. package/dist/client/components/fields/blocks-field/blocks-field.mjs +84 -104
  24. package/dist/client/components/fields/json-field.mjs +2 -2
  25. package/dist/client/components/fields/object-array-field.mjs +22 -22
  26. package/dist/client/components/fields/object-field.mjs +5 -5
  27. package/dist/client/components/fields/relation/displays/cards-display.mjs +16 -9
  28. package/dist/client/components/fields/relation/displays/chips-display.mjs +15 -12
  29. package/dist/client/components/fields/relation/displays/grid-display.mjs +15 -11
  30. package/dist/client/components/fields/relation/displays/list-display.mjs +33 -20
  31. package/dist/client/components/fields/relation/displays/table-display.mjs +62 -93
  32. package/dist/client/components/fields/relation/relation-items-display.mjs +1 -1
  33. package/dist/client/components/fields/relation-picker.mjs +7 -6
  34. package/dist/client/components/fields/relation-select.mjs +71 -47
  35. package/dist/client/components/fields/rich-text-editor/bubble-menu.mjs +392 -82
  36. package/dist/client/components/fields/rich-text-editor/extensions.mjs +54 -23
  37. package/dist/client/components/fields/rich-text-editor/image-popover.mjs +24 -50
  38. package/dist/client/components/fields/rich-text-editor/image-upload.mjs +66 -0
  39. package/dist/client/components/fields/rich-text-editor/index.d.mts +38 -0
  40. package/dist/client/components/fields/rich-text-editor/index.mjs +637 -376
  41. package/dist/client/components/fields/rich-text-editor/link-utils.mjs +26 -0
  42. package/dist/client/components/fields/rich-text-editor/presets.d.mts +10 -0
  43. package/dist/client/components/fields/rich-text-editor/slash-commands.mjs +27 -6
  44. package/dist/client/components/fields/rich-text-editor/toolbar.mjs +464 -346
  45. package/dist/client/components/fields/rich-text-editor/types.d.mts +77 -0
  46. package/dist/client/components/fields/upload-field.mjs +45 -49
  47. package/dist/client/components/filter-builder/columns-tab.mjs +69 -62
  48. package/dist/client/components/filter-builder/filter-builder-sheet.mjs +473 -308
  49. package/dist/client/components/filter-builder/filters-tab.mjs +109 -82
  50. package/dist/client/components/filter-builder/saved-views-tab.mjs +300 -198
  51. package/dist/client/components/history-sidebar.mjs +850 -340
  52. package/dist/client/components/layout/field-layout-renderer.mjs +6 -5
  53. package/dist/client/components/locale-switcher.mjs +8 -8
  54. package/dist/client/components/media/media-grid.mjs +106 -86
  55. package/dist/client/components/media/media-picker-dialog.mjs +242 -230
  56. package/dist/client/components/preview/live-preview-mode.mjs +1 -1
  57. package/dist/client/components/primitives/asset-preview.mjs +37 -22
  58. package/dist/client/components/primitives/date-input.mjs +212 -249
  59. package/dist/client/components/primitives/dropzone.mjs +192 -159
  60. package/dist/client/components/primitives/field-select-control.mjs +93 -0
  61. package/dist/client/components/primitives/select-multi.mjs +406 -365
  62. package/dist/client/components/primitives/select-single.mjs +391 -323
  63. package/dist/client/components/primitives/time-input.mjs +2 -2
  64. package/dist/client/components/sheets/resource-sheet.mjs +2 -0
  65. package/dist/client/components/ui/accordion.mjs +4 -4
  66. package/dist/client/components/ui/alert.mjs +3 -3
  67. package/dist/client/components/ui/badge.mjs +4 -4
  68. package/dist/client/components/ui/button.mjs +47 -37
  69. package/dist/client/components/ui/card.mjs +2 -2
  70. package/dist/client/components/ui/checkbox.mjs +1 -1
  71. package/dist/client/components/ui/command.mjs +5 -5
  72. package/dist/client/components/ui/dialog.mjs +3 -3
  73. package/dist/client/components/ui/drawer.mjs +1 -1
  74. package/dist/client/components/ui/dropdown-menu.mjs +157 -15
  75. package/dist/client/components/ui/empty-state.mjs +88 -59
  76. package/dist/client/components/ui/field.mjs +2 -2
  77. package/dist/client/components/ui/input-group.mjs +3 -3
  78. package/dist/client/components/ui/input.mjs +1 -1
  79. package/dist/client/components/ui/kbd.mjs +1 -1
  80. package/dist/client/components/ui/label.mjs +1 -1
  81. package/dist/client/components/ui/popover.mjs +19 -11
  82. package/dist/client/components/ui/scroll-fade.mjs +170 -0
  83. package/dist/client/components/ui/search-input.mjs +1 -1
  84. package/dist/client/components/ui/select.mjs +129 -27
  85. package/dist/client/components/ui/sheet.mjs +54 -34
  86. package/dist/client/components/ui/sidebar.mjs +15 -14
  87. package/dist/client/components/ui/skeleton.mjs +28 -12
  88. package/dist/client/components/ui/switch.mjs +2 -2
  89. package/dist/client/components/ui/table.mjs +82 -74
  90. package/dist/client/components/ui/tabs.mjs +26 -31
  91. package/dist/client/components/ui/textarea.mjs +1 -1
  92. package/dist/client/components/ui/tooltip.mjs +1 -1
  93. package/dist/client/components/widgets/chart-widget.mjs +154 -100
  94. package/dist/client/components/widgets/progress-widget.mjs +63 -36
  95. package/dist/client/components/widgets/quick-actions-widget.mjs +207 -115
  96. package/dist/client/components/widgets/recent-items-widget.mjs +147 -103
  97. package/dist/client/components/widgets/stats-widget.mjs +91 -72
  98. package/dist/client/components/widgets/table-widget.mjs +161 -247
  99. package/dist/client/components/widgets/timeline-widget.mjs +119 -78
  100. package/dist/client/components/widgets/value-widget.mjs +286 -157
  101. package/dist/client/components/widgets/widget-empty-state.mjs +88 -0
  102. package/dist/client/components/widgets/widget-skeletons.mjs +53 -20
  103. package/dist/client/contexts/focus-context.d.mts +2 -2
  104. package/dist/client/hooks/use-action.mjs +63 -55
  105. package/dist/client/hooks/use-audit-history.mjs +1 -65
  106. package/dist/client/hooks/use-collection-validation.mjs +36 -23
  107. package/dist/client/hooks/use-collection.mjs +96 -1
  108. package/dist/client/hooks/use-saved-views.mjs +70 -49
  109. package/dist/client/hooks/use-server-actions.mjs +70 -46
  110. package/dist/client/hooks/use-server-validation.mjs +156 -41
  111. package/dist/client/hooks/use-server-widget-data.mjs +1 -1
  112. package/dist/client/hooks/use-setup-status.d.mts +3 -3
  113. package/dist/client/hooks/use-setup-status.mjs +2 -2
  114. package/dist/client/hooks/use-transition-stage.mjs +2 -10
  115. package/dist/client/hooks/use-validation-error-map.mjs +31 -13
  116. package/dist/client/hooks/use-view-state.mjs +238 -174
  117. package/dist/client/i18n/date-locale.mjs +33 -0
  118. package/dist/client/i18n/hooks.mjs +17 -1
  119. package/dist/client/lib/utils.mjs +3 -2
  120. package/dist/client/preview/block-scope-context.d.mts +2 -2
  121. package/dist/client/preview/preview-banner.d.mts +2 -2
  122. package/dist/client/preview/preview-banner.mjs +75 -46
  123. package/dist/client/preview/preview-field.d.mts +4 -4
  124. package/dist/client/preview/preview-field.mjs +2 -2
  125. package/dist/client/runtime/provider.mjs +8 -1
  126. package/dist/client/runtime/translations-provider.mjs +1 -1
  127. package/dist/client/scope/picker.d.mts +2 -2
  128. package/dist/client/scope/provider.d.mts +2 -2
  129. package/dist/client/styles/base.css +1022 -0
  130. package/dist/client/styles/index.css +3 -589
  131. package/dist/client/utils/auto-expand-fields.mjs +4 -2
  132. package/dist/client/utils/keyboard-shortcuts.mjs +26 -0
  133. package/dist/client/utils/use-lazy-component.mjs +80 -0
  134. package/dist/client/views/auth/accept-invite-form.d.mts +2 -2
  135. package/dist/client/views/auth/auth-layout.d.mts +17 -10
  136. package/dist/client/views/auth/auth-layout.mjs +291 -80
  137. package/dist/client/views/auth/forgot-password-form.d.mts +2 -2
  138. package/dist/client/views/auth/forgot-password-form.mjs +2 -2
  139. package/dist/client/views/auth/login-form.d.mts +2 -2
  140. package/dist/client/views/auth/login-form.mjs +1 -1
  141. package/dist/client/views/auth/reset-password-form.d.mts +2 -2
  142. package/dist/client/views/auth/reset-password-form.mjs +2 -2
  143. package/dist/client/views/auth/setup-form.d.mts +2 -2
  144. package/dist/client/views/collection/auto-form-fields.mjs +11 -9
  145. package/dist/client/views/collection/bulk-action-toolbar.mjs +173 -138
  146. package/dist/client/views/collection/cells/complex-cells.mjs +22 -22
  147. package/dist/client/views/collection/cells/primitive-cells.mjs +1 -1
  148. package/dist/client/views/collection/cells/relation-cells.mjs +147 -129
  149. package/dist/client/views/collection/cells/shared/asset-thumbnail.mjs +224 -278
  150. package/dist/client/views/collection/cells/shared/relation-chip.mjs +64 -36
  151. package/dist/client/views/collection/cells/upload-cells.mjs +199 -9
  152. package/dist/client/views/collection/columns/build-columns.mjs +29 -9
  153. package/dist/client/views/collection/columns/column-defaults.mjs +2 -2
  154. package/dist/client/views/collection/field-renderer.mjs +50 -89
  155. package/dist/client/views/collection/form-view.mjs +237 -227
  156. package/dist/client/views/collection/table-view.mjs +1167 -234
  157. package/dist/client/views/collection/view-skeletons.mjs +222 -79
  158. package/dist/client/views/common/global-search.mjs +29 -18
  159. package/dist/client/views/dashboard/dashboard-grid.mjs +678 -501
  160. package/dist/client/views/dashboard/dashboard-widget.mjs +6 -3
  161. package/dist/client/views/dashboard/widget-card.mjs +23 -14
  162. package/dist/client/views/globals/global-form-view.mjs +634 -589
  163. package/dist/client/views/layout/admin-layout-provider.mjs +67 -70
  164. package/dist/client/views/layout/admin-layout.d.mts +3 -6
  165. package/dist/client/views/layout/admin-layout.mjs +152 -155
  166. package/dist/client/views/layout/admin-router.mjs +936 -616
  167. package/dist/client/views/layout/admin-sidebar.d.mts +38 -1
  168. package/dist/client/views/layout/admin-sidebar.mjs +762 -592
  169. package/dist/client/views/layout/admin-theme.d.mts +10 -0
  170. package/dist/client/views/layout/admin-theme.mjs +84 -0
  171. package/dist/client/views/layout/admin-view-layout.mjs +161 -0
  172. package/dist/client/views/pages/accept-invite-page.d.mts +2 -2
  173. package/dist/client/views/pages/accept-invite-page.mjs +49 -26
  174. package/dist/client/views/pages/dashboard-page.d.mts +2 -2
  175. package/dist/client/views/pages/forgot-password-page.d.mts +2 -2
  176. package/dist/client/views/pages/forgot-password-page.mjs +2 -19
  177. package/dist/client/views/pages/invite-page.d.mts +2 -2
  178. package/dist/client/views/pages/invite-page.mjs +2 -19
  179. package/dist/client/views/pages/login-page.d.mts +3 -3
  180. package/dist/client/views/pages/login-page.mjs +4 -21
  181. package/dist/client/views/pages/reset-password-page.d.mts +2 -2
  182. package/dist/client/views/pages/reset-password-page.mjs +3 -20
  183. package/dist/client/views/pages/setup-page.d.mts +2 -2
  184. package/dist/client/views/pages/setup-page.mjs +70 -71
  185. package/dist/client.d.mts +6 -2
  186. package/dist/client.mjs +2 -1
  187. package/dist/components/rich-text/rich-text-renderer.d.mts +2 -2
  188. package/dist/index.d.mts +6 -2
  189. package/dist/index.mjs +2 -1
  190. package/dist/server/augmentation/dashboard.d.mts +67 -3
  191. package/dist/server/augmentation/form-layout.d.mts +21 -0
  192. package/dist/server/augmentation/index.d.mts +1 -1
  193. package/dist/server/codegen/admin-client-template.mjs +4 -0
  194. package/dist/server/fields/blocks.d.mts +1 -1
  195. package/dist/server/fields/blocks.mjs +12 -0
  196. package/dist/server/fields/rich-text.d.mts +1 -1
  197. package/dist/server/fields/rich-text.mjs +8 -0
  198. package/dist/server/i18n/index.mjs +29 -7
  199. package/dist/server/i18n/messages/cs.mjs +414 -1
  200. package/dist/server/i18n/messages/de.mjs +412 -1
  201. package/dist/server/i18n/messages/en.mjs +166 -1
  202. package/dist/server/i18n/messages/es.mjs +412 -1
  203. package/dist/server/i18n/messages/fr.mjs +412 -1
  204. package/dist/server/i18n/messages/pl.mjs +416 -1
  205. package/dist/server/i18n/messages/pt.mjs +409 -1
  206. package/dist/server/i18n/messages/sk.mjs +216 -2
  207. package/dist/server/modules/admin/block/introspection.mjs +4 -1
  208. package/dist/server/modules/admin/block/prefetch.mjs +12 -2
  209. package/dist/server/modules/admin/collections/account.d.mts +2 -2
  210. package/dist/server/modules/admin/collections/admin-locks.d.mts +2 -2
  211. package/dist/server/modules/admin/collections/admin-preferences.d.mts +39 -39
  212. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +47 -47
  213. package/dist/server/modules/admin/collections/apikey.d.mts +42 -42
  214. package/dist/server/modules/admin/collections/assets.d.mts +20 -20
  215. package/dist/server/modules/admin/collections/assets.mjs +0 -1
  216. package/dist/server/modules/admin/collections/session.d.mts +42 -42
  217. package/dist/server/modules/admin/collections/user.d.mts +40 -28
  218. package/dist/server/modules/admin/collections/user.mjs +40 -9
  219. package/dist/server/modules/admin/collections/verification.d.mts +36 -36
  220. package/dist/server/modules/admin/dto/admin-config.dto.mjs +2 -0
  221. package/dist/server/modules/admin/factories.mjs +7 -18
  222. package/dist/server/modules/admin/index.d.mts +1 -1
  223. package/dist/server/modules/admin/routes/admin-config.d.mts +2 -2
  224. package/dist/server/modules/admin/routes/admin-config.mjs +34 -16
  225. package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
  226. package/dist/server/modules/admin/routes/execute-action.mjs +67 -28
  227. package/dist/server/modules/admin/routes/i18n-helpers.mjs +34 -0
  228. package/dist/server/modules/admin/routes/locales.d.mts +2 -2
  229. package/dist/server/modules/admin/routes/preview.mjs +25 -17
  230. package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
  231. package/dist/server/modules/admin/routes/route-helpers.mjs +1 -1
  232. package/dist/server/modules/admin/routes/setup.d.mts +10 -10
  233. package/dist/server/modules/admin/routes/setup.mjs +16 -13
  234. package/dist/server/modules/admin/routes/translations.d.mts +4 -4
  235. package/dist/server/modules/admin/routes/translations.mjs +5 -1
  236. package/dist/server/modules/admin-preferences/collections/admin-preferences.mjs +1 -1
  237. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +2 -2
  238. package/dist/server/modules/audit/.generated/module.d.mts +1 -1
  239. package/dist/server/modules/audit/.generated/module.mjs +1 -1
  240. package/dist/server/modules/audit/collections/audit-log.d.mts +2 -2
  241. package/dist/server/modules/audit/collections/audit-log.mjs +1 -1
  242. package/dist/server/modules/audit/config/app.mjs +99 -42
  243. package/dist/server/modules/audit/jobs/audit-cleanup.mjs +1 -1
  244. package/dist/server/plugin.mjs +4 -2
  245. package/dist/server/proxy-factories.d.mts +4 -3
  246. package/dist/server/proxy-factories.mjs +34 -8
  247. package/dist/shared/types/saved-views.types.d.mts +2 -0
  248. package/package.json +6 -4
  249. package/dist/client/components/fields/rich-text-editor/link-popover.mjs +0 -85
  250. package/dist/client/components/ui/spinner.mjs +0 -52
  251. package/dist/client/components/ui/toolbar.mjs +0 -136
  252. package/dist/client/contexts/breadcrumb-context.mjs +0 -60
  253. package/dist/client/views/layout/admin-topbar.mjs +0 -236
@@ -19,16 +19,36 @@ function isAuditDisabled(type, name) {
19
19
  * Compute field-level changes between original and current data.
20
20
  * Returns an object of `{ field: { from, to } }` or null if no meaningful changes.
21
21
  */
22
+ const SKIP_CHANGE_FIELDS = new Set([
23
+ "updatedAt",
24
+ "createdAt",
25
+ "id"
26
+ ]);
27
+ function shouldSkipChangeField(key) {
28
+ return SKIP_CHANGE_FIELDS.has(key) || key.startsWith("_");
29
+ }
30
+ function makeFieldChangeMap(data, direction) {
31
+ if (!data) return null;
32
+ const changes = {};
33
+ for (const key of Object.keys(data)) {
34
+ if (shouldSkipChangeField(key)) continue;
35
+ const value = data[key];
36
+ if (value == null) continue;
37
+ changes[key] = direction === "create" ? {
38
+ from: null,
39
+ to: value
40
+ } : {
41
+ from: value,
42
+ to: null
43
+ };
44
+ }
45
+ return Object.keys(changes).length > 0 ? changes : null;
46
+ }
22
47
  function computeChanges(original, current) {
23
48
  if (!original) return null;
24
49
  const changes = {};
25
- const skipFields = new Set([
26
- "updatedAt",
27
- "createdAt",
28
- "id"
29
- ]);
30
50
  for (const key of Object.keys(current)) {
31
- if (skipFields.has(key) || key.startsWith("_")) continue;
51
+ if (shouldSkipChangeField(key)) continue;
32
52
  const fromVal = original[key];
33
53
  const toVal = current[key];
34
54
  if (fromVal == null && toVal == null) continue;
@@ -85,24 +105,61 @@ function getResourceTypeLabel(type, name) {
85
105
  * Generate a human-readable title for the audit log entry.
86
106
  */
87
107
  function generateTitle(action, _resourceType, resourceTypeLabel, resourceLabel, userName) {
88
- const user = userName || "Unknown";
89
108
  const resource = resourceLabel || "(unnamed)";
90
- return `${user} ${{
109
+ return `${userName} ${{
91
110
  create: "created",
92
111
  update: "updated",
93
112
  delete: "deleted",
94
113
  transition: "changed status of"
95
114
  }[action] || action} ${resourceTypeLabel} '${resource}'`;
96
115
  }
116
+ function firstNonEmptyString(...values) {
117
+ for (const value of values) {
118
+ if (typeof value !== "string") continue;
119
+ const trimmed = value.trim();
120
+ if (trimmed.length > 0) return trimmed;
121
+ }
122
+ return null;
123
+ }
124
+ function resolveAuditActor(ctx) {
125
+ const user = ctx.session?.user;
126
+ const userId = user?.id != null ? String(user.id) : null;
127
+ const userName = firstNonEmptyString(user?.name, user?.email, userId);
128
+ if (userName) return {
129
+ actorType: "user",
130
+ userId,
131
+ userName
132
+ };
133
+ if (ctx.accessMode === "system") return {
134
+ actorType: "system",
135
+ userId: "system",
136
+ userName: "System"
137
+ };
138
+ return {
139
+ actorType: "anonymous",
140
+ userId: null,
141
+ userName: "Anonymous"
142
+ };
143
+ }
144
+ function buildAuditMetadata(ctx, actor, extra) {
145
+ return {
146
+ actorType: actor.actorType,
147
+ accessMode: ctx.accessMode ?? null,
148
+ ...extra
149
+ };
150
+ }
151
+ function logAuditFailure(ctx, message, err) {
152
+ ctx.logger?.error?.(message, err);
153
+ }
97
154
  async function collectionAfterChange(ctx) {
98
155
  try {
99
156
  const collections = ctx.collections ?? ctx.app?.collections;
100
157
  if (isAuditDisabled("collection", ctx.collection)) return;
101
158
  const action = ctx.operation === "create" ? "create" : "update";
102
- const changes = ctx.operation === "update" ? computeChanges(ctx.original, ctx.data) : null;
159
+ const changes = ctx.operation === "update" ? computeChanges(ctx.original, ctx.data) : makeFieldChangeMap(ctx.data, "create");
103
160
  if (ctx.operation === "update" && !changes) return;
104
161
  const resourceLabel = extractLabel(ctx.data);
105
- const userName = ctx.session?.user?.name || ctx.session?.user?.email || null;
162
+ const actor = resolveAuditActor(ctx);
106
163
  const resourceTypeLabel = getResourceTypeLabel("collection", ctx.collection);
107
164
  await collections[AUDIT_LOG_COLLECTION].create({
108
165
  action,
@@ -110,18 +167,18 @@ async function collectionAfterChange(ctx) {
110
167
  resource: ctx.collection,
111
168
  resourceId: ctx.data?.id ? String(ctx.data.id) : null,
112
169
  resourceLabel,
113
- userId: ctx.session?.user?.id ? String(ctx.session.user.id) : null,
114
- userName,
170
+ userId: actor.userId,
171
+ userName: actor.userName,
115
172
  locale: ctx.locale || null,
116
173
  changes,
117
- metadata: null,
118
- title: generateTitle(action, "collection", resourceTypeLabel, resourceLabel, userName)
174
+ metadata: buildAuditMetadata(ctx, actor, { operation: ctx.operation }),
175
+ title: generateTitle(action, "collection", resourceTypeLabel, resourceLabel, actor.userName)
119
176
  }, {
120
177
  accessMode: "system",
121
178
  db: ctx.db
122
179
  });
123
180
  } catch (err) {
124
- console.error(`[Audit] Failed to log ${ctx.operation} for collection "${ctx.collection}":`, err);
181
+ logAuditFailure(ctx, `[Audit] Failed to log ${ctx.operation} for collection "${ctx.collection}":`, err);
125
182
  }
126
183
  }
127
184
  async function collectionAfterDelete(ctx) {
@@ -129,7 +186,7 @@ async function collectionAfterDelete(ctx) {
129
186
  const collections = ctx.collections ?? ctx.app?.collections;
130
187
  if (isAuditDisabled("collection", ctx.collection)) return;
131
188
  const resourceLabel = extractLabel(ctx.data);
132
- const userName = ctx.session?.user?.name || ctx.session?.user?.email || null;
189
+ const actor = resolveAuditActor(ctx);
133
190
  const resourceTypeLabel = getResourceTypeLabel("collection", ctx.collection);
134
191
  await collections[AUDIT_LOG_COLLECTION].create({
135
192
  action: "delete",
@@ -137,18 +194,18 @@ async function collectionAfterDelete(ctx) {
137
194
  resource: ctx.collection,
138
195
  resourceId: ctx.data?.id ? String(ctx.data.id) : null,
139
196
  resourceLabel,
140
- userId: ctx.session?.user?.id ? String(ctx.session.user.id) : null,
141
- userName,
197
+ userId: actor.userId,
198
+ userName: actor.userName,
142
199
  locale: ctx.locale || null,
143
- changes: null,
144
- metadata: null,
145
- title: generateTitle("delete", "collection", resourceTypeLabel, resourceLabel, userName)
200
+ changes: makeFieldChangeMap(ctx.data, "delete"),
201
+ metadata: buildAuditMetadata(ctx, actor, { operation: "delete" }),
202
+ title: generateTitle("delete", "collection", resourceTypeLabel, resourceLabel, actor.userName)
146
203
  }, {
147
204
  accessMode: "system",
148
205
  db: ctx.db
149
206
  });
150
207
  } catch (err) {
151
- console.error(`[Audit] Failed to log delete for collection "${ctx.collection}":`, err);
208
+ logAuditFailure(ctx, `[Audit] Failed to log delete for collection "${ctx.collection}":`, err);
152
209
  }
153
210
  }
154
211
  async function collectionAfterTransition(ctx) {
@@ -156,7 +213,7 @@ async function collectionAfterTransition(ctx) {
156
213
  const collections = ctx.collections ?? ctx.app?.collections;
157
214
  if (isAuditDisabled("collection", ctx.collection)) return;
158
215
  const resourceLabel = extractLabel(ctx.data);
159
- const userName = ctx.session?.user?.name || ctx.session?.user?.email || null;
216
+ const actor = resolveAuditActor(ctx);
160
217
  const resourceTypeLabel = getResourceTypeLabel("collection", ctx.collection);
161
218
  await collections[AUDIT_LOG_COLLECTION].create({
162
219
  action: "transition",
@@ -164,31 +221,31 @@ async function collectionAfterTransition(ctx) {
164
221
  resource: ctx.collection,
165
222
  resourceId: ctx.data?.id ? String(ctx.data.id) : null,
166
223
  resourceLabel,
167
- userId: ctx.session?.user?.id ? String(ctx.session.user.id) : null,
168
- userName,
224
+ userId: actor.userId,
225
+ userName: actor.userName,
169
226
  locale: ctx.locale || null,
170
227
  changes: { stage: {
171
228
  from: ctx.fromStage,
172
229
  to: ctx.toStage
173
230
  } },
174
- metadata: {
231
+ metadata: buildAuditMetadata(ctx, actor, {
175
232
  fromStage: ctx.fromStage,
176
233
  toStage: ctx.toStage
177
- },
178
- title: generateTitle("transition", "collection", resourceTypeLabel, resourceLabel, userName)
234
+ }),
235
+ title: generateTitle("transition", "collection", resourceTypeLabel, resourceLabel, actor.userName)
179
236
  }, {
180
237
  accessMode: "system",
181
238
  db: ctx.db
182
239
  });
183
240
  } catch (err) {
184
- console.error(`[Audit] Failed to log transition for collection "${ctx.collection}":`, err);
241
+ logAuditFailure(ctx, `[Audit] Failed to log transition for collection "${ctx.collection}":`, err);
185
242
  }
186
243
  }
187
244
  async function globalAfterChange(ctx) {
188
245
  try {
189
246
  const collections = ctx.collections ?? ctx.app?.collections;
190
247
  if (isAuditDisabled("global", ctx.global)) return;
191
- const userName = ctx.session?.user?.name || ctx.session?.user?.email || null;
248
+ const actor = resolveAuditActor(ctx);
192
249
  const resourceTypeLabel = getResourceTypeLabel("global", ctx.global);
193
250
  await collections[AUDIT_LOG_COLLECTION].create({
194
251
  action: "update",
@@ -196,25 +253,25 @@ async function globalAfterChange(ctx) {
196
253
  resource: ctx.global,
197
254
  resourceId: null,
198
255
  resourceLabel: ctx.global,
199
- userId: ctx.session?.user?.id ? String(ctx.session.user.id) : null,
200
- userName,
256
+ userId: actor.userId,
257
+ userName: actor.userName,
201
258
  locale: ctx.locale || null,
202
259
  changes: null,
203
- metadata: null,
204
- title: generateTitle("update", "global", resourceTypeLabel, ctx.global, userName)
260
+ metadata: buildAuditMetadata(ctx, actor, { operation: "update" }),
261
+ title: generateTitle("update", "global", resourceTypeLabel, ctx.global, actor.userName)
205
262
  }, {
206
263
  accessMode: "system",
207
264
  db: ctx.db
208
265
  });
209
266
  } catch (err) {
210
- console.error(`[Audit] Failed to log update for global "${ctx.global}":`, err);
267
+ logAuditFailure(ctx, `[Audit] Failed to log update for global "${ctx.global}":`, err);
211
268
  }
212
269
  }
213
270
  async function globalAfterTransition(ctx) {
214
271
  try {
215
272
  const collections = ctx.collections ?? ctx.app?.collections;
216
273
  if (isAuditDisabled("global", ctx.global)) return;
217
- const userName = ctx.session?.user?.name || ctx.session?.user?.email || null;
274
+ const actor = resolveAuditActor(ctx);
218
275
  const resourceTypeLabel = getResourceTypeLabel("global", ctx.global);
219
276
  await collections[AUDIT_LOG_COLLECTION].create({
220
277
  action: "transition",
@@ -222,24 +279,24 @@ async function globalAfterTransition(ctx) {
222
279
  resource: ctx.global,
223
280
  resourceId: null,
224
281
  resourceLabel: ctx.global,
225
- userId: ctx.session?.user?.id ? String(ctx.session.user.id) : null,
226
- userName,
282
+ userId: actor.userId,
283
+ userName: actor.userName,
227
284
  locale: ctx.locale || null,
228
285
  changes: { stage: {
229
286
  from: ctx.fromStage,
230
287
  to: ctx.toStage
231
288
  } },
232
- metadata: {
289
+ metadata: buildAuditMetadata(ctx, actor, {
233
290
  fromStage: ctx.fromStage,
234
291
  toStage: ctx.toStage
235
- },
236
- title: generateTitle("transition", "global", resourceTypeLabel, ctx.global, userName)
292
+ }),
293
+ title: generateTitle("transition", "global", resourceTypeLabel, ctx.global, actor.userName)
237
294
  }, {
238
295
  accessMode: "system",
239
296
  db: ctx.db
240
297
  });
241
298
  } catch (err) {
242
- console.error(`[Audit] Failed to log transition for global "${ctx.global}":`, err);
299
+ logAuditFailure(ctx, `[Audit] Failed to log transition for global "${ctx.global}":`, err);
243
300
  }
244
301
  }
245
302
  /**
@@ -1,7 +1,7 @@
1
1
  import { AUDIT_LOG_COLLECTION } from "../collections/audit-log.mjs";
2
2
  import { z } from "zod";
3
3
  import { job } from "questpie";
4
- import { sql as sql$1 } from "drizzle-orm";
4
+ import { sql as sql$1 } from "questpie/drizzle";
5
5
 
6
6
  //#region src/server/modules/audit/jobs/audit-cleanup.ts
7
7
  /**
@@ -60,6 +60,7 @@ function adminPlugin() {
60
60
  dirs: ["blocks"],
61
61
  prefix: "bloc",
62
62
  factoryFunctions: ["block"],
63
+ keyFromProperty: "state.name",
63
64
  registryKey: true,
64
65
  includeInAppState: true,
65
66
  extractFromModules: true,
@@ -261,7 +262,7 @@ function adminPlugin() {
261
262
  block: {
262
263
  dir: "blocks",
263
264
  description: "Server-side block definition",
264
- template: ({ kebab, camel }) => `import { block } from "#questpie/factories";\n\nexport const ${camel}Block = block("${kebab}")\n\t.fields(({ f }) => ({\n\t\ttitle: f.text("Title"),\n\t}));\n`
265
+ template: ({ kebab, camel }) => `import { block } from "#questpie/factories";\n\nexport const ${camel}Block = block("${kebab}")\n\t.fields(({ f }) => ({\n\t\ttitle: f.text(255).label("Title").required(),\n\t}));\n`
265
266
  },
266
267
  view: {
267
268
  dir: "views",
@@ -283,6 +284,7 @@ function adminPlugin() {
283
284
  blocks: {
284
285
  dirs: ["blocks"],
285
286
  prefix: "block",
287
+ keyFromSource: "basename",
286
288
  registryKey: false,
287
289
  includeInAppState: false,
288
290
  extractFromModules: false
@@ -340,7 +342,7 @@ function adminPlugin() {
340
342
  const varName = `${key}Block`;
341
343
  const kebab = file.importPath.split("/").pop();
342
344
  ctx.addImport(`type { ${varName} }`, `../../server/blocks/${kebab}`);
343
- entries.push(`${JSON.stringify(key)}: typeof ${varName}`);
345
+ entries.push(`${JSON.stringify(kebab)}: typeof ${varName}`);
344
346
  }
345
347
  ctx.addTypeDeclaration(`type _ServerBlocks = { ${entries.join("; ")} };`);
346
348
  ctx.addTypeDeclaration("");
@@ -20,10 +20,11 @@ declare function createViewCallbackProxy(): Record<string, (config?: Record<stri
20
20
  */
21
21
  declare function createComponentCallbackProxy(): Record<string, (...args: unknown[]) => Record<string, unknown>>;
22
22
  /**
23
- * Create a simple action proxy for builder callback contexts.
24
- * Provides a `custom()` method and returns action names as strings for built-in actions.
23
+ * Create an action proxy for builder callback contexts.
25
24
  *
26
- * Used as the `a` callback param: `a.delete` `"delete"`, `a.custom("archive", config)` `{ id: "archive", ...config }`.
25
+ * The same `a` proxy is used by `.list()` (`a.delete`) and `.actions()`
26
+ * (`a.delete()`, `a.headerAction(...)`), so built-ins are callable values that
27
+ * also serialize back to their action name.
27
28
  */
28
29
  declare function createActionCallbackProxy(): Record<string, unknown>;
29
30
  /**
@@ -66,18 +66,44 @@ function createComponentCallbackProxy() {
66
66
  });
67
67
  } });
68
68
  }
69
+ function createBuiltinActionReference(name) {
70
+ const ref = (() => name);
71
+ Object.defineProperties(ref, {
72
+ type: { value: name },
73
+ toJSON: { value: () => name },
74
+ valueOf: { value: () => name },
75
+ [Symbol.toPrimitive]: { value: () => name }
76
+ });
77
+ return ref;
78
+ }
69
79
  /**
70
- * Create a simple action proxy for builder callback contexts.
71
- * Provides a `custom()` method and returns action names as strings for built-in actions.
80
+ * Create an action proxy for builder callback contexts.
72
81
  *
73
- * Used as the `a` callback param: `a.delete` `"delete"`, `a.custom("archive", config)` `{ id: "archive", ...config }`.
82
+ * The same `a` proxy is used by `.list()` (`a.delete`) and `.actions()`
83
+ * (`a.delete()`, `a.headerAction(...)`), so built-ins are callable values that
84
+ * also serialize back to their action name.
74
85
  */
75
86
  function createActionCallbackProxy() {
76
- return new Proxy({ custom: (name, config) => ({
77
- id: name,
78
- ...config
79
- }) }, { get: (target, prop) => {
80
- return target[prop] ?? String(prop);
87
+ return new Proxy({
88
+ custom: (name, config) => ({
89
+ type: name,
90
+ ...config ? { config } : {}
91
+ }),
92
+ action: (def) => ({
93
+ scope: "single",
94
+ ...def
95
+ }),
96
+ bulkAction: (def) => ({
97
+ ...def,
98
+ scope: "bulk"
99
+ }),
100
+ headerAction: (def) => ({
101
+ ...def,
102
+ scope: "header"
103
+ })
104
+ }, { get: (target, prop) => {
105
+ if (prop === "then") return void 0;
106
+ return target[prop] ?? createBuiltinActionReference(String(prop));
81
107
  } });
82
108
  }
83
109
  /**
@@ -36,6 +36,8 @@ interface ViewConfiguration {
36
36
  filters: FilterRule[];
37
37
  sortConfig: SortConfig | null;
38
38
  visibleColumns: string[];
39
+ groupBy?: string | null;
40
+ collapsedGroups?: string[];
39
41
  realtime?: boolean;
40
42
  includeDeleted?: boolean;
41
43
  pagination?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@questpie/admin",
3
- "version": "3.0.3",
3
+ "version": "3.0.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/questpie/questpie.git",
@@ -28,6 +28,7 @@
28
28
  "./server": "./dist/server.mjs",
29
29
  "./shared": "./dist/shared.mjs",
30
30
  "./*": "./*",
31
+ "./client/styles/base.css": "./dist/client/styles/base.css",
31
32
  "./client/styles/index.css": "./dist/client/styles/index.css"
32
33
  },
33
34
  "publishConfig": {
@@ -40,6 +41,7 @@
40
41
  "./server": "./dist/server.mjs",
41
42
  "./shared": "./dist/shared.mjs",
42
43
  "./*": "./*",
44
+ "./client/styles/base.css": "./dist/client/styles/base.css",
43
45
  "./client/styles/index.css": "./dist/client/styles/index.css"
44
46
  }
45
47
  },
@@ -60,7 +62,7 @@
60
62
  "@fontsource-variable/jetbrains-mono": "^5.2.8",
61
63
  "@hookform/resolvers": "^5.1.0",
62
64
  "@iconify/react": "^6.0.2",
63
- "@questpie/tanstack-query": "^3.0.3",
65
+ "@questpie/tanstack-query": "^3.0.5",
64
66
  "@tailwindcss/vite": "^4.0.6",
65
67
  "@tiptap/core": "^2.x",
66
68
  "@tiptap/extension-character-count": "^2.x",
@@ -86,7 +88,7 @@
86
88
  "date-fns": "^4.1.0",
87
89
  "lowlight": "^3.x",
88
90
  "next-themes": "^0.4.6",
89
- "questpie": "^3.0.3",
91
+ "questpie": "^3.0.5",
90
92
  "react-day-picker": "^9.12.0",
91
93
  "react-hook-form": "^7.54.0",
92
94
  "react-resizable-panels": "^4.4.2",
@@ -101,11 +103,11 @@
101
103
  },
102
104
  "devDependencies": {
103
105
  "@rollup/plugin-babel": "^6.1.0",
104
- "bun-types": "latest",
105
106
  "@tanstack/db": "^0.1.1",
106
107
  "@types/react": "^19.2.0",
107
108
  "@types/react-dom": "^19.2.0",
108
109
  "babel-plugin-react-compiler": "^1.0.0",
110
+ "bun-types": "latest",
109
111
  "shadcn": "^3.6.1",
110
112
  "tinyglobby": "^0.2.15",
111
113
  "tsdown": "^0.18.3",
@@ -1,85 +0,0 @@
1
- import { useTranslation } from "../../../i18n/hooks.mjs";
2
- import { Button } from "../../ui/button.mjs";
3
- import { Input } from "../../ui/input.mjs";
4
- import { Popover, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger } from "../../ui/popover.mjs";
5
- import * as React from "react";
6
- import { jsx, jsxs } from "react/jsx-runtime";
7
-
8
- //#region src/client/components/fields/rich-text-editor/link-popover.tsx
9
- /**
10
- * Link insertion/editing popover
11
- */
12
- function LinkPopover({ editor, open, onOpenChange, disabled }) {
13
- const { t } = useTranslation();
14
- const [linkUrl, setLinkUrl] = React.useState("");
15
- const prevOpenRef = React.useRef(false);
16
- if (open && !prevOpenRef.current && editor) {
17
- const currentLink = editor.getAttributes("link").href;
18
- setLinkUrl(currentLink || "");
19
- }
20
- prevOpenRef.current = open;
21
- const handleApplyLink = React.useCallback(() => {
22
- if (!editor) return;
23
- if (!linkUrl) {
24
- editor.chain().focus().unsetLink().run();
25
- onOpenChange(false);
26
- return;
27
- }
28
- editor.chain().focus().setLink({
29
- href: linkUrl,
30
- target: "_blank",
31
- rel: "noopener noreferrer"
32
- }).run();
33
- onOpenChange(false);
34
- }, [
35
- editor,
36
- linkUrl,
37
- onOpenChange
38
- ]);
39
- const handleRemoveLink = React.useCallback(() => {
40
- if (!editor) return;
41
- editor.chain().focus().unsetLink().run();
42
- onOpenChange(false);
43
- }, [editor, onOpenChange]);
44
- return /* @__PURE__ */ jsxs(Popover, {
45
- open,
46
- onOpenChange,
47
- children: [/* @__PURE__ */ jsx(PopoverTrigger, { render: /* @__PURE__ */ jsx("div", { className: "sr-only" }) }), /* @__PURE__ */ jsxs(PopoverContent, {
48
- className: "w-72",
49
- children: [/* @__PURE__ */ jsx(PopoverHeader, { children: /* @__PURE__ */ jsx(PopoverTitle, { children: t("editor.link") }) }), /* @__PURE__ */ jsxs("div", {
50
- className: "space-y-2",
51
- children: [/* @__PURE__ */ jsx(Input, {
52
- value: linkUrl,
53
- placeholder: "https://example.com",
54
- onChange: (event) => setLinkUrl(event.target.value),
55
- onKeyDown: (event_0) => {
56
- if (event_0.key === "Enter") {
57
- event_0.preventDefault();
58
- handleApplyLink();
59
- }
60
- },
61
- disabled
62
- }), /* @__PURE__ */ jsxs("div", {
63
- className: "flex justify-end gap-2",
64
- children: [/* @__PURE__ */ jsx(Button, {
65
- type: "button",
66
- size: "xs",
67
- variant: "outline",
68
- onClick: handleRemoveLink,
69
- disabled: disabled || !editor?.isActive("link"),
70
- children: t("common.remove")
71
- }), /* @__PURE__ */ jsx(Button, {
72
- type: "button",
73
- size: "xs",
74
- onClick: handleApplyLink,
75
- disabled,
76
- children: t("common.apply")
77
- })]
78
- })]
79
- })]
80
- })]
81
- });
82
- }
83
-
84
- //#endregion
85
- export { LinkPopover };
@@ -1,52 +0,0 @@
1
- import { cn } from "../../lib/utils.mjs";
2
- import { c } from "react/compiler-runtime";
3
- import { Icon } from "@iconify/react";
4
- import { jsx } from "react/jsx-runtime";
5
-
6
- //#region src/client/components/ui/spinner.tsx
7
- function Spinner(t0) {
8
- const $ = c(10);
9
- let className;
10
- let props;
11
- if ($[0] !== t0) {
12
- ({className, ...props} = t0);
13
- $[0] = t0;
14
- $[1] = className;
15
- $[2] = props;
16
- } else {
17
- className = $[1];
18
- props = $[2];
19
- }
20
- let t1;
21
- if ($[3] !== className) {
22
- t1 = cn("qa-spinner inline-flex size-4 animate-spin", className);
23
- $[3] = className;
24
- $[4] = t1;
25
- } else t1 = $[4];
26
- let t2;
27
- if ($[5] !== props) {
28
- t2 = /* @__PURE__ */ jsx(Icon, {
29
- icon: "ph:spinner",
30
- role: "status",
31
- "aria-label": "Loading",
32
- className: "size-full",
33
- ...props
34
- });
35
- $[5] = props;
36
- $[6] = t2;
37
- } else t2 = $[6];
38
- let t3;
39
- if ($[7] !== t1 || $[8] !== t2) {
40
- t3 = /* @__PURE__ */ jsx("span", {
41
- className: t1,
42
- children: t2
43
- });
44
- $[7] = t1;
45
- $[8] = t2;
46
- $[9] = t3;
47
- } else t3 = $[9];
48
- return t3;
49
- }
50
-
51
- //#endregion
52
- export { Spinner };