convex-cms 0.0.5-alpha.0 → 0.0.5-alpha.3
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.
- package/README.md +95 -144
- package/admin/README.md +99 -0
- package/admin/src/components/AdminLayout.tsx +22 -0
- package/admin/src/components/BreakingChangesWarningDialog.tsx +81 -0
- package/admin/src/components/BulkActionBar.tsx +190 -0
- package/admin/src/components/BulkOperationModal.tsx +177 -0
- package/admin/src/components/ContentEntryEditor.tsx +1104 -0
- package/admin/src/components/ContentTypeFormModal.tsx +1012 -0
- package/admin/src/components/ErrorBoundary.tsx +83 -0
- package/admin/src/components/ErrorState.tsx +147 -0
- package/admin/src/components/Header.tsx +294 -0
- package/admin/src/components/RouteGuard.tsx +264 -0
- package/admin/src/components/Sidebar.tsx +90 -0
- package/admin/src/components/TaxonomyEditor.tsx +348 -0
- package/admin/src/components/TermTree.tsx +533 -0
- package/admin/src/components/UploadDropzone.tsx +383 -0
- package/admin/src/components/VersionCompare.tsx +250 -0
- package/admin/src/components/VersionHistory.tsx +279 -0
- package/admin/src/components/VersionRollbackModal.tsx +79 -0
- package/admin/src/components/cmsds/CmsButton.tsx +101 -0
- package/admin/src/components/cmsds/CmsDialog.tsx +139 -0
- package/admin/src/components/cmsds/CmsDropdown.tsx +62 -0
- package/admin/src/components/cmsds/CmsEmptyState.tsx +54 -0
- package/admin/src/components/cmsds/CmsField.tsx +47 -0
- package/admin/src/components/cmsds/CmsPageHeader.tsx +35 -0
- package/admin/src/components/cmsds/CmsStatusBadge.tsx +153 -0
- package/admin/src/components/cmsds/CmsSurface.tsx +52 -0
- package/admin/src/components/cmsds/CmsTable.tsx +164 -0
- package/admin/src/components/cmsds/CmsToolbar.tsx +58 -0
- package/admin/src/components/cmsds/index.ts +10 -0
- package/admin/src/components/fields/BooleanField.tsx +74 -0
- package/admin/src/components/fields/CategoryField.tsx +394 -0
- package/admin/src/components/fields/DateField.tsx +173 -0
- package/admin/src/components/fields/DefaultFieldRenderer.tsx +74 -0
- package/admin/src/components/fields/FieldRenderer.tsx +180 -0
- package/admin/src/components/fields/FieldWrapper.tsx +57 -0
- package/admin/src/components/fields/JsonField.tsx +172 -0
- package/admin/src/components/fields/MediaField.tsx +367 -0
- package/admin/src/components/fields/MultiSelectField.tsx +118 -0
- package/admin/src/components/fields/NumberField.tsx +77 -0
- package/admin/src/components/fields/ReferenceField.tsx +386 -0
- package/admin/src/components/fields/RichTextField.tsx +171 -0
- package/admin/src/components/fields/SelectField.tsx +62 -0
- package/admin/src/components/fields/TagField.tsx +325 -0
- package/admin/src/components/fields/TextAreaField.tsx +68 -0
- package/admin/src/components/fields/TextField.tsx +56 -0
- package/admin/src/components/fields/index.ts +54 -0
- package/admin/src/components/fields/registry.ts +64 -0
- package/admin/src/components/fields/types.ts +217 -0
- package/admin/src/components/filters/TaxonomyFilter.tsx +254 -0
- package/admin/src/components/filters/index.ts +1 -0
- package/admin/src/components/index.ts +8 -0
- package/admin/src/components/media/MediaAssetActions.tsx +115 -0
- package/admin/src/components/media/MediaAssetEditDialog.tsx +217 -0
- package/admin/src/components/media/MediaBulkActionBar.tsx +51 -0
- package/admin/src/components/media/MediaFolderActions.tsx +69 -0
- package/admin/src/components/media/MediaFolderEditDialog.tsx +126 -0
- package/admin/src/components/media/MediaMoveModal.tsx +179 -0
- package/admin/src/components/media/MediaPreviewModal.tsx +370 -0
- package/admin/src/components/media/MediaTaxonomyPicker.tsx +304 -0
- package/admin/src/components/media/MediaTrashBulkActionBar.tsx +59 -0
- package/admin/src/components/ui/accordion.tsx +64 -0
- package/admin/src/components/ui/alert-dialog.tsx +155 -0
- package/admin/src/components/ui/alert.tsx +66 -0
- package/admin/src/components/ui/avatar.tsx +53 -0
- package/admin/src/components/ui/badge.tsx +46 -0
- package/admin/src/components/ui/breadcrumb.tsx +109 -0
- package/admin/src/components/ui/button.tsx +62 -0
- package/admin/src/components/ui/calendar.tsx +220 -0
- package/admin/src/components/ui/card.tsx +92 -0
- package/admin/src/components/ui/checkbox.tsx +30 -0
- package/admin/src/components/ui/command.tsx +182 -0
- package/admin/src/components/ui/dialog.tsx +143 -0
- package/admin/src/components/ui/dropdown-menu.tsx +257 -0
- package/admin/src/components/ui/form.tsx +167 -0
- package/admin/src/components/ui/input.tsx +21 -0
- package/admin/src/components/ui/label.tsx +24 -0
- package/admin/src/components/ui/popover.tsx +46 -0
- package/admin/src/components/ui/scroll-area.tsx +56 -0
- package/admin/src/components/ui/select.tsx +190 -0
- package/admin/src/components/ui/separator.tsx +26 -0
- package/admin/src/components/ui/sheet.tsx +137 -0
- package/admin/src/components/ui/sidebar.tsx +724 -0
- package/admin/src/components/ui/skeleton.tsx +13 -0
- package/admin/src/components/ui/sonner.tsx +38 -0
- package/admin/src/components/ui/switch.tsx +31 -0
- package/admin/src/components/ui/table.tsx +114 -0
- package/admin/src/components/ui/tabs.tsx +66 -0
- package/admin/src/components/ui/textarea.tsx +18 -0
- package/admin/src/components/ui/tooltip.tsx +61 -0
- package/admin/src/contexts/AdminConfigContext.tsx +30 -0
- package/admin/src/contexts/AuthContext.tsx +330 -0
- package/admin/src/contexts/BreadcrumbContext.tsx +49 -0
- package/admin/src/contexts/SettingsConfigContext.tsx +57 -0
- package/admin/src/contexts/ThemeContext.tsx +91 -0
- package/admin/src/contexts/index.ts +20 -0
- package/admin/src/embed/components/EmbedHeader.tsx +103 -0
- package/admin/src/embed/components/EmbedLayout.tsx +29 -0
- package/admin/src/embed/components/EmbedSidebar.tsx +119 -0
- package/admin/src/embed/components/index.ts +3 -0
- package/admin/src/embed/contexts/ApiContext.tsx +32 -0
- package/admin/src/embed/index.tsx +184 -0
- package/admin/src/embed/navigation.tsx +202 -0
- package/admin/src/embed/pages/Content.tsx +19 -0
- package/admin/src/embed/pages/ContentTypes.tsx +19 -0
- package/admin/src/embed/pages/Dashboard.tsx +19 -0
- package/admin/src/embed/pages/Media.tsx +19 -0
- package/admin/src/embed/pages/Settings.tsx +22 -0
- package/admin/src/embed/pages/Taxonomies.tsx +22 -0
- package/admin/src/embed/pages/Trash.tsx +22 -0
- package/admin/src/embed/pages/index.ts +7 -0
- package/admin/src/embed/types.ts +24 -0
- package/admin/src/hooks/index.ts +2 -0
- package/admin/src/hooks/use-mobile.ts +19 -0
- package/admin/src/hooks/useBreadcrumbLabel.ts +15 -0
- package/admin/src/hooks/usePermissions.ts +211 -0
- package/admin/src/lib/admin-config.ts +111 -0
- package/admin/src/lib/cn.ts +6 -0
- package/admin/src/lib/config.server.ts +56 -0
- package/admin/src/lib/convex.ts +26 -0
- package/admin/src/lib/embed-adapter.ts +80 -0
- package/admin/src/lib/icons.tsx +96 -0
- package/admin/src/lib/loadAdminConfig.ts +92 -0
- package/admin/src/lib/motion.ts +29 -0
- package/admin/src/lib/navigation.ts +43 -0
- package/admin/src/lib/tanstack-adapter.ts +82 -0
- package/admin/src/pages/ContentPage.tsx +337 -0
- package/admin/src/pages/ContentTypesPage.tsx +457 -0
- package/admin/src/pages/DashboardPage.tsx +163 -0
- package/admin/src/pages/MediaPage.tsx +34 -0
- package/admin/src/pages/SettingsPage.tsx +486 -0
- package/admin/src/pages/TaxonomiesPage.tsx +289 -0
- package/admin/src/pages/TrashPage.tsx +421 -0
- package/admin/src/pages/index.ts +14 -0
- package/admin/src/routeTree.gen.ts +262 -0
- package/admin/src/router.tsx +22 -0
- package/admin/src/routes/__root.tsx +250 -0
- package/admin/src/routes/content-types.tsx +20 -0
- package/admin/src/routes/content.tsx +20 -0
- package/admin/src/routes/entries/$entryId.tsx +107 -0
- package/admin/src/routes/entries/new.$contentTypeId.tsx +69 -0
- package/admin/src/routes/entries/type/$contentTypeId.tsx +503 -0
- package/admin/src/routes/index.tsx +20 -0
- package/admin/src/routes/media.tsx +1095 -0
- package/admin/src/routes/settings.tsx +20 -0
- package/admin/src/routes/taxonomies.tsx +20 -0
- package/admin/src/routes/trash.tsx +20 -0
- package/admin/src/styles/globals.css +69 -0
- package/admin/src/styles/tailwind-config.css +74 -0
- package/admin/src/styles/theme.css +73 -0
- package/admin/src/types/index.ts +221 -0
- package/admin/src/utils/errorParsing.ts +163 -0
- package/admin/src/utils/index.ts +5 -0
- package/admin/src/vite-env.d.ts +14 -0
- package/admin/tailwind.preset.cjs +102 -0
- package/admin-dist/nitro.json +1 -1
- package/admin-dist/public/assets/{CmsEmptyState-CiMQwSQV.js → CmsEmptyState-CkqBIab3.js} +1 -1
- package/admin-dist/public/assets/{CmsPageHeader-ohOq0luT.js → CmsPageHeader-CUtl5MMG.js} +1 -1
- package/admin-dist/public/assets/{CmsStatusBadge-BdNf0V9v.js → CmsStatusBadge-CUYFgEe-.js} +1 -1
- package/admin-dist/public/assets/{CmsSurface-CWup6Jh7.js → CmsSurface-CsJfAVa3.js} +1 -1
- package/admin-dist/public/assets/{CmsToolbar-cEBlCHa3.js → CmsToolbar-CnfbcxeP.js} +1 -1
- package/admin-dist/public/assets/{ContentEntryEditor-BY5ypfUs.js → ContentEntryEditor-BU220CCy.js} +1 -1
- package/admin-dist/public/assets/TaxonomyFilter-CWCxC5HZ.js +1 -0
- package/admin-dist/public/assets/_contentTypeId-DK8cskRt.js +1 -0
- package/admin-dist/public/assets/{_entryId-BpSmrfAm.js → _entryId-CuVMExbb.js} +1 -1
- package/admin-dist/public/assets/{alert-Bf2l8kxw.js → alert-CF1BSzGR.js} +1 -1
- package/admin-dist/public/assets/{badge-qPrc4AUM.js → badge-CmuOIVKp.js} +1 -1
- package/admin-dist/public/assets/{circle-check-big-Dgozy3vV.js → circle-check-big-BKDVG6DU.js} +1 -1
- package/admin-dist/public/assets/{command-QOmNhlb0.js → command-XJxnF2Sd.js} +1 -1
- package/admin-dist/public/assets/content-QBUxdxbS.js +1 -0
- package/admin-dist/public/assets/content-types-CrNEm8Hf.js +2 -0
- package/admin-dist/public/assets/globals-B7Wsfh_v.css +1 -0
- package/admin-dist/public/assets/index-C7xOwudI.js +1 -0
- package/admin-dist/public/assets/{label-DCsUdvFh.js → label-CHCnXeBk.js} +1 -1
- package/admin-dist/public/assets/{link-2-Czw1N61H.js → link-2-Bb34judH.js} +1 -1
- package/admin-dist/public/assets/{list-DtCsXj8-.js → list-9Pzt48ld.js} +1 -1
- package/admin-dist/public/assets/{main-CXgkZMhe.js → main-CjQ2VI9L.js} +3 -3
- package/admin-dist/public/assets/media-Dc5PWt2Q.js +1 -0
- package/admin-dist/public/assets/{new._contentTypeId-CoTDxKzf.js → new._contentTypeId-C_I4YxIa.js} +1 -1
- package/admin-dist/public/assets/{plus-xCFJK0RC.js → plus-Ceef7DHk.js} +1 -1
- package/admin-dist/public/assets/{rotate-ccw-DIqK63wY.js → rotate-ccw-7k7-4VUq.js} +1 -1
- package/admin-dist/public/assets/{scroll-area-B-yrE66a.js → scroll-area-CC6wujnp.js} +1 -1
- package/admin-dist/public/assets/{search-CbCbboeU.js → search-DwoUV2pv.js} +1 -1
- package/admin-dist/public/assets/{select-Co3TZFJb.js → select-hOZTp8aC.js} +1 -1
- package/admin-dist/public/assets/{settings-BspTTv_o.js → settings-t2PbCZh4.js} +1 -1
- package/admin-dist/public/assets/{switch-CfavASmR.js → switch-jX2pDaNU.js} +1 -1
- package/admin-dist/public/assets/{tabs-CN5s5u2W.js → tabs-q4EbZk7c.js} +1 -1
- package/admin-dist/public/assets/{tanstack-adapter-npeE3RdY.js → tanstack-adapter-B-Glm4kH.js} +1 -1
- package/admin-dist/public/assets/taxonomies-kyk5P4ZW.js +1 -0
- package/admin-dist/public/assets/{textarea-BJ0XFZpT.js → textarea-B6SfBmr0.js} +1 -1
- package/admin-dist/public/assets/trash-BOCnIznD.js +1 -0
- package/admin-dist/public/assets/{triangle-alert-BZRcqsUg.js → triangle-alert-CXFIO_Gu.js} +1 -1
- package/admin-dist/public/assets/{useBreadcrumbLabel-DwZlwvFF.js → useBreadcrumbLabel-_6qBagc3.js} +1 -1
- package/admin-dist/public/assets/{usePermissions-C1JQhfqb.js → usePermissions-M1ijZ7a6.js} +1 -1
- package/admin-dist/server/_ssr/{CmsButton-B45JAKR1.mjs → CmsButton-DOiTVKQq.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsEmptyState-D_BQFAVR.mjs → CmsEmptyState-fbnGt3LD.mjs} +2 -2
- package/admin-dist/server/_ssr/{CmsPageHeader-CrUZA59A.mjs → CmsPageHeader-DHRrdOZa.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsStatusBadge-B-sj6yaj.mjs → CmsStatusBadge-s7obWbKZ.mjs} +2 -2
- package/admin-dist/server/_ssr/{CmsSurface-DKJZhpjk.mjs → CmsSurface-rFoYjb62.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsToolbar-ByaW5iXf.mjs → CmsToolbar-zTE45z2q.mjs} +2 -2
- package/admin-dist/server/_ssr/{ContentEntryEditor-D3_Jb1dq.mjs → ContentEntryEditor-BLoEjT_m.mjs} +12 -12
- package/admin-dist/server/_ssr/{TaxonomyFilter-BRJkuCtA.mjs → TaxonomyFilter-XAtaJC2z.mjs} +5 -5
- package/admin-dist/server/_ssr/{_contentTypeId-B9kA6CaM.mjs → _contentTypeId-Csl4822C.mjs} +13 -13
- package/admin-dist/server/_ssr/{_entryId-BddcMkZN.mjs → _entryId-D8alLFBx.mjs} +15 -15
- package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BffZedId.mjs +4 -0
- package/admin-dist/server/_ssr/{command-CGtVr8Gb.mjs → command-C0Di14--.mjs} +1 -1
- package/admin-dist/server/_ssr/{content-D1tbeOd0.mjs → content-CT-FPsmV.mjs} +12 -55
- package/admin-dist/server/_ssr/{content-types-BZqY_BER.mjs → content-types-C8cBFdzE.mjs} +15 -46
- package/admin-dist/server/_ssr/{index-BIdq4xaC.mjs → index-BJtcrEc-.mjs} +5 -5
- package/admin-dist/server/_ssr/index.mjs +2 -2
- package/admin-dist/server/_ssr/{label-T-QNKAr6.mjs → label-qn2Afwl4.mjs} +1 -1
- package/admin-dist/server/_ssr/{media-C-xqjBrl.mjs → media-qv5IAsMZ.mjs} +14 -14
- package/admin-dist/server/_ssr/{new._contentTypeId-DWic9cRq.mjs → new._contentTypeId-DdGyrhqs.mjs} +13 -13
- package/admin-dist/server/_ssr/{router-D1BMAMJT.mjs → router-nSVkxb6Y.mjs} +11 -11
- package/admin-dist/server/_ssr/{scroll-area-C0pic_WA.mjs → scroll-area-BCinP455.mjs} +1 -1
- package/admin-dist/server/_ssr/{select-CqmuN2F6.mjs → select-BKQlQScw.mjs} +1 -1
- package/admin-dist/server/_ssr/{settings-CAkncGGV.mjs → settings-BCr2KQlk.mjs} +55 -40
- package/admin-dist/server/_ssr/{switch-CgmuJkT9.mjs → switch-BaOi42fE.mjs} +1 -1
- package/admin-dist/server/_ssr/{tabs-CnMj0aRy.mjs → tabs-DYXEi9kq.mjs} +2 -2
- package/admin-dist/server/_ssr/{tanstack-adapter-BXZrMauE.mjs → tanstack-adapter-Bsz8kha-.mjs} +1 -1
- package/admin-dist/server/_ssr/{taxonomies-thl3BfVm.mjs → taxonomies-CueMHTbE.mjs} +30 -19
- package/admin-dist/server/_ssr/{textarea-4K5OJgeh.mjs → textarea-CI0Jqx2x.mjs} +1 -1
- package/admin-dist/server/_ssr/{trash-B40Gx5zP.mjs → trash-DE6W8GoX.mjs} +20 -17
- package/admin-dist/server/_ssr/{useBreadcrumbLabel-rn-fL4zV.mjs → useBreadcrumbLabel-B5Yi72lM.mjs} +1 -1
- package/admin-dist/server/_ssr/{usePermissions-CKeM6_Vw.mjs → usePermissions-C3nZ-Izm.mjs} +1 -1
- package/admin-dist/server/index.mjs +187 -194
- package/dist/client/admin/bulk.d.ts +79 -0
- package/dist/client/admin/bulk.d.ts.map +1 -0
- package/dist/client/admin/bulk.js +72 -0
- package/dist/client/admin/bulk.js.map +1 -0
- package/dist/client/admin/contentLock.d.ts +118 -0
- package/dist/client/admin/contentLock.d.ts.map +1 -0
- package/dist/client/admin/contentLock.js +81 -0
- package/dist/client/admin/contentLock.js.map +1 -0
- package/dist/client/{adminApi.d.ts → admin/contentTypes.d.ts} +39 -1134
- package/dist/client/admin/contentTypes.d.ts.map +1 -0
- package/dist/client/admin/contentTypes.js +122 -0
- package/dist/client/admin/contentTypes.js.map +1 -0
- package/dist/client/admin/dashboard.d.ts +16 -0
- package/dist/client/admin/dashboard.d.ts.map +1 -0
- package/dist/client/admin/dashboard.js +33 -0
- package/dist/client/admin/dashboard.js.map +1 -0
- package/dist/client/admin/entries.d.ts +358 -0
- package/dist/client/admin/entries.d.ts.map +1 -0
- package/dist/client/admin/entries.js +220 -0
- package/dist/client/admin/entries.js.map +1 -0
- package/dist/client/admin/index.d.ts +6568 -0
- package/dist/client/admin/index.d.ts.map +1 -0
- package/dist/client/admin/index.js +305 -0
- package/dist/client/admin/index.js.map +1 -0
- package/dist/client/admin/media.d.ts +1038 -0
- package/dist/client/admin/media.d.ts.map +1 -0
- package/dist/client/admin/media.js +489 -0
- package/dist/client/admin/media.js.map +1 -0
- package/dist/client/admin/taxonomies.d.ts +339 -0
- package/dist/client/admin/taxonomies.d.ts.map +1 -0
- package/dist/client/admin/taxonomies.js +364 -0
- package/dist/client/admin/taxonomies.js.map +1 -0
- package/dist/client/admin/trash.d.ts +91 -0
- package/dist/client/admin/trash.d.ts.map +1 -0
- package/dist/client/admin/trash.js +71 -0
- package/dist/client/admin/trash.js.map +1 -0
- package/dist/client/admin/types.d.ts +320 -0
- package/dist/client/admin/types.d.ts.map +1 -0
- package/dist/client/admin/types.js +7 -0
- package/dist/client/admin/types.js.map +1 -0
- package/dist/client/admin/validators.d.ts +3886 -0
- package/dist/client/admin/validators.d.ts.map +1 -0
- package/dist/client/admin/validators.js +322 -0
- package/dist/client/admin/validators.js.map +1 -0
- package/dist/client/admin/versions.d.ts +106 -0
- package/dist/client/admin/versions.d.ts.map +1 -0
- package/dist/client/admin/versions.js +57 -0
- package/dist/client/admin/versions.js.map +1 -0
- package/dist/client/adminApiTypes.d.ts +27 -0
- package/dist/client/adminApiTypes.d.ts.map +1 -0
- package/dist/client/adminApiTypes.js +12 -0
- package/dist/client/adminApiTypes.js.map +1 -0
- package/dist/client/{admin-config.d.ts → adminConfig.d.ts} +2 -2
- package/dist/client/adminConfig.d.ts.map +1 -0
- package/dist/client/{admin-config.js → adminConfig.js} +1 -1
- package/dist/client/adminConfig.js.map +1 -0
- package/dist/client/agentTools.d.ts +4 -4
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +15 -2
- package/dist/client/index.js.map +1 -1
- package/dist/component/contentEntries.d.ts +4 -4
- package/dist/component/contentEntryMutations.d.ts +46 -0
- package/dist/component/contentEntryMutations.d.ts.map +1 -1
- package/dist/component/contentEntryMutations.js +1 -1
- package/dist/component/contentEntryMutations.js.map +1 -1
- package/dist/component/contentTypeMigration.d.ts +1 -1
- package/dist/component/contentTypeMutations.d.ts +22 -0
- package/dist/component/contentTypeMutations.d.ts.map +1 -1
- package/dist/component/contentTypeMutations.js +1 -1
- package/dist/component/contentTypeMutations.js.map +1 -1
- package/dist/component/mediaAssetMutations.d.ts +47 -0
- package/dist/component/mediaAssetMutations.d.ts.map +1 -1
- package/dist/component/mediaAssetMutations.js +1 -1
- package/dist/component/mediaAssetMutations.js.map +1 -1
- package/dist/component/schema.d.ts +9 -0
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +1 -1
- package/dist/component/schema.js.map +1 -1
- package/package.json +85 -3
- package/admin-dist/public/assets/ErrorState-C4nJ-ml4.js +0 -1
- package/admin-dist/public/assets/TaxonomyFilter-BgE_SR_O.js +0 -1
- package/admin-dist/public/assets/_contentTypeId-DtZectcC.js +0 -1
- package/admin-dist/public/assets/content-OEBGlxg1.js +0 -1
- package/admin-dist/public/assets/content-types-CjQliqVV.js +0 -2
- package/admin-dist/public/assets/globals-hAmgC66w.css +0 -1
- package/admin-dist/public/assets/index-BH_ECMhv.js +0 -1
- package/admin-dist/public/assets/media-DTJ3-ViE.js +0 -1
- package/admin-dist/public/assets/taxonomies-CgG46fIF.js +0 -1
- package/admin-dist/public/assets/trash-B3daldm5.js +0 -1
- package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +0 -89
- package/admin-dist/server/_ssr/_tanstack-start-manifest_v-Dd7AmelK.mjs +0 -4
- package/dist/client/admin-config.d.ts.map +0 -1
- package/dist/client/admin-config.js.map +0 -1
- package/dist/client/adminApi.d.ts.map +0 -1
- package/dist/client/adminApi.js +0 -736
- package/dist/client/adminApi.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,189 +1,140 @@
|
|
|
1
1
|
# Convex CMS
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A headless CMS built as a [Convex Component](https://docs.convex.dev/components) — content management that runs inside your Convex app.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Choose Your Path
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **Flexible content modeling** - Define content types with 13 field types
|
|
9
|
-
- **Publishing workflows** - Draft, publish, schedule, and version content
|
|
10
|
-
- **Media management** - Upload, organize, and serve media with variants
|
|
11
|
-
- **Role-based access control** - Fine-grained permissions with custom roles
|
|
12
|
-
- **Multi-locale support** - Content localization with fallback chains
|
|
13
|
-
- **Admin UI** - Ready-to-use React admin interface (CLI or embeddable)
|
|
7
|
+
### Need an Admin Interface?
|
|
14
8
|
|
|
15
|
-
|
|
9
|
+
Use **`defineAdminAPI`** — one line creates all the backend functions for a working admin UI.
|
|
16
10
|
|
|
17
|
-
```
|
|
18
|
-
|
|
11
|
+
```
|
|
12
|
+
Your App Admin UI
|
|
13
|
+
│ │
|
|
14
|
+
└── convex/admin.ts ────────────┘
|
|
15
|
+
defineAdminAPI()
|
|
16
|
+
│
|
|
17
|
+
├── listContentTypes
|
|
18
|
+
├── getEntry
|
|
19
|
+
├── publishEntry
|
|
20
|
+
└── ... (30+ functions)
|
|
21
|
+
│
|
|
22
|
+
▼
|
|
23
|
+
CMS Component
|
|
19
24
|
```
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
→ **[Admin UI Setup Guide](./docs/guides/admin-ui-setup.md)**
|
|
22
27
|
|
|
23
|
-
###
|
|
28
|
+
### Building Custom Content Logic?
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
// convex/convex.config.ts
|
|
27
|
-
import { defineApp } from "convex/server";
|
|
28
|
-
import convexCms from "convex-cms/convex.config";
|
|
30
|
+
Use **`createCmsClient`** — full programmatic control with typed methods in your Convex functions.
|
|
29
31
|
|
|
30
|
-
const app = defineApp();
|
|
31
|
-
app.use(convexCms);
|
|
32
|
-
export default app;
|
|
33
32
|
```
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
Your Convex Functions
|
|
34
|
+
│
|
|
35
|
+
└── cms.contentEntries.list(ctx, { status: "published" })
|
|
36
|
+
cms.contentTypes.create(ctx, { name: "blog", ... })
|
|
37
|
+
cms.mediaAssets.upload(ctx, { ... })
|
|
38
|
+
│
|
|
39
|
+
▼
|
|
40
|
+
CMS Component
|
|
39
41
|
```
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
→ **[Getting Started Guide](./docs/guides/getting-started.md)**
|
|
42
44
|
|
|
43
|
-
###
|
|
45
|
+
### Want Full Type Safety?
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
npx convex dev
|
|
47
|
-
```
|
|
47
|
+
Use **code-first schemas** — define content types with Convex validators, get TypeScript inference.
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
```typescript
|
|
50
|
+
const blogPost = defineContentType({
|
|
51
|
+
name: "blog_post",
|
|
52
|
+
validator: v.object({
|
|
53
|
+
title: v.string(),
|
|
54
|
+
content: v.string(),
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
// TypeScript knows entry.data.title is string
|
|
59
|
+
const entry = await cms.typedContentEntries.get<"blog_post">(ctx, id);
|
|
53
60
|
```
|
|
54
61
|
|
|
55
|
-
|
|
62
|
+
→ **[Code-First Schema Reference](./docs/api/code-first-schema.md)**
|
|
56
63
|
|
|
57
|
-
|
|
64
|
+
### Need Both?
|
|
58
65
|
|
|
59
|
-
|
|
66
|
+
**Most apps use both.** This is the typical setup:
|
|
60
67
|
|
|
61
|
-
|
|
68
|
+
- `defineAdminAPI` powers the admin interface for content editors
|
|
69
|
+
- `createCmsClient` gives you typed methods for custom queries on your frontend
|
|
62
70
|
|
|
63
|
-
|
|
64
|
-
npx convex-cms admin
|
|
65
|
-
npx convex-cms admin --port 4000 # Custom port
|
|
66
|
-
npx convex-cms admin --demo # Demo mode with mock data
|
|
67
|
-
```
|
|
71
|
+
They work together through the same CMS component.
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
For production deployments, embed the admin UI in your application:
|
|
72
|
-
|
|
73
|
-
```tsx
|
|
74
|
-
import { CmsAdmin } from "convex-cms-admin";
|
|
75
|
-
|
|
76
|
-
function AdminPage() {
|
|
77
|
-
return (
|
|
78
|
-
<CmsAdmin
|
|
79
|
-
convexUrl={process.env.VITE_CONVEX_URL}
|
|
80
|
-
auth={{
|
|
81
|
-
getUser: () => yourAuthProvider.getUser(),
|
|
82
|
-
getUserRole: (id) => yourAuthProvider.getRole(id),
|
|
83
|
-
onLogout: () => yourAuthProvider.signOut(),
|
|
84
|
-
}}
|
|
85
|
-
config={{
|
|
86
|
-
branding: {
|
|
87
|
-
appName: "My CMS",
|
|
88
|
-
logoUrl: "/logo.svg",
|
|
89
|
-
},
|
|
90
|
-
}}
|
|
91
|
-
/>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Using the CMS Client
|
|
73
|
+
## Quick Start
|
|
97
74
|
|
|
98
|
-
|
|
75
|
+
### 1. Install
|
|
99
76
|
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
import { createCmsClient } from "convex-cms";
|
|
103
|
-
import { components } from "./_generated/api";
|
|
104
|
-
|
|
105
|
-
export const cms = createCmsClient(components.convexCms, {
|
|
106
|
-
defaultLocale: "en",
|
|
107
|
-
features: {
|
|
108
|
-
versioning: true,
|
|
109
|
-
localization: true,
|
|
110
|
-
},
|
|
111
|
-
});
|
|
77
|
+
```bash
|
|
78
|
+
npm install convex-cms
|
|
112
79
|
```
|
|
113
80
|
|
|
81
|
+
### 2. Add the Component
|
|
82
|
+
|
|
114
83
|
```typescript
|
|
115
|
-
// convex/
|
|
116
|
-
import {
|
|
117
|
-
import
|
|
118
|
-
import { cms } from "./cms";
|
|
119
|
-
|
|
120
|
-
export const getPosts = query({
|
|
121
|
-
handler: async (ctx) => {
|
|
122
|
-
const result = await cms.contentEntries.list(ctx, {
|
|
123
|
-
status: "published",
|
|
124
|
-
});
|
|
125
|
-
return result.items;
|
|
126
|
-
},
|
|
127
|
-
});
|
|
84
|
+
// convex/convex.config.ts
|
|
85
|
+
import { defineApp } from "convex/server";
|
|
86
|
+
import convexCms from "convex-cms/convex.config";
|
|
128
87
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return await cms.contentEntries.create(ctx, {
|
|
133
|
-
contentTypeId: blogTypeId,
|
|
134
|
-
data: args,
|
|
135
|
-
});
|
|
136
|
-
},
|
|
137
|
-
});
|
|
88
|
+
const app = defineApp();
|
|
89
|
+
app.use(convexCms);
|
|
90
|
+
export default app;
|
|
138
91
|
```
|
|
139
92
|
|
|
140
|
-
|
|
93
|
+
### 3. Choose Your Setup
|
|
141
94
|
|
|
142
|
-
|
|
95
|
+
**For Admin UI:** Run `npx convex-cms init` then `npx convex-cms admin`
|
|
96
|
+
→ [Full Admin UI Setup](./docs/guides/admin-ui-setup.md)
|
|
143
97
|
|
|
144
|
-
|
|
98
|
+
**For Custom Functions:** Create a CMS client and use it in your functions
|
|
99
|
+
→ [Full Getting Started Guide](./docs/guides/getting-started.md)
|
|
145
100
|
|
|
146
|
-
|
|
147
|
-
<CmsAdmin
|
|
148
|
-
auth={{
|
|
149
|
-
// Return current user for display
|
|
150
|
-
getUser: () => ({
|
|
151
|
-
id: currentUser.id,
|
|
152
|
-
name: currentUser.name,
|
|
153
|
-
email: currentUser.email,
|
|
154
|
-
}),
|
|
155
|
-
// Map user ID to CMS role
|
|
156
|
-
getUserRole: (userId) => userRoles[userId] ?? null,
|
|
157
|
-
// Handle logout
|
|
158
|
-
onLogout: () => signOut(),
|
|
159
|
-
}}
|
|
160
|
-
/>
|
|
161
|
-
```
|
|
101
|
+
## What's Included
|
|
162
102
|
|
|
163
|
-
|
|
103
|
+
- **13 field types** — text, richText, media, reference, select, and more
|
|
104
|
+
- **Publishing workflows** — draft → scheduled → published with version history
|
|
105
|
+
- **Media management** — uploads, folders, variants, and metadata
|
|
106
|
+
- **RBAC** — 4 built-in roles + custom roles with fine-grained permissions
|
|
107
|
+
- **Multi-locale** — content localization with fallback chains
|
|
108
|
+
- **Admin UI** — pre-built React interface (CLI or embeddable)
|
|
109
|
+
- **Agent tools** — Zod schemas for AI integration
|
|
164
110
|
|
|
165
|
-
|
|
111
|
+
## Admin UI Modes
|
|
166
112
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// Optionally check operation.type for fine-grained access
|
|
175
|
-
return identity.subject;
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
```
|
|
113
|
+
| Mode | Command | Best For |
|
|
114
|
+
|------|---------|----------|
|
|
115
|
+
| **CLI** | `npx convex-cms admin` | Development |
|
|
116
|
+
| **Embed** | `<CmsAdmin api={api.admin} />` | Production |
|
|
117
|
+
|
|
118
|
+
Both modes call the same functions from your `convex/admin.ts`.
|
|
179
119
|
|
|
180
120
|
## Documentation
|
|
181
121
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
122
|
+
| Guide | Description |
|
|
123
|
+
|-------|-------------|
|
|
124
|
+
| [Admin UI Setup](./docs/guides/admin-ui-setup.md) | CLI and embed modes, auth integration |
|
|
125
|
+
| [Getting Started](./docs/guides/getting-started.md) | Programmatic usage with createCmsClient |
|
|
126
|
+
| [Integration Patterns](./docs/guides/integration-patterns.md) | Common setups and when to use each |
|
|
127
|
+
| [Content Modeling](./docs/guides/content-modeling.md) | Content types and field definitions |
|
|
128
|
+
| [Authorization](./docs/guides/authorization.md) | Roles, permissions, and custom auth |
|
|
129
|
+
| [Media Management](./docs/guides/media.md) | Uploads, folders, and variants |
|
|
130
|
+
|
|
131
|
+
| Reference | Description |
|
|
132
|
+
|-----------|-------------|
|
|
133
|
+
| [Client API](./docs/api/client-api.md) | createCmsClient methods |
|
|
134
|
+
| [Admin API](./docs/api/admin-api.md) | defineAdminAPI functions |
|
|
135
|
+
| [Code-First Schema](./docs/api/code-first-schema.md) | TypeScript-first content types |
|
|
136
|
+
| [Field Types](./docs/api/field-types.md) | All 13 field types |
|
|
137
|
+
| [Configuration](./docs/api/configuration.md) | All config options |
|
|
187
138
|
|
|
188
139
|
## Requirements
|
|
189
140
|
|
package/admin/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Convex CMS Admin UI
|
|
2
|
+
|
|
3
|
+
The Admin UI for Convex CMS, built with TanStack Start and React.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
### Prerequisites
|
|
8
|
+
|
|
9
|
+
- Node.js 18+
|
|
10
|
+
- A Convex account and project
|
|
11
|
+
|
|
12
|
+
### Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cd admin
|
|
16
|
+
npm install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Development
|
|
20
|
+
|
|
21
|
+
1. Start the Convex development server:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx convex dev
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This will generate the Convex API types and provide a `VITE_CONVEX_URL`.
|
|
28
|
+
|
|
29
|
+
2. Create a `.env` file with your Convex URL:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
cp .env.example .env
|
|
33
|
+
# Edit .env with your VITE_CONVEX_URL from step 1
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
3. Start the development server:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm run dev:vite
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or run both Convex and Vite together:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm run dev
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
4. Open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
49
|
+
|
|
50
|
+
## Project Structure
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
admin/
|
|
54
|
+
├── convex/ # Convex configuration
|
|
55
|
+
│ └── convex.config.ts # CMS component integration
|
|
56
|
+
├── src/
|
|
57
|
+
│ ├── routes/ # TanStack Router routes
|
|
58
|
+
│ │ ├── __root.tsx # Root layout with Convex provider
|
|
59
|
+
│ │ ├── index.tsx # Dashboard page
|
|
60
|
+
│ │ ├── content.tsx # Content entries list
|
|
61
|
+
│ │ ├── media.tsx # Media library
|
|
62
|
+
│ │ ├── content-types.tsx # Content type management
|
|
63
|
+
│ │ └── settings.tsx # CMS settings
|
|
64
|
+
│ ├── components/ # Reusable React components
|
|
65
|
+
│ ├── lib/ # Utilities and helpers
|
|
66
|
+
│ │ └── convex.ts # Convex client utilities
|
|
67
|
+
│ ├── styles/ # CSS styles
|
|
68
|
+
│ │ └── app.css # Main stylesheet
|
|
69
|
+
│ └── router.tsx # Router configuration
|
|
70
|
+
├── public/ # Static assets
|
|
71
|
+
├── vite.config.ts # Vite + TanStack Start configuration
|
|
72
|
+
├── tsconfig.json # TypeScript configuration
|
|
73
|
+
└── package.json # Dependencies and scripts
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Available Scripts
|
|
77
|
+
|
|
78
|
+
- `npm run dev` - Start Convex and Vite development servers
|
|
79
|
+
- `npm run dev:vite` - Start only the Vite development server
|
|
80
|
+
- `npm run dev:convex` - Start only the Convex development server
|
|
81
|
+
- `npm run build` - Build for production
|
|
82
|
+
- `npm run preview` - Preview production build
|
|
83
|
+
- `npm run typecheck` - Run TypeScript type checking
|
|
84
|
+
|
|
85
|
+
## Features
|
|
86
|
+
|
|
87
|
+
- **Dashboard** - Overview of CMS status and quick navigation
|
|
88
|
+
- **Content Management** - Browse and manage content entries
|
|
89
|
+
- **Media Library** - Upload and organize media assets
|
|
90
|
+
- **Content Types** - Define content schemas with custom fields
|
|
91
|
+
- **Settings** - Configure CMS features and preferences
|
|
92
|
+
|
|
93
|
+
## Technology Stack
|
|
94
|
+
|
|
95
|
+
- [TanStack Start](https://tanstack.com/start) - Full-stack React framework
|
|
96
|
+
- [TanStack Router](https://tanstack.com/router) - Type-safe routing
|
|
97
|
+
- [Convex](https://convex.dev) - Real-time backend platform
|
|
98
|
+
- [React 19](https://react.dev) - UI library
|
|
99
|
+
- [Vite 7](https://vitejs.dev) - Build tool
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { Sidebar } from "./Sidebar";
|
|
3
|
+
import { Header } from "./Header";
|
|
4
|
+
import { useAdminConfig } from "~/contexts";
|
|
5
|
+
|
|
6
|
+
interface AdminLayoutProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function AdminLayout({ children }: AdminLayoutProps) {
|
|
11
|
+
const { layout } = useAdminConfig();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className="flex min-h-screen bg-background">
|
|
15
|
+
<Sidebar />
|
|
16
|
+
<div className="flex flex-1 flex-col" style={{ marginLeft: layout.sidebarWidth }}>
|
|
17
|
+
<Header />
|
|
18
|
+
<main className="flex-1 overflow-auto p-6">{children}</main>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { AlertTriangle } from 'lucide-react'
|
|
2
|
+
import { CmsDialog } from '~/components/cmsds/CmsDialog'
|
|
3
|
+
import { CmsButton } from '~/components/cmsds/CmsButton'
|
|
4
|
+
|
|
5
|
+
interface BreakingChangesWarningDialogProps {
|
|
6
|
+
isOpen: boolean
|
|
7
|
+
onClose: () => void
|
|
8
|
+
breakingChanges: string[]
|
|
9
|
+
onForceUpdate: () => void
|
|
10
|
+
onCancel: () => void
|
|
11
|
+
isLoading: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function BreakingChangesWarningDialog({
|
|
15
|
+
isOpen,
|
|
16
|
+
onClose,
|
|
17
|
+
breakingChanges,
|
|
18
|
+
onForceUpdate,
|
|
19
|
+
onCancel,
|
|
20
|
+
isLoading,
|
|
21
|
+
}: BreakingChangesWarningDialogProps) {
|
|
22
|
+
const handleCancel = () => {
|
|
23
|
+
onCancel()
|
|
24
|
+
onClose()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<CmsDialog
|
|
29
|
+
open={isOpen}
|
|
30
|
+
onOpenChange={(open) => !open && !isLoading && handleCancel()}
|
|
31
|
+
title="Breaking Changes Detected"
|
|
32
|
+
size="lg"
|
|
33
|
+
footer={
|
|
34
|
+
<>
|
|
35
|
+
<CmsButton variant="outline" onClick={handleCancel} disabled={isLoading}>
|
|
36
|
+
Cancel
|
|
37
|
+
</CmsButton>
|
|
38
|
+
<CmsButton variant="danger" onClick={onForceUpdate} loading={isLoading}>
|
|
39
|
+
Force Update
|
|
40
|
+
</CmsButton>
|
|
41
|
+
</>
|
|
42
|
+
}
|
|
43
|
+
>
|
|
44
|
+
<div className="space-y-4">
|
|
45
|
+
<div className="flex items-start gap-3 rounded-lg border border-amber-200 bg-amber-50 p-3">
|
|
46
|
+
<AlertTriangle className="mt-0.5 size-5 shrink-0 text-amber-600" />
|
|
47
|
+
<div className="space-y-1">
|
|
48
|
+
<p className="text-sm font-medium text-amber-800">
|
|
49
|
+
These changes may affect existing content
|
|
50
|
+
</p>
|
|
51
|
+
<p className="text-sm text-amber-700">
|
|
52
|
+
The following changes could cause data loss or validation errors for existing entries.
|
|
53
|
+
Review carefully before proceeding.
|
|
54
|
+
</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div className="space-y-2">
|
|
59
|
+
<p className="text-sm font-medium text-foreground">
|
|
60
|
+
{breakingChanges.length} breaking change{breakingChanges.length !== 1 ? 's' : ''} detected:
|
|
61
|
+
</p>
|
|
62
|
+
<ul className="space-y-2">
|
|
63
|
+
{breakingChanges.map((change, index) => (
|
|
64
|
+
<li
|
|
65
|
+
key={index}
|
|
66
|
+
className="flex items-start gap-2 rounded-md border bg-muted/30 px-3 py-2 text-sm"
|
|
67
|
+
>
|
|
68
|
+
<span className="mt-0.5 size-1.5 shrink-0 rounded-full bg-amber-500" />
|
|
69
|
+
<span className="text-muted-foreground">{change}</span>
|
|
70
|
+
</li>
|
|
71
|
+
))}
|
|
72
|
+
</ul>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<p className="text-xs text-muted-foreground">
|
|
76
|
+
Click "Force Update" to apply these changes anyway, or "Cancel" to go back and modify your changes.
|
|
77
|
+
</p>
|
|
78
|
+
</div>
|
|
79
|
+
</CmsDialog>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react'
|
|
2
|
+
import { useMutation } from 'convex/react'
|
|
3
|
+
import { api } from '../../convex/_generated/api'
|
|
4
|
+
import { BulkOperationModal } from './BulkOperationModal'
|
|
5
|
+
import { CmsButton } from '~/components/cmsds/CmsButton'
|
|
6
|
+
import { Badge } from '~/components/ui/badge'
|
|
7
|
+
import { X } from 'lucide-react'
|
|
8
|
+
|
|
9
|
+
type BulkAction = 'publish' | 'unpublish' | 'delete' | 'archive'
|
|
10
|
+
|
|
11
|
+
interface BulkActionBarProps {
|
|
12
|
+
selectedIds: string[]
|
|
13
|
+
onClearSelection: () => void
|
|
14
|
+
onOperationComplete?: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function BulkActionBar({
|
|
18
|
+
selectedIds,
|
|
19
|
+
onClearSelection,
|
|
20
|
+
onOperationComplete,
|
|
21
|
+
}: BulkActionBarProps) {
|
|
22
|
+
const [activeAction, setActiveAction] = useState<BulkAction | null>(null)
|
|
23
|
+
const [isProcessing, setIsProcessing] = useState(false)
|
|
24
|
+
const [result, setResult] = useState<{
|
|
25
|
+
succeeded: number
|
|
26
|
+
failed: number
|
|
27
|
+
errors?: string[]
|
|
28
|
+
} | null>(null)
|
|
29
|
+
|
|
30
|
+
const bulkPublish = useMutation(api.bulkOperations.bulkPublish)
|
|
31
|
+
const bulkUnpublish = useMutation(api.bulkOperations.bulkUnpublish)
|
|
32
|
+
const bulkDelete = useMutation(api.bulkOperations.bulkDelete)
|
|
33
|
+
const bulkUpdate = useMutation(api.bulkOperations.bulkUpdate)
|
|
34
|
+
|
|
35
|
+
const handleAction = useCallback((action: BulkAction) => {
|
|
36
|
+
setActiveAction(action)
|
|
37
|
+
setResult(null)
|
|
38
|
+
}, [])
|
|
39
|
+
|
|
40
|
+
const handleConfirm = useCallback(async () => {
|
|
41
|
+
if (!activeAction || selectedIds.length === 0) return
|
|
42
|
+
|
|
43
|
+
setIsProcessing(true)
|
|
44
|
+
setResult(null)
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
let response: {
|
|
48
|
+
succeeded: number
|
|
49
|
+
failed: number
|
|
50
|
+
errors?: { id: string; error: string }[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
switch (activeAction) {
|
|
54
|
+
case 'publish':
|
|
55
|
+
response = await bulkPublish({
|
|
56
|
+
ids: selectedIds,
|
|
57
|
+
changeDescription: 'Bulk published from admin',
|
|
58
|
+
})
|
|
59
|
+
break
|
|
60
|
+
case 'unpublish':
|
|
61
|
+
response = await bulkUnpublish({
|
|
62
|
+
ids: selectedIds,
|
|
63
|
+
})
|
|
64
|
+
break
|
|
65
|
+
case 'delete':
|
|
66
|
+
response = await bulkDelete({
|
|
67
|
+
ids: selectedIds,
|
|
68
|
+
hardDelete: false,
|
|
69
|
+
})
|
|
70
|
+
break
|
|
71
|
+
case 'archive':
|
|
72
|
+
response = await bulkUpdate({
|
|
73
|
+
ids: selectedIds,
|
|
74
|
+
status: 'archived',
|
|
75
|
+
})
|
|
76
|
+
break
|
|
77
|
+
default:
|
|
78
|
+
throw new Error(`Unknown action: ${activeAction}`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setResult({
|
|
82
|
+
succeeded: response.succeeded,
|
|
83
|
+
failed: response.failed,
|
|
84
|
+
errors: response.errors?.map((e) => `${e.id}: ${e.error}`),
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if (response.failed === 0) {
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
setActiveAction(null)
|
|
90
|
+
onClearSelection()
|
|
91
|
+
onOperationComplete?.()
|
|
92
|
+
}, 1500)
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
const message = error instanceof Error ? error.message : 'Operation failed'
|
|
96
|
+
setResult({
|
|
97
|
+
succeeded: 0,
|
|
98
|
+
failed: selectedIds.length,
|
|
99
|
+
errors: [message],
|
|
100
|
+
})
|
|
101
|
+
} finally {
|
|
102
|
+
setIsProcessing(false)
|
|
103
|
+
}
|
|
104
|
+
}, [
|
|
105
|
+
activeAction,
|
|
106
|
+
selectedIds,
|
|
107
|
+
bulkPublish,
|
|
108
|
+
bulkUnpublish,
|
|
109
|
+
bulkDelete,
|
|
110
|
+
bulkUpdate,
|
|
111
|
+
onClearSelection,
|
|
112
|
+
onOperationComplete,
|
|
113
|
+
])
|
|
114
|
+
|
|
115
|
+
const handleCancel = useCallback(() => {
|
|
116
|
+
setActiveAction(null)
|
|
117
|
+
setResult(null)
|
|
118
|
+
}, [])
|
|
119
|
+
|
|
120
|
+
if (selectedIds.length === 0) {
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<>
|
|
126
|
+
<div className="fixed inset-x-0 bottom-0 z-50 border-t bg-background/95 px-6 py-3 shadow-lg backdrop-blur supports-[backdrop-filter]:bg-background/80">
|
|
127
|
+
<div className="mx-auto flex max-w-7xl items-center justify-between">
|
|
128
|
+
<div className="flex items-center gap-3">
|
|
129
|
+
<Badge variant="secondary" className="text-sm font-semibold">
|
|
130
|
+
{selectedIds.length}
|
|
131
|
+
</Badge>
|
|
132
|
+
<span className="text-sm text-muted-foreground">
|
|
133
|
+
{selectedIds.length === 1 ? 'item' : 'items'} selected
|
|
134
|
+
</span>
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
137
|
+
onClick={onClearSelection}
|
|
138
|
+
className="flex items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-foreground"
|
|
139
|
+
>
|
|
140
|
+
<X className="size-3" />
|
|
141
|
+
Clear
|
|
142
|
+
</button>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div className="flex items-center gap-2">
|
|
146
|
+
<CmsButton
|
|
147
|
+
variant="success"
|
|
148
|
+
size="sm"
|
|
149
|
+
onClick={() => handleAction('publish')}
|
|
150
|
+
>
|
|
151
|
+
Publish
|
|
152
|
+
</CmsButton>
|
|
153
|
+
<CmsButton
|
|
154
|
+
variant="warning"
|
|
155
|
+
size="sm"
|
|
156
|
+
onClick={() => handleAction('unpublish')}
|
|
157
|
+
>
|
|
158
|
+
Unpublish
|
|
159
|
+
</CmsButton>
|
|
160
|
+
<CmsButton
|
|
161
|
+
variant="secondary"
|
|
162
|
+
size="sm"
|
|
163
|
+
onClick={() => handleAction('archive')}
|
|
164
|
+
>
|
|
165
|
+
Archive
|
|
166
|
+
</CmsButton>
|
|
167
|
+
<CmsButton
|
|
168
|
+
variant="danger"
|
|
169
|
+
size="sm"
|
|
170
|
+
onClick={() => handleAction('delete')}
|
|
171
|
+
>
|
|
172
|
+
Delete
|
|
173
|
+
</CmsButton>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
{activeAction && (
|
|
179
|
+
<BulkOperationModal
|
|
180
|
+
action={activeAction}
|
|
181
|
+
count={selectedIds.length}
|
|
182
|
+
isProcessing={isProcessing}
|
|
183
|
+
result={result}
|
|
184
|
+
onConfirm={handleConfirm}
|
|
185
|
+
onCancel={handleCancel}
|
|
186
|
+
/>
|
|
187
|
+
)}
|
|
188
|
+
</>
|
|
189
|
+
)
|
|
190
|
+
}
|