convex-cms 0.0.5-alpha.0 → 0.0.5-alpha.2

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 (323) hide show
  1. package/README.md +95 -144
  2. package/admin/README.md +99 -0
  3. package/admin/src/components/AdminLayout.tsx +22 -0
  4. package/admin/src/components/BreakingChangesWarningDialog.tsx +81 -0
  5. package/admin/src/components/BulkActionBar.tsx +190 -0
  6. package/admin/src/components/BulkOperationModal.tsx +177 -0
  7. package/admin/src/components/ContentEntryEditor.tsx +1104 -0
  8. package/admin/src/components/ContentTypeFormModal.tsx +1012 -0
  9. package/admin/src/components/ErrorBoundary.tsx +83 -0
  10. package/admin/src/components/ErrorState.tsx +147 -0
  11. package/admin/src/components/Header.tsx +294 -0
  12. package/admin/src/components/RouteGuard.tsx +264 -0
  13. package/admin/src/components/Sidebar.tsx +90 -0
  14. package/admin/src/components/TaxonomyEditor.tsx +348 -0
  15. package/admin/src/components/TermTree.tsx +533 -0
  16. package/admin/src/components/UploadDropzone.tsx +383 -0
  17. package/admin/src/components/VersionCompare.tsx +250 -0
  18. package/admin/src/components/VersionHistory.tsx +279 -0
  19. package/admin/src/components/VersionRollbackModal.tsx +79 -0
  20. package/admin/src/components/cmsds/CmsButton.tsx +101 -0
  21. package/admin/src/components/cmsds/CmsDialog.tsx +139 -0
  22. package/admin/src/components/cmsds/CmsDropdown.tsx +62 -0
  23. package/admin/src/components/cmsds/CmsEmptyState.tsx +54 -0
  24. package/admin/src/components/cmsds/CmsField.tsx +47 -0
  25. package/admin/src/components/cmsds/CmsPageHeader.tsx +35 -0
  26. package/admin/src/components/cmsds/CmsStatusBadge.tsx +153 -0
  27. package/admin/src/components/cmsds/CmsSurface.tsx +52 -0
  28. package/admin/src/components/cmsds/CmsTable.tsx +164 -0
  29. package/admin/src/components/cmsds/CmsToolbar.tsx +58 -0
  30. package/admin/src/components/cmsds/index.ts +10 -0
  31. package/admin/src/components/fields/BooleanField.tsx +74 -0
  32. package/admin/src/components/fields/CategoryField.tsx +394 -0
  33. package/admin/src/components/fields/DateField.tsx +173 -0
  34. package/admin/src/components/fields/DefaultFieldRenderer.tsx +74 -0
  35. package/admin/src/components/fields/FieldRenderer.tsx +180 -0
  36. package/admin/src/components/fields/FieldWrapper.tsx +57 -0
  37. package/admin/src/components/fields/JsonField.tsx +172 -0
  38. package/admin/src/components/fields/MediaField.tsx +367 -0
  39. package/admin/src/components/fields/MultiSelectField.tsx +118 -0
  40. package/admin/src/components/fields/NumberField.tsx +77 -0
  41. package/admin/src/components/fields/ReferenceField.tsx +386 -0
  42. package/admin/src/components/fields/RichTextField.tsx +171 -0
  43. package/admin/src/components/fields/SelectField.tsx +62 -0
  44. package/admin/src/components/fields/TagField.tsx +325 -0
  45. package/admin/src/components/fields/TextAreaField.tsx +68 -0
  46. package/admin/src/components/fields/TextField.tsx +56 -0
  47. package/admin/src/components/fields/index.ts +54 -0
  48. package/admin/src/components/fields/registry.ts +64 -0
  49. package/admin/src/components/fields/types.ts +217 -0
  50. package/admin/src/components/filters/TaxonomyFilter.tsx +254 -0
  51. package/admin/src/components/filters/index.ts +1 -0
  52. package/admin/src/components/index.ts +8 -0
  53. package/admin/src/components/media/MediaAssetActions.tsx +115 -0
  54. package/admin/src/components/media/MediaAssetEditDialog.tsx +217 -0
  55. package/admin/src/components/media/MediaBulkActionBar.tsx +51 -0
  56. package/admin/src/components/media/MediaFolderActions.tsx +69 -0
  57. package/admin/src/components/media/MediaFolderEditDialog.tsx +126 -0
  58. package/admin/src/components/media/MediaMoveModal.tsx +179 -0
  59. package/admin/src/components/media/MediaPreviewModal.tsx +370 -0
  60. package/admin/src/components/media/MediaTaxonomyPicker.tsx +304 -0
  61. package/admin/src/components/media/MediaTrashBulkActionBar.tsx +59 -0
  62. package/admin/src/components/ui/accordion.tsx +64 -0
  63. package/admin/src/components/ui/alert-dialog.tsx +155 -0
  64. package/admin/src/components/ui/alert.tsx +66 -0
  65. package/admin/src/components/ui/avatar.tsx +53 -0
  66. package/admin/src/components/ui/badge.tsx +46 -0
  67. package/admin/src/components/ui/breadcrumb.tsx +109 -0
  68. package/admin/src/components/ui/button.tsx +62 -0
  69. package/admin/src/components/ui/calendar.tsx +220 -0
  70. package/admin/src/components/ui/card.tsx +92 -0
  71. package/admin/src/components/ui/checkbox.tsx +30 -0
  72. package/admin/src/components/ui/command.tsx +182 -0
  73. package/admin/src/components/ui/dialog.tsx +143 -0
  74. package/admin/src/components/ui/dropdown-menu.tsx +257 -0
  75. package/admin/src/components/ui/form.tsx +167 -0
  76. package/admin/src/components/ui/input.tsx +21 -0
  77. package/admin/src/components/ui/label.tsx +24 -0
  78. package/admin/src/components/ui/popover.tsx +46 -0
  79. package/admin/src/components/ui/scroll-area.tsx +56 -0
  80. package/admin/src/components/ui/select.tsx +190 -0
  81. package/admin/src/components/ui/separator.tsx +26 -0
  82. package/admin/src/components/ui/sheet.tsx +137 -0
  83. package/admin/src/components/ui/sidebar.tsx +724 -0
  84. package/admin/src/components/ui/skeleton.tsx +13 -0
  85. package/admin/src/components/ui/sonner.tsx +38 -0
  86. package/admin/src/components/ui/switch.tsx +31 -0
  87. package/admin/src/components/ui/table.tsx +114 -0
  88. package/admin/src/components/ui/tabs.tsx +66 -0
  89. package/admin/src/components/ui/textarea.tsx +18 -0
  90. package/admin/src/components/ui/tooltip.tsx +61 -0
  91. package/admin/src/contexts/AdminConfigContext.tsx +30 -0
  92. package/admin/src/contexts/AuthContext.tsx +330 -0
  93. package/admin/src/contexts/BreadcrumbContext.tsx +49 -0
  94. package/admin/src/contexts/SettingsConfigContext.tsx +57 -0
  95. package/admin/src/contexts/ThemeContext.tsx +91 -0
  96. package/admin/src/contexts/index.ts +20 -0
  97. package/admin/src/embed/components/EmbedHeader.tsx +103 -0
  98. package/admin/src/embed/components/EmbedLayout.tsx +29 -0
  99. package/admin/src/embed/components/EmbedSidebar.tsx +119 -0
  100. package/admin/src/embed/components/index.ts +3 -0
  101. package/admin/src/embed/contexts/ApiContext.tsx +32 -0
  102. package/admin/src/embed/index.tsx +184 -0
  103. package/admin/src/embed/navigation.tsx +202 -0
  104. package/admin/src/embed/pages/Content.tsx +19 -0
  105. package/admin/src/embed/pages/ContentTypes.tsx +19 -0
  106. package/admin/src/embed/pages/Dashboard.tsx +19 -0
  107. package/admin/src/embed/pages/Media.tsx +19 -0
  108. package/admin/src/embed/pages/Settings.tsx +22 -0
  109. package/admin/src/embed/pages/Taxonomies.tsx +22 -0
  110. package/admin/src/embed/pages/Trash.tsx +22 -0
  111. package/admin/src/embed/pages/index.ts +7 -0
  112. package/admin/src/embed/types.ts +24 -0
  113. package/admin/src/hooks/index.ts +2 -0
  114. package/admin/src/hooks/use-mobile.ts +19 -0
  115. package/admin/src/hooks/useBreadcrumbLabel.ts +15 -0
  116. package/admin/src/hooks/usePermissions.ts +211 -0
  117. package/admin/src/lib/admin-config.ts +111 -0
  118. package/admin/src/lib/cn.ts +6 -0
  119. package/admin/src/lib/config.server.ts +56 -0
  120. package/admin/src/lib/convex.ts +26 -0
  121. package/admin/src/lib/embed-adapter.ts +80 -0
  122. package/admin/src/lib/icons.tsx +96 -0
  123. package/admin/src/lib/loadAdminConfig.ts +92 -0
  124. package/admin/src/lib/motion.ts +29 -0
  125. package/admin/src/lib/navigation.ts +43 -0
  126. package/admin/src/lib/tanstack-adapter.ts +82 -0
  127. package/admin/src/pages/ContentPage.tsx +337 -0
  128. package/admin/src/pages/ContentTypesPage.tsx +457 -0
  129. package/admin/src/pages/DashboardPage.tsx +163 -0
  130. package/admin/src/pages/MediaPage.tsx +34 -0
  131. package/admin/src/pages/SettingsPage.tsx +486 -0
  132. package/admin/src/pages/TaxonomiesPage.tsx +289 -0
  133. package/admin/src/pages/TrashPage.tsx +421 -0
  134. package/admin/src/pages/index.ts +14 -0
  135. package/admin/src/routeTree.gen.ts +262 -0
  136. package/admin/src/router.tsx +22 -0
  137. package/admin/src/routes/__root.tsx +250 -0
  138. package/admin/src/routes/content-types.tsx +20 -0
  139. package/admin/src/routes/content.tsx +20 -0
  140. package/admin/src/routes/entries/$entryId.tsx +107 -0
  141. package/admin/src/routes/entries/new.$contentTypeId.tsx +69 -0
  142. package/admin/src/routes/entries/type/$contentTypeId.tsx +503 -0
  143. package/admin/src/routes/index.tsx +20 -0
  144. package/admin/src/routes/media.tsx +1095 -0
  145. package/admin/src/routes/settings.tsx +20 -0
  146. package/admin/src/routes/taxonomies.tsx +20 -0
  147. package/admin/src/routes/trash.tsx +20 -0
  148. package/admin/src/styles/globals.css +69 -0
  149. package/admin/src/styles/tailwind-config.css +74 -0
  150. package/admin/src/styles/theme.css +73 -0
  151. package/admin/src/types/index.ts +221 -0
  152. package/admin/src/utils/errorParsing.ts +163 -0
  153. package/admin/src/utils/index.ts +5 -0
  154. package/admin/src/vite-env.d.ts +14 -0
  155. package/admin/tailwind.preset.cjs +102 -0
  156. package/admin-dist/nitro.json +1 -1
  157. package/admin-dist/public/assets/{CmsEmptyState-CiMQwSQV.js → CmsEmptyState-CkqBIab3.js} +1 -1
  158. package/admin-dist/public/assets/{CmsPageHeader-ohOq0luT.js → CmsPageHeader-CUtl5MMG.js} +1 -1
  159. package/admin-dist/public/assets/{CmsStatusBadge-BdNf0V9v.js → CmsStatusBadge-CUYFgEe-.js} +1 -1
  160. package/admin-dist/public/assets/{CmsSurface-CWup6Jh7.js → CmsSurface-CsJfAVa3.js} +1 -1
  161. package/admin-dist/public/assets/{CmsToolbar-cEBlCHa3.js → CmsToolbar-CnfbcxeP.js} +1 -1
  162. package/admin-dist/public/assets/{ContentEntryEditor-BY5ypfUs.js → ContentEntryEditor-BU220CCy.js} +1 -1
  163. package/admin-dist/public/assets/TaxonomyFilter-CWCxC5HZ.js +1 -0
  164. package/admin-dist/public/assets/_contentTypeId-DK8cskRt.js +1 -0
  165. package/admin-dist/public/assets/{_entryId-BpSmrfAm.js → _entryId-CuVMExbb.js} +1 -1
  166. package/admin-dist/public/assets/{alert-Bf2l8kxw.js → alert-CF1BSzGR.js} +1 -1
  167. package/admin-dist/public/assets/{badge-qPrc4AUM.js → badge-CmuOIVKp.js} +1 -1
  168. package/admin-dist/public/assets/{circle-check-big-Dgozy3vV.js → circle-check-big-BKDVG6DU.js} +1 -1
  169. package/admin-dist/public/assets/{command-QOmNhlb0.js → command-XJxnF2Sd.js} +1 -1
  170. package/admin-dist/public/assets/content-QBUxdxbS.js +1 -0
  171. package/admin-dist/public/assets/content-types-CrNEm8Hf.js +2 -0
  172. package/admin-dist/public/assets/globals-B7Wsfh_v.css +1 -0
  173. package/admin-dist/public/assets/index-C7xOwudI.js +1 -0
  174. package/admin-dist/public/assets/{label-DCsUdvFh.js → label-CHCnXeBk.js} +1 -1
  175. package/admin-dist/public/assets/{link-2-Czw1N61H.js → link-2-Bb34judH.js} +1 -1
  176. package/admin-dist/public/assets/{list-DtCsXj8-.js → list-9Pzt48ld.js} +1 -1
  177. package/admin-dist/public/assets/{main-CXgkZMhe.js → main-CjQ2VI9L.js} +3 -3
  178. package/admin-dist/public/assets/media-Dc5PWt2Q.js +1 -0
  179. package/admin-dist/public/assets/{new._contentTypeId-CoTDxKzf.js → new._contentTypeId-C_I4YxIa.js} +1 -1
  180. package/admin-dist/public/assets/{plus-xCFJK0RC.js → plus-Ceef7DHk.js} +1 -1
  181. package/admin-dist/public/assets/{rotate-ccw-DIqK63wY.js → rotate-ccw-7k7-4VUq.js} +1 -1
  182. package/admin-dist/public/assets/{scroll-area-B-yrE66a.js → scroll-area-CC6wujnp.js} +1 -1
  183. package/admin-dist/public/assets/{search-CbCbboeU.js → search-DwoUV2pv.js} +1 -1
  184. package/admin-dist/public/assets/{select-Co3TZFJb.js → select-hOZTp8aC.js} +1 -1
  185. package/admin-dist/public/assets/{settings-BspTTv_o.js → settings-t2PbCZh4.js} +1 -1
  186. package/admin-dist/public/assets/{switch-CfavASmR.js → switch-jX2pDaNU.js} +1 -1
  187. package/admin-dist/public/assets/{tabs-CN5s5u2W.js → tabs-q4EbZk7c.js} +1 -1
  188. package/admin-dist/public/assets/{tanstack-adapter-npeE3RdY.js → tanstack-adapter-B-Glm4kH.js} +1 -1
  189. package/admin-dist/public/assets/taxonomies-kyk5P4ZW.js +1 -0
  190. package/admin-dist/public/assets/{textarea-BJ0XFZpT.js → textarea-B6SfBmr0.js} +1 -1
  191. package/admin-dist/public/assets/trash-BOCnIznD.js +1 -0
  192. package/admin-dist/public/assets/{triangle-alert-BZRcqsUg.js → triangle-alert-CXFIO_Gu.js} +1 -1
  193. package/admin-dist/public/assets/{useBreadcrumbLabel-DwZlwvFF.js → useBreadcrumbLabel-_6qBagc3.js} +1 -1
  194. package/admin-dist/public/assets/{usePermissions-C1JQhfqb.js → usePermissions-M1ijZ7a6.js} +1 -1
  195. package/admin-dist/server/_ssr/{CmsButton-B45JAKR1.mjs → CmsButton-DOiTVKQq.mjs} +1 -1
  196. package/admin-dist/server/_ssr/{CmsEmptyState-D_BQFAVR.mjs → CmsEmptyState-fbnGt3LD.mjs} +2 -2
  197. package/admin-dist/server/_ssr/{CmsPageHeader-CrUZA59A.mjs → CmsPageHeader-DHRrdOZa.mjs} +1 -1
  198. package/admin-dist/server/_ssr/{CmsStatusBadge-B-sj6yaj.mjs → CmsStatusBadge-s7obWbKZ.mjs} +2 -2
  199. package/admin-dist/server/_ssr/{CmsSurface-DKJZhpjk.mjs → CmsSurface-rFoYjb62.mjs} +1 -1
  200. package/admin-dist/server/_ssr/{CmsToolbar-ByaW5iXf.mjs → CmsToolbar-zTE45z2q.mjs} +2 -2
  201. package/admin-dist/server/_ssr/{ContentEntryEditor-D3_Jb1dq.mjs → ContentEntryEditor-BLoEjT_m.mjs} +12 -12
  202. package/admin-dist/server/_ssr/{TaxonomyFilter-BRJkuCtA.mjs → TaxonomyFilter-XAtaJC2z.mjs} +5 -5
  203. package/admin-dist/server/_ssr/{_contentTypeId-B9kA6CaM.mjs → _contentTypeId-Csl4822C.mjs} +13 -13
  204. package/admin-dist/server/_ssr/{_entryId-BddcMkZN.mjs → _entryId-D8alLFBx.mjs} +15 -15
  205. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BffZedId.mjs +4 -0
  206. package/admin-dist/server/_ssr/{command-CGtVr8Gb.mjs → command-C0Di14--.mjs} +1 -1
  207. package/admin-dist/server/_ssr/{content-D1tbeOd0.mjs → content-CT-FPsmV.mjs} +12 -55
  208. package/admin-dist/server/_ssr/{content-types-BZqY_BER.mjs → content-types-C8cBFdzE.mjs} +15 -46
  209. package/admin-dist/server/_ssr/{index-BIdq4xaC.mjs → index-BJtcrEc-.mjs} +5 -5
  210. package/admin-dist/server/_ssr/index.mjs +2 -2
  211. package/admin-dist/server/_ssr/{label-T-QNKAr6.mjs → label-qn2Afwl4.mjs} +1 -1
  212. package/admin-dist/server/_ssr/{media-C-xqjBrl.mjs → media-qv5IAsMZ.mjs} +14 -14
  213. package/admin-dist/server/_ssr/{new._contentTypeId-DWic9cRq.mjs → new._contentTypeId-DdGyrhqs.mjs} +13 -13
  214. package/admin-dist/server/_ssr/{router-D1BMAMJT.mjs → router-nSVkxb6Y.mjs} +11 -11
  215. package/admin-dist/server/_ssr/{scroll-area-C0pic_WA.mjs → scroll-area-BCinP455.mjs} +1 -1
  216. package/admin-dist/server/_ssr/{select-CqmuN2F6.mjs → select-BKQlQScw.mjs} +1 -1
  217. package/admin-dist/server/_ssr/{settings-CAkncGGV.mjs → settings-BCr2KQlk.mjs} +55 -40
  218. package/admin-dist/server/_ssr/{switch-CgmuJkT9.mjs → switch-BaOi42fE.mjs} +1 -1
  219. package/admin-dist/server/_ssr/{tabs-CnMj0aRy.mjs → tabs-DYXEi9kq.mjs} +2 -2
  220. package/admin-dist/server/_ssr/{tanstack-adapter-BXZrMauE.mjs → tanstack-adapter-Bsz8kha-.mjs} +1 -1
  221. package/admin-dist/server/_ssr/{taxonomies-thl3BfVm.mjs → taxonomies-CueMHTbE.mjs} +30 -19
  222. package/admin-dist/server/_ssr/{textarea-4K5OJgeh.mjs → textarea-CI0Jqx2x.mjs} +1 -1
  223. package/admin-dist/server/_ssr/{trash-B40Gx5zP.mjs → trash-DE6W8GoX.mjs} +20 -17
  224. package/admin-dist/server/_ssr/{useBreadcrumbLabel-rn-fL4zV.mjs → useBreadcrumbLabel-B5Yi72lM.mjs} +1 -1
  225. package/admin-dist/server/_ssr/{usePermissions-CKeM6_Vw.mjs → usePermissions-C3nZ-Izm.mjs} +1 -1
  226. package/admin-dist/server/index.mjs +183 -190
  227. package/dist/client/admin/bulk.d.ts +79 -0
  228. package/dist/client/admin/bulk.d.ts.map +1 -0
  229. package/dist/client/admin/bulk.js +72 -0
  230. package/dist/client/admin/bulk.js.map +1 -0
  231. package/dist/client/admin/contentLock.d.ts +118 -0
  232. package/dist/client/admin/contentLock.d.ts.map +1 -0
  233. package/dist/client/admin/contentLock.js +81 -0
  234. package/dist/client/admin/contentLock.js.map +1 -0
  235. package/dist/client/{adminApi.d.ts → admin/contentTypes.d.ts} +39 -1134
  236. package/dist/client/admin/contentTypes.d.ts.map +1 -0
  237. package/dist/client/admin/contentTypes.js +122 -0
  238. package/dist/client/admin/contentTypes.js.map +1 -0
  239. package/dist/client/admin/dashboard.d.ts +16 -0
  240. package/dist/client/admin/dashboard.d.ts.map +1 -0
  241. package/dist/client/admin/dashboard.js +33 -0
  242. package/dist/client/admin/dashboard.js.map +1 -0
  243. package/dist/client/admin/entries.d.ts +358 -0
  244. package/dist/client/admin/entries.d.ts.map +1 -0
  245. package/dist/client/admin/entries.js +220 -0
  246. package/dist/client/admin/entries.js.map +1 -0
  247. package/dist/client/admin/index.d.ts +6568 -0
  248. package/dist/client/admin/index.d.ts.map +1 -0
  249. package/dist/client/admin/index.js +305 -0
  250. package/dist/client/admin/index.js.map +1 -0
  251. package/dist/client/admin/media.d.ts +1038 -0
  252. package/dist/client/admin/media.d.ts.map +1 -0
  253. package/dist/client/admin/media.js +489 -0
  254. package/dist/client/admin/media.js.map +1 -0
  255. package/dist/client/admin/taxonomies.d.ts +339 -0
  256. package/dist/client/admin/taxonomies.d.ts.map +1 -0
  257. package/dist/client/admin/taxonomies.js +364 -0
  258. package/dist/client/admin/taxonomies.js.map +1 -0
  259. package/dist/client/admin/trash.d.ts +91 -0
  260. package/dist/client/admin/trash.d.ts.map +1 -0
  261. package/dist/client/admin/trash.js +71 -0
  262. package/dist/client/admin/trash.js.map +1 -0
  263. package/dist/client/admin/types.d.ts +320 -0
  264. package/dist/client/admin/types.d.ts.map +1 -0
  265. package/dist/client/admin/types.js +7 -0
  266. package/dist/client/admin/types.js.map +1 -0
  267. package/dist/client/admin/validators.d.ts +3886 -0
  268. package/dist/client/admin/validators.d.ts.map +1 -0
  269. package/dist/client/admin/validators.js +322 -0
  270. package/dist/client/admin/validators.js.map +1 -0
  271. package/dist/client/admin/versions.d.ts +106 -0
  272. package/dist/client/admin/versions.d.ts.map +1 -0
  273. package/dist/client/admin/versions.js +57 -0
  274. package/dist/client/admin/versions.js.map +1 -0
  275. package/dist/client/adminApiTypes.d.ts +27 -0
  276. package/dist/client/adminApiTypes.d.ts.map +1 -0
  277. package/dist/client/adminApiTypes.js +12 -0
  278. package/dist/client/adminApiTypes.js.map +1 -0
  279. package/dist/client/{admin-config.d.ts → adminConfig.d.ts} +2 -2
  280. package/dist/client/adminConfig.d.ts.map +1 -0
  281. package/dist/client/{admin-config.js → adminConfig.js} +1 -1
  282. package/dist/client/adminConfig.js.map +1 -0
  283. package/dist/client/agentTools.d.ts +4 -4
  284. package/dist/client/index.d.ts +2 -2
  285. package/dist/client/index.d.ts.map +1 -1
  286. package/dist/client/index.js +15 -2
  287. package/dist/client/index.js.map +1 -1
  288. package/dist/component/contentEntries.d.ts +4 -4
  289. package/dist/component/contentEntryMutations.d.ts +46 -0
  290. package/dist/component/contentEntryMutations.d.ts.map +1 -1
  291. package/dist/component/contentEntryMutations.js +1 -1
  292. package/dist/component/contentEntryMutations.js.map +1 -1
  293. package/dist/component/contentTypeMigration.d.ts +1 -1
  294. package/dist/component/contentTypeMutations.d.ts +22 -0
  295. package/dist/component/contentTypeMutations.d.ts.map +1 -1
  296. package/dist/component/contentTypeMutations.js +1 -1
  297. package/dist/component/contentTypeMutations.js.map +1 -1
  298. package/dist/component/mediaAssetMutations.d.ts +47 -0
  299. package/dist/component/mediaAssetMutations.d.ts.map +1 -1
  300. package/dist/component/mediaAssetMutations.js +1 -1
  301. package/dist/component/mediaAssetMutations.js.map +1 -1
  302. package/dist/component/schema.d.ts +9 -0
  303. package/dist/component/schema.d.ts.map +1 -1
  304. package/dist/component/schema.js +1 -1
  305. package/dist/component/schema.js.map +1 -1
  306. package/package.json +85 -3
  307. package/admin-dist/public/assets/ErrorState-C4nJ-ml4.js +0 -1
  308. package/admin-dist/public/assets/TaxonomyFilter-BgE_SR_O.js +0 -1
  309. package/admin-dist/public/assets/_contentTypeId-DtZectcC.js +0 -1
  310. package/admin-dist/public/assets/content-OEBGlxg1.js +0 -1
  311. package/admin-dist/public/assets/content-types-CjQliqVV.js +0 -2
  312. package/admin-dist/public/assets/globals-hAmgC66w.css +0 -1
  313. package/admin-dist/public/assets/index-BH_ECMhv.js +0 -1
  314. package/admin-dist/public/assets/media-DTJ3-ViE.js +0 -1
  315. package/admin-dist/public/assets/taxonomies-CgG46fIF.js +0 -1
  316. package/admin-dist/public/assets/trash-B3daldm5.js +0 -1
  317. package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +0 -89
  318. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-Dd7AmelK.mjs +0 -4
  319. package/dist/client/admin-config.d.ts.map +0 -1
  320. package/dist/client/admin-config.js.map +0 -1
  321. package/dist/client/adminApi.d.ts.map +0 -1
  322. package/dist/client/adminApi.js +0 -736
  323. package/dist/client/adminApi.js.map +0 -1
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Shared Taxonomies Page Component
3
+ *
4
+ * Manages taxonomies and their terms (tags, categories, etc.).
5
+ * Used by both CLI routes and embed pages.
6
+ */
7
+
8
+ import { useState, useCallback } from "react";
9
+ import { useQuery, useMutation } from "convex/react";
10
+ import { TaxonomyEditor } from "~/components/TaxonomyEditor";
11
+ import { TermTree } from "~/components/TermTree";
12
+ import { CmsPageHeader } from "~/components/cmsds/CmsPageHeader";
13
+ import { CmsEmptyState } from "~/components/cmsds/CmsEmptyState";
14
+ import { CmsSurface } from "~/components/cmsds/CmsSurface";
15
+ import { CmsButton } from "~/components/cmsds/CmsButton";
16
+ import { CmsConfirmDialog } from "~/components/cmsds/CmsDialog";
17
+ import { Badge } from "~/components/ui/badge";
18
+ import { ScrollArea } from "~/components/ui/scroll-area";
19
+ import { cn } from "~/lib/cn";
20
+ import { Plus, Tag, FolderTree, Pencil, Trash2 } from "lucide-react";
21
+ import type { AdminNavigation } from "~/lib/navigation";
22
+ import { CmsAdminApi } from "~/embed/contexts/ApiContext";
23
+
24
+ interface Taxonomy {
25
+ _id: string;
26
+ name: string;
27
+ displayName: string;
28
+ description?: string;
29
+ isHierarchical: boolean;
30
+ allowInlineCreation: boolean;
31
+ isActive: boolean;
32
+ icon?: string;
33
+ sortOrder?: number;
34
+ termCount?: number;
35
+ }
36
+
37
+ export interface TaxonomiesPageProps {
38
+ api: CmsAdminApi;
39
+ navigation: AdminNavigation;
40
+ }
41
+
42
+ export function TaxonomiesPage({
43
+ api,
44
+ navigation: _navigation,
45
+ }: TaxonomiesPageProps) {
46
+ const [selectedTaxonomy, setSelectedTaxonomy] = useState<Taxonomy | null>(
47
+ null,
48
+ );
49
+ const [showCreateModal, setShowCreateModal] = useState(false);
50
+ const [editingTaxonomy, setEditingTaxonomy] = useState<Taxonomy | null>(null);
51
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState<string | null>(
52
+ null,
53
+ );
54
+ const [deleteError, setDeleteError] = useState<string | null>(null);
55
+
56
+ const taxonomiesQuery = useQuery(api.listTaxonomies, {
57
+ isActive: undefined,
58
+ includeDeleted: false,
59
+ });
60
+
61
+ const deleteTaxonomy = useMutation(api.deleteTaxonomy);
62
+
63
+ const taxonomies = (taxonomiesQuery?.page ?? []) as Taxonomy[];
64
+ const isLoading = taxonomiesQuery === undefined;
65
+
66
+ const handleCreate = useCallback(() => {
67
+ setEditingTaxonomy(null);
68
+ setShowCreateModal(true);
69
+ }, []);
70
+
71
+ const handleEdit = useCallback((taxonomy: Taxonomy) => {
72
+ setEditingTaxonomy(taxonomy);
73
+ setShowCreateModal(true);
74
+ }, []);
75
+
76
+ const handleDelete = useCallback(
77
+ async (taxonomyId: string) => {
78
+ setDeleteError(null);
79
+ try {
80
+ await deleteTaxonomy({ id: taxonomyId });
81
+ setShowDeleteConfirm(null);
82
+ if (selectedTaxonomy?._id === taxonomyId) {
83
+ setSelectedTaxonomy(null);
84
+ }
85
+ } catch (error) {
86
+ const message =
87
+ error instanceof Error ? error.message : "Failed to delete taxonomy";
88
+ setDeleteError(message);
89
+ }
90
+ },
91
+ [deleteTaxonomy, selectedTaxonomy],
92
+ );
93
+
94
+ const handleSaveComplete = useCallback(() => {
95
+ setShowCreateModal(false);
96
+ setEditingTaxonomy(null);
97
+ }, []);
98
+
99
+ const getTypeIcon = (taxonomy: Taxonomy) => {
100
+ return taxonomy.isHierarchical ? (
101
+ <FolderTree className="size-5" />
102
+ ) : (
103
+ <Tag className="size-5" />
104
+ );
105
+ };
106
+
107
+ return (
108
+ <div className="flex h-[calc(100vh-4rem)] flex-col p-6">
109
+ <div className="mb-6 flex items-start justify-between">
110
+ <CmsPageHeader
111
+ title="Taxonomies"
112
+ description="Manage tags, categories, and other classification systems"
113
+ />
114
+ <CmsButton onClick={handleCreate}>
115
+ <Plus className="size-4" />
116
+ Create Taxonomy
117
+ </CmsButton>
118
+ </div>
119
+
120
+ <div className="flex min-h-0 flex-1 gap-6">
121
+ <CmsSurface elevation="base" className="w-80 shrink-0 overflow-hidden">
122
+ {isLoading ? (
123
+ <div className="flex flex-col items-center justify-center py-12">
124
+ <div className="size-6 animate-spin rounded-full border-2 border-muted border-t-primary" />
125
+ <p className="mt-3 text-sm text-muted-foreground">
126
+ Loading taxonomies...
127
+ </p>
128
+ </div>
129
+ ) : taxonomies.length === 0 ? (
130
+ <div className="flex flex-col items-center justify-center p-6 text-center">
131
+ <Tag className="mb-3 size-8 text-muted-foreground" />
132
+ <p className="text-sm text-muted-foreground">
133
+ No taxonomies created yet.
134
+ </p>
135
+ <CmsButton
136
+ variant="secondary"
137
+ className="mt-4"
138
+ onClick={handleCreate}
139
+ >
140
+ Create your first taxonomy
141
+ </CmsButton>
142
+ </div>
143
+ ) : (
144
+ <ScrollArea className="h-full">
145
+ <div className="divide-y">
146
+ {taxonomies.map((taxonomy) => (
147
+ <div
148
+ key={taxonomy._id}
149
+ className={cn(
150
+ "group flex cursor-pointer items-center gap-3 p-3 transition-colors hover:bg-muted/50",
151
+ selectedTaxonomy?._id === taxonomy._id && "bg-primary/5",
152
+ !taxonomy.isActive && "opacity-60",
153
+ )}
154
+ onClick={() => setSelectedTaxonomy(taxonomy)}
155
+ >
156
+ <div className="flex size-9 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground">
157
+ {getTypeIcon(taxonomy)}
158
+ </div>
159
+ <div className="min-w-0 flex-1">
160
+ <p className="truncate text-sm font-medium text-foreground">
161
+ {taxonomy.displayName}
162
+ </p>
163
+ <p className="truncate text-xs text-muted-foreground">
164
+ {taxonomy.name}
165
+ </p>
166
+ </div>
167
+ <div className="flex shrink-0 items-center gap-2">
168
+ <Badge
169
+ variant="secondary"
170
+ className="text-xs font-normal"
171
+ >
172
+ {taxonomy.isHierarchical ? "Hierarchical" : "Flat"}
173
+ </Badge>
174
+ {!taxonomy.isActive && (
175
+ <Badge
176
+ variant="outline"
177
+ className="text-xs font-normal"
178
+ >
179
+ Inactive
180
+ </Badge>
181
+ )}
182
+ </div>
183
+ <div className="flex shrink-0 items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100">
184
+ <CmsButton
185
+ variant="ghost"
186
+ size="icon-sm"
187
+ onClick={(e) => {
188
+ e.stopPropagation();
189
+ handleEdit(taxonomy);
190
+ }}
191
+ title="Edit taxonomy"
192
+ >
193
+ <Pencil className="size-4" />
194
+ </CmsButton>
195
+ <CmsButton
196
+ variant="ghost"
197
+ size="icon-sm"
198
+ onClick={(e) => {
199
+ e.stopPropagation();
200
+ setShowDeleteConfirm(taxonomy._id);
201
+ }}
202
+ title="Delete taxonomy"
203
+ className="text-destructive hover:bg-destructive/10 hover:text-destructive"
204
+ >
205
+ <Trash2 className="size-4" />
206
+ </CmsButton>
207
+ </div>
208
+ </div>
209
+ ))}
210
+ </div>
211
+ </ScrollArea>
212
+ )}
213
+ </CmsSurface>
214
+
215
+ <CmsSurface elevation="base" className="min-w-0 flex-1 overflow-hidden">
216
+ {selectedTaxonomy ? (
217
+ <div className="flex h-full flex-col">
218
+ <div className="flex items-start justify-between border-b p-4">
219
+ <div className="flex items-center gap-3">
220
+ <div className="flex size-10 items-center justify-center rounded-md bg-muted text-muted-foreground">
221
+ {getTypeIcon(selectedTaxonomy)}
222
+ </div>
223
+ <div>
224
+ <h2 className="text-lg font-semibold text-foreground">
225
+ {selectedTaxonomy.displayName}
226
+ </h2>
227
+ {selectedTaxonomy.description && (
228
+ <p className="text-sm text-muted-foreground">
229
+ {selectedTaxonomy.description}
230
+ </p>
231
+ )}
232
+ </div>
233
+ </div>
234
+ <CmsButton
235
+ variant="secondary"
236
+ onClick={() => handleEdit(selectedTaxonomy)}
237
+ >
238
+ Edit Taxonomy
239
+ </CmsButton>
240
+ </div>
241
+
242
+ <div className="flex-1 overflow-auto p-4">
243
+ <TermTree
244
+ taxonomyId={selectedTaxonomy._id}
245
+ isHierarchical={selectedTaxonomy.isHierarchical}
246
+ allowInlineCreation={selectedTaxonomy.allowInlineCreation}
247
+ />
248
+ </div>
249
+ </div>
250
+ ) : (
251
+ <CmsEmptyState
252
+ icon={<Tag className="size-6" />}
253
+ title="Select a taxonomy"
254
+ description="Select a taxonomy to view and manage its terms"
255
+ className="h-full"
256
+ />
257
+ )}
258
+ </CmsSurface>
259
+ </div>
260
+
261
+ {showCreateModal && (
262
+ <TaxonomyEditor
263
+ taxonomy={editingTaxonomy}
264
+ onSave={handleSaveComplete}
265
+ onCancel={() => {
266
+ setShowCreateModal(false);
267
+ setEditingTaxonomy(null);
268
+ }}
269
+ />
270
+ )}
271
+
272
+ <CmsConfirmDialog
273
+ open={showDeleteConfirm !== null}
274
+ onOpenChange={(open) => {
275
+ if (!open) {
276
+ setShowDeleteConfirm(null);
277
+ setDeleteError(null);
278
+ }
279
+ }}
280
+ title="Delete Taxonomy"
281
+ description="Are you sure you want to delete this taxonomy? All associated terms will also be deleted."
282
+ confirmLabel="Delete"
283
+ onConfirm={() => showDeleteConfirm && handleDelete(showDeleteConfirm)}
284
+ variant="danger"
285
+ error={deleteError}
286
+ />
287
+ </div>
288
+ );
289
+ }
@@ -0,0 +1,421 @@
1
+ /**
2
+ * Shared Trash Page Component
3
+ *
4
+ * Displays deleted items and allows restoration or permanent deletion.
5
+ * Used by both CLI routes and embed pages.
6
+ */
7
+
8
+ import { useState, useCallback } from "react";
9
+ import { useQuery, useMutation } from "convex/react";
10
+ import { CmsPageHeader } from "~/components/cmsds/CmsPageHeader";
11
+ import { CmsToolbar } from "~/components/cmsds/CmsToolbar";
12
+ import { CmsEmptyState } from "~/components/cmsds/CmsEmptyState";
13
+ import { CmsSurface } from "~/components/cmsds/CmsSurface";
14
+ import { CmsButton } from "~/components/cmsds/CmsButton";
15
+ import { CmsConfirmDialog } from "~/components/cmsds/CmsDialog";
16
+ import { Input } from "~/components/ui/input";
17
+ import {
18
+ Select,
19
+ SelectContent,
20
+ SelectItem,
21
+ SelectTrigger,
22
+ SelectValue,
23
+ } from "~/components/ui/select";
24
+ import { Checkbox } from "~/components/ui/checkbox";
25
+ import { Badge } from "~/components/ui/badge";
26
+ import { Alert, AlertDescription } from "~/components/ui/alert";
27
+ import { cn } from "~/lib/cn";
28
+ import { Search, Trash2, RotateCcw, AlertTriangle, X } from "lucide-react";
29
+ import type { AdminNavigation } from "~/lib/navigation";
30
+ import { CmsAdminApi } from "~/embed/contexts/ApiContext";
31
+
32
+ interface TrashItem {
33
+ _id: string;
34
+ contentTypeId?: string;
35
+ contentTypeName?: string;
36
+ slug?: string;
37
+ name?: string;
38
+ title?: string;
39
+ status?: string;
40
+ deletedAt: number;
41
+ deletedBy?: string;
42
+ data?: Record<string, unknown>;
43
+ }
44
+
45
+ export interface TrashPageProps {
46
+ api: CmsAdminApi;
47
+ navigation: AdminNavigation;
48
+ }
49
+
50
+ export function TrashPage({ api, navigation: _navigation }: TrashPageProps) {
51
+ const [selectedContentType, setSelectedContentType] = useState<string>("");
52
+ const [searchQuery, setSearchQuery] = useState("");
53
+ const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set());
54
+ const [restoreError, setRestoreError] = useState<string | null>(null);
55
+ const [isRestoring, setIsRestoring] = useState(false);
56
+ const [showEmptyConfirm, setShowEmptyConfirm] = useState(false);
57
+ const [isEmptying, setIsEmptying] = useState(false);
58
+ const [emptyError, setEmptyError] = useState<string | null>(null);
59
+
60
+ const trashQuery = useQuery(api.listTrash, {
61
+ contentTypeId: selectedContentType || undefined,
62
+ search: searchQuery || undefined,
63
+ paginationOpts: { numItems: 50, cursor: null },
64
+ });
65
+
66
+ const configQuery = useQuery(api.getTrashConfig, {});
67
+ const statsQuery = useQuery(api.getTrashStats, {});
68
+ const contentTypesQuery = useQuery(api.listContentTypes, {});
69
+
70
+ const contentTypes = contentTypesQuery?.page ?? [];
71
+
72
+ const restoreMutation = useMutation(api.bulkRestore);
73
+ const emptyMutation = useMutation(api.emptyTrash);
74
+
75
+ const trashItems = (trashQuery?.page ?? []) as TrashItem[];
76
+ const isLoading = trashQuery === undefined;
77
+ const config = configQuery;
78
+ const stats = statsQuery;
79
+
80
+ const handleSelectItem = useCallback((itemId: string, selected: boolean) => {
81
+ setSelectedItems((prev) => {
82
+ const next = new Set(prev);
83
+ if (selected) {
84
+ next.add(itemId);
85
+ } else {
86
+ next.delete(itemId);
87
+ }
88
+ return next;
89
+ });
90
+ }, []);
91
+
92
+ const handleSelectAll = useCallback(() => {
93
+ if (selectedItems.size === trashItems.length) {
94
+ setSelectedItems(new Set());
95
+ } else {
96
+ setSelectedItems(new Set(trashItems.map((item) => item._id)));
97
+ }
98
+ }, [selectedItems.size, trashItems]);
99
+
100
+ const handleRestore = useCallback(
101
+ async (ids: string[]) => {
102
+ setIsRestoring(true);
103
+ setRestoreError(null);
104
+
105
+ try {
106
+ await restoreMutation({ ids });
107
+ setSelectedItems((prev) => {
108
+ const next = new Set(prev);
109
+ ids.forEach((id) => next.delete(id));
110
+ return next;
111
+ });
112
+ } catch (error) {
113
+ const message =
114
+ error instanceof Error ? error.message : "Failed to restore";
115
+ setRestoreError(message);
116
+ } finally {
117
+ setIsRestoring(false);
118
+ }
119
+ },
120
+ [restoreMutation],
121
+ );
122
+
123
+ const handleEmptyTrash = useCallback(async () => {
124
+ setIsEmptying(true);
125
+ setEmptyError(null);
126
+
127
+ try {
128
+ await emptyMutation({});
129
+ setShowEmptyConfirm(false);
130
+ setSelectedItems(new Set());
131
+ } catch (error) {
132
+ const message =
133
+ error instanceof Error ? error.message : "Failed to empty trash";
134
+ setEmptyError(message);
135
+ } finally {
136
+ setIsEmptying(false);
137
+ }
138
+ }, [emptyMutation]);
139
+
140
+ const formatDate = (timestamp: number) => {
141
+ return new Date(timestamp).toLocaleString(undefined, {
142
+ year: "numeric",
143
+ month: "short",
144
+ day: "numeric",
145
+ hour: "2-digit",
146
+ minute: "2-digit",
147
+ });
148
+ };
149
+
150
+ const getDaysUntilDeletion = (deletedAt: number) => {
151
+ if (!config?.retentionDays) return null;
152
+ const expiresAt = deletedAt + config.retentionDays * 24 * 60 * 60 * 1000;
153
+ const daysLeft = Math.ceil(
154
+ (expiresAt - Date.now()) / (24 * 60 * 60 * 1000),
155
+ );
156
+ return Math.max(0, daysLeft);
157
+ };
158
+
159
+ const getItemTitle = (item: TrashItem) => {
160
+ if (item.title) return item.title;
161
+ if (item.name) return item.name;
162
+ if (item.data) {
163
+ const titleField = item.data.title || item.data.name;
164
+ if (titleField && typeof titleField === "string") return titleField;
165
+ }
166
+ return item.slug || item._id;
167
+ };
168
+
169
+ return (
170
+ <div className="space-y-6 p-6">
171
+ <div className="flex items-start justify-between">
172
+ <CmsPageHeader
173
+ title="Trash"
174
+ description={`Deleted items are kept for ${
175
+ config?.retentionDays ?? 30
176
+ } days before permanent deletion`}
177
+ />
178
+ {trashItems.length > 0 && (
179
+ <CmsButton variant="danger" onClick={() => setShowEmptyConfirm(true)}>
180
+ <Trash2 className="size-4" />
181
+ Empty Trash
182
+ </CmsButton>
183
+ )}
184
+ </div>
185
+
186
+ {stats && (
187
+ <div className="grid gap-4 sm:grid-cols-2">
188
+ <CmsSurface elevation="base" className="p-4">
189
+ <p className="text-2xl font-semibold text-foreground">
190
+ {stats.totalCount ?? 0}
191
+ </p>
192
+ <p className="text-sm text-muted-foreground">Items in Trash</p>
193
+ </CmsSurface>
194
+ <CmsSurface elevation="base" className="p-4">
195
+ <p className="text-2xl font-semibold text-foreground">
196
+ {stats.expiredCount ?? 0}
197
+ </p>
198
+ <p className="text-sm text-muted-foreground">Expired</p>
199
+ </CmsSurface>
200
+ </div>
201
+ )}
202
+
203
+ <CmsToolbar
204
+ left={
205
+ <div className="flex items-center gap-3">
206
+ <div className="relative">
207
+ <Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
208
+ <Input
209
+ type="text"
210
+ placeholder="Search deleted items..."
211
+ value={searchQuery}
212
+ onChange={(e) => setSearchQuery(e.target.value)}
213
+ className="w-64 pl-9"
214
+ />
215
+ </div>
216
+ <Select
217
+ value={selectedContentType || "all"}
218
+ onValueChange={(v) =>
219
+ setSelectedContentType(v === "all" ? "" : v)
220
+ }
221
+ >
222
+ <SelectTrigger className="w-48">
223
+ <SelectValue placeholder="All Content Types" />
224
+ </SelectTrigger>
225
+ <SelectContent>
226
+ <SelectItem value="all">All Content Types</SelectItem>
227
+ {contentTypes.map((type: any) => (
228
+ <SelectItem key={type._id} value={type._id}>
229
+ {type.displayName}
230
+ </SelectItem>
231
+ ))}
232
+ </SelectContent>
233
+ </Select>
234
+ </div>
235
+ }
236
+ />
237
+
238
+ {restoreError && (
239
+ <Alert variant="destructive">
240
+ <AlertTriangle className="size-4" />
241
+ <AlertDescription className="flex items-center justify-between">
242
+ {restoreError}
243
+ <CmsButton
244
+ variant="ghost"
245
+ size="icon-sm"
246
+ onClick={() => setRestoreError(null)}
247
+ >
248
+ <X className="size-4" />
249
+ </CmsButton>
250
+ </AlertDescription>
251
+ </Alert>
252
+ )}
253
+
254
+ {emptyError && (
255
+ <Alert variant="destructive">
256
+ <AlertTriangle className="size-4" />
257
+ <AlertDescription className="flex items-center justify-between">
258
+ {emptyError}
259
+ <CmsButton
260
+ variant="ghost"
261
+ size="icon-sm"
262
+ onClick={() => setEmptyError(null)}
263
+ >
264
+ <X className="size-4" />
265
+ </CmsButton>
266
+ </AlertDescription>
267
+ </Alert>
268
+ )}
269
+
270
+ {selectedItems.size > 0 && (
271
+ <div className="flex items-center gap-3 rounded-lg border border-primary/20 bg-primary/5 p-3">
272
+ <span className="text-sm font-medium">
273
+ {selectedItems.size} {selectedItems.size === 1 ? "item" : "items"}{" "}
274
+ selected
275
+ </span>
276
+ <CmsButton
277
+ variant="primary"
278
+ size="sm"
279
+ onClick={() => handleRestore(Array.from(selectedItems))}
280
+ loading={isRestoring}
281
+ >
282
+ <RotateCcw className="size-4" />
283
+ Restore Selected
284
+ </CmsButton>
285
+ <CmsButton
286
+ variant="secondary"
287
+ size="sm"
288
+ onClick={() => setSelectedItems(new Set())}
289
+ >
290
+ Clear Selection
291
+ </CmsButton>
292
+ </div>
293
+ )}
294
+
295
+ {isLoading ? (
296
+ <div className="flex flex-col items-center justify-center py-12">
297
+ <div className="size-8 animate-spin rounded-full border-2 border-muted border-t-primary" />
298
+ <p className="mt-4 text-sm text-muted-foreground">Loading trash...</p>
299
+ </div>
300
+ ) : trashItems.length === 0 ? (
301
+ <CmsEmptyState
302
+ icon={<Trash2 className="size-6" />}
303
+ title="Trash is empty"
304
+ description="Deleted items will appear here"
305
+ />
306
+ ) : (
307
+ <div className="rounded-lg border bg-card">
308
+ <table className="w-full">
309
+ <thead>
310
+ <tr className="border-b">
311
+ <th className="w-10 p-3 text-left">
312
+ <Checkbox
313
+ checked={
314
+ selectedItems.size === trashItems.length &&
315
+ trashItems.length > 0
316
+ }
317
+ onCheckedChange={handleSelectAll}
318
+ />
319
+ </th>
320
+ <th className="p-3 text-left text-sm font-medium text-muted-foreground">
321
+ Name
322
+ </th>
323
+ <th className="p-3 text-left text-sm font-medium text-muted-foreground">
324
+ Type
325
+ </th>
326
+ <th className="p-3 text-left text-sm font-medium text-muted-foreground">
327
+ Deleted
328
+ </th>
329
+ <th className="p-3 text-left text-sm font-medium text-muted-foreground">
330
+ Expires In
331
+ </th>
332
+ <th className="p-3 text-left text-sm font-medium text-muted-foreground">
333
+ Actions
334
+ </th>
335
+ </tr>
336
+ </thead>
337
+ <tbody>
338
+ {trashItems.map((item) => {
339
+ const daysLeft = getDaysUntilDeletion(item.deletedAt);
340
+
341
+ return (
342
+ <tr
343
+ key={item._id}
344
+ className={cn(
345
+ "border-b last:border-0 transition-colors hover:bg-muted/50",
346
+ selectedItems.has(item._id) && "bg-primary/5",
347
+ )}
348
+ >
349
+ <td className="p-3">
350
+ <Checkbox
351
+ checked={selectedItems.has(item._id)}
352
+ onCheckedChange={(checked) =>
353
+ handleSelectItem(item._id, checked as boolean)
354
+ }
355
+ />
356
+ </td>
357
+ <td className="p-3">
358
+ <span className="font-medium text-foreground">
359
+ {getItemTitle(item)}
360
+ </span>
361
+ {item.slug && (
362
+ <span className="block text-xs text-muted-foreground">
363
+ {item.slug}
364
+ </span>
365
+ )}
366
+ </td>
367
+ <td className="p-3 text-sm text-muted-foreground">
368
+ {item.contentTypeName || "Unknown"}
369
+ </td>
370
+ <td className="p-3">
371
+ <span className="text-sm text-muted-foreground">
372
+ {formatDate(item.deletedAt)}
373
+ </span>
374
+ {item.deletedBy && (
375
+ <span className="block text-xs text-muted-foreground">
376
+ by {item.deletedBy}
377
+ </span>
378
+ )}
379
+ </td>
380
+ <td className="p-3">
381
+ {daysLeft !== null && (
382
+ <Badge
383
+ variant={daysLeft <= 3 ? "destructive" : "secondary"}
384
+ className="font-normal"
385
+ >
386
+ {daysLeft} {daysLeft === 1 ? "day" : "days"}
387
+ </Badge>
388
+ )}
389
+ </td>
390
+ <td className="p-3">
391
+ <CmsButton
392
+ variant="outline"
393
+ size="sm"
394
+ onClick={() => handleRestore([item._id])}
395
+ loading={isRestoring}
396
+ >
397
+ <RotateCcw className="size-4" />
398
+ Restore
399
+ </CmsButton>
400
+ </td>
401
+ </tr>
402
+ );
403
+ })}
404
+ </tbody>
405
+ </table>
406
+ </div>
407
+ )}
408
+
409
+ <CmsConfirmDialog
410
+ open={showEmptyConfirm}
411
+ onOpenChange={setShowEmptyConfirm}
412
+ title="Empty Trash"
413
+ description="This will permanently delete all items in the trash. This action cannot be undone."
414
+ confirmLabel={isEmptying ? "Deleting..." : "Empty Trash"}
415
+ onConfirm={handleEmptyTrash}
416
+ variant="danger"
417
+ loading={isEmptying}
418
+ />
419
+ </div>
420
+ );
421
+ }