convex-cms 0.0.9-alpha.8 → 0.0.10

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 (136) hide show
  1. package/README.md +27 -0
  2. package/admin/src/components/Header.tsx +1 -1
  3. package/admin/src/components/RouteGuard.tsx +1 -1
  4. package/admin/src/components/UploadDropzone.tsx +1 -1
  5. package/admin/src/components/cmsds/CmsFilterBar.tsx +74 -0
  6. package/admin/src/components/cmsds/CmsInput.tsx +24 -0
  7. package/admin/src/components/cmsds/CmsPagination.tsx +79 -0
  8. package/admin/src/components/cmsds/CmsSelect.tsx +59 -0
  9. package/admin/src/components/cmsds/CmsStatCard.tsx +79 -0
  10. package/admin/src/components/cmsds/CmsStatusBadge.tsx +1 -1
  11. package/admin/src/components/cmsds/index.ts +5 -0
  12. package/admin/src/components/ui/sidebar.tsx +1 -1
  13. package/admin/src/contexts/AuthContext.tsx +1 -1
  14. package/admin/src/contexts/ThemeContext.tsx +85 -17
  15. package/admin/src/embed/components/EmbedHeader.tsx +11 -9
  16. package/admin/src/embed/components/EmbedLayout.tsx +2 -6
  17. package/admin/src/embed/components/EmbedSidebar.tsx +16 -13
  18. package/admin/src/embed/contexts/ApiContext.tsx +1 -1
  19. package/admin/src/embed/index.tsx +3 -2
  20. package/admin/src/embed/types.ts +6 -0
  21. package/admin/src/hooks/usePermissions.ts +1 -1
  22. package/admin/src/index.css +432 -0
  23. package/admin/src/lib/cmsExports.ts +6 -0
  24. package/admin/src/pages/ContentPage.tsx +116 -172
  25. package/admin/src/pages/ContentTypeEntriesPage.tsx +120 -194
  26. package/admin/src/pages/ContentTypesPage.tsx +136 -139
  27. package/admin/src/pages/DashboardPage.tsx +15 -55
  28. package/admin/src/pages/MediaPage.tsx +31 -57
  29. package/admin/src/pages/SettingsPage.tsx +5 -1
  30. package/admin/src/pages/TrashPage.tsx +115 -170
  31. package/admin/src/routes/__root.tsx +1 -1
  32. package/admin-dist/nitro.json +1 -1
  33. package/admin-dist/public/assets/{CmsEmptyState-DTlpzjOI.js → CmsEmptyState-BKeL4DBB.js} +1 -1
  34. package/admin-dist/public/assets/CmsFilterBar-CEpMHd_c.js +1 -0
  35. package/admin-dist/public/assets/{CmsPageHeader-0REGRH4X.js → CmsPageHeader-CIEkTbyH.js} +1 -1
  36. package/admin-dist/public/assets/{CmsStatusBadge-D_n8u8xa.js → CmsStatusBadge-BFMOsfMW.js} +1 -1
  37. package/admin-dist/public/assets/{CmsSurface-BHmvNai4.js → CmsSurface-kqqaFKUI.js} +1 -1
  38. package/admin-dist/public/assets/CmsTable-Db53Exq0.js +1 -0
  39. package/admin-dist/public/assets/ContentEntryEditor-Ct7cHayy.js +4 -0
  40. package/admin-dist/public/assets/TaxonomyFilter-Bm1DI1A7.js +1 -0
  41. package/admin-dist/public/assets/_contentTypeId-BekeCblX.js +1 -0
  42. package/admin-dist/public/assets/{_entryId-jPXz4z9T.js → _entryId-CoZDE0l0.js} +1 -1
  43. package/admin-dist/public/assets/{alert-CG97cMfC.js → alert-CpLdsTGU.js} +1 -1
  44. package/admin-dist/public/assets/{badge-C6qt24oj.js → badge-BQAotc5B.js} +1 -1
  45. package/admin-dist/public/assets/{circle-check-big-PltpxuB1.js → circle-check-big-BF3Y5nES.js} +1 -1
  46. package/admin-dist/public/assets/{command-CJ8i86fd.js → command-lEq6f_Ee.js} +1 -1
  47. package/admin-dist/public/assets/content-DH6k0dN6.js +1 -0
  48. package/admin-dist/public/assets/content-types-DHr9tc2V.js +1 -0
  49. package/admin-dist/public/assets/index-Cf0lbl0G.js +1 -0
  50. package/admin-dist/public/assets/index-D-4wFfgU.css +1 -0
  51. package/admin-dist/public/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  52. package/admin-dist/public/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  53. package/admin-dist/public/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  54. package/admin-dist/public/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  55. package/admin-dist/public/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  56. package/admin-dist/public/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  57. package/admin-dist/public/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  58. package/admin-dist/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  59. package/admin-dist/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  60. package/admin-dist/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  61. package/admin-dist/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  62. package/admin-dist/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  63. package/admin-dist/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  64. package/admin-dist/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  65. package/admin-dist/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  66. package/admin-dist/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  67. package/admin-dist/public/assets/main-B-6700eG.js +137 -0
  68. package/admin-dist/public/assets/media-DY5zD52L.js +1 -0
  69. package/admin-dist/public/assets/{new._contentTypeId-qsvo01mH.js → new._contentTypeId-Dq_NqTQV.js} +1 -1
  70. package/admin-dist/public/assets/{pencil-gAL0R34f.js → pencil-CI_KfxSx.js} +1 -1
  71. package/admin-dist/public/assets/refresh-cw-BrXg9a2r.js +1 -0
  72. package/admin-dist/public/assets/rotate-ccw-PwzxdPxd.js +1 -0
  73. package/admin-dist/public/assets/{scroll-area-CJBhf9pf.js → scroll-area-DX_nZYp8.js} +1 -1
  74. package/admin-dist/public/assets/{search-WXp6KxDJ.js → search-DlwBH4C5.js} +1 -1
  75. package/admin-dist/public/assets/settings-2mx3_ORG.js +1 -0
  76. package/admin-dist/public/assets/{switch-Ck9ecqEX.js → switch-CjPi4DKH.js} +1 -1
  77. package/admin-dist/public/assets/{tabs-vQYu8rjC.js → tabs-B5X37GEM.js} +1 -1
  78. package/admin-dist/public/assets/tanstack-adapter-KSm-nO5L.js +1 -0
  79. package/admin-dist/public/assets/{taxonomies-DvILUNvr.js → taxonomies-CHjJKNlR.js} +1 -1
  80. package/admin-dist/public/assets/trash-Cle-tcqq.js +1 -0
  81. package/admin-dist/public/assets/{useBreadcrumbLabel-tlSh7dtO.js → useBreadcrumbLabel-yZQG_N_3.js} +1 -1
  82. package/admin-dist/public/assets/{usePermissions-BTGdTOJS.js → usePermissions-D6vsoaJf.js} +1 -1
  83. package/admin-dist/server/_libs/convex-helpers.mjs +1077 -2
  84. package/admin-dist/server/_libs/convex.mjs +222 -13
  85. package/admin-dist/server/_libs/lucide-react.mjs +57 -51
  86. package/admin-dist/server/_ssr/{CmsEmptyState-CB6e53i5.mjs → CmsEmptyState-DzzuQG0S.mjs} +1 -1
  87. package/admin-dist/server/_ssr/CmsFilterBar-C5XADS12.mjs +81 -0
  88. package/admin-dist/server/_ssr/{CmsPageHeader-COUHuECp.mjs → CmsPageHeader-DZ6h7smh.mjs} +1 -1
  89. package/admin-dist/server/_ssr/{CmsStatusBadge-kMTL6koE.mjs → CmsStatusBadge-D-YFSAa1.mjs} +3 -3
  90. package/admin-dist/server/_ssr/{CmsSurface-D1HDYjRg.mjs → CmsSurface-Cv51NBLZ.mjs} +1 -1
  91. package/admin-dist/server/_ssr/CmsTable-DG88C5nO.mjs +189 -0
  92. package/admin-dist/server/_ssr/{ContentEntryEditor-Bq8FR_uK.mjs → ContentEntryEditor-CRjwXB17.mjs} +10 -10
  93. package/admin-dist/server/_ssr/{TaxonomyFilter-bm_p4ADg.mjs → TaxonomyFilter-xGwcgtjr.mjs} +3 -3
  94. package/admin-dist/server/_ssr/{_contentTypeId-B7obLmi_.mjs → _contentTypeId-DRCfeKkm.mjs} +53 -12
  95. package/admin-dist/server/_ssr/{_entryId-B4zhQqFg.mjs → _entryId-DULm2TDy.mjs} +11 -11
  96. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-iX3K33p1.mjs +4 -0
  97. package/admin-dist/server/_ssr/{badge-NOEC9bkk.mjs → badge-CbjIvhb6.mjs} +1 -1
  98. package/admin-dist/server/_ssr/{command-h4-OYNBo.mjs → command-xB2uiYps.mjs} +2 -2
  99. package/admin-dist/server/_ssr/{content-CShtLuhK.mjs → content-BfLBaJCZ.mjs} +108 -138
  100. package/admin-dist/server/_ssr/{content-types-PeyRyfbc.mjs → content-types-DZbF6O2q.mjs} +130 -119
  101. package/admin-dist/server/_ssr/{index-CplFXpGg.mjs → index-Cfe8sZv5.mjs} +65 -39
  102. package/admin-dist/server/_ssr/index.mjs +2 -2
  103. package/admin-dist/server/_ssr/{media-QAkNdX54.mjs → media-Bds2AnPC.mjs} +36 -56
  104. package/admin-dist/server/_ssr/{new._contentTypeId-DEJyMphJ.mjs → new._contentTypeId-DGvz_tlW.mjs} +10 -10
  105. package/admin-dist/server/_ssr/{router-CQXMuGMF.mjs → router-DxF7GBcO.mjs} +8804 -4995
  106. package/admin-dist/server/_ssr/{scroll-area-B7zoNyWB.mjs → scroll-area-DLDlXI07.mjs} +1 -1
  107. package/admin-dist/server/_ssr/{settings-CNaqVa4D.mjs → settings-BbaiS6z9.mjs} +13 -10
  108. package/admin-dist/server/_ssr/{switch-BKZhvryc.mjs → switch-Bl89Pfxu.mjs} +1 -1
  109. package/admin-dist/server/_ssr/{tabs-DtIIQxiD.mjs → tabs-QkbR0iir.mjs} +3 -3
  110. package/admin-dist/server/_ssr/{tanstack-adapter-CLavdbUY.mjs → tanstack-adapter-CKknPtcU.mjs} +19 -1
  111. package/admin-dist/server/_ssr/{taxonomies-vIZYICzr.mjs → taxonomies-S_Ontd0z.mjs} +9 -9
  112. package/admin-dist/server/_ssr/{trash-7yGR4-dF.mjs → trash-BzAIsbbN.mjs} +109 -132
  113. package/admin-dist/server/_ssr/{useBreadcrumbLabel-DR5FaAMf.mjs → useBreadcrumbLabel-BjiR1fM_.mjs} +1 -1
  114. package/admin-dist/server/_ssr/{usePermissions-DKkpETj_.mjs → usePermissions-CDHN95Nz.mjs} +1 -1
  115. package/admin-dist/server/index.mjs +284 -165
  116. package/package.json +3 -2
  117. package/admin/src/styles/globals.css +0 -104
  118. package/admin/src/styles/tailwind-config.css +0 -99
  119. package/admin/src/styles/theme.css +0 -261
  120. package/admin-dist/public/assets/CmsToolbar-CY6GV2L8.js +0 -1
  121. package/admin-dist/public/assets/ContentEntryEditor-CRgcRkk5.js +0 -4
  122. package/admin-dist/public/assets/TaxonomyFilter-Ohv5Jg9c.js +0 -1
  123. package/admin-dist/public/assets/_contentTypeId-C_vJq22X.js +0 -1
  124. package/admin-dist/public/assets/content-pKaIL2ru.js +0 -1
  125. package/admin-dist/public/assets/content-types-Bl_8I1Re.js +0 -1
  126. package/admin-dist/public/assets/globals-CoCRjt0K.css +0 -1
  127. package/admin-dist/public/assets/index-CtHq_P5q.js +0 -1
  128. package/admin-dist/public/assets/main-CA-4LyFT.js +0 -107
  129. package/admin-dist/public/assets/media-Bl1tBbJQ.js +0 -1
  130. package/admin-dist/public/assets/refresh-cw-sdVUGJNs.js +0 -1
  131. package/admin-dist/public/assets/rotate-ccw-6OcXCcxb.js +0 -1
  132. package/admin-dist/public/assets/settings-D8crrFCn.js +0 -1
  133. package/admin-dist/public/assets/tanstack-adapter-BRt2CUCw.js +0 -1
  134. package/admin-dist/public/assets/trash-YyYaC3L9.js +0 -1
  135. package/admin-dist/server/_ssr/CmsToolbar-NB014hsd.mjs +0 -48
  136. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-DndoqCo7.mjs +0 -4
@@ -5,35 +5,27 @@
5
5
  * Used by both CLI routes and embed pages.
6
6
  */
7
7
 
8
- import { useState, useCallback, useEffect } from "react";
8
+ import { useState, useCallback, useEffect, useMemo } from "react";
9
9
  import { useQuery } from "convex/react";
10
10
  import { usePermissions } from "~/hooks";
11
11
  import { BulkActionBar } from "~/components/BulkActionBar";
12
- import { CmsPageHeader } from "~/components/cmsds/CmsPageHeader";
13
- import { CmsToolbar } from "~/components/cmsds/CmsToolbar";
14
- import { CmsEmptyState } from "~/components/cmsds/CmsEmptyState";
15
12
  import {
13
+ CmsPageHeader,
14
+ CmsEmptyState,
16
15
  CmsStatusBadge,
17
16
  type ContentStatus,
18
- } from "~/components/cmsds/CmsStatusBadge";
19
- import { CmsButton } from "~/components/cmsds/CmsButton";
20
- import { Input } from "~/components/ui/input";
21
- import {
22
- Select,
23
- SelectContent,
24
- SelectItem,
25
- SelectTrigger,
26
- SelectValue,
27
- } from "~/components/ui/select";
17
+ CmsButton,
18
+ CmsFilterBar,
19
+ CmsTable,
20
+ type CmsTableColumn,
21
+ } from "~/components/cmsds";
28
22
  import {
29
23
  DropdownMenu,
30
24
  DropdownMenuContent,
31
25
  DropdownMenuItem,
32
26
  DropdownMenuTrigger,
33
27
  } from "~/components/ui/dropdown-menu";
34
- import { Checkbox } from "~/components/ui/checkbox";
35
- import { cn } from "~/lib/cn";
36
- import { Plus, Search, FileText, ChevronDown } from "lucide-react";
28
+ import { Plus, FileText, ChevronDown } from "lucide-react";
37
29
  import type { AdminNavigation } from "~/lib/navigation";
38
30
  import { CmsAdminApi } from "~/embed/contexts/ApiContext";
39
31
 
@@ -114,30 +106,72 @@ export function ContentPage({ api, navigation }: ContentPageProps) {
114
106
  });
115
107
  };
116
108
 
117
- const handleSelectItem = useCallback((id: string, selected: boolean) => {
118
- setSelectedIds((prev) => {
119
- const next = new Set(prev);
120
- if (selected) {
121
- next.add(id);
122
- } else {
123
- next.delete(id);
124
- }
125
- return next;
126
- });
127
- }, []);
128
-
129
- const handleSelectAll = useCallback(() => {
130
- if (selectedIds.size === entries.length && entries.length > 0) {
131
- setSelectedIds(new Set());
132
- } else {
133
- setSelectedIds(new Set(entries.map((e) => e._id)));
134
- }
135
- }, [selectedIds.size, entries]);
136
-
137
109
  const handleClearSelection = useCallback(() => {
138
110
  setSelectedIds(new Set());
139
111
  }, []);
140
112
 
113
+ type Entry = (typeof entries)[number];
114
+
115
+ const entryColumns: CmsTableColumn<Entry>[] = useMemo(
116
+ () => [
117
+ {
118
+ key: "title",
119
+ header: "Title",
120
+ cell: (entry) => (
121
+ <button
122
+ type="button"
123
+ onClick={() => navigation.navigateToEntry(entry._id)}
124
+ className="block text-left"
125
+ >
126
+ <span className="font-medium text-foreground hover:text-primary">
127
+ {getEntryTitle(entry, entry.contentTypeName)}
128
+ </span>
129
+ <span className="block text-xs text-muted-foreground">
130
+ {entry.slug}
131
+ </span>
132
+ </button>
133
+ ),
134
+ },
135
+ {
136
+ key: "type",
137
+ header: "Type",
138
+ cell: (entry) => (
139
+ <span className="text-sm text-muted-foreground">
140
+ {getContentTypeDisplayName(entry.contentTypeName)}
141
+ </span>
142
+ ),
143
+ },
144
+ {
145
+ key: "status",
146
+ header: "Status",
147
+ cell: (entry) => <CmsStatusBadge status={entry.status as ContentStatus} />,
148
+ },
149
+ {
150
+ key: "updated",
151
+ header: "Updated",
152
+ cell: (entry) => (
153
+ <span className="text-sm text-muted-foreground">
154
+ {formatDate(entry._creationTime)}
155
+ </span>
156
+ ),
157
+ },
158
+ {
159
+ key: "actions",
160
+ header: "Actions",
161
+ cell: (entry) => (
162
+ <CmsButton
163
+ variant="outline"
164
+ size="sm"
165
+ onClick={() => navigation.navigateToEntry(entry._id)}
166
+ >
167
+ {canUpdate("contentEntries") ? "Edit" : "View"}
168
+ </CmsButton>
169
+ ),
170
+ },
171
+ ],
172
+ [navigation, contentTypes, canUpdate]
173
+ );
174
+
141
175
  if (isLoading) {
142
176
  return (
143
177
  <div className="space-y-6 p-6">
@@ -162,56 +196,42 @@ export function ContentPage({ api, navigation }: ContentPageProps) {
162
196
  description="Browse and manage content entries across all content types."
163
197
  />
164
198
 
165
- <CmsToolbar
166
- left={
167
- <div className="flex items-center gap-3">
168
- <div className="relative">
169
- <Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
170
- <Input
171
- type="text"
172
- placeholder="Search content..."
173
- value={searchQuery}
174
- onChange={(e) => setSearchQuery(e.target.value)}
175
- className="w-64 pl-9"
176
- data-testid="content-search-input"
177
- />
178
- </div>
179
- <Select
180
- value={selectedTypeId || "all"}
181
- onValueChange={(v) => setSelectedTypeId(v === "all" ? "" : v)}
182
- >
183
- <SelectTrigger className="w-48">
184
- <SelectValue placeholder="All Content Types" />
185
- </SelectTrigger>
186
- <SelectContent>
187
- <SelectItem value="all">All Content Types</SelectItem>
188
- {contentTypes.map((type) => (
189
- <SelectItem key={type._id} value={type._id}>
190
- {type.displayName}
191
- </SelectItem>
192
- ))}
193
- </SelectContent>
194
- </Select>
195
- <Select
196
- value={selectedStatus || "all"}
197
- onValueChange={(v) =>
198
- setSelectedStatus(v === "all" ? "" : (v as ContentStatus))
199
- }
200
- >
201
- <SelectTrigger className="w-36">
202
- <SelectValue placeholder="All Statuses" />
203
- </SelectTrigger>
204
- <SelectContent>
205
- <SelectItem value="all">All Statuses</SelectItem>
206
- <SelectItem value="draft">Draft</SelectItem>
207
- <SelectItem value="published">Published</SelectItem>
208
- <SelectItem value="scheduled">Scheduled</SelectItem>
209
- <SelectItem value="archived">Archived</SelectItem>
210
- </SelectContent>
211
- </Select>
212
- </div>
213
- }
214
- right={
199
+ <CmsFilterBar
200
+ search={{
201
+ value: searchQuery,
202
+ onChange: setSearchQuery,
203
+ placeholder: "Search content...",
204
+ className: "w-64",
205
+ }}
206
+ filters={[
207
+ {
208
+ key: "contentType",
209
+ value: selectedTypeId || "all",
210
+ onChange: (v) => setSelectedTypeId(v === "all" ? "" : v),
211
+ options: [
212
+ { value: "all", label: "All Content Types" },
213
+ ...contentTypes.map((type) => ({
214
+ value: type._id,
215
+ label: type.displayName,
216
+ })),
217
+ ],
218
+ className: "w-48",
219
+ },
220
+ {
221
+ key: "status",
222
+ value: selectedStatus || "all",
223
+ onChange: (v) => setSelectedStatus(v === "all" ? "" : (v as ContentStatus)),
224
+ options: [
225
+ { value: "all", label: "All Statuses" },
226
+ { value: "draft", label: "Draft" },
227
+ { value: "published", label: "Published" },
228
+ { value: "scheduled", label: "Scheduled" },
229
+ { value: "archived", label: "Archived" },
230
+ ],
231
+ className: "w-36",
232
+ },
233
+ ]}
234
+ actions={
215
235
  canCreate("contentEntries") && (
216
236
  <DropdownMenu>
217
237
  <DropdownMenuTrigger asChild>
@@ -253,91 +273,15 @@ export function ContentPage({ api, navigation }: ContentPageProps) {
253
273
  }
254
274
  />
255
275
  ) : (
256
- <div className="rounded-lg border bg-card">
257
- <table className="w-full">
258
- <thead>
259
- <tr className="border-b">
260
- <th className="w-10 p-3 text-left">
261
- <Checkbox
262
- checked={
263
- selectedIds.size === entries.length && entries.length > 0
264
- }
265
- onCheckedChange={handleSelectAll}
266
- aria-label="Select all entries"
267
- />
268
- </th>
269
- <th className="p-3 text-left text-sm font-medium text-muted-foreground">
270
- Title
271
- </th>
272
- <th className="p-3 text-left text-sm font-medium text-muted-foreground">
273
- Type
274
- </th>
275
- <th className="p-3 text-left text-sm font-medium text-muted-foreground">
276
- Status
277
- </th>
278
- <th className="p-3 text-left text-sm font-medium text-muted-foreground">
279
- Updated
280
- </th>
281
- <th className="p-3 text-left text-sm font-medium text-muted-foreground">
282
- Actions
283
- </th>
284
- </tr>
285
- </thead>
286
- <tbody>
287
- {entries.map((entry) => (
288
- <tr
289
- key={entry._id}
290
- className={cn(
291
- "border-b last:border-0 transition-colors hover:bg-muted/50",
292
- selectedIds.has(entry._id) && "bg-primary/5"
293
- )}
294
- >
295
- <td className="p-3">
296
- <Checkbox
297
- checked={selectedIds.has(entry._id)}
298
- onCheckedChange={(checked) =>
299
- handleSelectItem(entry._id, checked as boolean)
300
- }
301
- aria-label={`Select ${getEntryTitle(entry, entry.contentTypeName)}`}
302
- />
303
- </td>
304
- <td className="p-3">
305
- <button
306
- type="button"
307
- onClick={() => navigation.navigateToEntry(entry._id)}
308
- className="block text-left"
309
- >
310
- <span className="font-medium text-foreground hover:text-primary">
311
- {getEntryTitle(entry, entry.contentTypeName)}
312
- </span>
313
- <span className="block text-xs text-muted-foreground">
314
- {entry.slug}
315
- </span>
316
- </button>
317
- </td>
318
- <td className="p-3 text-sm text-muted-foreground">
319
- {getContentTypeDisplayName(entry.contentTypeName)}
320
- </td>
321
- <td className="p-3">
322
- <CmsStatusBadge status={entry.status as ContentStatus} />
323
- </td>
324
- <td className="p-3 text-sm text-muted-foreground">
325
- {formatDate(entry._creationTime)}
326
- </td>
327
- <td className="p-3">
328
- <CmsButton
329
- variant="outline"
330
- size="sm"
331
- onClick={() => navigation.navigateToEntry(entry._id)}
332
- >
333
- {canUpdate("contentEntries") ? "Edit" : "View"}
334
- </CmsButton>
335
- </td>
336
- </tr>
337
- ))}
338
- </tbody>
339
- </table>
340
- </div>
276
+ <CmsTable
277
+ columns={entryColumns}
278
+ data={entries}
279
+ getRowId={(e) => e._id}
280
+ selectable
281
+ selectedIds={selectedIds}
282
+ onSelectionChange={setSelectedIds}
283
+ emptyMessage="No content entries found"
284
+ />
341
285
  )}
342
286
  </div>
343
287
  );