@btst/plugin-cms 0.0.0-stage → 1.0.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 (204) hide show
  1. package/LICENSE.md +17 -0
  2. package/dist/api/getters.cjs +328 -0
  3. package/dist/api/getters.mjs +314 -0
  4. package/dist/api/index.cjs +29 -0
  5. package/dist/api/index.d.cts +209 -0
  6. package/dist/api/index.d.mts +209 -0
  7. package/dist/api/index.d.ts +209 -0
  8. package/dist/api/index.mjs +7 -0
  9. package/dist/api/lifecycle-migrations.cjs +13 -0
  10. package/dist/api/lifecycle-migrations.mjs +11 -0
  11. package/dist/api/mutations.cjs +59 -0
  12. package/dist/api/mutations.mjs +57 -0
  13. package/dist/api/operations.cjs +1769 -0
  14. package/dist/api/operations.mjs +1762 -0
  15. package/dist/api/plugin.cjs +337 -0
  16. package/dist/api/plugin.mjs +331 -0
  17. package/dist/api/query-key-defs.cjs +44 -0
  18. package/dist/api/query-key-defs.mjs +39 -0
  19. package/dist/api/relations.cjs +76 -0
  20. package/dist/api/relations.mjs +70 -0
  21. package/dist/client/components/forms/content-form.cjs +286 -0
  22. package/dist/client/components/forms/content-form.mjs +284 -0
  23. package/dist/client/components/forms/file-upload.cjs +160 -0
  24. package/dist/client/components/forms/file-upload.mjs +158 -0
  25. package/dist/client/components/forms/relation-field.cjs +237 -0
  26. package/dist/client/components/forms/relation-field.mjs +235 -0
  27. package/dist/client/components/index.cjs +14 -0
  28. package/dist/client/components/index.d.cts +20 -0
  29. package/dist/client/components/index.d.mts +20 -0
  30. package/dist/client/components/index.d.ts +20 -0
  31. package/dist/client/components/index.mjs +10 -0
  32. package/dist/client/components/inverse-relations-panel.cjs +272 -0
  33. package/dist/client/components/inverse-relations-panel.mjs +270 -0
  34. package/dist/client/components/loading/dashboard-skeleton.cjs +18 -0
  35. package/dist/client/components/loading/dashboard-skeleton.mjs +16 -0
  36. package/dist/client/components/loading/editor-skeleton.cjs +32 -0
  37. package/dist/client/components/loading/editor-skeleton.mjs +30 -0
  38. package/dist/client/components/loading/list-skeleton.cjs +32 -0
  39. package/dist/client/components/loading/list-skeleton.mjs +30 -0
  40. package/dist/client/components/pages/404-page.cjs +26 -0
  41. package/dist/client/components/pages/404-page.mjs +24 -0
  42. package/dist/client/components/pages/content-editor-page.cjs +84 -0
  43. package/dist/client/components/pages/content-editor-page.internal.cjs +170 -0
  44. package/dist/client/components/pages/content-editor-page.internal.mjs +168 -0
  45. package/dist/client/components/pages/content-editor-page.mjs +82 -0
  46. package/dist/client/components/pages/content-list-page.cjs +49 -0
  47. package/dist/client/components/pages/content-list-page.internal.cjs +334 -0
  48. package/dist/client/components/pages/content-list-page.internal.mjs +332 -0
  49. package/dist/client/components/pages/content-list-page.mjs +47 -0
  50. package/dist/client/components/pages/dashboard-page.cjs +42 -0
  51. package/dist/client/components/pages/dashboard-page.internal.cjs +102 -0
  52. package/dist/client/components/pages/dashboard-page.internal.mjs +100 -0
  53. package/dist/client/components/pages/dashboard-page.mjs +40 -0
  54. package/dist/client/components/shared/default-error.cjs +17 -0
  55. package/dist/client/components/shared/default-error.mjs +15 -0
  56. package/dist/client/components/shared/empty-state.cjs +16 -0
  57. package/dist/client/components/shared/empty-state.mjs +14 -0
  58. package/dist/client/components/shared/page-wrapper.cjs +29 -0
  59. package/dist/client/components/shared/page-wrapper.mjs +27 -0
  60. package/dist/client/constants.cjs +5 -0
  61. package/dist/client/constants.mjs +3 -0
  62. package/dist/client/hooks/cms-hooks.cjs +300 -0
  63. package/dist/client/hooks/cms-hooks.mjs +280 -0
  64. package/dist/client/hooks/cms-resource.cjs +13 -0
  65. package/dist/client/hooks/cms-resource.mjs +11 -0
  66. package/dist/client/hooks/index.cjs +25 -0
  67. package/dist/client/hooks/index.d.cts +693 -0
  68. package/dist/client/hooks/index.d.mts +693 -0
  69. package/dist/client/hooks/index.d.ts +693 -0
  70. package/dist/client/hooks/index.mjs +1 -0
  71. package/dist/client/index.cjs +15 -0
  72. package/dist/client/index.d.cts +654 -0
  73. package/dist/client/index.d.mts +654 -0
  74. package/dist/client/index.d.ts +654 -0
  75. package/dist/client/index.mjs +3 -0
  76. package/dist/client/overrides.cjs +26 -0
  77. package/dist/client/overrides.mjs +24 -0
  78. package/dist/client/plugin.cjs +316 -0
  79. package/dist/client/plugin.mjs +314 -0
  80. package/dist/client.css +3 -0
  81. package/dist/db.cjs +118 -0
  82. package/dist/db.mjs +116 -0
  83. package/dist/permissions.cjs +46 -0
  84. package/dist/permissions.d.cts +42 -0
  85. package/dist/permissions.d.mts +42 -0
  86. package/dist/permissions.d.ts +42 -0
  87. package/dist/permissions.mjs +44 -0
  88. package/dist/query-keys.cjs +229 -0
  89. package/dist/query-keys.d.cts +8 -0
  90. package/dist/query-keys.d.mts +8 -0
  91. package/dist/query-keys.d.ts +8 -0
  92. package/dist/query-keys.mjs +225 -0
  93. package/dist/schemas.cjs +81 -0
  94. package/dist/schemas.mjs +72 -0
  95. package/dist/shared/plugin-cms.BBshPRkb.d.cts +6 -0
  96. package/dist/shared/plugin-cms.BBshPRkb.d.mts +6 -0
  97. package/dist/shared/plugin-cms.BBshPRkb.d.ts +6 -0
  98. package/dist/shared/plugin-cms.DQLAoogy.d.ts +1749 -0
  99. package/dist/shared/plugin-cms.W4Le8D91.d.mts +1749 -0
  100. package/dist/shared/plugin-cms.c-GeWvTf.d.cts +1749 -0
  101. package/dist/style.css +6 -0
  102. package/dist/types.cjs +2 -0
  103. package/dist/types.d.cts +406 -0
  104. package/dist/types.d.mts +406 -0
  105. package/dist/types.d.ts +406 -0
  106. package/dist/types.mjs +1 -0
  107. package/dist/ui/src/components/accordion.cjs +81 -0
  108. package/dist/ui/src/components/accordion.mjs +62 -0
  109. package/dist/ui/src/components/alert-dialog.cjs +157 -0
  110. package/dist/ui/src/components/alert-dialog.mjs +132 -0
  111. package/dist/ui/src/components/auto-form/common/label.cjs +18 -0
  112. package/dist/ui/src/components/auto-form/common/label.mjs +16 -0
  113. package/dist/ui/src/components/auto-form/common/tooltip.cjs +9 -0
  114. package/dist/ui/src/components/auto-form/common/tooltip.mjs +7 -0
  115. package/dist/ui/src/components/auto-form/config.cjs +40 -0
  116. package/dist/ui/src/components/auto-form/config.mjs +37 -0
  117. package/dist/ui/src/components/auto-form/dependencies.cjs +48 -0
  118. package/dist/ui/src/components/auto-form/dependencies.mjs +46 -0
  119. package/dist/ui/src/components/auto-form/fields/array.cjs +266 -0
  120. package/dist/ui/src/components/auto-form/fields/array.mjs +264 -0
  121. package/dist/ui/src/components/auto-form/fields/checkbox.cjs +38 -0
  122. package/dist/ui/src/components/auto-form/fields/checkbox.mjs +36 -0
  123. package/dist/ui/src/components/auto-form/fields/date.cjs +67 -0
  124. package/dist/ui/src/components/auto-form/fields/date.mjs +65 -0
  125. package/dist/ui/src/components/auto-form/fields/enum.cjs +73 -0
  126. package/dist/ui/src/components/auto-form/fields/enum.mjs +71 -0
  127. package/dist/ui/src/components/auto-form/fields/input.cjs +32 -0
  128. package/dist/ui/src/components/auto-form/fields/input.mjs +30 -0
  129. package/dist/ui/src/components/auto-form/fields/number.cjs +31 -0
  130. package/dist/ui/src/components/auto-form/fields/number.mjs +29 -0
  131. package/dist/ui/src/components/auto-form/fields/object.cjs +233 -0
  132. package/dist/ui/src/components/auto-form/fields/object.mjs +217 -0
  133. package/dist/ui/src/components/auto-form/fields/radio-group.cjs +67 -0
  134. package/dist/ui/src/components/auto-form/fields/radio-group.mjs +65 -0
  135. package/dist/ui/src/components/auto-form/fields/switch.cjs +38 -0
  136. package/dist/ui/src/components/auto-form/fields/switch.mjs +36 -0
  137. package/dist/ui/src/components/auto-form/fields/textarea.cjs +31 -0
  138. package/dist/ui/src/components/auto-form/fields/textarea.mjs +29 -0
  139. package/dist/ui/src/components/auto-form/helpers.cjs +270 -0
  140. package/dist/ui/src/components/auto-form/helpers.mjs +259 -0
  141. package/dist/ui/src/components/auto-form/index.cjs +92 -0
  142. package/dist/ui/src/components/auto-form/index.mjs +86 -0
  143. package/dist/ui/src/components/auto-form/stepped-auto-form.cjs +374 -0
  144. package/dist/ui/src/components/auto-form/stepped-auto-form.mjs +368 -0
  145. package/dist/ui/src/components/auto-form/types.cjs +11 -0
  146. package/dist/ui/src/components/auto-form/types.mjs +9 -0
  147. package/dist/ui/src/components/badge.cjs +42 -0
  148. package/dist/ui/src/components/badge.mjs +39 -0
  149. package/dist/ui/src/components/button.cjs +56 -0
  150. package/dist/ui/src/components/button.mjs +53 -0
  151. package/dist/ui/src/components/calendar.cjs +207 -0
  152. package/dist/ui/src/components/calendar.mjs +190 -0
  153. package/dist/ui/src/components/card.cjs +56 -0
  154. package/dist/ui/src/components/card.mjs +51 -0
  155. package/dist/ui/src/components/checkbox.cjs +48 -0
  156. package/dist/ui/src/components/checkbox.mjs +32 -0
  157. package/dist/ui/src/components/command.cjs +78 -0
  158. package/dist/ui/src/components/command.mjs +73 -0
  159. package/dist/ui/src/components/date-picker.cjs +41 -0
  160. package/dist/ui/src/components/date-picker.mjs +39 -0
  161. package/dist/ui/src/components/dialog.cjs +119 -0
  162. package/dist/ui/src/components/dialog.mjs +97 -0
  163. package/dist/ui/src/components/form.cjs +120 -0
  164. package/dist/ui/src/components/form.mjs +98 -0
  165. package/dist/ui/src/components/input.cjs +23 -0
  166. package/dist/ui/src/components/input.mjs +21 -0
  167. package/dist/ui/src/components/label.cjs +39 -0
  168. package/dist/ui/src/components/label.mjs +23 -0
  169. package/dist/ui/src/components/multi-select.cjs +555 -0
  170. package/dist/ui/src/components/multi-select.mjs +540 -0
  171. package/dist/ui/src/components/page-layout.cjs +25 -0
  172. package/dist/ui/src/components/page-layout.mjs +23 -0
  173. package/dist/ui/src/components/page-wrapper.cjs +20 -0
  174. package/dist/ui/src/components/page-wrapper.mjs +18 -0
  175. package/dist/ui/src/components/popover.cjs +60 -0
  176. package/dist/ui/src/components/popover.mjs +42 -0
  177. package/dist/ui/src/components/radio-group.cjs +62 -0
  178. package/dist/ui/src/components/radio-group.mjs +45 -0
  179. package/dist/ui/src/components/select.cjs +161 -0
  180. package/dist/ui/src/components/select.mjs +139 -0
  181. package/dist/ui/src/components/separator.cjs +43 -0
  182. package/dist/ui/src/components/separator.mjs +27 -0
  183. package/dist/ui/src/components/skeleton.cjs +17 -0
  184. package/dist/ui/src/components/skeleton.mjs +15 -0
  185. package/dist/ui/src/components/stack-attribution.cjs +24 -0
  186. package/dist/ui/src/components/stack-attribution.mjs +22 -0
  187. package/dist/ui/src/components/switch.cjs +48 -0
  188. package/dist/ui/src/components/switch.mjs +32 -0
  189. package/dist/ui/src/components/table.cjs +89 -0
  190. package/dist/ui/src/components/table.mjs +82 -0
  191. package/dist/ui/src/components/textarea.cjs +20 -0
  192. package/dist/ui/src/components/textarea.mjs +18 -0
  193. package/dist/ui/src/hooks/use-debounce.cjs +16 -0
  194. package/dist/ui/src/hooks/use-debounce.mjs +14 -0
  195. package/dist/ui/src/hooks/use-route-lifecycle.cjs +31 -0
  196. package/dist/ui/src/hooks/use-route-lifecycle.mjs +29 -0
  197. package/dist/ui/src/lib/schema-converter.cjs +133 -0
  198. package/dist/ui/src/lib/schema-converter.mjs +127 -0
  199. package/dist/ui/src/lib/utils.cjs +10 -0
  200. package/dist/ui/src/lib/utils.mjs +8 -0
  201. package/dist/utils.cjs +19 -0
  202. package/dist/utils.mjs +12 -0
  203. package/package.json +170 -3
  204. package/README.md +0 -3
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ const schemas = require('../schemas.cjs');
4
+
5
+ function contentReadDiscriminator(options) {
6
+ return {
7
+ ...options?.view ? { view: options.view } : {},
8
+ ...options?.fields !== void 0 ? { fields: schemas.normalizeContentFields(options.fields) } : {}
9
+ };
10
+ }
11
+ function contentReadKey(options) {
12
+ return options?.view || options?.fields !== void 0 ? [contentReadDiscriminator(options)] : [];
13
+ }
14
+ function contentListDiscriminator(params) {
15
+ return {
16
+ ...contentReadDiscriminator(params),
17
+ typeSlug: params.typeSlug,
18
+ limit: params.limit ?? 20,
19
+ offset: params.offset ?? 0,
20
+ search: params.search !== void 0 && params.search.trim() === "" ? void 0 : params.search
21
+ };
22
+ }
23
+ const CMS_QUERY_KEYS = {
24
+ /**
25
+ * Key for the cmsTypes.list() query.
26
+ * Full key: ["cmsTypes", "list", "list"]
27
+ */
28
+ typesList: () => ["cmsTypes", "list", "list"],
29
+ /**
30
+ * Key for the cmsContent.list({ typeSlug, limit, offset }) query.
31
+ * Full key: ["cmsContent", "list", { typeSlug, limit, offset, search }]
32
+ */
33
+ contentList: (params) => ["cmsContent", "list", contentListDiscriminator(params)],
34
+ /**
35
+ * Key for the cmsContent.detail(typeSlug, id) query.
36
+ * Full key: ["cmsContent", "detail", typeSlug, id]
37
+ */
38
+ contentDetail: (typeSlug, id, options) => ["cmsContent", "detail", typeSlug, id, ...contentReadKey(options)]
39
+ };
40
+
41
+ exports.CMS_QUERY_KEYS = CMS_QUERY_KEYS;
42
+ exports.contentListDiscriminator = contentListDiscriminator;
43
+ exports.contentReadDiscriminator = contentReadDiscriminator;
44
+ exports.contentReadKey = contentReadKey;
@@ -0,0 +1,39 @@
1
+ import { normalizeContentFields } from '../schemas.mjs';
2
+
3
+ function contentReadDiscriminator(options) {
4
+ return {
5
+ ...options?.view ? { view: options.view } : {},
6
+ ...options?.fields !== void 0 ? { fields: normalizeContentFields(options.fields) } : {}
7
+ };
8
+ }
9
+ function contentReadKey(options) {
10
+ return options?.view || options?.fields !== void 0 ? [contentReadDiscriminator(options)] : [];
11
+ }
12
+ function contentListDiscriminator(params) {
13
+ return {
14
+ ...contentReadDiscriminator(params),
15
+ typeSlug: params.typeSlug,
16
+ limit: params.limit ?? 20,
17
+ offset: params.offset ?? 0,
18
+ search: params.search !== void 0 && params.search.trim() === "" ? void 0 : params.search
19
+ };
20
+ }
21
+ const CMS_QUERY_KEYS = {
22
+ /**
23
+ * Key for the cmsTypes.list() query.
24
+ * Full key: ["cmsTypes", "list", "list"]
25
+ */
26
+ typesList: () => ["cmsTypes", "list", "list"],
27
+ /**
28
+ * Key for the cmsContent.list({ typeSlug, limit, offset }) query.
29
+ * Full key: ["cmsContent", "list", { typeSlug, limit, offset, search }]
30
+ */
31
+ contentList: (params) => ["cmsContent", "list", contentListDiscriminator(params)],
32
+ /**
33
+ * Key for the cmsContent.detail(typeSlug, id) query.
34
+ * Full key: ["cmsContent", "detail", typeSlug, id]
35
+ */
36
+ contentDetail: (typeSlug, id, options) => ["cmsContent", "detail", typeSlug, id, ...contentReadKey(options)]
37
+ };
38
+
39
+ export { CMS_QUERY_KEYS, contentListDiscriminator, contentReadDiscriminator, contentReadKey };
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ function extractRelationFields(contentType) {
4
+ const jsonSchema = JSON.parse(
5
+ contentType.jsonSchema
6
+ );
7
+ const properties = jsonSchema.properties || {};
8
+ const relationFields = {};
9
+ for (const [fieldName, fieldSchema] of Object.entries(properties)) {
10
+ if (fieldSchema.fieldType === "relation" && fieldSchema.relation) {
11
+ relationFields[fieldName] = fieldSchema.relation;
12
+ }
13
+ }
14
+ return relationFields;
15
+ }
16
+ function isNewRelationValue(value) {
17
+ return typeof value === "object" && value !== null && "_new" in value && value._new === true && "data" in value;
18
+ }
19
+ function isExistingRelationValue(value) {
20
+ return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string";
21
+ }
22
+ function collectExistingRelationIds(data, relationFields) {
23
+ const relationIds = {};
24
+ for (const [fieldName, relationConfig] of Object.entries(relationFields)) {
25
+ if (!(fieldName in data)) continue;
26
+ const fieldValue = data[fieldName];
27
+ if (!fieldValue) {
28
+ relationIds[fieldName] = [];
29
+ continue;
30
+ }
31
+ const ids = [];
32
+ if (relationConfig.type === "belongsTo") {
33
+ const value = fieldValue;
34
+ if (isExistingRelationValue(value)) {
35
+ ids.push(value.id);
36
+ }
37
+ } else {
38
+ const values = Array.isArray(fieldValue) ? fieldValue : [];
39
+ for (const value of values) {
40
+ if (isExistingRelationValue(value)) {
41
+ ids.push(value.id);
42
+ }
43
+ }
44
+ }
45
+ relationIds[fieldName] = ids;
46
+ }
47
+ return relationIds;
48
+ }
49
+ async function syncRelations(adapter, sourceId, relationIds) {
50
+ for (const [fieldName, targetIds] of Object.entries(relationIds)) {
51
+ await adapter.delete({
52
+ model: "contentRelation",
53
+ where: [
54
+ { field: "sourceId", value: sourceId, operator: "eq" },
55
+ { field: "fieldName", value: fieldName, operator: "eq" }
56
+ ]
57
+ });
58
+ for (const targetId of targetIds) {
59
+ await adapter.create({
60
+ model: "contentRelation",
61
+ data: {
62
+ sourceId,
63
+ targetId,
64
+ fieldName,
65
+ createdAt: /* @__PURE__ */ new Date()
66
+ }
67
+ });
68
+ }
69
+ }
70
+ }
71
+
72
+ exports.collectExistingRelationIds = collectExistingRelationIds;
73
+ exports.extractRelationFields = extractRelationFields;
74
+ exports.isExistingRelationValue = isExistingRelationValue;
75
+ exports.isNewRelationValue = isNewRelationValue;
76
+ exports.syncRelations = syncRelations;
@@ -0,0 +1,70 @@
1
+ function extractRelationFields(contentType) {
2
+ const jsonSchema = JSON.parse(
3
+ contentType.jsonSchema
4
+ );
5
+ const properties = jsonSchema.properties || {};
6
+ const relationFields = {};
7
+ for (const [fieldName, fieldSchema] of Object.entries(properties)) {
8
+ if (fieldSchema.fieldType === "relation" && fieldSchema.relation) {
9
+ relationFields[fieldName] = fieldSchema.relation;
10
+ }
11
+ }
12
+ return relationFields;
13
+ }
14
+ function isNewRelationValue(value) {
15
+ return typeof value === "object" && value !== null && "_new" in value && value._new === true && "data" in value;
16
+ }
17
+ function isExistingRelationValue(value) {
18
+ return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string";
19
+ }
20
+ function collectExistingRelationIds(data, relationFields) {
21
+ const relationIds = {};
22
+ for (const [fieldName, relationConfig] of Object.entries(relationFields)) {
23
+ if (!(fieldName in data)) continue;
24
+ const fieldValue = data[fieldName];
25
+ if (!fieldValue) {
26
+ relationIds[fieldName] = [];
27
+ continue;
28
+ }
29
+ const ids = [];
30
+ if (relationConfig.type === "belongsTo") {
31
+ const value = fieldValue;
32
+ if (isExistingRelationValue(value)) {
33
+ ids.push(value.id);
34
+ }
35
+ } else {
36
+ const values = Array.isArray(fieldValue) ? fieldValue : [];
37
+ for (const value of values) {
38
+ if (isExistingRelationValue(value)) {
39
+ ids.push(value.id);
40
+ }
41
+ }
42
+ }
43
+ relationIds[fieldName] = ids;
44
+ }
45
+ return relationIds;
46
+ }
47
+ async function syncRelations(adapter, sourceId, relationIds) {
48
+ for (const [fieldName, targetIds] of Object.entries(relationIds)) {
49
+ await adapter.delete({
50
+ model: "contentRelation",
51
+ where: [
52
+ { field: "sourceId", value: sourceId, operator: "eq" },
53
+ { field: "fieldName", value: fieldName, operator: "eq" }
54
+ ]
55
+ });
56
+ for (const targetId of targetIds) {
57
+ await adapter.create({
58
+ model: "contentRelation",
59
+ data: {
60
+ sourceId,
61
+ targetId,
62
+ fieldName,
63
+ createdAt: /* @__PURE__ */ new Date()
64
+ }
65
+ });
66
+ }
67
+ }
68
+ }
69
+
70
+ export { collectExistingRelationIds, extractRelationFields, isExistingRelationValue, isNewRelationValue, syncRelations };
@@ -0,0 +1,286 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const React = require('react');
6
+ const z = require('zod');
7
+ const steppedAutoForm = require('../../../ui/src/components/auto-form/stepped-auto-form.cjs');
8
+ const helpers = require('../../../ui/src/components/auto-form/helpers.cjs');
9
+ const schemaConverter = require('../../../ui/src/lib/schema-converter.cjs');
10
+ const input = require('../../../ui/src/components/input.cjs');
11
+ const label = require('../../../ui/src/components/label.cjs');
12
+ const badge = require('../../../ui/src/components/badge.cjs');
13
+ const context = require('@btst/stack/context');
14
+ const constants = require('../../constants.cjs');
15
+ const utils = require('../../../utils.cjs');
16
+ const fileUpload = require('./file-upload.cjs');
17
+ const relationField = require('./relation-field.cjs');
18
+
19
+ function useServerFieldErrors(form, fieldErrors) {
20
+ const appliedFieldsRef = React.useRef([]);
21
+ React.useEffect(() => {
22
+ if (!form) return;
23
+ for (const field of appliedFieldsRef.current) {
24
+ if (field in fieldErrors) continue;
25
+ const { error } = form.getFieldState(field);
26
+ if (error?.type === "server") {
27
+ form.clearErrors(field);
28
+ }
29
+ }
30
+ appliedFieldsRef.current = Object.keys(fieldErrors);
31
+ for (const [field, message] of Object.entries(fieldErrors)) {
32
+ form.setError(field, {
33
+ type: "server",
34
+ message: Array.isArray(message) ? message.join(", ") : message
35
+ });
36
+ }
37
+ }, [fieldErrors, form]);
38
+ }
39
+ function buildFieldConfigFromJsonSchema(jsonSchema, uploadImage, fieldComponents, imagePicker, imageInputField) {
40
+ const baseConfig = helpers.buildFieldConfigFromJsonSchema(jsonSchema, fieldComponents);
41
+ const properties = jsonSchema.properties;
42
+ if (!properties) return baseConfig;
43
+ const injectCustomFieldTypes = (props, targetConfig) => {
44
+ for (const [key, prop] of Object.entries(props)) {
45
+ const existing = targetConfig[key] ?? {};
46
+ let updated = existing;
47
+ if (prop.fieldType === "file" && !fieldComponents?.["file"]) {
48
+ if (!uploadImage && !imageInputField) {
49
+ updated = {
50
+ ...updated,
51
+ fieldType: () => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md border border-destructive bg-destructive/10 p-3 text-sm text-destructive", children: [
52
+ "File upload requires an ",
53
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "uploadImage" }),
54
+ " or",
55
+ " ",
56
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "imageInputField" }),
57
+ " function in CMS overrides."
58
+ ] })
59
+ };
60
+ } else {
61
+ updated = {
62
+ ...updated,
63
+ fieldType: (componentProps) => /* @__PURE__ */ jsxRuntime.jsx(
64
+ fileUpload.CMSFileUpload,
65
+ {
66
+ ...componentProps,
67
+ uploadImage: uploadImage ?? (() => Promise.resolve("")),
68
+ imageInputField,
69
+ imagePicker
70
+ }
71
+ )
72
+ };
73
+ }
74
+ }
75
+ if (prop.fieldType === "relation" && prop.relation && !fieldComponents?.["relation"]) {
76
+ const relationConfig = prop.relation;
77
+ updated = {
78
+ ...updated,
79
+ fieldType: (componentProps) => /* @__PURE__ */ jsxRuntime.jsx(relationField.RelationField, { ...componentProps, relation: relationConfig })
80
+ };
81
+ }
82
+ if (prop.properties) {
83
+ injectCustomFieldTypes(
84
+ prop.properties,
85
+ updated
86
+ );
87
+ }
88
+ const items = prop.items;
89
+ if (items?.properties) {
90
+ injectCustomFieldTypes(
91
+ items.properties,
92
+ updated
93
+ );
94
+ }
95
+ if (Object.keys(updated).length > 0) {
96
+ targetConfig[key] = updated;
97
+ }
98
+ }
99
+ };
100
+ injectCustomFieldTypes(
101
+ properties,
102
+ baseConfig
103
+ );
104
+ return baseConfig;
105
+ }
106
+ function findSlugSourceField(jsonSchema) {
107
+ const properties = jsonSchema.properties;
108
+ if (!properties) return null;
109
+ const priorityFields = ["name", "title", "heading", "label"];
110
+ for (const field of priorityFields) {
111
+ if (properties[field]?.type === "string") {
112
+ return field;
113
+ }
114
+ }
115
+ for (const [key, value] of Object.entries(properties)) {
116
+ if (value.type === "string") {
117
+ return key;
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+ function ContentForm({
123
+ contentType,
124
+ initialData = {},
125
+ initialSlug = "",
126
+ isEditing = false,
127
+ onSubmit,
128
+ onCancel,
129
+ fieldErrors,
130
+ errorMessage,
131
+ isSubmitting: isSubmittingProp
132
+ }) {
133
+ const t = context.useTranslate();
134
+ const {
135
+ localization,
136
+ uploadImage,
137
+ imagePicker,
138
+ imageInputField,
139
+ fieldComponents
140
+ } = context.usePluginOverrides(constants.CMS_PLUGIN_ID);
141
+ const [slug, setSlug] = React.useState(initialSlug);
142
+ const [slugManuallyEdited, setSlugManuallyEdited] = React.useState(isEditing);
143
+ const [isSubmittingLocal, setIsSubmittingLocal] = React.useState(false);
144
+ const [formData, setFormData] = React.useState(initialData);
145
+ const [slugError, setSlugError] = React.useState(null);
146
+ const [submitError, setSubmitError] = React.useState(null);
147
+ const isSubmitting = isSubmittingProp || isSubmittingLocal;
148
+ const [formInstance, setFormInstance] = React.useState(null);
149
+ const serverFieldErrors = React.useMemo(() => fieldErrors ?? {}, [fieldErrors]);
150
+ useServerFieldErrors(formInstance, serverFieldErrors);
151
+ const hasFieldErrors = Object.keys(serverFieldErrors).length > 0;
152
+ const hasSyncedPrefillRef = React.useRef(false);
153
+ React.useEffect(() => {
154
+ const hasData = Object.keys(initialData).length > 0;
155
+ const shouldSync = hasData && (isEditing || !hasSyncedPrefillRef.current);
156
+ if (shouldSync) {
157
+ setFormData(initialData);
158
+ if (!isEditing) {
159
+ hasSyncedPrefillRef.current = true;
160
+ }
161
+ }
162
+ }, [initialData, isEditing]);
163
+ React.useEffect(() => {
164
+ if (isEditing && initialSlug) {
165
+ setSlug(initialSlug);
166
+ }
167
+ }, [initialSlug, isEditing]);
168
+ const jsonSchema = React.useMemo(() => {
169
+ try {
170
+ return JSON.parse(contentType.jsonSchema);
171
+ } catch {
172
+ return {};
173
+ }
174
+ }, [contentType.jsonSchema]);
175
+ const zodSchema = React.useMemo(() => {
176
+ try {
177
+ return schemaConverter.formSchemaToZod(jsonSchema);
178
+ } catch {
179
+ return z.z.object({});
180
+ }
181
+ }, [jsonSchema]);
182
+ const fieldConfig = React.useMemo(
183
+ () => buildFieldConfigFromJsonSchema(
184
+ jsonSchema,
185
+ uploadImage,
186
+ fieldComponents,
187
+ imagePicker,
188
+ imageInputField
189
+ ),
190
+ [jsonSchema, uploadImage, fieldComponents, imagePicker, imageInputField]
191
+ );
192
+ const slugSourceField = React.useMemo(
193
+ () => findSlugSourceField(jsonSchema),
194
+ [jsonSchema]
195
+ );
196
+ const handleValuesChange = (values, form) => {
197
+ if (form) {
198
+ setFormInstance((current) => current === form ? current : form);
199
+ }
200
+ setFormData(values);
201
+ if (!isEditing && !slugManuallyEdited && slugSourceField) {
202
+ const sourceValue = values[slugSourceField];
203
+ if (typeof sourceValue === "string" && sourceValue.trim()) {
204
+ setSlug(utils.slugify(sourceValue));
205
+ }
206
+ }
207
+ };
208
+ const handleSubmit = async (data) => {
209
+ setSlugError(null);
210
+ setSubmitError(null);
211
+ if (!slug.trim()) {
212
+ setSlugError(
213
+ localization?.CMS_EDITOR_SLUG_REQUIRED ?? t("cms.editor.slugRequired", "Slug is required")
214
+ );
215
+ return;
216
+ }
217
+ setIsSubmittingLocal(true);
218
+ try {
219
+ await onSubmit({ slug, data });
220
+ } catch (error) {
221
+ const message = error instanceof Error ? error.message : localization?.CMS_TOAST_ERROR ?? t("cms.toasts.error", "An error occurred. Please try again.");
222
+ setSubmitError(message);
223
+ } finally {
224
+ setIsSubmittingLocal(false);
225
+ }
226
+ };
227
+ const bannerMessage = errorMessage ?? submitError ?? (hasFieldErrors && !formInstance ? Object.entries(serverFieldErrors).map(
228
+ ([field, message]) => `${field}: ${Array.isArray(message) ? message.join(", ") : message}`
229
+ ).join(" \xB7 ") : void 0);
230
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
231
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
232
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
233
+ /* @__PURE__ */ jsxRuntime.jsx(label.Label, { htmlFor: "slug", children: localization?.CMS_LABEL_SLUG ?? t("cms.common.slugLabel", "Slug") }),
234
+ !isEditing && /* @__PURE__ */ jsxRuntime.jsx(badge.Badge, { variant: "outline", className: "text-xs", children: slugManuallyEdited ? localization?.CMS_EDITOR_SLUG_MANUAL ?? t("cms.editor.slugManual", "Manually set") : localization?.CMS_EDITOR_SLUG_AUTO ?? t("cms.editor.slugAuto", "Auto-generated from first field") })
235
+ ] }),
236
+ /* @__PURE__ */ jsxRuntime.jsx(
237
+ input.Input,
238
+ {
239
+ id: "slug",
240
+ value: slug,
241
+ onChange: (e) => {
242
+ setSlug(e.target.value);
243
+ setSlugError(null);
244
+ if (!isEditing) {
245
+ setSlugManuallyEdited(true);
246
+ }
247
+ },
248
+ disabled: isEditing,
249
+ placeholder: slugSourceField ? (localization?.CMS_EDITOR_SLUG_PLACEHOLDER_AUTO ?? t(
250
+ "cms.editor.slugPlaceholderAuto",
251
+ "Auto-generated from {field}"
252
+ )).replace("{field}", slugSourceField) : localization?.CMS_EDITOR_SLUG_PLACEHOLDER ?? t("cms.editor.slugPlaceholder", "Enter slug...")
253
+ }
254
+ ),
255
+ slugError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive", children: slugError }),
256
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: localization?.CMS_LABEL_SLUG_DESCRIPTION ?? t(
257
+ "cms.common.slugDescription",
258
+ "URL-friendly identifier for this item"
259
+ ) })
260
+ ] }),
261
+ bannerMessage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-md border border-destructive/50 bg-destructive/10 p-3", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive", children: bannerMessage }) }),
262
+ /* @__PURE__ */ jsxRuntime.jsx(
263
+ steppedAutoForm.SteppedAutoForm,
264
+ {
265
+ formSchema: zodSchema,
266
+ values: formData,
267
+ onValuesChange: handleValuesChange,
268
+ onSubmit: handleSubmit,
269
+ fieldConfig,
270
+ isSubmitting,
271
+ submitButtonText: isSubmitting ? localization?.CMS_STATUS_SAVING ?? t("cms.common.saving", "Saving...") : localization?.CMS_BUTTON_SAVE ?? t("cms.common.save", "Save"),
272
+ children: onCancel && /* @__PURE__ */ jsxRuntime.jsx(
273
+ "button",
274
+ {
275
+ type: "button",
276
+ onClick: onCancel,
277
+ className: "px-4 py-2 text-sm text-muted-foreground hover:text-foreground",
278
+ children: localization?.CMS_BUTTON_CANCEL ?? t("cms.common.cancel", "Cancel")
279
+ }
280
+ )
281
+ }
282
+ )
283
+ ] });
284
+ }
285
+
286
+ exports.ContentForm = ContentForm;