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.
- 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 +183 -190
- 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
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for field renderer components.
|
|
3
|
+
*
|
|
4
|
+
* These types define the props interface for all field renderers,
|
|
5
|
+
* ensuring consistent behavior across different field types.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Built-in field types supported by the CMS.
|
|
10
|
+
*/
|
|
11
|
+
export type BuiltInFieldType =
|
|
12
|
+
| 'text'
|
|
13
|
+
| 'richText'
|
|
14
|
+
| 'number'
|
|
15
|
+
| 'boolean'
|
|
16
|
+
| 'date'
|
|
17
|
+
| 'datetime'
|
|
18
|
+
| 'reference'
|
|
19
|
+
| 'media'
|
|
20
|
+
| 'json'
|
|
21
|
+
| 'select'
|
|
22
|
+
| 'multiSelect'
|
|
23
|
+
| 'tags'
|
|
24
|
+
| 'category';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Field types supported by the CMS.
|
|
28
|
+
* Includes built-in types and allows custom types via string.
|
|
29
|
+
*/
|
|
30
|
+
export type FieldType = BuiltInFieldType | (string & {});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Select option for select/multiSelect fields.
|
|
34
|
+
*/
|
|
35
|
+
export interface SelectOption {
|
|
36
|
+
value: string;
|
|
37
|
+
label: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Field-specific options that control validation and behavior.
|
|
42
|
+
* These are passed from the content type field definition.
|
|
43
|
+
*/
|
|
44
|
+
export interface FieldOptions {
|
|
45
|
+
// Text fields
|
|
46
|
+
minLength?: number;
|
|
47
|
+
maxLength?: number;
|
|
48
|
+
pattern?: string;
|
|
49
|
+
|
|
50
|
+
// Number fields
|
|
51
|
+
min?: number;
|
|
52
|
+
max?: number;
|
|
53
|
+
step?: number;
|
|
54
|
+
/** Number of decimal places. 0 for integer. */
|
|
55
|
+
precision?: number;
|
|
56
|
+
|
|
57
|
+
// Reference fields
|
|
58
|
+
allowedContentTypes?: string[];
|
|
59
|
+
multiple?: boolean;
|
|
60
|
+
minItems?: number;
|
|
61
|
+
|
|
62
|
+
// Media fields
|
|
63
|
+
allowedMimeTypes?: string[];
|
|
64
|
+
maxFileSize?: number;
|
|
65
|
+
|
|
66
|
+
// Select fields
|
|
67
|
+
options?: SelectOption[];
|
|
68
|
+
|
|
69
|
+
// Rich text fields
|
|
70
|
+
allowedBlocks?: string[];
|
|
71
|
+
allowedMarks?: string[];
|
|
72
|
+
|
|
73
|
+
// Tag fields
|
|
74
|
+
taxonomyId?: string;
|
|
75
|
+
allowCreate?: boolean;
|
|
76
|
+
maxTags?: number;
|
|
77
|
+
minTags?: number;
|
|
78
|
+
|
|
79
|
+
// Category fields
|
|
80
|
+
allowMultiple?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Field definition as stored in content type.
|
|
85
|
+
*/
|
|
86
|
+
export interface FieldDefinition {
|
|
87
|
+
/** Unique field identifier within the content type */
|
|
88
|
+
name: string;
|
|
89
|
+
/** Human-readable label */
|
|
90
|
+
label: string;
|
|
91
|
+
/** Field type */
|
|
92
|
+
type: FieldType;
|
|
93
|
+
/** Whether the field is required */
|
|
94
|
+
required: boolean;
|
|
95
|
+
/** Whether the field is searchable */
|
|
96
|
+
searchable?: boolean;
|
|
97
|
+
/** Whether the field supports localization */
|
|
98
|
+
localized?: boolean;
|
|
99
|
+
/** Help text displayed below the field */
|
|
100
|
+
description?: string;
|
|
101
|
+
/** Default value for new entries */
|
|
102
|
+
defaultValue?: unknown;
|
|
103
|
+
/** Type-specific options */
|
|
104
|
+
options?: FieldOptions;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Validation error for a field.
|
|
109
|
+
*/
|
|
110
|
+
export interface FieldError {
|
|
111
|
+
/** Error message to display */
|
|
112
|
+
message: string;
|
|
113
|
+
/** Optional error code for programmatic handling */
|
|
114
|
+
code?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Base props shared by all field renderer components.
|
|
119
|
+
*/
|
|
120
|
+
export interface BaseFieldProps<T = unknown> {
|
|
121
|
+
/** The field definition from the content type */
|
|
122
|
+
field: FieldDefinition;
|
|
123
|
+
/** Current value of the field */
|
|
124
|
+
value: T;
|
|
125
|
+
/** Callback when the value changes */
|
|
126
|
+
onChange: (value: T) => void;
|
|
127
|
+
/** Validation error to display */
|
|
128
|
+
error?: FieldError;
|
|
129
|
+
/** Whether the field is disabled */
|
|
130
|
+
disabled?: boolean;
|
|
131
|
+
/** Whether the field is in read-only mode */
|
|
132
|
+
readOnly?: boolean;
|
|
133
|
+
/** Optional CSS class name */
|
|
134
|
+
className?: string;
|
|
135
|
+
/** Unique ID for the field (for label association) */
|
|
136
|
+
id?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Props for text input field.
|
|
141
|
+
*/
|
|
142
|
+
export interface TextFieldProps extends BaseFieldProps<string> {
|
|
143
|
+
/** Placeholder text */
|
|
144
|
+
placeholder?: string;
|
|
145
|
+
/** Input type (text, email, url, etc.) */
|
|
146
|
+
inputType?: 'text' | 'email' | 'url' | 'tel';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Props for textarea field.
|
|
151
|
+
*/
|
|
152
|
+
export interface TextAreaFieldProps extends BaseFieldProps<string> {
|
|
153
|
+
/** Placeholder text */
|
|
154
|
+
placeholder?: string;
|
|
155
|
+
/** Number of visible rows */
|
|
156
|
+
rows?: number;
|
|
157
|
+
/** Whether to auto-resize based on content */
|
|
158
|
+
autoResize?: boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Props for number input field.
|
|
163
|
+
*/
|
|
164
|
+
export interface NumberFieldProps extends BaseFieldProps<number | null> {
|
|
165
|
+
/** Placeholder text */
|
|
166
|
+
placeholder?: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Props for boolean toggle field.
|
|
171
|
+
*/
|
|
172
|
+
export interface BooleanFieldProps extends BaseFieldProps<boolean> {
|
|
173
|
+
/** Label for the true state */
|
|
174
|
+
trueLabel?: string;
|
|
175
|
+
/** Label for the false state */
|
|
176
|
+
falseLabel?: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Props for date picker field.
|
|
181
|
+
*/
|
|
182
|
+
export interface DateFieldProps extends BaseFieldProps<string | null> {
|
|
183
|
+
/** Whether to include time picker (datetime) */
|
|
184
|
+
includeTime?: boolean;
|
|
185
|
+
/** Placeholder text */
|
|
186
|
+
placeholder?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Props for rich text (markdown) field.
|
|
191
|
+
*/
|
|
192
|
+
export interface RichTextFieldProps extends BaseFieldProps<string> {
|
|
193
|
+
/** Placeholder text */
|
|
194
|
+
placeholder?: string;
|
|
195
|
+
/** Minimum height for the editor */
|
|
196
|
+
minHeight?: number;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Props for the unified FieldRenderer component.
|
|
201
|
+
*/
|
|
202
|
+
export interface FieldRendererProps {
|
|
203
|
+
/** The field definition from the content type */
|
|
204
|
+
field: FieldDefinition;
|
|
205
|
+
/** Current value of the field */
|
|
206
|
+
value: unknown;
|
|
207
|
+
/** Callback when the value changes */
|
|
208
|
+
onChange: (value: unknown) => void;
|
|
209
|
+
/** Validation error to display */
|
|
210
|
+
error?: FieldError;
|
|
211
|
+
/** Whether the field is disabled */
|
|
212
|
+
disabled?: boolean;
|
|
213
|
+
/** Whether the field is in read-only mode */
|
|
214
|
+
readOnly?: boolean;
|
|
215
|
+
/** Optional CSS class name */
|
|
216
|
+
className?: string;
|
|
217
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { useState, useCallback, useMemo } from 'react'
|
|
2
|
+
import { useQuery } from 'convex/react'
|
|
3
|
+
import { api } from '../../../convex/_generated/api'
|
|
4
|
+
import { CmsButton } from '~/components/cmsds/CmsButton'
|
|
5
|
+
import {
|
|
6
|
+
Popover,
|
|
7
|
+
PopoverContent,
|
|
8
|
+
PopoverTrigger,
|
|
9
|
+
} from '~/components/ui/popover'
|
|
10
|
+
import {
|
|
11
|
+
Command,
|
|
12
|
+
CommandEmpty,
|
|
13
|
+
CommandGroup,
|
|
14
|
+
CommandInput,
|
|
15
|
+
CommandItem,
|
|
16
|
+
CommandList,
|
|
17
|
+
CommandSeparator,
|
|
18
|
+
} from '~/components/ui/command'
|
|
19
|
+
import { Badge } from '~/components/ui/badge'
|
|
20
|
+
import { Checkbox } from '~/components/ui/checkbox'
|
|
21
|
+
import { cn } from '~/lib/cn'
|
|
22
|
+
import { Tags, ChevronDown, X, Check } from 'lucide-react'
|
|
23
|
+
|
|
24
|
+
interface TaxonomyTerm {
|
|
25
|
+
_id: string
|
|
26
|
+
name: string
|
|
27
|
+
slug: string
|
|
28
|
+
color?: string
|
|
29
|
+
usageCount: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface TaxonomyFilterProps {
|
|
33
|
+
selectedTermIds: string[]
|
|
34
|
+
onChange: (termIds: string[]) => void
|
|
35
|
+
taxonomyId?: string
|
|
36
|
+
placeholder?: string
|
|
37
|
+
disabled?: boolean
|
|
38
|
+
className?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function TaxonomyFilter({
|
|
42
|
+
selectedTermIds,
|
|
43
|
+
onChange,
|
|
44
|
+
taxonomyId,
|
|
45
|
+
placeholder = 'Filter by tags...',
|
|
46
|
+
disabled = false,
|
|
47
|
+
className,
|
|
48
|
+
}: TaxonomyFilterProps) {
|
|
49
|
+
const [open, setOpen] = useState(false)
|
|
50
|
+
const [search, setSearch] = useState('')
|
|
51
|
+
|
|
52
|
+
const taxonomiesResult = useQuery(api.taxonomies.list, { isActive: true })
|
|
53
|
+
const taxonomies = taxonomiesResult?.page ?? []
|
|
54
|
+
|
|
55
|
+
const activeTaxonomy = useMemo(() => {
|
|
56
|
+
if (taxonomyId) {
|
|
57
|
+
return taxonomies.find((t) => t._id === taxonomyId)
|
|
58
|
+
}
|
|
59
|
+
return null
|
|
60
|
+
}, [taxonomyId, taxonomies])
|
|
61
|
+
|
|
62
|
+
const targetTaxonomyId = taxonomyId ?? taxonomies[0]?._id
|
|
63
|
+
|
|
64
|
+
const termsResult = useQuery(
|
|
65
|
+
api.taxonomies.listTerms,
|
|
66
|
+
targetTaxonomyId
|
|
67
|
+
? {
|
|
68
|
+
taxonomyId: targetTaxonomyId,
|
|
69
|
+
paginationOpts: { numItems: 200, cursor: null },
|
|
70
|
+
}
|
|
71
|
+
: 'skip'
|
|
72
|
+
)
|
|
73
|
+
const terms = (termsResult?.page ?? []) as TaxonomyTerm[]
|
|
74
|
+
|
|
75
|
+
const selectedTermsDetails = useMemo(() => {
|
|
76
|
+
return terms.filter((t) => selectedTermIds.includes(t._id))
|
|
77
|
+
}, [terms, selectedTermIds])
|
|
78
|
+
|
|
79
|
+
const filteredTerms = useMemo(() => {
|
|
80
|
+
if (!search.trim()) return terms
|
|
81
|
+
const lowerSearch = search.toLowerCase()
|
|
82
|
+
return terms.filter((term) => term.name.toLowerCase().includes(lowerSearch))
|
|
83
|
+
}, [terms, search])
|
|
84
|
+
|
|
85
|
+
const handleToggleTerm = useCallback(
|
|
86
|
+
(termId: string) => {
|
|
87
|
+
if (selectedTermIds.includes(termId)) {
|
|
88
|
+
onChange(selectedTermIds.filter((id) => id !== termId))
|
|
89
|
+
} else {
|
|
90
|
+
onChange([...selectedTermIds, termId])
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
[selectedTermIds, onChange]
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const handleClear = useCallback(() => {
|
|
97
|
+
onChange([])
|
|
98
|
+
setOpen(false)
|
|
99
|
+
}, [onChange])
|
|
100
|
+
|
|
101
|
+
const handleRemoveTerm = useCallback(
|
|
102
|
+
(termId: string, e: React.MouseEvent) => {
|
|
103
|
+
e.stopPropagation()
|
|
104
|
+
onChange(selectedTermIds.filter((id) => id !== termId))
|
|
105
|
+
},
|
|
106
|
+
[selectedTermIds, onChange]
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
const displayTaxonomyName = activeTaxonomy?.displayName ?? taxonomies[0]?.displayName ?? 'Tags'
|
|
110
|
+
const isLoading = taxonomiesResult === undefined || (!!targetTaxonomyId && termsResult === undefined)
|
|
111
|
+
const hasNoTaxonomies = taxonomies.length === 0 && taxonomiesResult !== undefined
|
|
112
|
+
|
|
113
|
+
if (hasNoTaxonomies) {
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
119
|
+
<PopoverTrigger asChild>
|
|
120
|
+
<CmsButton
|
|
121
|
+
variant="outline"
|
|
122
|
+
role="combobox"
|
|
123
|
+
aria-expanded={open}
|
|
124
|
+
disabled={disabled || isLoading}
|
|
125
|
+
className={cn(
|
|
126
|
+
'h-9 min-w-[140px] justify-between gap-2',
|
|
127
|
+
selectedTermIds.length > 0 && 'border-primary/50',
|
|
128
|
+
className
|
|
129
|
+
)}
|
|
130
|
+
>
|
|
131
|
+
<div className="flex items-center gap-2">
|
|
132
|
+
<Tags className="size-4 text-muted-foreground" />
|
|
133
|
+
{selectedTermIds.length === 0 ? (
|
|
134
|
+
<span className="text-muted-foreground">{placeholder}</span>
|
|
135
|
+
) : selectedTermIds.length === 1 ? (
|
|
136
|
+
<span className="max-w-[100px] truncate">
|
|
137
|
+
{selectedTermsDetails[0]?.name ?? 'Tag'}
|
|
138
|
+
</span>
|
|
139
|
+
) : (
|
|
140
|
+
<span>{selectedTermIds.length} tags</span>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
<div className="flex items-center gap-1">
|
|
144
|
+
{selectedTermIds.length > 0 && (
|
|
145
|
+
<Badge
|
|
146
|
+
variant="secondary"
|
|
147
|
+
className="size-5 rounded-full p-0 text-xs"
|
|
148
|
+
>
|
|
149
|
+
{selectedTermIds.length}
|
|
150
|
+
</Badge>
|
|
151
|
+
)}
|
|
152
|
+
<ChevronDown className="size-4 text-muted-foreground" />
|
|
153
|
+
</div>
|
|
154
|
+
</CmsButton>
|
|
155
|
+
</PopoverTrigger>
|
|
156
|
+
|
|
157
|
+
<PopoverContent className="w-[280px] p-0" align="start">
|
|
158
|
+
<Command shouldFilter={false}>
|
|
159
|
+
<CommandInput
|
|
160
|
+
placeholder={`Search ${displayTaxonomyName.toLowerCase()}...`}
|
|
161
|
+
value={search}
|
|
162
|
+
onValueChange={setSearch}
|
|
163
|
+
/>
|
|
164
|
+
<CommandList>
|
|
165
|
+
{filteredTerms.length === 0 ? (
|
|
166
|
+
<CommandEmpty>
|
|
167
|
+
{terms.length === 0 ? `No ${displayTaxonomyName.toLowerCase()} found` : 'No matches'}
|
|
168
|
+
</CommandEmpty>
|
|
169
|
+
) : (
|
|
170
|
+
<CommandGroup heading={displayTaxonomyName}>
|
|
171
|
+
{filteredTerms.map((term) => (
|
|
172
|
+
<TermItem
|
|
173
|
+
key={term._id}
|
|
174
|
+
term={term}
|
|
175
|
+
isSelected={selectedTermIds.includes(term._id)}
|
|
176
|
+
onToggle={() => handleToggleTerm(term._id)}
|
|
177
|
+
/>
|
|
178
|
+
))}
|
|
179
|
+
</CommandGroup>
|
|
180
|
+
)}
|
|
181
|
+
</CommandList>
|
|
182
|
+
|
|
183
|
+
{selectedTermIds.length > 0 && (
|
|
184
|
+
<>
|
|
185
|
+
<CommandSeparator />
|
|
186
|
+
<div className="p-2">
|
|
187
|
+
<div className="mb-2 flex flex-wrap gap-1">
|
|
188
|
+
{selectedTermsDetails.map((term) => (
|
|
189
|
+
<Badge
|
|
190
|
+
key={term._id}
|
|
191
|
+
variant="secondary"
|
|
192
|
+
className="gap-1 pr-1"
|
|
193
|
+
style={
|
|
194
|
+
term.color
|
|
195
|
+
? { backgroundColor: term.color, color: '#fff' }
|
|
196
|
+
: undefined
|
|
197
|
+
}
|
|
198
|
+
>
|
|
199
|
+
{term.name}
|
|
200
|
+
<button
|
|
201
|
+
type="button"
|
|
202
|
+
className="ml-0.5 rounded-full p-0.5 hover:bg-black/10"
|
|
203
|
+
onClick={(e) => handleRemoveTerm(term._id, e)}
|
|
204
|
+
aria-label={`Remove ${term.name}`}
|
|
205
|
+
>
|
|
206
|
+
<X className="size-3" />
|
|
207
|
+
</button>
|
|
208
|
+
</Badge>
|
|
209
|
+
))}
|
|
210
|
+
</div>
|
|
211
|
+
<CmsButton
|
|
212
|
+
variant="ghost"
|
|
213
|
+
size="sm"
|
|
214
|
+
className="w-full"
|
|
215
|
+
onClick={handleClear}
|
|
216
|
+
>
|
|
217
|
+
Clear all filters
|
|
218
|
+
</CmsButton>
|
|
219
|
+
</div>
|
|
220
|
+
</>
|
|
221
|
+
)}
|
|
222
|
+
</Command>
|
|
223
|
+
</PopoverContent>
|
|
224
|
+
</Popover>
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
interface TermItemProps {
|
|
229
|
+
term: TaxonomyTerm
|
|
230
|
+
isSelected: boolean
|
|
231
|
+
onToggle: () => void
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function TermItem({ term, isSelected, onToggle }: TermItemProps) {
|
|
235
|
+
return (
|
|
236
|
+
<CommandItem onSelect={onToggle} className="cursor-pointer">
|
|
237
|
+
<Checkbox
|
|
238
|
+
checked={isSelected}
|
|
239
|
+
className="mr-2"
|
|
240
|
+
aria-hidden="true"
|
|
241
|
+
tabIndex={-1}
|
|
242
|
+
/>
|
|
243
|
+
{term.color && (
|
|
244
|
+
<span
|
|
245
|
+
className="size-2.5 shrink-0 rounded-full"
|
|
246
|
+
style={{ backgroundColor: term.color }}
|
|
247
|
+
/>
|
|
248
|
+
)}
|
|
249
|
+
<span className="flex-1 truncate">{term.name}</span>
|
|
250
|
+
<span className="text-xs text-muted-foreground">{term.usageCount}</span>
|
|
251
|
+
{isSelected && <Check className="ml-1 size-4 text-primary" />}
|
|
252
|
+
</CommandItem>
|
|
253
|
+
)
|
|
254
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TaxonomyFilter, type TaxonomyFilterProps } from './TaxonomyFilter'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { AdminLayout } from './AdminLayout'
|
|
2
|
+
export { Sidebar } from './Sidebar'
|
|
3
|
+
export { Header } from './Header'
|
|
4
|
+
export { RouteGuard, type RouteGuardProps } from './RouteGuard'
|
|
5
|
+
export { UploadDropzone, type UploadDropzoneProps, type UploadedFile } from './UploadDropzone'
|
|
6
|
+
export { ContentTypeFormModal } from './ContentTypeFormModal'
|
|
7
|
+
export { ErrorBoundary } from './ErrorBoundary'
|
|
8
|
+
export { ErrorState, ErrorAlert } from './ErrorState'
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DropdownMenu,
|
|
3
|
+
DropdownMenuContent,
|
|
4
|
+
DropdownMenuItem,
|
|
5
|
+
DropdownMenuSeparator,
|
|
6
|
+
DropdownMenuTrigger,
|
|
7
|
+
} from "~/components/ui/dropdown-menu";
|
|
8
|
+
import { CmsButton } from "~/components/cmsds/CmsButton";
|
|
9
|
+
import {
|
|
10
|
+
Eye,
|
|
11
|
+
Pencil,
|
|
12
|
+
FolderInput,
|
|
13
|
+
Download,
|
|
14
|
+
Link2,
|
|
15
|
+
Trash2,
|
|
16
|
+
MoreVertical,
|
|
17
|
+
} from "lucide-react";
|
|
18
|
+
|
|
19
|
+
export interface MediaAssetForActions {
|
|
20
|
+
_id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
url: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface MediaAssetActionsProps {
|
|
26
|
+
asset: MediaAssetForActions;
|
|
27
|
+
onView?: () => void;
|
|
28
|
+
onEdit?: () => void;
|
|
29
|
+
onMove?: () => void;
|
|
30
|
+
onDelete?: () => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function MediaAssetActions({
|
|
34
|
+
asset,
|
|
35
|
+
onView,
|
|
36
|
+
onEdit,
|
|
37
|
+
onMove,
|
|
38
|
+
onDelete,
|
|
39
|
+
}: MediaAssetActionsProps) {
|
|
40
|
+
const handleDownload = () => {
|
|
41
|
+
if (!asset.url) return;
|
|
42
|
+
const link = document.createElement("a");
|
|
43
|
+
link.href = asset.url;
|
|
44
|
+
link.download = asset.name;
|
|
45
|
+
link.target = "_blank";
|
|
46
|
+
document.body.appendChild(link);
|
|
47
|
+
link.click();
|
|
48
|
+
document.body.removeChild(link);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleCopyUrl = async () => {
|
|
52
|
+
if (!asset.url) return;
|
|
53
|
+
await navigator.clipboard.writeText(asset.url);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<DropdownMenu>
|
|
58
|
+
<DropdownMenuTrigger asChild>
|
|
59
|
+
<CmsButton
|
|
60
|
+
variant="ghost"
|
|
61
|
+
size="icon-sm"
|
|
62
|
+
className="opacity-0 group-hover:opacity-100"
|
|
63
|
+
onClick={(e) => e.stopPropagation()}
|
|
64
|
+
>
|
|
65
|
+
<MoreVertical className="size-4" />
|
|
66
|
+
</CmsButton>
|
|
67
|
+
</DropdownMenuTrigger>
|
|
68
|
+
<DropdownMenuContent
|
|
69
|
+
// align="start"
|
|
70
|
+
side="bottom"
|
|
71
|
+
onClick={(e) => e.stopPropagation()}
|
|
72
|
+
>
|
|
73
|
+
{onView && (
|
|
74
|
+
<DropdownMenuItem onClick={onView}>
|
|
75
|
+
<Eye className="mr-2 size-4" />
|
|
76
|
+
View Details
|
|
77
|
+
</DropdownMenuItem>
|
|
78
|
+
)}
|
|
79
|
+
{onEdit && (
|
|
80
|
+
<DropdownMenuItem onClick={onEdit}>
|
|
81
|
+
<Pencil className="mr-2 size-4" />
|
|
82
|
+
Edit
|
|
83
|
+
</DropdownMenuItem>
|
|
84
|
+
)}
|
|
85
|
+
{onMove && (
|
|
86
|
+
<DropdownMenuItem onClick={onMove}>
|
|
87
|
+
<FolderInput className="mr-2 size-4" />
|
|
88
|
+
Move to...
|
|
89
|
+
</DropdownMenuItem>
|
|
90
|
+
)}
|
|
91
|
+
<DropdownMenuSeparator />
|
|
92
|
+
<DropdownMenuItem onClick={handleDownload} disabled={!asset.url}>
|
|
93
|
+
<Download className="mr-2 size-4" />
|
|
94
|
+
Download
|
|
95
|
+
</DropdownMenuItem>
|
|
96
|
+
<DropdownMenuItem onClick={handleCopyUrl} disabled={!asset.url}>
|
|
97
|
+
<Link2 className="mr-2 size-4" />
|
|
98
|
+
Copy URL
|
|
99
|
+
</DropdownMenuItem>
|
|
100
|
+
{onDelete && (
|
|
101
|
+
<>
|
|
102
|
+
<DropdownMenuSeparator />
|
|
103
|
+
<DropdownMenuItem
|
|
104
|
+
onClick={onDelete}
|
|
105
|
+
className="text-destructive focus:text-destructive"
|
|
106
|
+
>
|
|
107
|
+
<Trash2 className="mr-2 size-4" />
|
|
108
|
+
Delete
|
|
109
|
+
</DropdownMenuItem>
|
|
110
|
+
</>
|
|
111
|
+
)}
|
|
112
|
+
</DropdownMenuContent>
|
|
113
|
+
</DropdownMenu>
|
|
114
|
+
);
|
|
115
|
+
}
|