@btst/plugin-cms 0.0.0-stage → 1.0.0-beta.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.Bptwe33B.d.cts +1749 -0
  99. package/dist/shared/plugin-cms.Csx7tNDO.d.mts +1749 -0
  100. package/dist/shared/plugin-cms.eNW9_FO5.d.ts +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,284 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useState, useMemo, useRef, useEffect } from 'react';
4
+ import { z } from 'zod';
5
+ import { SteppedAutoForm } from '../../../ui/src/components/auto-form/stepped-auto-form.mjs';
6
+ import { buildFieldConfigFromJsonSchema as buildFieldConfigFromJsonSchema$1 } from '../../../ui/src/components/auto-form/helpers.mjs';
7
+ import { formSchemaToZod } from '../../../ui/src/lib/schema-converter.mjs';
8
+ import { Input } from '../../../ui/src/components/input.mjs';
9
+ import { Label } from '../../../ui/src/components/label.mjs';
10
+ import { Badge } from '../../../ui/src/components/badge.mjs';
11
+ import { useTranslate, usePluginOverrides } from '@btst/stack/context';
12
+ import { CMS_PLUGIN_ID } from '../../constants.mjs';
13
+ import { slugify } from '../../../utils.mjs';
14
+ import { CMSFileUpload } from './file-upload.mjs';
15
+ import { RelationField } from './relation-field.mjs';
16
+
17
+ function useServerFieldErrors(form, fieldErrors) {
18
+ const appliedFieldsRef = useRef([]);
19
+ useEffect(() => {
20
+ if (!form) return;
21
+ for (const field of appliedFieldsRef.current) {
22
+ if (field in fieldErrors) continue;
23
+ const { error } = form.getFieldState(field);
24
+ if (error?.type === "server") {
25
+ form.clearErrors(field);
26
+ }
27
+ }
28
+ appliedFieldsRef.current = Object.keys(fieldErrors);
29
+ for (const [field, message] of Object.entries(fieldErrors)) {
30
+ form.setError(field, {
31
+ type: "server",
32
+ message: Array.isArray(message) ? message.join(", ") : message
33
+ });
34
+ }
35
+ }, [fieldErrors, form]);
36
+ }
37
+ function buildFieldConfigFromJsonSchema(jsonSchema, uploadImage, fieldComponents, imagePicker, imageInputField) {
38
+ const baseConfig = buildFieldConfigFromJsonSchema$1(jsonSchema, fieldComponents);
39
+ const properties = jsonSchema.properties;
40
+ if (!properties) return baseConfig;
41
+ const injectCustomFieldTypes = (props, targetConfig) => {
42
+ for (const [key, prop] of Object.entries(props)) {
43
+ const existing = targetConfig[key] ?? {};
44
+ let updated = existing;
45
+ if (prop.fieldType === "file" && !fieldComponents?.["file"]) {
46
+ if (!uploadImage && !imageInputField) {
47
+ updated = {
48
+ ...updated,
49
+ fieldType: () => /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-destructive bg-destructive/10 p-3 text-sm text-destructive", children: [
50
+ "File upload requires an ",
51
+ /* @__PURE__ */ jsx("code", { children: "uploadImage" }),
52
+ " or",
53
+ " ",
54
+ /* @__PURE__ */ jsx("code", { children: "imageInputField" }),
55
+ " function in CMS overrides."
56
+ ] })
57
+ };
58
+ } else {
59
+ updated = {
60
+ ...updated,
61
+ fieldType: (componentProps) => /* @__PURE__ */ jsx(
62
+ CMSFileUpload,
63
+ {
64
+ ...componentProps,
65
+ uploadImage: uploadImage ?? (() => Promise.resolve("")),
66
+ imageInputField,
67
+ imagePicker
68
+ }
69
+ )
70
+ };
71
+ }
72
+ }
73
+ if (prop.fieldType === "relation" && prop.relation && !fieldComponents?.["relation"]) {
74
+ const relationConfig = prop.relation;
75
+ updated = {
76
+ ...updated,
77
+ fieldType: (componentProps) => /* @__PURE__ */ jsx(RelationField, { ...componentProps, relation: relationConfig })
78
+ };
79
+ }
80
+ if (prop.properties) {
81
+ injectCustomFieldTypes(
82
+ prop.properties,
83
+ updated
84
+ );
85
+ }
86
+ const items = prop.items;
87
+ if (items?.properties) {
88
+ injectCustomFieldTypes(
89
+ items.properties,
90
+ updated
91
+ );
92
+ }
93
+ if (Object.keys(updated).length > 0) {
94
+ targetConfig[key] = updated;
95
+ }
96
+ }
97
+ };
98
+ injectCustomFieldTypes(
99
+ properties,
100
+ baseConfig
101
+ );
102
+ return baseConfig;
103
+ }
104
+ function findSlugSourceField(jsonSchema) {
105
+ const properties = jsonSchema.properties;
106
+ if (!properties) return null;
107
+ const priorityFields = ["name", "title", "heading", "label"];
108
+ for (const field of priorityFields) {
109
+ if (properties[field]?.type === "string") {
110
+ return field;
111
+ }
112
+ }
113
+ for (const [key, value] of Object.entries(properties)) {
114
+ if (value.type === "string") {
115
+ return key;
116
+ }
117
+ }
118
+ return null;
119
+ }
120
+ function ContentForm({
121
+ contentType,
122
+ initialData = {},
123
+ initialSlug = "",
124
+ isEditing = false,
125
+ onSubmit,
126
+ onCancel,
127
+ fieldErrors,
128
+ errorMessage,
129
+ isSubmitting: isSubmittingProp
130
+ }) {
131
+ const t = useTranslate();
132
+ const {
133
+ localization,
134
+ uploadImage,
135
+ imagePicker,
136
+ imageInputField,
137
+ fieldComponents
138
+ } = usePluginOverrides(CMS_PLUGIN_ID);
139
+ const [slug, setSlug] = useState(initialSlug);
140
+ const [slugManuallyEdited, setSlugManuallyEdited] = useState(isEditing);
141
+ const [isSubmittingLocal, setIsSubmittingLocal] = useState(false);
142
+ const [formData, setFormData] = useState(initialData);
143
+ const [slugError, setSlugError] = useState(null);
144
+ const [submitError, setSubmitError] = useState(null);
145
+ const isSubmitting = isSubmittingProp || isSubmittingLocal;
146
+ const [formInstance, setFormInstance] = useState(null);
147
+ const serverFieldErrors = useMemo(() => fieldErrors ?? {}, [fieldErrors]);
148
+ useServerFieldErrors(formInstance, serverFieldErrors);
149
+ const hasFieldErrors = Object.keys(serverFieldErrors).length > 0;
150
+ const hasSyncedPrefillRef = useRef(false);
151
+ useEffect(() => {
152
+ const hasData = Object.keys(initialData).length > 0;
153
+ const shouldSync = hasData && (isEditing || !hasSyncedPrefillRef.current);
154
+ if (shouldSync) {
155
+ setFormData(initialData);
156
+ if (!isEditing) {
157
+ hasSyncedPrefillRef.current = true;
158
+ }
159
+ }
160
+ }, [initialData, isEditing]);
161
+ useEffect(() => {
162
+ if (isEditing && initialSlug) {
163
+ setSlug(initialSlug);
164
+ }
165
+ }, [initialSlug, isEditing]);
166
+ const jsonSchema = useMemo(() => {
167
+ try {
168
+ return JSON.parse(contentType.jsonSchema);
169
+ } catch {
170
+ return {};
171
+ }
172
+ }, [contentType.jsonSchema]);
173
+ const zodSchema = useMemo(() => {
174
+ try {
175
+ return formSchemaToZod(jsonSchema);
176
+ } catch {
177
+ return z.object({});
178
+ }
179
+ }, [jsonSchema]);
180
+ const fieldConfig = useMemo(
181
+ () => buildFieldConfigFromJsonSchema(
182
+ jsonSchema,
183
+ uploadImage,
184
+ fieldComponents,
185
+ imagePicker,
186
+ imageInputField
187
+ ),
188
+ [jsonSchema, uploadImage, fieldComponents, imagePicker, imageInputField]
189
+ );
190
+ const slugSourceField = useMemo(
191
+ () => findSlugSourceField(jsonSchema),
192
+ [jsonSchema]
193
+ );
194
+ const handleValuesChange = (values, form) => {
195
+ if (form) {
196
+ setFormInstance((current) => current === form ? current : form);
197
+ }
198
+ setFormData(values);
199
+ if (!isEditing && !slugManuallyEdited && slugSourceField) {
200
+ const sourceValue = values[slugSourceField];
201
+ if (typeof sourceValue === "string" && sourceValue.trim()) {
202
+ setSlug(slugify(sourceValue));
203
+ }
204
+ }
205
+ };
206
+ const handleSubmit = async (data) => {
207
+ setSlugError(null);
208
+ setSubmitError(null);
209
+ if (!slug.trim()) {
210
+ setSlugError(
211
+ localization?.CMS_EDITOR_SLUG_REQUIRED ?? t("cms.editor.slugRequired", "Slug is required")
212
+ );
213
+ return;
214
+ }
215
+ setIsSubmittingLocal(true);
216
+ try {
217
+ await onSubmit({ slug, data });
218
+ } catch (error) {
219
+ const message = error instanceof Error ? error.message : localization?.CMS_TOAST_ERROR ?? t("cms.toasts.error", "An error occurred. Please try again.");
220
+ setSubmitError(message);
221
+ } finally {
222
+ setIsSubmittingLocal(false);
223
+ }
224
+ };
225
+ const bannerMessage = errorMessage ?? submitError ?? (hasFieldErrors && !formInstance ? Object.entries(serverFieldErrors).map(
226
+ ([field, message]) => `${field}: ${Array.isArray(message) ? message.join(", ") : message}`
227
+ ).join(" \xB7 ") : void 0);
228
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
229
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
230
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
231
+ /* @__PURE__ */ jsx(Label, { htmlFor: "slug", children: localization?.CMS_LABEL_SLUG ?? t("cms.common.slugLabel", "Slug") }),
232
+ !isEditing && /* @__PURE__ */ jsx(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") })
233
+ ] }),
234
+ /* @__PURE__ */ jsx(
235
+ Input,
236
+ {
237
+ id: "slug",
238
+ value: slug,
239
+ onChange: (e) => {
240
+ setSlug(e.target.value);
241
+ setSlugError(null);
242
+ if (!isEditing) {
243
+ setSlugManuallyEdited(true);
244
+ }
245
+ },
246
+ disabled: isEditing,
247
+ placeholder: slugSourceField ? (localization?.CMS_EDITOR_SLUG_PLACEHOLDER_AUTO ?? t(
248
+ "cms.editor.slugPlaceholderAuto",
249
+ "Auto-generated from {field}"
250
+ )).replace("{field}", slugSourceField) : localization?.CMS_EDITOR_SLUG_PLACEHOLDER ?? t("cms.editor.slugPlaceholder", "Enter slug...")
251
+ }
252
+ ),
253
+ slugError && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: slugError }),
254
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: localization?.CMS_LABEL_SLUG_DESCRIPTION ?? t(
255
+ "cms.common.slugDescription",
256
+ "URL-friendly identifier for this item"
257
+ ) })
258
+ ] }),
259
+ bannerMessage && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-destructive/50 bg-destructive/10 p-3", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: bannerMessage }) }),
260
+ /* @__PURE__ */ jsx(
261
+ SteppedAutoForm,
262
+ {
263
+ formSchema: zodSchema,
264
+ values: formData,
265
+ onValuesChange: handleValuesChange,
266
+ onSubmit: handleSubmit,
267
+ fieldConfig,
268
+ isSubmitting,
269
+ submitButtonText: isSubmitting ? localization?.CMS_STATUS_SAVING ?? t("cms.common.saving", "Saving...") : localization?.CMS_BUTTON_SAVE ?? t("cms.common.save", "Save"),
270
+ children: onCancel && /* @__PURE__ */ jsx(
271
+ "button",
272
+ {
273
+ type: "button",
274
+ onClick: onCancel,
275
+ className: "px-4 py-2 text-sm text-muted-foreground hover:text-foreground",
276
+ children: localization?.CMS_BUTTON_CANCEL ?? t("cms.common.cancel", "Cancel")
277
+ }
278
+ )
279
+ }
280
+ )
281
+ ] });
282
+ }
283
+
284
+ export { ContentForm };
@@ -0,0 +1,160 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const React = require('react');
6
+ const context = require('@btst/stack/context');
7
+ const constants = require('../../constants.cjs');
8
+ const input = require('../../../ui/src/components/input.cjs');
9
+ const button = require('../../../ui/src/components/button.cjs');
10
+ const form = require('../../../ui/src/components/form.cjs');
11
+ const lucideReact = require('lucide-react');
12
+ const label = require('../../../ui/src/components/auto-form/common/label.cjs');
13
+ const tooltip = require('../../../ui/src/components/auto-form/common/tooltip.cjs');
14
+
15
+ function CMSFileUpload({
16
+ label: label$1,
17
+ isRequired,
18
+ fieldConfigItem,
19
+ fieldProps,
20
+ field,
21
+ uploadImage,
22
+ imageInputField: ImageInputField,
23
+ imagePicker: ImagePickerTrigger
24
+ }) {
25
+ const {
26
+ showLabel: _showLabel,
27
+ value: _value,
28
+ ...safeFieldProps
29
+ } = fieldProps;
30
+ const showLabel = _showLabel === void 0 ? true : _showLabel;
31
+ const t = context.useTranslate();
32
+ const notify = context.useNotify();
33
+ const { localization } = context.usePluginOverrides(constants.CMS_PLUGIN_ID);
34
+ const [isUploading, setIsUploading] = React.useState(false);
35
+ const [previewUrl, setPreviewUrl] = React.useState(
36
+ field.value || null
37
+ );
38
+ React.useEffect(() => {
39
+ const normalizedValue = field.value || null;
40
+ if (normalizedValue !== previewUrl) {
41
+ setPreviewUrl(normalizedValue);
42
+ }
43
+ }, [field.value, previewUrl]);
44
+ const handleFileChange = React.useCallback(
45
+ async (e) => {
46
+ const file = e.target.files?.[0];
47
+ if (!file) return;
48
+ if (!file.type.startsWith("image/")) {
49
+ notify.error(
50
+ localization?.CMS_EDITOR_FILE_INVALID_TYPE ?? t("cms.editor.fileInvalidType", "Please select an image file")
51
+ );
52
+ return;
53
+ }
54
+ setIsUploading(true);
55
+ try {
56
+ const url = await uploadImage(file);
57
+ setPreviewUrl(url);
58
+ field.onChange(url);
59
+ } catch (error) {
60
+ console.error("Image upload failed:", error);
61
+ notify.error(
62
+ localization?.CMS_EDITOR_FILE_UPLOAD_FAILED ?? t("cms.editor.fileUploadFailed", "Failed to upload image")
63
+ );
64
+ } finally {
65
+ setIsUploading(false);
66
+ }
67
+ },
68
+ [field, uploadImage, notify, localization, t]
69
+ );
70
+ const handleRemove = React.useCallback(() => {
71
+ setPreviewUrl(null);
72
+ field.onChange("");
73
+ }, [field]);
74
+ if (ImageInputField) {
75
+ return /* @__PURE__ */ jsxRuntime.jsxs(form.FormItem, { children: [
76
+ showLabel && /* @__PURE__ */ jsxRuntime.jsx(
77
+ label,
78
+ {
79
+ label: fieldConfigItem?.label || label$1,
80
+ isRequired
81
+ }
82
+ ),
83
+ /* @__PURE__ */ jsxRuntime.jsx(form.FormControl, { children: /* @__PURE__ */ jsxRuntime.jsx(
84
+ ImageInputField,
85
+ {
86
+ value: field.value || "",
87
+ onChange: field.onChange,
88
+ isRequired
89
+ }
90
+ ) }),
91
+ /* @__PURE__ */ jsxRuntime.jsx(tooltip, { fieldConfigItem }),
92
+ /* @__PURE__ */ jsxRuntime.jsx(form.FormMessage, {})
93
+ ] });
94
+ }
95
+ return /* @__PURE__ */ jsxRuntime.jsxs(form.FormItem, { children: [
96
+ showLabel && /* @__PURE__ */ jsxRuntime.jsx(
97
+ label,
98
+ {
99
+ label: fieldConfigItem?.label || label$1,
100
+ isRequired
101
+ }
102
+ ),
103
+ !previewUrl && /* @__PURE__ */ jsxRuntime.jsx(form.FormControl, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
104
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
105
+ /* @__PURE__ */ jsxRuntime.jsx(
106
+ input.Input,
107
+ {
108
+ type: "file",
109
+ accept: "image/*",
110
+ ...safeFieldProps,
111
+ onChange: handleFileChange,
112
+ disabled: isUploading,
113
+ className: "cursor-pointer",
114
+ "data-testid": "image-upload-input"
115
+ }
116
+ ),
117
+ isUploading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-background/80", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }) })
118
+ ] }),
119
+ ImagePickerTrigger && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": "image-picker-trigger", children: /* @__PURE__ */ jsxRuntime.jsx(
120
+ ImagePickerTrigger,
121
+ {
122
+ onSelect: (url) => {
123
+ setPreviewUrl(url);
124
+ field.onChange(url);
125
+ }
126
+ }
127
+ ) })
128
+ ] }) }),
129
+ previewUrl && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
130
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-20 w-20 overflow-hidden rounded-md border", children: /* @__PURE__ */ jsxRuntime.jsx(
131
+ "img",
132
+ {
133
+ src: previewUrl,
134
+ alt: "Preview",
135
+ className: "h-full w-full object-cover",
136
+ "data-testid": "image-preview"
137
+ }
138
+ ) }),
139
+ /* @__PURE__ */ jsxRuntime.jsxs(
140
+ button.Button,
141
+ {
142
+ type: "button",
143
+ variant: "outline",
144
+ size: "sm",
145
+ onClick: handleRemove,
146
+ className: "flex items-center gap-1",
147
+ "data-testid": "remove-image-button",
148
+ children: [
149
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" }),
150
+ "Remove"
151
+ ]
152
+ }
153
+ )
154
+ ] }),
155
+ /* @__PURE__ */ jsxRuntime.jsx(tooltip, { fieldConfigItem }),
156
+ /* @__PURE__ */ jsxRuntime.jsx(form.FormMessage, {})
157
+ ] });
158
+ }
159
+
160
+ exports.CMSFileUpload = CMSFileUpload;
@@ -0,0 +1,158 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useState, useEffect, useCallback } from 'react';
4
+ import { useTranslate, useNotify, usePluginOverrides } from '@btst/stack/context';
5
+ import { CMS_PLUGIN_ID } from '../../constants.mjs';
6
+ import { Input } from '../../../ui/src/components/input.mjs';
7
+ import { Button } from '../../../ui/src/components/button.mjs';
8
+ import { FormItem, FormControl, FormMessage } from '../../../ui/src/components/form.mjs';
9
+ import { Loader2, Trash2 } from 'lucide-react';
10
+ import AutoFormLabel from '../../../ui/src/components/auto-form/common/label.mjs';
11
+ import AutoFormTooltip from '../../../ui/src/components/auto-form/common/tooltip.mjs';
12
+
13
+ function CMSFileUpload({
14
+ label,
15
+ isRequired,
16
+ fieldConfigItem,
17
+ fieldProps,
18
+ field,
19
+ uploadImage,
20
+ imageInputField: ImageInputField,
21
+ imagePicker: ImagePickerTrigger
22
+ }) {
23
+ const {
24
+ showLabel: _showLabel,
25
+ value: _value,
26
+ ...safeFieldProps
27
+ } = fieldProps;
28
+ const showLabel = _showLabel === void 0 ? true : _showLabel;
29
+ const t = useTranslate();
30
+ const notify = useNotify();
31
+ const { localization } = usePluginOverrides(CMS_PLUGIN_ID);
32
+ const [isUploading, setIsUploading] = useState(false);
33
+ const [previewUrl, setPreviewUrl] = useState(
34
+ field.value || null
35
+ );
36
+ useEffect(() => {
37
+ const normalizedValue = field.value || null;
38
+ if (normalizedValue !== previewUrl) {
39
+ setPreviewUrl(normalizedValue);
40
+ }
41
+ }, [field.value, previewUrl]);
42
+ const handleFileChange = useCallback(
43
+ async (e) => {
44
+ const file = e.target.files?.[0];
45
+ if (!file) return;
46
+ if (!file.type.startsWith("image/")) {
47
+ notify.error(
48
+ localization?.CMS_EDITOR_FILE_INVALID_TYPE ?? t("cms.editor.fileInvalidType", "Please select an image file")
49
+ );
50
+ return;
51
+ }
52
+ setIsUploading(true);
53
+ try {
54
+ const url = await uploadImage(file);
55
+ setPreviewUrl(url);
56
+ field.onChange(url);
57
+ } catch (error) {
58
+ console.error("Image upload failed:", error);
59
+ notify.error(
60
+ localization?.CMS_EDITOR_FILE_UPLOAD_FAILED ?? t("cms.editor.fileUploadFailed", "Failed to upload image")
61
+ );
62
+ } finally {
63
+ setIsUploading(false);
64
+ }
65
+ },
66
+ [field, uploadImage, notify, localization, t]
67
+ );
68
+ const handleRemove = useCallback(() => {
69
+ setPreviewUrl(null);
70
+ field.onChange("");
71
+ }, [field]);
72
+ if (ImageInputField) {
73
+ return /* @__PURE__ */ jsxs(FormItem, { children: [
74
+ showLabel && /* @__PURE__ */ jsx(
75
+ AutoFormLabel,
76
+ {
77
+ label: fieldConfigItem?.label || label,
78
+ isRequired
79
+ }
80
+ ),
81
+ /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(
82
+ ImageInputField,
83
+ {
84
+ value: field.value || "",
85
+ onChange: field.onChange,
86
+ isRequired
87
+ }
88
+ ) }),
89
+ /* @__PURE__ */ jsx(AutoFormTooltip, { fieldConfigItem }),
90
+ /* @__PURE__ */ jsx(FormMessage, {})
91
+ ] });
92
+ }
93
+ return /* @__PURE__ */ jsxs(FormItem, { children: [
94
+ showLabel && /* @__PURE__ */ jsx(
95
+ AutoFormLabel,
96
+ {
97
+ label: fieldConfigItem?.label || label,
98
+ isRequired
99
+ }
100
+ ),
101
+ !previewUrl && /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
102
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
103
+ /* @__PURE__ */ jsx(
104
+ Input,
105
+ {
106
+ type: "file",
107
+ accept: "image/*",
108
+ ...safeFieldProps,
109
+ onChange: handleFileChange,
110
+ disabled: isUploading,
111
+ className: "cursor-pointer",
112
+ "data-testid": "image-upload-input"
113
+ }
114
+ ),
115
+ isUploading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-background/80", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) })
116
+ ] }),
117
+ ImagePickerTrigger && /* @__PURE__ */ jsx("div", { "data-testid": "image-picker-trigger", children: /* @__PURE__ */ jsx(
118
+ ImagePickerTrigger,
119
+ {
120
+ onSelect: (url) => {
121
+ setPreviewUrl(url);
122
+ field.onChange(url);
123
+ }
124
+ }
125
+ ) })
126
+ ] }) }),
127
+ previewUrl && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
128
+ /* @__PURE__ */ jsx("div", { className: "relative h-20 w-20 overflow-hidden rounded-md border", children: /* @__PURE__ */ jsx(
129
+ "img",
130
+ {
131
+ src: previewUrl,
132
+ alt: "Preview",
133
+ className: "h-full w-full object-cover",
134
+ "data-testid": "image-preview"
135
+ }
136
+ ) }),
137
+ /* @__PURE__ */ jsxs(
138
+ Button,
139
+ {
140
+ type: "button",
141
+ variant: "outline",
142
+ size: "sm",
143
+ onClick: handleRemove,
144
+ className: "flex items-center gap-1",
145
+ "data-testid": "remove-image-button",
146
+ children: [
147
+ /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }),
148
+ "Remove"
149
+ ]
150
+ }
151
+ )
152
+ ] }),
153
+ /* @__PURE__ */ jsx(AutoFormTooltip, { fieldConfigItem }),
154
+ /* @__PURE__ */ jsx(FormMessage, {})
155
+ ] });
156
+ }
157
+
158
+ export { CMSFileUpload };