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,457 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Content Types Page Component
|
|
3
|
+
*
|
|
4
|
+
* Manages content type definitions with create/edit capabilities.
|
|
5
|
+
* Used by both CLI routes and embed pages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useMemo, useCallback } from "react";
|
|
9
|
+
import { useQuery } from "convex/react";
|
|
10
|
+
import { ContentTypeFormModal } from "~/components/ContentTypeFormModal";
|
|
11
|
+
|
|
12
|
+
interface ContentTypeField {
|
|
13
|
+
name: string;
|
|
14
|
+
label: string;
|
|
15
|
+
type: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ContentTypeWithCount {
|
|
19
|
+
_id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
displayName: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
fields: ContentTypeField[];
|
|
24
|
+
isActive: boolean;
|
|
25
|
+
singleton?: boolean;
|
|
26
|
+
entryCount?: number;
|
|
27
|
+
_creationTime: number;
|
|
28
|
+
}
|
|
29
|
+
import { CmsPageHeader } from "~/components/cmsds/CmsPageHeader";
|
|
30
|
+
import { CmsToolbar } from "~/components/cmsds/CmsToolbar";
|
|
31
|
+
import { CmsEmptyState } from "~/components/cmsds/CmsEmptyState";
|
|
32
|
+
import { CmsButton } from "~/components/cmsds/CmsButton";
|
|
33
|
+
import { Input } from "~/components/ui/input";
|
|
34
|
+
import { Checkbox } from "~/components/ui/checkbox";
|
|
35
|
+
import { Badge } from "~/components/ui/badge";
|
|
36
|
+
import { cn } from "~/lib/cn";
|
|
37
|
+
import {
|
|
38
|
+
Search,
|
|
39
|
+
Grid3X3,
|
|
40
|
+
List,
|
|
41
|
+
Plus,
|
|
42
|
+
FileType,
|
|
43
|
+
Hash,
|
|
44
|
+
ToggleLeft,
|
|
45
|
+
Calendar,
|
|
46
|
+
Link2,
|
|
47
|
+
Image,
|
|
48
|
+
Braces,
|
|
49
|
+
ChevronDown,
|
|
50
|
+
Tag,
|
|
51
|
+
FolderOpen,
|
|
52
|
+
AlignLeft,
|
|
53
|
+
Pencil,
|
|
54
|
+
} from "lucide-react";
|
|
55
|
+
import type { AdminNavigation } from "~/lib/navigation";
|
|
56
|
+
import { CmsAdminApi } from "~/embed/contexts/ApiContext";
|
|
57
|
+
|
|
58
|
+
type ViewMode = "grid" | "list";
|
|
59
|
+
|
|
60
|
+
const FIELD_ICONS: Record<string, React.ReactNode> = {
|
|
61
|
+
text: <AlignLeft className="size-3" />,
|
|
62
|
+
richText: <FileType className="size-3" />,
|
|
63
|
+
number: <Hash className="size-3" />,
|
|
64
|
+
boolean: <ToggleLeft className="size-3" />,
|
|
65
|
+
date: <Calendar className="size-3" />,
|
|
66
|
+
datetime: <Calendar className="size-3" />,
|
|
67
|
+
reference: <Link2 className="size-3" />,
|
|
68
|
+
media: <Image className="size-3" />,
|
|
69
|
+
json: <Braces className="size-3" />,
|
|
70
|
+
select: <ChevronDown className="size-3" />,
|
|
71
|
+
multiSelect: <List className="size-3" />,
|
|
72
|
+
tags: <Tag className="size-3" />,
|
|
73
|
+
category: <FolderOpen className="size-3" />,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export interface ContentTypesPageProps {
|
|
77
|
+
api: CmsAdminApi;
|
|
78
|
+
navigation: AdminNavigation;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function ContentTypesPage({ api, navigation }: ContentTypesPageProps) {
|
|
82
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
83
|
+
const [viewMode, setViewMode] = useState<ViewMode>("grid");
|
|
84
|
+
const [showActiveOnly, setShowActiveOnly] = useState(true);
|
|
85
|
+
const [showCreateModal, setShowCreateModal] = useState(false);
|
|
86
|
+
const [
|
|
87
|
+
editingContentType,
|
|
88
|
+
setEditingContentType,
|
|
89
|
+
] = useState<ContentTypeWithCount | null>(null);
|
|
90
|
+
|
|
91
|
+
const handleContentTypeCreated = useCallback(() => {
|
|
92
|
+
setShowCreateModal(false);
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
const handleContentTypeUpdated = useCallback(() => {
|
|
96
|
+
setEditingContentType(null);
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
const contentTypesResult = useQuery(api.listContentTypes, {
|
|
100
|
+
isActive: showActiveOnly ? true : undefined,
|
|
101
|
+
includeEntryCounts: true,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const contentTypes = (contentTypesResult?.page ??
|
|
105
|
+
[]) as ContentTypeWithCount[];
|
|
106
|
+
const isLoading = contentTypesResult === undefined;
|
|
107
|
+
|
|
108
|
+
const filteredContentTypes = useMemo(() => {
|
|
109
|
+
if (!searchQuery.trim()) {
|
|
110
|
+
return contentTypes;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const query = searchQuery.toLowerCase();
|
|
114
|
+
return contentTypes.filter(
|
|
115
|
+
(type) =>
|
|
116
|
+
type.name.toLowerCase().includes(query) ||
|
|
117
|
+
type.displayName.toLowerCase().includes(query),
|
|
118
|
+
);
|
|
119
|
+
}, [contentTypes, searchQuery]);
|
|
120
|
+
|
|
121
|
+
const formatDate = (timestamp: number) => {
|
|
122
|
+
return new Date(timestamp).toLocaleDateString("en-US", {
|
|
123
|
+
year: "numeric",
|
|
124
|
+
month: "short",
|
|
125
|
+
day: "numeric",
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const getRelativeTime = (timestamp: number) => {
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
const diff = now - timestamp;
|
|
132
|
+
const seconds = Math.floor(diff / 1000);
|
|
133
|
+
const minutes = Math.floor(seconds / 60);
|
|
134
|
+
const hours = Math.floor(minutes / 60);
|
|
135
|
+
const days = Math.floor(hours / 24);
|
|
136
|
+
|
|
137
|
+
if (days > 30) {
|
|
138
|
+
return formatDate(timestamp);
|
|
139
|
+
} else if (days > 0) {
|
|
140
|
+
return `${days}d ago`;
|
|
141
|
+
} else if (hours > 0) {
|
|
142
|
+
return `${hours}h ago`;
|
|
143
|
+
} else if (minutes > 0) {
|
|
144
|
+
return `${minutes}m ago`;
|
|
145
|
+
} else {
|
|
146
|
+
return "Just now";
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div className="space-y-6 p-6">
|
|
152
|
+
<CmsPageHeader
|
|
153
|
+
title="Content Types"
|
|
154
|
+
description="Define the structure of your content with custom fields and validation rules."
|
|
155
|
+
/>
|
|
156
|
+
|
|
157
|
+
<CmsToolbar
|
|
158
|
+
left={
|
|
159
|
+
<div className="flex items-center gap-3">
|
|
160
|
+
<div className="relative">
|
|
161
|
+
<Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
162
|
+
<Input
|
|
163
|
+
type="search"
|
|
164
|
+
placeholder="Search content types..."
|
|
165
|
+
value={searchQuery}
|
|
166
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
167
|
+
className="w-64 pl-9"
|
|
168
|
+
/>
|
|
169
|
+
</div>
|
|
170
|
+
<label className="flex cursor-pointer items-center gap-2 text-sm">
|
|
171
|
+
<Checkbox
|
|
172
|
+
checked={showActiveOnly}
|
|
173
|
+
onCheckedChange={(checked) =>
|
|
174
|
+
setShowActiveOnly(checked as boolean)
|
|
175
|
+
}
|
|
176
|
+
/>
|
|
177
|
+
Active only
|
|
178
|
+
</label>
|
|
179
|
+
</div>
|
|
180
|
+
}
|
|
181
|
+
right={
|
|
182
|
+
<div className="flex items-center gap-2">
|
|
183
|
+
<div className="flex rounded-md border bg-muted/30">
|
|
184
|
+
<button
|
|
185
|
+
className={cn(
|
|
186
|
+
"rounded-l-md p-2 transition-colors",
|
|
187
|
+
viewMode === "grid"
|
|
188
|
+
? "bg-background shadow-sm"
|
|
189
|
+
: "hover:bg-muted/50",
|
|
190
|
+
)}
|
|
191
|
+
onClick={() => setViewMode("grid")}
|
|
192
|
+
title="Grid view"
|
|
193
|
+
aria-label="Grid view"
|
|
194
|
+
>
|
|
195
|
+
<Grid3X3 className="size-4" />
|
|
196
|
+
</button>
|
|
197
|
+
<button
|
|
198
|
+
className={cn(
|
|
199
|
+
"rounded-r-md p-2 transition-colors",
|
|
200
|
+
viewMode === "list"
|
|
201
|
+
? "bg-background shadow-sm"
|
|
202
|
+
: "hover:bg-muted/50",
|
|
203
|
+
)}
|
|
204
|
+
onClick={() => setViewMode("list")}
|
|
205
|
+
title="List view"
|
|
206
|
+
aria-label="List view"
|
|
207
|
+
>
|
|
208
|
+
<List className="size-4" />
|
|
209
|
+
</button>
|
|
210
|
+
</div>
|
|
211
|
+
<CmsButton
|
|
212
|
+
onClick={() => setShowCreateModal(true)}
|
|
213
|
+
data-testid="create-content-type-button"
|
|
214
|
+
>
|
|
215
|
+
<Plus className="size-4" />
|
|
216
|
+
Create Content Type
|
|
217
|
+
</CmsButton>
|
|
218
|
+
</div>
|
|
219
|
+
}
|
|
220
|
+
/>
|
|
221
|
+
|
|
222
|
+
{isLoading ? (
|
|
223
|
+
<div className="flex flex-col items-center justify-center py-12">
|
|
224
|
+
<div className="size-8 animate-spin rounded-full border-2 border-muted border-t-primary" />
|
|
225
|
+
<p className="mt-4 text-sm text-muted-foreground">
|
|
226
|
+
Loading content types...
|
|
227
|
+
</p>
|
|
228
|
+
</div>
|
|
229
|
+
) : filteredContentTypes.length === 0 ? (
|
|
230
|
+
<CmsEmptyState
|
|
231
|
+
icon={<FileType className="size-6" />}
|
|
232
|
+
title={
|
|
233
|
+
searchQuery
|
|
234
|
+
? "No content types match your search"
|
|
235
|
+
: "No content types defined"
|
|
236
|
+
}
|
|
237
|
+
description={
|
|
238
|
+
searchQuery
|
|
239
|
+
? "Try adjusting your search query or filters."
|
|
240
|
+
: "Content types define the schema for your content. Create one to get started."
|
|
241
|
+
}
|
|
242
|
+
/>
|
|
243
|
+
) : viewMode === "grid" ? (
|
|
244
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
|
245
|
+
{filteredContentTypes.map((contentType) => (
|
|
246
|
+
<div
|
|
247
|
+
key={contentType._id}
|
|
248
|
+
className="group flex flex-col rounded-lg border bg-card p-4 transition-all hover:border-primary/50 hover:shadow-md"
|
|
249
|
+
>
|
|
250
|
+
<div className="mb-3 flex items-center justify-between">
|
|
251
|
+
<div className="flex size-10 items-center justify-center rounded-md bg-muted text-muted-foreground">
|
|
252
|
+
<FileType className="size-5" />
|
|
253
|
+
</div>
|
|
254
|
+
<div className="flex items-center gap-1.5">
|
|
255
|
+
{!contentType.isActive && (
|
|
256
|
+
<Badge variant="secondary" className="text-xs font-normal">
|
|
257
|
+
Inactive
|
|
258
|
+
</Badge>
|
|
259
|
+
)}
|
|
260
|
+
{contentType.singleton && (
|
|
261
|
+
<Badge
|
|
262
|
+
variant="secondary"
|
|
263
|
+
className="border-amber-200 bg-amber-50 text-xs font-normal text-amber-700"
|
|
264
|
+
>
|
|
265
|
+
Singleton
|
|
266
|
+
</Badge>
|
|
267
|
+
)}
|
|
268
|
+
<button
|
|
269
|
+
onClick={() => setEditingContentType(contentType)}
|
|
270
|
+
className="rounded p-1.5 text-muted-foreground opacity-0 transition-all hover:bg-muted hover:text-foreground group-hover:opacity-100"
|
|
271
|
+
title="Edit content type"
|
|
272
|
+
aria-label="Edit content type"
|
|
273
|
+
>
|
|
274
|
+
<Pencil className="size-4" />
|
|
275
|
+
</button>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<button
|
|
280
|
+
type="button"
|
|
281
|
+
onClick={() =>
|
|
282
|
+
navigation.navigateToContentType(contentType._id)
|
|
283
|
+
}
|
|
284
|
+
className="flex flex-1 flex-col text-left"
|
|
285
|
+
>
|
|
286
|
+
<h3 className="text-base font-semibold text-foreground">
|
|
287
|
+
{contentType.displayName}
|
|
288
|
+
</h3>
|
|
289
|
+
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
290
|
+
{contentType.name}
|
|
291
|
+
</p>
|
|
292
|
+
|
|
293
|
+
<div className="mt-3 flex items-center gap-4">
|
|
294
|
+
<div className="text-center">
|
|
295
|
+
<p className="text-lg font-semibold text-foreground">
|
|
296
|
+
{contentType.fields.length}
|
|
297
|
+
</p>
|
|
298
|
+
<p className="text-xs text-muted-foreground">
|
|
299
|
+
{contentType.fields.length === 1 ? "Field" : "Fields"}
|
|
300
|
+
</p>
|
|
301
|
+
</div>
|
|
302
|
+
<div className="text-center">
|
|
303
|
+
<p className="text-lg font-semibold text-foreground">
|
|
304
|
+
{contentType.entryCount ?? 0}
|
|
305
|
+
</p>
|
|
306
|
+
<p className="text-xs text-muted-foreground">
|
|
307
|
+
{contentType.entryCount === 1 ? "Entry" : "Entries"}
|
|
308
|
+
</p>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
<div className="mt-3 flex flex-wrap gap-1.5">
|
|
313
|
+
{contentType.fields.slice(0, 4).map((field) => (
|
|
314
|
+
<span
|
|
315
|
+
key={field.name}
|
|
316
|
+
className="flex items-center gap-1 rounded-md bg-muted px-1.5 py-0.5 text-xs text-muted-foreground"
|
|
317
|
+
title={field.label}
|
|
318
|
+
>
|
|
319
|
+
{FIELD_ICONS[field.type] || (
|
|
320
|
+
<AlignLeft className="size-3" />
|
|
321
|
+
)}
|
|
322
|
+
<span className="truncate">{field.label}</span>
|
|
323
|
+
</span>
|
|
324
|
+
))}
|
|
325
|
+
{contentType.fields.length > 4 && (
|
|
326
|
+
<span className="rounded-md bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">
|
|
327
|
+
+{contentType.fields.length - 4} more
|
|
328
|
+
</span>
|
|
329
|
+
)}
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
<p className="mt-auto pt-3 text-xs text-muted-foreground">
|
|
333
|
+
Updated {getRelativeTime(contentType._creationTime)}
|
|
334
|
+
</p>
|
|
335
|
+
</button>
|
|
336
|
+
</div>
|
|
337
|
+
))}
|
|
338
|
+
</div>
|
|
339
|
+
) : (
|
|
340
|
+
<div className="rounded-lg border bg-card">
|
|
341
|
+
<table className="w-full">
|
|
342
|
+
<thead>
|
|
343
|
+
<tr className="border-b">
|
|
344
|
+
<th className="p-3 text-left text-sm font-medium text-muted-foreground">
|
|
345
|
+
Name
|
|
346
|
+
</th>
|
|
347
|
+
<th className="p-3 text-left text-sm font-medium text-muted-foreground">
|
|
348
|
+
Fields
|
|
349
|
+
</th>
|
|
350
|
+
<th className="p-3 text-left text-sm font-medium text-muted-foreground">
|
|
351
|
+
Entries
|
|
352
|
+
</th>
|
|
353
|
+
<th className="p-3 text-left text-sm font-medium text-muted-foreground">
|
|
354
|
+
Status
|
|
355
|
+
</th>
|
|
356
|
+
<th className="p-3 text-left text-sm font-medium text-muted-foreground">
|
|
357
|
+
Last Updated
|
|
358
|
+
</th>
|
|
359
|
+
<th className="p-3 text-left text-sm font-medium text-muted-foreground">
|
|
360
|
+
Actions
|
|
361
|
+
</th>
|
|
362
|
+
</tr>
|
|
363
|
+
</thead>
|
|
364
|
+
<tbody>
|
|
365
|
+
{filteredContentTypes.map((contentType) => (
|
|
366
|
+
<tr
|
|
367
|
+
key={contentType._id}
|
|
368
|
+
className="border-b last:border-0 transition-colors hover:bg-muted/50"
|
|
369
|
+
>
|
|
370
|
+
<td className="p-3">
|
|
371
|
+
<p className="font-medium text-foreground">
|
|
372
|
+
{contentType.displayName}
|
|
373
|
+
</p>
|
|
374
|
+
<p className="text-xs text-muted-foreground">
|
|
375
|
+
{contentType.name}
|
|
376
|
+
</p>
|
|
377
|
+
</td>
|
|
378
|
+
<td className="p-3 text-sm text-muted-foreground">
|
|
379
|
+
{contentType.fields.length}
|
|
380
|
+
</td>
|
|
381
|
+
<td className="p-3 text-sm text-muted-foreground">
|
|
382
|
+
{contentType.entryCount ?? 0}
|
|
383
|
+
</td>
|
|
384
|
+
<td className="p-3">
|
|
385
|
+
<div className="flex items-center gap-1.5">
|
|
386
|
+
<Badge
|
|
387
|
+
variant={contentType.isActive ? "default" : "secondary"}
|
|
388
|
+
className={cn(
|
|
389
|
+
"text-xs font-normal",
|
|
390
|
+
contentType.isActive &&
|
|
391
|
+
"border-emerald-200 bg-emerald-50 text-emerald-700",
|
|
392
|
+
)}
|
|
393
|
+
>
|
|
394
|
+
{contentType.isActive ? "Active" : "Inactive"}
|
|
395
|
+
</Badge>
|
|
396
|
+
{contentType.singleton && (
|
|
397
|
+
<Badge
|
|
398
|
+
variant="secondary"
|
|
399
|
+
className="border-amber-200 bg-amber-50 text-xs font-normal text-amber-700"
|
|
400
|
+
>
|
|
401
|
+
Singleton
|
|
402
|
+
</Badge>
|
|
403
|
+
)}
|
|
404
|
+
</div>
|
|
405
|
+
</td>
|
|
406
|
+
<td className="p-3 text-sm text-muted-foreground">
|
|
407
|
+
{formatDate(contentType._creationTime)}
|
|
408
|
+
</td>
|
|
409
|
+
<td className="p-3">
|
|
410
|
+
<div className="flex items-center gap-2">
|
|
411
|
+
<CmsButton
|
|
412
|
+
variant="outline"
|
|
413
|
+
size="sm"
|
|
414
|
+
onClick={() => setEditingContentType(contentType)}
|
|
415
|
+
>
|
|
416
|
+
<Pencil className="size-3.5" />
|
|
417
|
+
Edit
|
|
418
|
+
</CmsButton>
|
|
419
|
+
<CmsButton
|
|
420
|
+
variant="outline"
|
|
421
|
+
size="sm"
|
|
422
|
+
onClick={() =>
|
|
423
|
+
navigation.navigateToContentType(contentType._id)
|
|
424
|
+
}
|
|
425
|
+
>
|
|
426
|
+
View Entries
|
|
427
|
+
</CmsButton>
|
|
428
|
+
</div>
|
|
429
|
+
</td>
|
|
430
|
+
</tr>
|
|
431
|
+
))}
|
|
432
|
+
</tbody>
|
|
433
|
+
</table>
|
|
434
|
+
</div>
|
|
435
|
+
)}
|
|
436
|
+
|
|
437
|
+
{!isLoading && filteredContentTypes.length > 0 && (
|
|
438
|
+
<p className="text-center text-sm text-muted-foreground">
|
|
439
|
+
Showing {filteredContentTypes.length} of {contentTypes.length} content
|
|
440
|
+
type
|
|
441
|
+
{contentTypes.length !== 1 ? "s" : ""}
|
|
442
|
+
</p>
|
|
443
|
+
)}
|
|
444
|
+
|
|
445
|
+
<ContentTypeFormModal
|
|
446
|
+
isOpen={showCreateModal || !!editingContentType}
|
|
447
|
+
onClose={() => {
|
|
448
|
+
setShowCreateModal(false);
|
|
449
|
+
setEditingContentType(null);
|
|
450
|
+
}}
|
|
451
|
+
contentType={editingContentType as any}
|
|
452
|
+
onCreated={handleContentTypeCreated}
|
|
453
|
+
onUpdated={handleContentTypeUpdated}
|
|
454
|
+
/>
|
|
455
|
+
</div>
|
|
456
|
+
);
|
|
457
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Dashboard Page Component
|
|
3
|
+
*
|
|
4
|
+
* Shows CMS overview with quick navigation cards and statistics.
|
|
5
|
+
* Used by both CLI routes and embed pages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useQuery } from "convex/react";
|
|
9
|
+
import {
|
|
10
|
+
Card,
|
|
11
|
+
CardContent,
|
|
12
|
+
CardDescription,
|
|
13
|
+
CardHeader,
|
|
14
|
+
CardTitle,
|
|
15
|
+
} from "~/components/ui/card";
|
|
16
|
+
import { Skeleton } from "~/components/ui/skeleton";
|
|
17
|
+
import { CmsPageHeader } from "~/components/cmsds";
|
|
18
|
+
import { FileText, Image, Layers, Settings, TrendingUp } from "lucide-react";
|
|
19
|
+
import type { AdminNavigation } from "~/lib/navigation";
|
|
20
|
+
import type { CmsAdminApi } from "~/embed/contexts/ApiContext";
|
|
21
|
+
|
|
22
|
+
interface DashboardPageProps {
|
|
23
|
+
api: CmsAdminApi;
|
|
24
|
+
navigation: AdminNavigation;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function DashboardPage({ api, navigation }: DashboardPageProps) {
|
|
28
|
+
const stats = useQuery(api.getDashboardStats, {});
|
|
29
|
+
const isLoading = stats === undefined;
|
|
30
|
+
const hasError = stats === null;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="space-y-8">
|
|
34
|
+
<CmsPageHeader
|
|
35
|
+
title="Dashboard"
|
|
36
|
+
description="Welcome to Convex CMS Admin. Manage your content, media, and publishing workflows."
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
40
|
+
<DashboardCard
|
|
41
|
+
title="Content Entries"
|
|
42
|
+
description="Create and manage your content"
|
|
43
|
+
onClick={() => navigation.navigate("/content")}
|
|
44
|
+
icon={<FileText className="size-5" />}
|
|
45
|
+
/>
|
|
46
|
+
<DashboardCard
|
|
47
|
+
title="Media Library"
|
|
48
|
+
description="Upload and organize media assets"
|
|
49
|
+
onClick={() => navigation.navigate("/media")}
|
|
50
|
+
icon={<Image className="size-5" />}
|
|
51
|
+
/>
|
|
52
|
+
<DashboardCard
|
|
53
|
+
title="Content Types"
|
|
54
|
+
description="Define content schemas and fields"
|
|
55
|
+
onClick={() => navigation.navigate("/content-types")}
|
|
56
|
+
icon={<Layers className="size-5" />}
|
|
57
|
+
/>
|
|
58
|
+
<DashboardCard
|
|
59
|
+
title="Settings"
|
|
60
|
+
description="Configure CMS settings"
|
|
61
|
+
onClick={() => navigation.navigate("/settings")}
|
|
62
|
+
icon={<Settings className="size-5" />}
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<section className="space-y-4">
|
|
67
|
+
<div className="flex items-center gap-2">
|
|
68
|
+
<TrendingUp className="size-5 text-muted-foreground" />
|
|
69
|
+
<h2 className="text-lg font-semibold">Quick Stats</h2>
|
|
70
|
+
</div>
|
|
71
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
72
|
+
<StatCard
|
|
73
|
+
label="Content Types"
|
|
74
|
+
value={
|
|
75
|
+
isLoading
|
|
76
|
+
? undefined
|
|
77
|
+
: hasError
|
|
78
|
+
? "—"
|
|
79
|
+
: String(stats.contentTypes)
|
|
80
|
+
}
|
|
81
|
+
isLoading={isLoading}
|
|
82
|
+
/>
|
|
83
|
+
<StatCard
|
|
84
|
+
label="Content Entries"
|
|
85
|
+
value={
|
|
86
|
+
isLoading
|
|
87
|
+
? undefined
|
|
88
|
+
: hasError
|
|
89
|
+
? "—"
|
|
90
|
+
: String(stats.contentEntries)
|
|
91
|
+
}
|
|
92
|
+
isLoading={isLoading}
|
|
93
|
+
/>
|
|
94
|
+
<StatCard
|
|
95
|
+
label="Media Assets"
|
|
96
|
+
value={
|
|
97
|
+
isLoading ? undefined : hasError ? "—" : String(stats.mediaAssets)
|
|
98
|
+
}
|
|
99
|
+
isLoading={isLoading}
|
|
100
|
+
/>
|
|
101
|
+
<StatCard
|
|
102
|
+
label="Published"
|
|
103
|
+
value={
|
|
104
|
+
isLoading ? undefined : hasError ? "—" : String(stats.published)
|
|
105
|
+
}
|
|
106
|
+
isLoading={isLoading}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
</section>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function DashboardCard({
|
|
115
|
+
title,
|
|
116
|
+
description,
|
|
117
|
+
onClick,
|
|
118
|
+
icon,
|
|
119
|
+
}: {
|
|
120
|
+
title: string;
|
|
121
|
+
description: string;
|
|
122
|
+
onClick: () => void;
|
|
123
|
+
icon: React.ReactNode;
|
|
124
|
+
}) {
|
|
125
|
+
return (
|
|
126
|
+
<button type="button" onClick={onClick} className="text-left">
|
|
127
|
+
<Card className="h-full transition-colors hover:bg-accent/50">
|
|
128
|
+
<CardHeader className="pb-2">
|
|
129
|
+
<div className="mb-2 flex size-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
130
|
+
{icon}
|
|
131
|
+
</div>
|
|
132
|
+
<CardTitle className="text-base">{title}</CardTitle>
|
|
133
|
+
</CardHeader>
|
|
134
|
+
<CardContent>
|
|
135
|
+
<CardDescription>{description}</CardDescription>
|
|
136
|
+
</CardContent>
|
|
137
|
+
</Card>
|
|
138
|
+
</button>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function StatCard({
|
|
143
|
+
label,
|
|
144
|
+
value,
|
|
145
|
+
isLoading = false,
|
|
146
|
+
}: {
|
|
147
|
+
label: string;
|
|
148
|
+
value?: string;
|
|
149
|
+
isLoading?: boolean;
|
|
150
|
+
}) {
|
|
151
|
+
return (
|
|
152
|
+
<Card>
|
|
153
|
+
<CardContent className="p-4">
|
|
154
|
+
{isLoading ? (
|
|
155
|
+
<Skeleton className="mb-1 h-8 w-16" />
|
|
156
|
+
) : (
|
|
157
|
+
<div className="text-2xl font-bold">{value}</div>
|
|
158
|
+
)}
|
|
159
|
+
<div className="text-sm text-muted-foreground">{label}</div>
|
|
160
|
+
</CardContent>
|
|
161
|
+
</Card>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Media Page Component
|
|
3
|
+
*
|
|
4
|
+
* Manages media assets and folders.
|
|
5
|
+
* Used by both CLI routes and embed pages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CmsPageHeader } from "~/components/cmsds/CmsPageHeader";
|
|
9
|
+
import { CmsEmptyState } from "~/components/cmsds/CmsEmptyState";
|
|
10
|
+
import { Image } from "lucide-react";
|
|
11
|
+
import type { AdminNavigation } from "~/lib/navigation";
|
|
12
|
+
import type { CmsAdminApi } from "~/embed/contexts/ApiContext";
|
|
13
|
+
|
|
14
|
+
export interface MediaPageProps {
|
|
15
|
+
api: CmsAdminApi;
|
|
16
|
+
navigation: AdminNavigation;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function MediaPage({ api: _api, navigation: _navigation }: MediaPageProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="space-y-6 p-6">
|
|
22
|
+
<CmsPageHeader
|
|
23
|
+
title="Media Library"
|
|
24
|
+
description="Upload and manage media assets like images, videos, and documents."
|
|
25
|
+
/>
|
|
26
|
+
|
|
27
|
+
<CmsEmptyState
|
|
28
|
+
icon={<Image className="size-6" />}
|
|
29
|
+
title="Media library coming soon"
|
|
30
|
+
description="The media management interface is under development."
|
|
31
|
+
/>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|