@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,334 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const React = require('react');
6
+ const lucideReact = require('lucide-react');
7
+ const button = require('../../../ui/src/components/button.cjs');
8
+ const input = require('../../../ui/src/components/input.cjs');
9
+ const table = require('../../../ui/src/components/table.cjs');
10
+ const context = require('@btst/stack/context');
11
+ const permissions = require('../../../permissions.cjs');
12
+ const hooks = require('@btst/stack/client/hooks');
13
+ const overrides = require('../../overrides.cjs');
14
+ const constants = require('../../constants.cjs');
15
+ const utils = require('../../../utils.cjs');
16
+ const emptyState = require('../shared/empty-state.cjs');
17
+ const pageWrapper = require('../shared/page-wrapper.cjs');
18
+ const useRouteLifecycle = require('../../../ui/src/hooks/use-route-lifecycle.cjs');
19
+ const cmsHooks = require('../../hooks/cms-hooks.cjs');
20
+
21
+ const LIST_STATE_SCHEMA = {
22
+ q: { type: "string", default: "", history: "replace" }
23
+ };
24
+ const SEARCH_DEBOUNCE_MS = 300;
25
+ function ContentListPage({ typeSlug }) {
26
+ const t = context.useTranslate();
27
+ const notify = context.useNotify();
28
+ const overrides$1 = context.usePluginOverrides(constants.CMS_PLUGIN_ID);
29
+ const { localization, classNames } = overrides$1;
30
+ const { router, plugins, basePath: stackBasePath } = context.useStack();
31
+ const navigate = router?.navigate;
32
+ const Link = router?.Link;
33
+ const basePath = plugins?.[constants.CMS_PLUGIN_ID]?.site.basePath ?? stackBasePath;
34
+ useRouteLifecycle.useRouteLifecycle({
35
+ routeName: "contentList",
36
+ context: {
37
+ path: `/cms/${typeSlug}`,
38
+ params: { typeSlug },
39
+ isSSR: typeof window === "undefined"
40
+ },
41
+ overrides: overrides$1
42
+ });
43
+ const limit = 20;
44
+ const [{ q: search }, setListState] = hooks.useListState(
45
+ `cms-${typeSlug}`,
46
+ LIST_STATE_SCHEMA
47
+ );
48
+ const [searchInput, setSearchInput] = React.useState(search);
49
+ const lastSyncedSearch = React.useRef(search);
50
+ React.useEffect(() => {
51
+ if (search !== lastSyncedSearch.current) {
52
+ lastSyncedSearch.current = search;
53
+ setSearchInput(search);
54
+ }
55
+ }, [search]);
56
+ React.useEffect(() => {
57
+ if (searchInput === search) return;
58
+ const timeout = setTimeout(() => {
59
+ lastSyncedSearch.current = searchInput;
60
+ setListState({ q: searchInput });
61
+ }, SEARCH_DEBOUNCE_MS);
62
+ return () => clearTimeout(timeout);
63
+ }, [searchInput, search, setListState]);
64
+ const hasSearch = search.trim().length > 0;
65
+ const { contentTypes: serverContentTypes } = cmsHooks.useSuspenseContentTypes();
66
+ const contentTypes = overrides.orderCMSContentTypes(
67
+ serverContentTypes,
68
+ plugins?.[constants.CMS_PLUGIN_ID]?.config
69
+ );
70
+ const contentType = contentTypes.find((ct) => ct.slug === typeSlug);
71
+ const defaultList = cmsHooks.useSuspenseContent(typeSlug, { limit });
72
+ const searchedList = cmsHooks.useContent(typeSlug, {
73
+ limit,
74
+ search,
75
+ enabled: hasSearch
76
+ });
77
+ const activeList = hasSearch ? searchedList : defaultList;
78
+ const { items, total, loadMore, hasMore, isLoadingMore } = activeList;
79
+ const isSearchLoading = hasSearch && searchedList.isLoading;
80
+ const deleteContent = cmsHooks.useDeleteContent(typeSlug);
81
+ const LinkComponent = Link || "a";
82
+ const handleDelete = async (id) => {
83
+ try {
84
+ await deleteContent.mutateAsync(id);
85
+ } catch {
86
+ notify.error(
87
+ localization?.CMS_TOAST_ERROR ?? t("cms.toasts.error", "An error occurred. Please try again.")
88
+ );
89
+ return;
90
+ }
91
+ notify.success(
92
+ localization?.CMS_TOAST_DELETE_SUCCESS ?? t("cms.toasts.deleteSuccess", "Item deleted successfully")
93
+ );
94
+ };
95
+ const formatDate = (dateString) => {
96
+ return new Date(dateString).toLocaleDateString();
97
+ };
98
+ if (!contentType) {
99
+ return /* @__PURE__ */ jsxRuntime.jsx(pageWrapper.PageWrapper, { testId: "cms-list-page", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full max-w-5xl", children: /* @__PURE__ */ jsxRuntime.jsx(
100
+ emptyState.EmptyState,
101
+ {
102
+ title: localization?.CMS_ERROR_NOT_FOUND ?? t("cms.common.notFound", "Not found"),
103
+ description: localization?.CMS_ERROR_TYPE_NOT_FOUND_DESCRIPTION ?? t("cms.common.typeNotFoundDescription", "Content type not found")
104
+ }
105
+ ) }) });
106
+ }
107
+ return /* @__PURE__ */ jsxRuntime.jsx(pageWrapper.PageWrapper, { testId: "cms-list-page", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-5xl space-y-6", children: [
108
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
109
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
110
+ /* @__PURE__ */ jsxRuntime.jsx(
111
+ button.Button,
112
+ {
113
+ variant: "ghost",
114
+ size: "icon",
115
+ onClick: () => void navigate?.(context.joinBasePath(basePath, "/cms")),
116
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "h-4 w-4" })
117
+ }
118
+ ),
119
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
120
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold tracking-tight", children: contentType.name }),
121
+ contentType.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground", children: contentType.description })
122
+ ] })
123
+ ] }),
124
+ /* @__PURE__ */ jsxRuntime.jsx(
125
+ context.PermissionAccess,
126
+ {
127
+ permission: permissions.cmsPermissions.record.create({
128
+ contentType: typeSlug
129
+ }),
130
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
131
+ button.Button,
132
+ {
133
+ onClick: () => void navigate?.(context.joinBasePath(basePath, `/cms/${typeSlug}/new`)),
134
+ children: [
135
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4 mr-2" }),
136
+ localization?.CMS_BUTTON_NEW_ITEM ?? t("cms.common.newItem", "New Item")
137
+ ]
138
+ }
139
+ )
140
+ }
141
+ )
142
+ ] }),
143
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative max-w-sm", children: [
144
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
145
+ /* @__PURE__ */ jsxRuntime.jsx(
146
+ input.Input,
147
+ {
148
+ "data-testid": "cms-list-search",
149
+ value: searchInput,
150
+ onChange: (e) => setSearchInput(e.target.value),
151
+ placeholder: localization?.CMS_LIST_SEARCH_PLACEHOLDER ?? t("cms.list.searchPlaceholder", "Search items..."),
152
+ className: "pl-9"
153
+ }
154
+ ),
155
+ isSearchLoading && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" })
156
+ ] }),
157
+ items.length === 0 ? isSearchLoading ? null : hasSearch ? /* @__PURE__ */ jsxRuntime.jsx(
158
+ emptyState.EmptyState,
159
+ {
160
+ title: localization?.CMS_LIST_SEARCH_EMPTY ?? t("cms.list.searchEmpty", "No items match your search"),
161
+ description: localization?.CMS_LIST_SEARCH_EMPTY_DESCRIPTION ?? t(
162
+ "cms.list.searchEmptyDescription",
163
+ "Try a different search term."
164
+ )
165
+ }
166
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
167
+ emptyState.EmptyState,
168
+ {
169
+ title: localization?.CMS_LIST_EMPTY ?? t("cms.list.empty", "No items yet"),
170
+ description: localization?.CMS_LIST_EMPTY_DESCRIPTION ?? t(
171
+ "cms.list.emptyDescription",
172
+ "Create your first item to get started."
173
+ ),
174
+ action: /* @__PURE__ */ jsxRuntime.jsx(
175
+ context.PermissionAccess,
176
+ {
177
+ permission: permissions.cmsPermissions.record.create({
178
+ contentType: typeSlug
179
+ }),
180
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
181
+ button.Button,
182
+ {
183
+ onClick: () => void navigate?.(
184
+ context.joinBasePath(basePath, `/cms/${typeSlug}/new`)
185
+ ),
186
+ children: [
187
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4 mr-2" }),
188
+ localization?.CMS_BUTTON_CREATE ?? t("cms.common.create", "Create")
189
+ ]
190
+ }
191
+ )
192
+ }
193
+ )
194
+ }
195
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
196
+ ContentTable,
197
+ {
198
+ items,
199
+ total,
200
+ typeSlug,
201
+ basePath,
202
+ LinkComponent,
203
+ navigate,
204
+ onDelete: handleDelete,
205
+ isDeleting: deleteContent.isPending,
206
+ formatDate,
207
+ loadMore,
208
+ hasMore,
209
+ isLoadingMore,
210
+ localization,
211
+ classNames,
212
+ t
213
+ }
214
+ )
215
+ ] }) });
216
+ }
217
+ function ContentTable({
218
+ items,
219
+ total,
220
+ typeSlug,
221
+ basePath,
222
+ LinkComponent,
223
+ navigate,
224
+ onDelete,
225
+ isDeleting,
226
+ formatDate,
227
+ loadMore,
228
+ hasMore,
229
+ isLoadingMore,
230
+ localization,
231
+ classNames,
232
+ t
233
+ }) {
234
+ return /* @__PURE__ */ jsxRuntime.jsxs(
235
+ "div",
236
+ {
237
+ className: utils.cn("border rounded-lg", classNames?.table),
238
+ "data-testid": "cms-content-table",
239
+ children: [
240
+ /* @__PURE__ */ jsxRuntime.jsxs(table.Table, { children: [
241
+ /* @__PURE__ */ jsxRuntime.jsx(table.TableHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(table.TableRow, { children: [
242
+ /* @__PURE__ */ jsxRuntime.jsx(table.TableHead, { children: localization?.CMS_LIST_COLUMN_SLUG ?? t("cms.list.columnSlug", "Slug") }),
243
+ /* @__PURE__ */ jsxRuntime.jsx(table.TableHead, { children: localization?.CMS_LIST_COLUMN_CREATED ?? t("cms.list.columnCreated", "Created") }),
244
+ /* @__PURE__ */ jsxRuntime.jsx(table.TableHead, { children: localization?.CMS_LIST_COLUMN_UPDATED ?? t("cms.list.columnUpdated", "Updated") }),
245
+ /* @__PURE__ */ jsxRuntime.jsx(table.TableHead, { className: "w-[100px]", children: localization?.CMS_LIST_COLUMN_ACTIONS ?? t("cms.list.columnActions", "Actions") })
246
+ ] }) }),
247
+ /* @__PURE__ */ jsxRuntime.jsx(table.TableBody, { children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(table.TableRow, { className: classNames?.tableRow, children: [
248
+ /* @__PURE__ */ jsxRuntime.jsx(table.TableCell, { className: "font-medium", children: /* @__PURE__ */ jsxRuntime.jsx(
249
+ LinkComponent,
250
+ {
251
+ href: context.joinBasePath(basePath, `/cms/${typeSlug}/${item.id}`),
252
+ className: "hover:underline",
253
+ children: item.slug
254
+ }
255
+ ) }),
256
+ /* @__PURE__ */ jsxRuntime.jsx(table.TableCell, { children: formatDate(item.createdAt) }),
257
+ /* @__PURE__ */ jsxRuntime.jsx(table.TableCell, { children: formatDate(item.updatedAt) }),
258
+ /* @__PURE__ */ jsxRuntime.jsx(table.TableCell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
259
+ /* @__PURE__ */ jsxRuntime.jsx(
260
+ context.PermissionAccess,
261
+ {
262
+ permission: permissions.cmsPermissions.record.update({
263
+ contentType: item.contentType?.slug ?? typeSlug,
264
+ recordId: item.id,
265
+ ...item.authorId ? { authorId: item.authorId } : {}
266
+ }),
267
+ children: /* @__PURE__ */ jsxRuntime.jsx(
268
+ button.Button,
269
+ {
270
+ variant: "ghost",
271
+ size: "icon",
272
+ onClick: () => void navigate?.(
273
+ context.joinBasePath(basePath, `/cms/${typeSlug}/${item.id}`)
274
+ ),
275
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "h-4 w-4" })
276
+ }
277
+ )
278
+ }
279
+ ),
280
+ /* @__PURE__ */ jsxRuntime.jsx(
281
+ context.PermissionAccess,
282
+ {
283
+ permission: permissions.cmsPermissions.record.delete({
284
+ contentType: item.contentType?.slug ?? typeSlug,
285
+ recordId: item.id,
286
+ ...item.authorId ? { authorId: item.authorId } : {}
287
+ }),
288
+ children: /* @__PURE__ */ jsxRuntime.jsx(
289
+ button.Button,
290
+ {
291
+ variant: "ghost",
292
+ size: "icon",
293
+ onClick: () => onDelete(item.id),
294
+ disabled: isDeleting,
295
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4 text-destructive" })
296
+ }
297
+ )
298
+ }
299
+ )
300
+ ] }) })
301
+ ] }, item.id)) })
302
+ ] }),
303
+ /* @__PURE__ */ jsxRuntime.jsxs(
304
+ "div",
305
+ {
306
+ className: utils.cn(
307
+ "flex items-center justify-between px-4 py-3 border-t",
308
+ classNames?.pagination
309
+ ),
310
+ "data-testid": "cms-content-pagination",
311
+ children: [
312
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: (localization?.CMS_LIST_PAGINATION_SHOWING ?? t("cms.list.paginationShowing", "Showing {from}-{to} of {total}")).replace("{from}", "1").replace("{to}", String(items.length)).replace("{total}", String(total)) }),
313
+ hasMore && /* @__PURE__ */ jsxRuntime.jsxs(
314
+ button.Button,
315
+ {
316
+ variant: "outline",
317
+ size: "sm",
318
+ onClick: () => loadMore(),
319
+ disabled: isLoadingMore,
320
+ children: [
321
+ isLoadingMore && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 mr-2 animate-spin" }),
322
+ localization?.CMS_LIST_PAGINATION_NEXT ?? t("cms.list.paginationNext", "Next")
323
+ ]
324
+ }
325
+ )
326
+ ]
327
+ }
328
+ )
329
+ ]
330
+ }
331
+ );
332
+ }
333
+
334
+ exports.ContentListPage = ContentListPage;
@@ -0,0 +1,332 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { useState, useRef, useEffect } from 'react';
4
+ import { ArrowLeft, Plus, Search, Loader2, Pencil, Trash2 } from 'lucide-react';
5
+ import { Button } from '../../../ui/src/components/button.mjs';
6
+ import { Input } from '../../../ui/src/components/input.mjs';
7
+ import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../../../ui/src/components/table.mjs';
8
+ import { useTranslate, useNotify, usePluginOverrides, useStack, joinBasePath, PermissionAccess } from '@btst/stack/context';
9
+ import { cmsPermissions } from '../../../permissions.mjs';
10
+ import { useListState } from '@btst/stack/client/hooks';
11
+ import { orderCMSContentTypes } from '../../overrides.mjs';
12
+ import { CMS_PLUGIN_ID } from '../../constants.mjs';
13
+ import { cn } from '../../../utils.mjs';
14
+ import { EmptyState } from '../shared/empty-state.mjs';
15
+ import { PageWrapper } from '../shared/page-wrapper.mjs';
16
+ import { useRouteLifecycle } from '../../../ui/src/hooks/use-route-lifecycle.mjs';
17
+ import { useSuspenseContentTypes, useSuspenseContent, useContent, useDeleteContent } from '../../hooks/cms-hooks.mjs';
18
+
19
+ const LIST_STATE_SCHEMA = {
20
+ q: { type: "string", default: "", history: "replace" }
21
+ };
22
+ const SEARCH_DEBOUNCE_MS = 300;
23
+ function ContentListPage({ typeSlug }) {
24
+ const t = useTranslate();
25
+ const notify = useNotify();
26
+ const overrides = usePluginOverrides(CMS_PLUGIN_ID);
27
+ const { localization, classNames } = overrides;
28
+ const { router, plugins, basePath: stackBasePath } = useStack();
29
+ const navigate = router?.navigate;
30
+ const Link = router?.Link;
31
+ const basePath = plugins?.[CMS_PLUGIN_ID]?.site.basePath ?? stackBasePath;
32
+ useRouteLifecycle({
33
+ routeName: "contentList",
34
+ context: {
35
+ path: `/cms/${typeSlug}`,
36
+ params: { typeSlug },
37
+ isSSR: typeof window === "undefined"
38
+ },
39
+ overrides
40
+ });
41
+ const limit = 20;
42
+ const [{ q: search }, setListState] = useListState(
43
+ `cms-${typeSlug}`,
44
+ LIST_STATE_SCHEMA
45
+ );
46
+ const [searchInput, setSearchInput] = useState(search);
47
+ const lastSyncedSearch = useRef(search);
48
+ useEffect(() => {
49
+ if (search !== lastSyncedSearch.current) {
50
+ lastSyncedSearch.current = search;
51
+ setSearchInput(search);
52
+ }
53
+ }, [search]);
54
+ useEffect(() => {
55
+ if (searchInput === search) return;
56
+ const timeout = setTimeout(() => {
57
+ lastSyncedSearch.current = searchInput;
58
+ setListState({ q: searchInput });
59
+ }, SEARCH_DEBOUNCE_MS);
60
+ return () => clearTimeout(timeout);
61
+ }, [searchInput, search, setListState]);
62
+ const hasSearch = search.trim().length > 0;
63
+ const { contentTypes: serverContentTypes } = useSuspenseContentTypes();
64
+ const contentTypes = orderCMSContentTypes(
65
+ serverContentTypes,
66
+ plugins?.[CMS_PLUGIN_ID]?.config
67
+ );
68
+ const contentType = contentTypes.find((ct) => ct.slug === typeSlug);
69
+ const defaultList = useSuspenseContent(typeSlug, { limit });
70
+ const searchedList = useContent(typeSlug, {
71
+ limit,
72
+ search,
73
+ enabled: hasSearch
74
+ });
75
+ const activeList = hasSearch ? searchedList : defaultList;
76
+ const { items, total, loadMore, hasMore, isLoadingMore } = activeList;
77
+ const isSearchLoading = hasSearch && searchedList.isLoading;
78
+ const deleteContent = useDeleteContent(typeSlug);
79
+ const LinkComponent = Link || "a";
80
+ const handleDelete = async (id) => {
81
+ try {
82
+ await deleteContent.mutateAsync(id);
83
+ } catch {
84
+ notify.error(
85
+ localization?.CMS_TOAST_ERROR ?? t("cms.toasts.error", "An error occurred. Please try again.")
86
+ );
87
+ return;
88
+ }
89
+ notify.success(
90
+ localization?.CMS_TOAST_DELETE_SUCCESS ?? t("cms.toasts.deleteSuccess", "Item deleted successfully")
91
+ );
92
+ };
93
+ const formatDate = (dateString) => {
94
+ return new Date(dateString).toLocaleDateString();
95
+ };
96
+ if (!contentType) {
97
+ return /* @__PURE__ */ jsx(PageWrapper, { testId: "cms-list-page", children: /* @__PURE__ */ jsx("div", { className: "w-full max-w-5xl", children: /* @__PURE__ */ jsx(
98
+ EmptyState,
99
+ {
100
+ title: localization?.CMS_ERROR_NOT_FOUND ?? t("cms.common.notFound", "Not found"),
101
+ description: localization?.CMS_ERROR_TYPE_NOT_FOUND_DESCRIPTION ?? t("cms.common.typeNotFoundDescription", "Content type not found")
102
+ }
103
+ ) }) });
104
+ }
105
+ return /* @__PURE__ */ jsx(PageWrapper, { testId: "cms-list-page", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-5xl space-y-6", children: [
106
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
107
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
108
+ /* @__PURE__ */ jsx(
109
+ Button,
110
+ {
111
+ variant: "ghost",
112
+ size: "icon",
113
+ onClick: () => void navigate?.(joinBasePath(basePath, "/cms")),
114
+ children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
115
+ }
116
+ ),
117
+ /* @__PURE__ */ jsxs("div", { children: [
118
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-tight", children: contentType.name }),
119
+ contentType.description && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: contentType.description })
120
+ ] })
121
+ ] }),
122
+ /* @__PURE__ */ jsx(
123
+ PermissionAccess,
124
+ {
125
+ permission: cmsPermissions.record.create({
126
+ contentType: typeSlug
127
+ }),
128
+ children: /* @__PURE__ */ jsxs(
129
+ Button,
130
+ {
131
+ onClick: () => void navigate?.(joinBasePath(basePath, `/cms/${typeSlug}/new`)),
132
+ children: [
133
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-2" }),
134
+ localization?.CMS_BUTTON_NEW_ITEM ?? t("cms.common.newItem", "New Item")
135
+ ]
136
+ }
137
+ )
138
+ }
139
+ )
140
+ ] }),
141
+ /* @__PURE__ */ jsxs("div", { className: "relative max-w-sm", children: [
142
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
143
+ /* @__PURE__ */ jsx(
144
+ Input,
145
+ {
146
+ "data-testid": "cms-list-search",
147
+ value: searchInput,
148
+ onChange: (e) => setSearchInput(e.target.value),
149
+ placeholder: localization?.CMS_LIST_SEARCH_PLACEHOLDER ?? t("cms.list.searchPlaceholder", "Search items..."),
150
+ className: "pl-9"
151
+ }
152
+ ),
153
+ isSearchLoading && /* @__PURE__ */ jsx(Loader2, { className: "absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" })
154
+ ] }),
155
+ items.length === 0 ? isSearchLoading ? null : hasSearch ? /* @__PURE__ */ jsx(
156
+ EmptyState,
157
+ {
158
+ title: localization?.CMS_LIST_SEARCH_EMPTY ?? t("cms.list.searchEmpty", "No items match your search"),
159
+ description: localization?.CMS_LIST_SEARCH_EMPTY_DESCRIPTION ?? t(
160
+ "cms.list.searchEmptyDescription",
161
+ "Try a different search term."
162
+ )
163
+ }
164
+ ) : /* @__PURE__ */ jsx(
165
+ EmptyState,
166
+ {
167
+ title: localization?.CMS_LIST_EMPTY ?? t("cms.list.empty", "No items yet"),
168
+ description: localization?.CMS_LIST_EMPTY_DESCRIPTION ?? t(
169
+ "cms.list.emptyDescription",
170
+ "Create your first item to get started."
171
+ ),
172
+ action: /* @__PURE__ */ jsx(
173
+ PermissionAccess,
174
+ {
175
+ permission: cmsPermissions.record.create({
176
+ contentType: typeSlug
177
+ }),
178
+ children: /* @__PURE__ */ jsxs(
179
+ Button,
180
+ {
181
+ onClick: () => void navigate?.(
182
+ joinBasePath(basePath, `/cms/${typeSlug}/new`)
183
+ ),
184
+ children: [
185
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-2" }),
186
+ localization?.CMS_BUTTON_CREATE ?? t("cms.common.create", "Create")
187
+ ]
188
+ }
189
+ )
190
+ }
191
+ )
192
+ }
193
+ ) : /* @__PURE__ */ jsx(
194
+ ContentTable,
195
+ {
196
+ items,
197
+ total,
198
+ typeSlug,
199
+ basePath,
200
+ LinkComponent,
201
+ navigate,
202
+ onDelete: handleDelete,
203
+ isDeleting: deleteContent.isPending,
204
+ formatDate,
205
+ loadMore,
206
+ hasMore,
207
+ isLoadingMore,
208
+ localization,
209
+ classNames,
210
+ t
211
+ }
212
+ )
213
+ ] }) });
214
+ }
215
+ function ContentTable({
216
+ items,
217
+ total,
218
+ typeSlug,
219
+ basePath,
220
+ LinkComponent,
221
+ navigate,
222
+ onDelete,
223
+ isDeleting,
224
+ formatDate,
225
+ loadMore,
226
+ hasMore,
227
+ isLoadingMore,
228
+ localization,
229
+ classNames,
230
+ t
231
+ }) {
232
+ return /* @__PURE__ */ jsxs(
233
+ "div",
234
+ {
235
+ className: cn("border rounded-lg", classNames?.table),
236
+ "data-testid": "cms-content-table",
237
+ children: [
238
+ /* @__PURE__ */ jsxs(Table, { children: [
239
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
240
+ /* @__PURE__ */ jsx(TableHead, { children: localization?.CMS_LIST_COLUMN_SLUG ?? t("cms.list.columnSlug", "Slug") }),
241
+ /* @__PURE__ */ jsx(TableHead, { children: localization?.CMS_LIST_COLUMN_CREATED ?? t("cms.list.columnCreated", "Created") }),
242
+ /* @__PURE__ */ jsx(TableHead, { children: localization?.CMS_LIST_COLUMN_UPDATED ?? t("cms.list.columnUpdated", "Updated") }),
243
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[100px]", children: localization?.CMS_LIST_COLUMN_ACTIONS ?? t("cms.list.columnActions", "Actions") })
244
+ ] }) }),
245
+ /* @__PURE__ */ jsx(TableBody, { children: items.map((item) => /* @__PURE__ */ jsxs(TableRow, { className: classNames?.tableRow, children: [
246
+ /* @__PURE__ */ jsx(TableCell, { className: "font-medium", children: /* @__PURE__ */ jsx(
247
+ LinkComponent,
248
+ {
249
+ href: joinBasePath(basePath, `/cms/${typeSlug}/${item.id}`),
250
+ className: "hover:underline",
251
+ children: item.slug
252
+ }
253
+ ) }),
254
+ /* @__PURE__ */ jsx(TableCell, { children: formatDate(item.createdAt) }),
255
+ /* @__PURE__ */ jsx(TableCell, { children: formatDate(item.updatedAt) }),
256
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
257
+ /* @__PURE__ */ jsx(
258
+ PermissionAccess,
259
+ {
260
+ permission: cmsPermissions.record.update({
261
+ contentType: item.contentType?.slug ?? typeSlug,
262
+ recordId: item.id,
263
+ ...item.authorId ? { authorId: item.authorId } : {}
264
+ }),
265
+ children: /* @__PURE__ */ jsx(
266
+ Button,
267
+ {
268
+ variant: "ghost",
269
+ size: "icon",
270
+ onClick: () => void navigate?.(
271
+ joinBasePath(basePath, `/cms/${typeSlug}/${item.id}`)
272
+ ),
273
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
274
+ }
275
+ )
276
+ }
277
+ ),
278
+ /* @__PURE__ */ jsx(
279
+ PermissionAccess,
280
+ {
281
+ permission: cmsPermissions.record.delete({
282
+ contentType: item.contentType?.slug ?? typeSlug,
283
+ recordId: item.id,
284
+ ...item.authorId ? { authorId: item.authorId } : {}
285
+ }),
286
+ children: /* @__PURE__ */ jsx(
287
+ Button,
288
+ {
289
+ variant: "ghost",
290
+ size: "icon",
291
+ onClick: () => onDelete(item.id),
292
+ disabled: isDeleting,
293
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4 text-destructive" })
294
+ }
295
+ )
296
+ }
297
+ )
298
+ ] }) })
299
+ ] }, item.id)) })
300
+ ] }),
301
+ /* @__PURE__ */ jsxs(
302
+ "div",
303
+ {
304
+ className: cn(
305
+ "flex items-center justify-between px-4 py-3 border-t",
306
+ classNames?.pagination
307
+ ),
308
+ "data-testid": "cms-content-pagination",
309
+ children: [
310
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: (localization?.CMS_LIST_PAGINATION_SHOWING ?? t("cms.list.paginationShowing", "Showing {from}-{to} of {total}")).replace("{from}", "1").replace("{to}", String(items.length)).replace("{total}", String(total)) }),
311
+ hasMore && /* @__PURE__ */ jsxs(
312
+ Button,
313
+ {
314
+ variant: "outline",
315
+ size: "sm",
316
+ onClick: () => loadMore(),
317
+ disabled: isLoadingMore,
318
+ children: [
319
+ isLoadingMore && /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }),
320
+ localization?.CMS_LIST_PAGINATION_NEXT ?? t("cms.list.paginationNext", "Next")
321
+ ]
322
+ }
323
+ )
324
+ ]
325
+ }
326
+ )
327
+ ]
328
+ }
329
+ );
330
+ }
331
+
332
+ export { ContentListPage };