convex-cms 0.0.6 → 0.0.7-alpha.0
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 +59 -97
- package/admin/src/components/ContentEntryEditor.tsx +2 -2
- package/admin/src/components/ContentTypeFormModal.tsx +121 -81
- package/admin/src/components/fields/ReferenceField.tsx +9 -9
- package/admin/src/pages/ContentPage.tsx +11 -19
- package/admin/src/pages/ContentTypesPage.tsx +62 -16
- package/admin/src/routes/entries/$entryId.tsx +1 -1
- package/admin/src/routes/entries/type/$contentTypeId.tsx +1 -1
- package/admin-dist/nitro.json +1 -1
- package/admin-dist/public/assets/CmsEmptyState-CXVkI3FZ.js +1 -0
- package/admin-dist/public/assets/CmsPageHeader-DU9fD34s.js +1 -0
- package/admin-dist/public/assets/{CmsStatusBadge-Dd9uToHE.js → CmsStatusBadge-nZ9TeLBL.js} +1 -1
- package/admin-dist/public/assets/{CmsSurface-DBy5Lumx.js → CmsSurface-DF7OcKg_.js} +1 -1
- package/admin-dist/public/assets/CmsToolbar-5S8FQrSx.js +1 -0
- package/admin-dist/public/assets/ContentEntryEditor-BDb44eTo.js +4 -0
- package/admin-dist/public/assets/TaxonomyFilter-DEN2Q9Lo.js +1 -0
- package/admin-dist/public/assets/_contentTypeId-Ba5iowxH.js +1 -0
- package/admin-dist/public/assets/_entryId-OY3sLz6O.js +1 -0
- package/admin-dist/public/assets/alert-BbW1Q9CR.js +1 -0
- package/admin-dist/public/assets/badge-DdM8Eua8.js +1 -0
- package/admin-dist/public/assets/{circle-check-big-CpLxAvEj.js → circle-check-big-B7eCOM8r.js} +1 -1
- package/admin-dist/public/assets/command-BIjzeKOv.js +1 -0
- package/admin-dist/public/assets/content-BV3YeSSW.js +1 -0
- package/admin-dist/public/assets/content-types-Bm4b2tf8.js +1 -0
- package/admin-dist/public/assets/globals-D41WzvyZ.css +1 -0
- package/admin-dist/public/assets/index-DnJ5Twlv.js +1 -0
- package/admin-dist/public/assets/main-BZB1uYTH.js +102 -0
- package/admin-dist/public/assets/media-BIMN5jXt.js +1 -0
- package/admin-dist/public/assets/new._contentTypeId-DTWb8ZDl.js +1 -0
- package/admin-dist/public/assets/pencil-BDQ1ZWRw.js +1 -0
- package/admin-dist/public/assets/{rotate-ccw-BZpZtw0N.js → rotate-ccw-BWblSIsl.js} +1 -1
- package/admin-dist/public/assets/scroll-area-BoaB6x8v.js +1 -0
- package/admin-dist/public/assets/{search-BvgYr-c9.js → search-CYMIpd39.js} +1 -1
- package/admin-dist/public/assets/settings-DaNDUtr5.js +1 -0
- package/admin-dist/public/assets/switch-DN7TOCa5.js +1 -0
- package/admin-dist/public/assets/tabs-RN__emeJ.js +1 -0
- package/admin-dist/public/assets/tanstack-adapter-DQcKErwf.js +1 -0
- package/admin-dist/public/assets/taxonomies-DylY9HE1.js +1 -0
- package/admin-dist/public/assets/trash-Dp_a2mpb.js +1 -0
- package/admin-dist/public/assets/{useBreadcrumbLabel-D00rvqjw.js → useBreadcrumbLabel-BQ9dJI6T.js} +1 -1
- package/admin-dist/public/assets/usePermissions-WUBNg_Id.js +1 -0
- package/admin-dist/server/_chunks/_libs/@date-fns/tz.mjs +2 -2
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-avatar.mjs +1 -1
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-collection.mjs +1 -1
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-context.mjs +2 -2
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-dialog.mjs +2 -2
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-label.mjs +1 -1
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-menu.mjs +1 -1
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-popover.mjs +1 -1
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-primitive.mjs +6 -72
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-select.mjs +1 -1
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-separator.mjs +1 -1
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-slot.mjs +20 -435
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-visually-hidden.mjs +30 -3
- package/admin-dist/server/_chunks/_libs/@tanstack/history.mjs +0 -376
- package/admin-dist/server/_chunks/_libs/@tanstack/react-router.mjs +168 -383
- package/admin-dist/server/_chunks/_libs/@tanstack/router-core.mjs +451 -1195
- package/admin-dist/server/_chunks/_libs/react-dom.mjs +5 -5
- package/admin-dist/server/_chunks/_libs/react.mjs +2 -2
- package/admin-dist/server/_libs/cmdk.mjs +1 -1
- package/admin-dist/server/_libs/convex.mjs +3 -3
- package/admin-dist/server/_libs/cookie-es.mjs +1 -58
- package/admin-dist/server/_libs/date-fns.mjs +1 -1
- package/admin-dist/server/_libs/lucide-react.mjs +110 -103
- package/admin-dist/server/_libs/seroval-plugins.mjs +1 -58
- package/admin-dist/server/_libs/seroval.mjs +1 -1765
- package/admin-dist/server/_libs/zod.mjs +1 -1
- package/admin-dist/server/_ssr/CmsEmptyState-DYh_PPQE.mjs +38 -0
- package/admin-dist/server/_ssr/{CmsPageHeader-ClNPU7Up.mjs → CmsPageHeader-BcniLh49.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsStatusBadge-CojMbrY7.mjs → CmsStatusBadge-BShWDxwE.mjs} +2 -2
- package/admin-dist/server/_ssr/{CmsSurface-Dcv440rp.mjs → CmsSurface-CHEv-Kba.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsToolbar-BKv1nL6u.mjs → CmsToolbar-Dqqb216_.mjs} +2 -3
- package/admin-dist/server/_ssr/{ContentEntryEditor-weiXSBdZ.mjs → ContentEntryEditor-DOIAyWME.mjs} +18 -21
- package/admin-dist/server/_ssr/{TaxonomyFilter-BPQ57Mwk.mjs → TaxonomyFilter-BfsPAZ-Y.mjs} +4 -5
- package/admin-dist/server/_ssr/{_contentTypeId-DyyauLOs.mjs → _contentTypeId-CPjmri90.mjs} +34 -43
- package/admin-dist/server/_ssr/{_entryId-9Cafwxmw.mjs → _entryId-D0yu8HuP.mjs} +35 -47
- package/admin-dist/server/_ssr/_tanstack-start-manifest_v-DgCpSt_y.mjs +4 -0
- package/admin-dist/server/_ssr/badge-Cp61aQNc.mjs +39 -0
- package/admin-dist/server/_ssr/{command-CEf8YBxY.mjs → command-BfjE1WJf.mjs} +2 -2
- package/admin-dist/server/_ssr/{config.server-D7JHDcDv.mjs → config.server-BOr7Jxr4.mjs} +5 -14
- package/admin-dist/server/_ssr/{content-ZFWVzO25.mjs → content-DrODe6sA.mjs} +40 -49
- package/admin-dist/server/_ssr/content-types-BPgMwxiT.mjs +459 -0
- package/admin-dist/server/_ssr/{index-BlSIlH4Z.mjs → index-BTHmIC9W.mjs} +26 -28
- package/admin-dist/server/_ssr/index.mjs +3459 -62
- package/admin-dist/server/_ssr/{media-CD2_NUMw.mjs → media-DkvBfmD9.mjs} +31 -43
- package/admin-dist/server/_ssr/{new._contentTypeId-dmZy6PBX.mjs → new._contentTypeId-Co_73sDJ.mjs} +33 -45
- package/admin-dist/server/_ssr/router-CaDgRHfQ.mjs +3031 -0
- package/admin-dist/server/_ssr/{scroll-area-BH_1K-WT.mjs → scroll-area-D3v-O_jk.mjs} +1 -1
- package/admin-dist/server/_ssr/{settings-DVdsoWoh.mjs → settings-MaEXh2Hz.mjs} +30 -39
- package/admin-dist/server/_ssr/{switch-DX_X8vZl.mjs → switch-DmbR03dm.mjs} +1 -1
- package/admin-dist/server/_ssr/{tabs-4FWM0sn8.mjs → tabs-5oFlAGLz.mjs} +2 -3
- package/admin-dist/server/_ssr/{tanstack-adapter-D3ZcKtbY.mjs → tanstack-adapter-DNaUioIZ.mjs} +1 -1
- package/admin-dist/server/_ssr/{taxonomies-BHFfO9Yr.mjs → taxonomies-D3xMK23a.mjs} +30 -39
- package/admin-dist/server/_ssr/{trash-9tUB2KwI.mjs → trash-CNw1mtF1.mjs} +30 -39
- package/admin-dist/server/_ssr/{useBreadcrumbLabel-DVme3DSb.mjs → useBreadcrumbLabel-BQGjOTcy.mjs} +1 -1
- package/admin-dist/server/_ssr/{usePermissions-zAQj-ruE.mjs → usePermissions-D0qtvmNi.mjs} +1 -1
- package/admin-dist/server/index.mjs +162 -204
- package/dist/cli/templates/cmsClient.d.ts +1 -1
- package/dist/cli/templates/cmsClient.d.ts.map +1 -1
- package/dist/cli/templates/cmsClient.js +30 -11
- package/dist/cli/templates/cmsClient.js.map +1 -1
- package/dist/cli/templates/cmsConfig.d.ts +1 -1
- package/dist/cli/templates/cmsConfig.d.ts.map +1 -1
- package/dist/cli/templates/cmsConfig.js +3 -3
- package/dist/client/admin/contentLock.d.ts +4 -4
- package/dist/client/admin/contentLock.d.ts.map +1 -1
- package/dist/client/admin/contentLock.js +1 -1
- package/dist/client/admin/contentLock.js.map +1 -1
- package/dist/client/admin/contentTypes.d.ts +303 -290
- package/dist/client/admin/contentTypes.d.ts.map +1 -1
- package/dist/client/admin/contentTypes.js +101 -9
- package/dist/client/admin/contentTypes.js.map +1 -1
- package/dist/client/admin/entries.d.ts +18 -19
- package/dist/client/admin/entries.d.ts.map +1 -1
- package/dist/client/admin/entries.js +6 -8
- package/dist/client/admin/entries.js.map +1 -1
- package/dist/client/admin/index.d.ts +690 -642
- package/dist/client/admin/index.d.ts.map +1 -1
- package/dist/client/admin/index.js +29 -5
- package/dist/client/admin/index.js.map +1 -1
- package/dist/client/admin/media.d.ts.map +1 -1
- package/dist/client/admin/taxonomies.d.ts.map +1 -1
- package/dist/client/admin/trash.d.ts +3 -4
- package/dist/client/admin/trash.d.ts.map +1 -1
- package/dist/client/admin/types.d.ts +181 -4
- package/dist/client/admin/types.d.ts.map +1 -1
- package/dist/client/admin/validators.d.ts +2009 -25
- package/dist/client/admin/validators.d.ts.map +1 -1
- package/dist/client/admin/validators.js +15 -4
- package/dist/client/admin/validators.js.map +1 -1
- package/dist/client/admin/versions.d.ts +1 -1
- package/dist/client/admin/versions.d.ts.map +1 -1
- package/dist/client/agentTools.d.ts +1 -4
- package/dist/client/agentTools.d.ts.map +1 -1
- package/dist/client/agentTools.js +9 -21
- package/dist/client/agentTools.js.map +1 -1
- package/dist/client/config.d.ts +10 -0
- package/dist/client/config.d.ts.map +1 -1
- package/dist/client/config.js +1 -0
- package/dist/client/config.js.map +1 -1
- package/dist/client/defineContent.d.ts +338 -0
- package/dist/client/defineContent.d.ts.map +1 -0
- package/dist/client/defineContent.js +368 -0
- package/dist/client/defineContent.js.map +1 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/queryBuilder.d.ts +0 -15
- package/dist/client/queryBuilder.d.ts.map +1 -1
- package/dist/client/queryBuilder.js +0 -23
- package/dist/client/queryBuilder.js.map +1 -1
- package/dist/client/registry.d.ts +77 -0
- package/dist/client/registry.d.ts.map +1 -0
- package/dist/client/registry.js +95 -0
- package/dist/client/registry.js.map +1 -0
- package/dist/client/schema/defineContentType.d.ts +36 -24
- package/dist/client/schema/defineContentType.d.ts.map +1 -1
- package/dist/client/schema/defineContentType.js +77 -48
- package/dist/client/schema/defineContentType.js.map +1 -1
- package/dist/client/schema/typedClient.d.ts.map +1 -1
- package/dist/client/schema/typedClient.js +2 -10
- package/dist/client/schema/typedClient.js.map +1 -1
- package/dist/client/schema/types.d.ts +16 -9
- package/dist/client/schema/types.d.ts.map +1 -1
- package/dist/client/schema/types.js.map +1 -1
- package/dist/client/utils/toSlug.d.ts +60 -0
- package/dist/client/utils/toSlug.d.ts.map +1 -0
- package/dist/client/utils/toSlug.js +31 -0
- package/dist/client/utils/toSlug.js.map +1 -0
- package/dist/client/wrapper.d.ts +2 -2
- package/dist/client/wrapper.d.ts.map +1 -1
- package/dist/client/wrapper.js +22 -30
- package/dist/client/wrapper.js.map +1 -1
- package/dist/component/_generated/component.d.ts +24 -28
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/authorizationHooks.d.ts +1 -1
- package/dist/component/authorizationHooks.d.ts.map +1 -1
- package/dist/component/authorizationHooks.js +2 -2
- package/dist/component/authorizationHooks.js.map +1 -1
- package/dist/component/bulkOperations.d.ts.map +1 -1
- package/dist/component/bulkOperations.js +7 -4
- package/dist/component/bulkOperations.js.map +1 -1
- package/dist/component/contentEntries.d.ts +18 -56
- package/dist/component/contentEntries.d.ts.map +1 -1
- package/dist/component/contentEntries.js +45 -137
- package/dist/component/contentEntries.js.map +1 -1
- package/dist/component/contentEntryMutations.d.ts +14 -14
- package/dist/component/contentEntryMutations.d.ts.map +1 -1
- package/dist/component/contentEntryMutations.js +40 -43
- package/dist/component/contentEntryMutations.js.map +1 -1
- package/dist/component/contentEntryValidation.d.ts +3 -3
- package/dist/component/contentEntryValidation.js +6 -9
- package/dist/component/contentEntryValidation.js.map +1 -1
- package/dist/component/contentLock.d.ts +7 -7
- package/dist/component/contentLock.js +3 -3
- package/dist/component/contentLock.js.map +1 -1
- package/dist/component/contentTypeMigration.d.ts +1 -1
- package/dist/component/contentTypeMigration.js +2 -2
- package/dist/component/contentTypeMigration.js.map +1 -1
- package/dist/component/contentTypeMutations.js +2 -2
- package/dist/component/contentTypeMutations.js.map +1 -1
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/convex.config.js +1 -1
- package/dist/component/convex.config.js.map +1 -1
- package/dist/component/eventEmitter.d.ts +0 -1
- package/dist/component/eventEmitter.d.ts.map +1 -1
- package/dist/component/eventEmitter.js.map +1 -1
- package/dist/component/exportImport.d.ts +37 -37
- package/dist/component/exportImport.d.ts.map +1 -1
- package/dist/component/exportImport.js +34 -34
- package/dist/component/exportImport.js.map +1 -1
- package/dist/component/lib/deepReferenceResolver.d.ts +2 -2
- package/dist/component/lib/deepReferenceResolver.d.ts.map +1 -1
- package/dist/component/lib/deepReferenceResolver.js +13 -8
- package/dist/component/lib/deepReferenceResolver.js.map +1 -1
- package/dist/component/lib/ragContentChunker.d.ts +3 -3
- package/dist/component/lib/ragContentChunker.d.ts.map +1 -1
- package/dist/component/lib/ragContentChunker.js +4 -4
- package/dist/component/lib/ragContentChunker.js.map +1 -1
- package/dist/component/lib/referenceResolver.d.ts.map +1 -1
- package/dist/component/lib/referenceResolver.js +10 -17
- package/dist/component/lib/referenceResolver.js.map +1 -1
- package/dist/component/mediaAssetMutations.js +4 -4
- package/dist/component/mediaAssetMutations.js.map +1 -1
- package/dist/component/ragContentIndexer.d.ts +2 -2
- package/dist/component/ragContentIndexer.d.ts.map +1 -1
- package/dist/component/ragContentIndexer.js +44 -48
- package/dist/component/ragContentIndexer.js.map +1 -1
- package/dist/component/roles.d.ts +3 -3
- package/dist/component/scheduledPublish.d.ts +4 -4
- package/dist/component/scheduledPublish.js +3 -3
- package/dist/component/scheduledPublish.js.map +1 -1
- package/dist/component/schema.d.ts +18 -18
- package/dist/component/schema.js +5 -5
- package/dist/component/schema.js.map +1 -1
- package/dist/component/trash.d.ts +6 -9
- package/dist/component/trash.d.ts.map +1 -1
- package/dist/component/trash.js +12 -36
- package/dist/component/trash.js.map +1 -1
- package/dist/component/userContext.d.ts +1 -2
- package/dist/component/userContext.d.ts.map +1 -1
- package/dist/component/userContext.js +1 -2
- package/dist/component/userContext.js.map +1 -1
- package/dist/component/validators.d.ts +27 -33
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +3 -5
- package/dist/component/validators.js.map +1 -1
- package/dist/component/versionMutations.d.ts +1 -1
- package/dist/component/webhookTrigger.d.ts +14 -14
- package/dist/test.d.ts +30 -30
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +24 -24
- package/dist/test.js.map +1 -1
- package/package.json +1 -1
- package/admin-dist/public/assets/CmsEmptyState-Do_erIgn.js +0 -5
- package/admin-dist/public/assets/CmsPageHeader-qDwPGi48.js +0 -1
- package/admin-dist/public/assets/CmsToolbar-D1-Y-7SK.js +0 -1
- package/admin-dist/public/assets/ContentEntryEditor-CWBiIx52.js +0 -4
- package/admin-dist/public/assets/TaxonomyFilter-CdYQawxb.js +0 -1
- package/admin-dist/public/assets/_contentTypeId-D9VMP6Gs.js +0 -1
- package/admin-dist/public/assets/_entryId-2FlCfqE7.js +0 -1
- package/admin-dist/public/assets/alert-GxZx0y5c.js +0 -1
- package/admin-dist/public/assets/badge-BAlGIjop.js +0 -1
- package/admin-dist/public/assets/command-di7XCqcv.js +0 -1
- package/admin-dist/public/assets/content-D8zELsDG.js +0 -1
- package/admin-dist/public/assets/content-types-BmzD0krT.js +0 -2
- package/admin-dist/public/assets/globals-BvFfH-v9.css +0 -1
- package/admin-dist/public/assets/index-zqfj4T_v.js +0 -1
- package/admin-dist/public/assets/label-B6PPtKR5.js +0 -1
- package/admin-dist/public/assets/link-2-W2fVnVOf.js +0 -1
- package/admin-dist/public/assets/list-F8O0lZXC.js +0 -1
- package/admin-dist/public/assets/main-dZT72bAG.js +0 -97
- package/admin-dist/public/assets/media-CETueFbV.js +0 -1
- package/admin-dist/public/assets/new._contentTypeId-BV2-TyyR.js +0 -1
- package/admin-dist/public/assets/plus-AABQIF0N.js +0 -1
- package/admin-dist/public/assets/scroll-area-CDfk-zrz.js +0 -1
- package/admin-dist/public/assets/select-BuiHcMzS.js +0 -1
- package/admin-dist/public/assets/settings-DBxbYDvn.js +0 -1
- package/admin-dist/public/assets/switch-DiJvolcs.js +0 -1
- package/admin-dist/public/assets/tabs-Cgz6G_Xy.js +0 -1
- package/admin-dist/public/assets/tanstack-adapter-BknsSgra.js +0 -1
- package/admin-dist/public/assets/taxonomies-DOErsLl5.js +0 -1
- package/admin-dist/public/assets/textarea-CgggMxUX.js +0 -1
- package/admin-dist/public/assets/trash-BU4ANuaW.js +0 -1
- package/admin-dist/public/assets/triangle-alert-lvCbwp0s.js +0 -1
- package/admin-dist/public/assets/usePermissions-D7tQowaF.js +0 -1
- package/admin-dist/server/_libs/h3-v2.mjs +0 -277
- package/admin-dist/server/_ssr/CmsButton-DbzfJru_.mjs +0 -125
- package/admin-dist/server/_ssr/CmsEmptyState-CuvcXr3Z.mjs +0 -290
- package/admin-dist/server/_ssr/_tanstack-start-manifest_v-Dk-FIYPN.mjs +0 -4
- package/admin-dist/server/_ssr/content-types-D25lUE-j.mjs +0 -1312
- package/admin-dist/server/_ssr/label-PblVvdRv.mjs +0 -22
- package/admin-dist/server/_ssr/router-x6Ab8T4s.mjs +0 -1622
- package/admin-dist/server/_ssr/select-CrfEkFJw.mjs +0 -142
- package/admin-dist/server/_ssr/textarea-CZVaroMc.mjs +0 -18
package/README.md
CHANGED
|
@@ -1,86 +1,11 @@
|
|
|
1
1
|
# Convex CMS
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/convex-cms)
|
|
4
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
> **Alpha (v0.0.6)** Actively developed. APIs may change. [Report issues](https://github.com/obkaro/convex-cms/issues).
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
If you're building on Convex and need content management, this is the most integrated option:
|
|
10
|
-
|
|
11
|
-
- **Zero infrastructure** — Runs entirely within your Convex deployment
|
|
12
|
-
- **True real-time** — Content updates via Convex subscriptions, not polling
|
|
13
|
-
- **Type-safe** — Code-first schemas with full TypeScript inference
|
|
14
|
-
- **Component isolation** — Separate database tables, versioned independently
|
|
15
|
-
- **Agent-native** — 23 pre-built tools for AI agent integration via `@convex-dev/agent`
|
|
16
|
-
|
|
17
|
-
## Choose Your Path
|
|
18
|
-
|
|
19
|
-
### Need an Admin Interface?
|
|
20
|
-
|
|
21
|
-
Use **`defineAdminAPI`** — one line creates all the backend functions for a working admin UI.
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
Your App Admin UI
|
|
25
|
-
│ │
|
|
26
|
-
└── convex/admin.ts ────────────┘
|
|
27
|
-
defineAdminAPI()
|
|
28
|
-
│
|
|
29
|
-
├── listContentTypes
|
|
30
|
-
├── getEntry
|
|
31
|
-
├── publishEntry
|
|
32
|
-
└── ... (60+ functions across 11 domains)
|
|
33
|
-
│
|
|
34
|
-
▼
|
|
35
|
-
CMS Component
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
→ **[Admin UI Setup Guide](./docs/guides/admin-ui-setup.md)**
|
|
39
|
-
|
|
40
|
-
### Building Custom Content Logic?
|
|
41
|
-
|
|
42
|
-
Use **`createCmsClient`** — full programmatic control with typed methods in your Convex functions.
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
Your Convex Functions
|
|
46
|
-
│
|
|
47
|
-
└── cms.contentEntries.list(ctx, { status: "published" })
|
|
48
|
-
cms.contentTypes.create(ctx, { name: "blog", ... })
|
|
49
|
-
cms.mediaAssets.upload(ctx, { ... })
|
|
50
|
-
│
|
|
51
|
-
▼
|
|
52
|
-
CMS Component
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
→ **[Getting Started Guide](./docs/guides/getting-started.md)**
|
|
56
|
-
|
|
57
|
-
### Want Full Type Safety?
|
|
58
|
-
|
|
59
|
-
Use **code-first schemas** — define content types with Convex validators, get TypeScript inference.
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
const blogPost = defineContentType({
|
|
63
|
-
name: "blog_post",
|
|
64
|
-
validator: v.object({
|
|
65
|
-
title: v.string(),
|
|
66
|
-
content: v.string(),
|
|
67
|
-
}),
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// TypeScript knows entry.data.title is string
|
|
71
|
-
const entry = await cms.typedContentEntries.get<"blog_post">(ctx, id);
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
→ **[Code-First Schema Reference](./docs/api/code-first-schema.md)**
|
|
75
|
-
|
|
76
|
-
### Need Both?
|
|
77
|
-
|
|
78
|
-
**Most apps use both.** This is the typical setup:
|
|
79
|
-
|
|
80
|
-
- `defineAdminAPI` powers the admin interface for content editors
|
|
81
|
-
- `createCmsClient` gives you typed methods for custom queries on your frontend
|
|
82
|
-
|
|
83
|
-
They work together through the same CMS component.
|
|
8
|
+
A headless CMS built as a [Convex Component](https://docs.convex.dev/components). Content management that runs inside your Convex app.
|
|
84
9
|
|
|
85
10
|
## Quick Start
|
|
86
11
|
|
|
@@ -110,35 +35,72 @@ export default app;
|
|
|
110
35
|
**For Custom Functions:** Create a CMS client and use it in your functions
|
|
111
36
|
→ [Full Getting Started Guide](./docs/guides/getting-started.md)
|
|
112
37
|
|
|
38
|
+
## Why Convex CMS?
|
|
39
|
+
|
|
40
|
+
If you're building on Convex and need content management, this is the most integrated option:
|
|
41
|
+
|
|
42
|
+
- **Zero infrastructure.** Runs entirely within your Convex deployment
|
|
43
|
+
- **True real-time.** Content updates via Convex subscriptions, not polling
|
|
44
|
+
- **Type-safe.** Code-first schemas with full TypeScript inference
|
|
45
|
+
- **Component isolation.** Separate database tables, versioned independently
|
|
46
|
+
- **Agent-native.** 23 pre-built tools for AI agent integration via `@convex-dev/agent`
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
| Feature | What it does |
|
|
51
|
+
|---------|--------------|
|
|
52
|
+
| **Code-first config** | Define content types in TypeScript with full type inference |
|
|
53
|
+
| **UI-defined config** | Create and modify content types through the admin interface |
|
|
54
|
+
| **CMS Client** | Programmatic access via `createCmsClient` for custom queries and mutations |
|
|
55
|
+
| **Admin API** | Pre-built functions via `defineAdminAPI` that power the admin UI |
|
|
56
|
+
| **CLI Admin UI** | Run `npx convex-cms admin` for local development |
|
|
57
|
+
| **Embedded Admin UI** | Ship the admin interface as part of your React app |
|
|
58
|
+
|
|
59
|
+
Any combination of these features works together seamlessly. Pick what fits your workflow.
|
|
60
|
+
|
|
61
|
+
## In Practice
|
|
62
|
+
|
|
63
|
+
**Full control over the editorial experience?**
|
|
64
|
+
Code-first config + CMS Client. You define the schema in TypeScript and build exactly the UI you want.
|
|
65
|
+
|
|
66
|
+
**Ship fast with a ready-made admin?**
|
|
67
|
+
Code-first config + Admin API + Embedded Admin UI. Type-safe schemas with a working admin interface out of the box.
|
|
68
|
+
|
|
69
|
+
**Content team needs to modify schemas without deploys?**
|
|
70
|
+
UI-defined config + Admin API + Embedded Admin UI. Non-developers can add fields and content types.
|
|
71
|
+
|
|
72
|
+
**Automated content pipelines?**
|
|
73
|
+
CMS Client + agent tools. 23 pre-built tools for AI-driven workflows.
|
|
74
|
+
|
|
113
75
|
## What's Included
|
|
114
76
|
|
|
115
77
|
### Core Content
|
|
116
|
-
- **13 field types
|
|
117
|
-
- **Publishing workflows
|
|
118
|
-
- **Content versioning
|
|
119
|
-
- **Scheduled publishing
|
|
78
|
+
- **13 field types.** text, richText, number, boolean, date, datetime, select, multiSelect, reference, media, json, tags, category
|
|
79
|
+
- **Publishing workflows.** Draft → scheduled → published with version history
|
|
80
|
+
- **Content versioning.** Snapshots, comparison, and rollback
|
|
81
|
+
- **Scheduled publishing.** Convex scheduler integration for future publish dates
|
|
120
82
|
|
|
121
83
|
### Media Management
|
|
122
|
-
- **File uploads
|
|
123
|
-
- **Image variants
|
|
124
|
-
- **Metadata & tagging
|
|
84
|
+
- **File uploads.** Direct to Convex storage with folder organization
|
|
85
|
+
- **Image variants.** Automatic resizing and format conversion
|
|
86
|
+
- **Metadata & tagging.** Alt text, descriptions, taxonomy support
|
|
125
87
|
|
|
126
88
|
### Organization
|
|
127
|
-
- **Taxonomies
|
|
128
|
-
- **Content locking
|
|
129
|
-
- **Soft delete & trash
|
|
89
|
+
- **Taxonomies.** Hierarchical categories and flat tags
|
|
90
|
+
- **Content locking.** Prevent concurrent edit conflicts
|
|
91
|
+
- **Soft delete & trash.** Configurable retention with restore
|
|
130
92
|
|
|
131
93
|
### Integration
|
|
132
|
-
- **RBAC
|
|
133
|
-
- **Multi-locale
|
|
134
|
-
- **Webhooks
|
|
135
|
-
- **Event system
|
|
136
|
-
- **Agent tools
|
|
137
|
-
- **Query builder
|
|
94
|
+
- **RBAC.** 4 built-in roles + custom roles with fine-grained permissions
|
|
95
|
+
- **Multi-locale.** Content localization with fallback chains
|
|
96
|
+
- **Webhooks.** Event-driven integration with external systems
|
|
97
|
+
- **Event system.** All mutations emit events for async processing
|
|
98
|
+
- **Agent tools.** 23 pre-built tools with Zod schemas for AI integration
|
|
99
|
+
- **Query builder.** Fluent API for complex content queries
|
|
138
100
|
|
|
139
101
|
### Admin UI
|
|
140
|
-
- **Pre-built React interface
|
|
141
|
-
- **Visual content editing
|
|
102
|
+
- **Pre-built React interface.** CLI mode for development, embeddable for production
|
|
103
|
+
- **Visual content editing.** Rich text, media picker, reference selector
|
|
142
104
|
|
|
143
105
|
## Admin UI Modes
|
|
144
106
|
|
|
@@ -95,7 +95,7 @@ export interface ContentType {
|
|
|
95
95
|
|
|
96
96
|
export interface ContentEntry {
|
|
97
97
|
_id: string
|
|
98
|
-
|
|
98
|
+
contentTypeName: string
|
|
99
99
|
slug: string
|
|
100
100
|
status: 'draft' | 'published' | 'scheduled' | 'archived'
|
|
101
101
|
data: Record<string, unknown>
|
|
@@ -645,7 +645,7 @@ export function ContentEntryEditor({
|
|
|
645
645
|
})) as ContentEntry
|
|
646
646
|
} else {
|
|
647
647
|
savedEntry = (await createEntry({
|
|
648
|
-
|
|
648
|
+
contentTypeName: contentType.name,
|
|
649
649
|
data: dataForBackend,
|
|
650
650
|
})) as ContentEntry
|
|
651
651
|
}
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
List,
|
|
33
33
|
Tag,
|
|
34
34
|
FolderOpen,
|
|
35
|
+
Code2,
|
|
35
36
|
} from "lucide-react";
|
|
36
37
|
import { cn } from "~/lib/cn";
|
|
37
38
|
import { BreakingChangesWarningDialog } from "./BreakingChangesWarningDialog";
|
|
@@ -139,7 +140,7 @@ interface ContentTypeFormModalProps {
|
|
|
139
140
|
onClose: () => void;
|
|
140
141
|
onCreated?: (contentType: unknown) => void;
|
|
141
142
|
onUpdated?: (contentType: unknown) => void;
|
|
142
|
-
contentType?: ContentType | null;
|
|
143
|
+
contentType?: (ContentType & { source?: "code" | "database" }) | null;
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
function generateMachineName(displayName: string): string {
|
|
@@ -164,6 +165,8 @@ export function ContentTypeFormModal({
|
|
|
164
165
|
contentType,
|
|
165
166
|
}: ContentTypeFormModalProps) {
|
|
166
167
|
const isEditing = !!contentType;
|
|
168
|
+
const isCodeDefined = contentType?.source === "code";
|
|
169
|
+
const isReadOnly = isCodeDefined;
|
|
167
170
|
|
|
168
171
|
const [displayName, setDisplayName] = useState("");
|
|
169
172
|
const [machineName, setMachineName] = useState("");
|
|
@@ -512,34 +515,62 @@ export function ContentTypeFormModal({
|
|
|
512
515
|
<CmsDialog
|
|
513
516
|
open={isOpen}
|
|
514
517
|
onOpenChange={(open) => !open && handleClose()}
|
|
515
|
-
title={
|
|
518
|
+
title={
|
|
519
|
+
isCodeDefined
|
|
520
|
+
? "View Content Type"
|
|
521
|
+
: isEditing
|
|
522
|
+
? "Edit Content Type"
|
|
523
|
+
: "Create Content Type"
|
|
524
|
+
}
|
|
516
525
|
size="2xl"
|
|
517
526
|
footer={
|
|
518
|
-
|
|
519
|
-
<CmsButton
|
|
520
|
-
|
|
521
|
-
onClick={handleClose}
|
|
522
|
-
disabled={isSubmitting}
|
|
523
|
-
>
|
|
524
|
-
Cancel
|
|
525
|
-
</CmsButton>
|
|
526
|
-
<CmsButton
|
|
527
|
-
variant="primary"
|
|
528
|
-
onClick={handleSubmit}
|
|
529
|
-
disabled={validationErrors.length > 0}
|
|
530
|
-
loading={isSubmitting}
|
|
531
|
-
data-testid={
|
|
532
|
-
isEditing
|
|
533
|
-
? "update-content-type-submit"
|
|
534
|
-
: "create-content-type-submit"
|
|
535
|
-
}
|
|
536
|
-
>
|
|
537
|
-
{isEditing ? "Save Changes" : "Create Content Type"}
|
|
527
|
+
isReadOnly ? (
|
|
528
|
+
<CmsButton variant="outline" onClick={handleClose}>
|
|
529
|
+
Close
|
|
538
530
|
</CmsButton>
|
|
539
|
-
|
|
531
|
+
) : (
|
|
532
|
+
<>
|
|
533
|
+
<CmsButton
|
|
534
|
+
variant="outline"
|
|
535
|
+
onClick={handleClose}
|
|
536
|
+
disabled={isSubmitting}
|
|
537
|
+
>
|
|
538
|
+
Cancel
|
|
539
|
+
</CmsButton>
|
|
540
|
+
<CmsButton
|
|
541
|
+
variant="primary"
|
|
542
|
+
onClick={handleSubmit}
|
|
543
|
+
disabled={validationErrors.length > 0}
|
|
544
|
+
loading={isSubmitting}
|
|
545
|
+
data-testid={
|
|
546
|
+
isEditing
|
|
547
|
+
? "update-content-type-submit"
|
|
548
|
+
: "create-content-type-submit"
|
|
549
|
+
}
|
|
550
|
+
>
|
|
551
|
+
{isEditing ? "Save Changes" : "Create Content Type"}
|
|
552
|
+
</CmsButton>
|
|
553
|
+
</>
|
|
554
|
+
)
|
|
540
555
|
}
|
|
541
556
|
>
|
|
542
557
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
558
|
+
{isCodeDefined && (
|
|
559
|
+
<div className="flex items-start gap-3 rounded-lg border border-violet-200 bg-violet-50 p-3">
|
|
560
|
+
<Code2 className="mt-0.5 size-5 shrink-0 text-violet-600" />
|
|
561
|
+
<div className="space-y-1">
|
|
562
|
+
<p className="text-sm font-medium text-violet-900">
|
|
563
|
+
Managed by Code
|
|
564
|
+
</p>
|
|
565
|
+
<p className="text-sm text-violet-700">
|
|
566
|
+
This content type is defined in your codebase and cannot be
|
|
567
|
+
edited through the admin interface. To make changes, update
|
|
568
|
+
the definition in your code.
|
|
569
|
+
</p>
|
|
570
|
+
</div>
|
|
571
|
+
</div>
|
|
572
|
+
)}
|
|
573
|
+
|
|
543
574
|
{/* Basic Info Section */}
|
|
544
575
|
<div className="space-y-4">
|
|
545
576
|
<h4 className="text-sm font-semibold text-foreground">
|
|
@@ -555,8 +586,8 @@ export function ContentTypeFormModal({
|
|
|
555
586
|
value={displayName}
|
|
556
587
|
onChange={(e) => handleDisplayNameChange(e.target.value)}
|
|
557
588
|
placeholder="e.g., Blog Post"
|
|
558
|
-
disabled={isSubmitting}
|
|
559
|
-
autoFocus
|
|
589
|
+
disabled={isSubmitting || isReadOnly}
|
|
590
|
+
autoFocus={!isReadOnly}
|
|
560
591
|
data-testid="display-name-input"
|
|
561
592
|
/>
|
|
562
593
|
</div>
|
|
@@ -570,7 +601,7 @@ export function ContentTypeFormModal({
|
|
|
570
601
|
value={machineName}
|
|
571
602
|
onChange={(e) => handleMachineNameChange(e.target.value)}
|
|
572
603
|
placeholder="e.g., blog_post"
|
|
573
|
-
disabled={isSubmitting || isEditing}
|
|
604
|
+
disabled={isSubmitting || isEditing || isReadOnly}
|
|
574
605
|
className={cn(
|
|
575
606
|
!isValidMachineName(machineName) &&
|
|
576
607
|
machineName &&
|
|
@@ -592,7 +623,7 @@ export function ContentTypeFormModal({
|
|
|
592
623
|
value={description}
|
|
593
624
|
onChange={(e) => setDescription(e.target.value)}
|
|
594
625
|
placeholder="Optional description of this content type"
|
|
595
|
-
disabled={isSubmitting}
|
|
626
|
+
disabled={isSubmitting || isReadOnly}
|
|
596
627
|
rows={2}
|
|
597
628
|
/>
|
|
598
629
|
</div>
|
|
@@ -602,7 +633,7 @@ export function ContentTypeFormModal({
|
|
|
602
633
|
id="singleton"
|
|
603
634
|
checked={singleton}
|
|
604
635
|
onCheckedChange={(checked) => setSingleton(checked as boolean)}
|
|
605
|
-
disabled={isSubmitting}
|
|
636
|
+
disabled={isSubmitting || isReadOnly}
|
|
606
637
|
/>
|
|
607
638
|
<Label htmlFor="singleton" className="cursor-pointer">
|
|
608
639
|
Singleton (only one entry allowed)
|
|
@@ -614,17 +645,19 @@ export function ContentTypeFormModal({
|
|
|
614
645
|
<div className="space-y-4">
|
|
615
646
|
<div className="flex items-center justify-between">
|
|
616
647
|
<h4 className="text-sm font-semibold text-foreground">Fields</h4>
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
648
|
+
{!isReadOnly && (
|
|
649
|
+
<CmsButton
|
|
650
|
+
type="button"
|
|
651
|
+
variant="secondary"
|
|
652
|
+
size="sm"
|
|
653
|
+
onClick={addField}
|
|
654
|
+
disabled={isSubmitting}
|
|
655
|
+
data-testid="add-field-button"
|
|
656
|
+
>
|
|
657
|
+
<Plus className="size-3.5" />
|
|
658
|
+
Add Field
|
|
659
|
+
</CmsButton>
|
|
660
|
+
)}
|
|
628
661
|
</div>
|
|
629
662
|
|
|
630
663
|
<div className="space-y-2">
|
|
@@ -632,41 +665,46 @@ export function ContentTypeFormModal({
|
|
|
632
665
|
<div
|
|
633
666
|
key={index}
|
|
634
667
|
className={cn(
|
|
635
|
-
"flex
|
|
668
|
+
"flex items-center gap-2 rounded-lg border p-2 transition-colors",
|
|
669
|
+
!isReadOnly && "cursor-pointer hover:bg-muted/50",
|
|
636
670
|
activeFieldIndex === index && "border-primary bg-primary/5",
|
|
637
671
|
)}
|
|
638
672
|
onClick={() => {
|
|
639
|
-
|
|
640
|
-
|
|
673
|
+
if (!isReadOnly) {
|
|
674
|
+
setActiveFieldIndex(index);
|
|
675
|
+
setShowFieldEditor(true);
|
|
676
|
+
}
|
|
641
677
|
}}
|
|
642
678
|
data-testid={`field-item-${index}`}
|
|
643
679
|
>
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
e
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
<
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
e
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
680
|
+
{!isReadOnly && (
|
|
681
|
+
<div className="flex flex-col gap-0.5">
|
|
682
|
+
{index > 0 && (
|
|
683
|
+
<button
|
|
684
|
+
type="button"
|
|
685
|
+
onClick={(e) => {
|
|
686
|
+
e.stopPropagation();
|
|
687
|
+
moveField(index, index - 1);
|
|
688
|
+
}}
|
|
689
|
+
className="rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
690
|
+
>
|
|
691
|
+
<ChevronUp className="size-3" />
|
|
692
|
+
</button>
|
|
693
|
+
)}
|
|
694
|
+
{index < fields.length - 1 && (
|
|
695
|
+
<button
|
|
696
|
+
type="button"
|
|
697
|
+
onClick={(e) => {
|
|
698
|
+
e.stopPropagation();
|
|
699
|
+
moveField(index, index + 1);
|
|
700
|
+
}}
|
|
701
|
+
className="rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
702
|
+
>
|
|
703
|
+
<ChevronDown className="size-3" />
|
|
704
|
+
</button>
|
|
705
|
+
)}
|
|
706
|
+
</div>
|
|
707
|
+
)}
|
|
670
708
|
|
|
671
709
|
<div className="flex size-8 items-center justify-center rounded bg-muted text-muted-foreground">
|
|
672
710
|
{FIELD_TYPE_INFO[field.type].icon}
|
|
@@ -682,23 +720,25 @@ export function ContentTypeFormModal({
|
|
|
682
720
|
</p>
|
|
683
721
|
</div>
|
|
684
722
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
e
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
723
|
+
{!isReadOnly && (
|
|
724
|
+
<button
|
|
725
|
+
type="button"
|
|
726
|
+
onClick={(e) => {
|
|
727
|
+
e.stopPropagation();
|
|
728
|
+
removeField(index);
|
|
729
|
+
}}
|
|
730
|
+
disabled={isSubmitting || fields.length === 1}
|
|
731
|
+
className="rounded p-1 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive disabled:opacity-50"
|
|
732
|
+
>
|
|
733
|
+
<X className="size-4" />
|
|
734
|
+
</button>
|
|
735
|
+
)}
|
|
696
736
|
</div>
|
|
697
737
|
))}
|
|
698
738
|
</div>
|
|
699
739
|
|
|
700
|
-
{/* Field Editor Panel */}
|
|
701
|
-
{showFieldEditor && activeField && activeFieldIndex !== null && (
|
|
740
|
+
{/* Field Editor Panel - hidden in read-only mode */}
|
|
741
|
+
{!isReadOnly && showFieldEditor && activeField && activeFieldIndex !== null && (
|
|
702
742
|
<div
|
|
703
743
|
className="rounded-lg border bg-muted/30 p-4"
|
|
704
744
|
data-testid="field-editor"
|
|
@@ -113,7 +113,7 @@ export function ReferenceField({
|
|
|
113
113
|
api.admin.listEntries,
|
|
114
114
|
showPicker
|
|
115
115
|
? {
|
|
116
|
-
|
|
116
|
+
contentTypeName: contentTypeFilter || undefined,
|
|
117
117
|
search: searchQuery || undefined,
|
|
118
118
|
paginationOpts: { numItems: 50, cursor: null },
|
|
119
119
|
}
|
|
@@ -124,15 +124,15 @@ export function ReferenceField({
|
|
|
124
124
|
if (!entriesResult?.page) return []
|
|
125
125
|
if (allowedContentTypes.length === 0) return entriesResult.page
|
|
126
126
|
|
|
127
|
-
const
|
|
127
|
+
const allowedNames = filteredContentTypes.map((ct) => ct.name)
|
|
128
128
|
return entriesResult.page.filter((entry) =>
|
|
129
|
-
|
|
129
|
+
allowedNames.includes(entry.contentTypeName)
|
|
130
130
|
)
|
|
131
131
|
}, [entriesResult?.page, allowedContentTypes, filteredContentTypes])
|
|
132
132
|
|
|
133
|
-
const
|
|
134
|
-
(
|
|
135
|
-
const ct = contentTypes?.page?.find((c) => c.
|
|
133
|
+
const getContentTypeDisplayName = useCallback(
|
|
134
|
+
(contentTypeName: string) => {
|
|
135
|
+
const ct = contentTypes?.page?.find((c) => c.name === contentTypeName)
|
|
136
136
|
return ct?.displayName || ct?.name || 'Unknown'
|
|
137
137
|
},
|
|
138
138
|
[contentTypes?.page]
|
|
@@ -177,7 +177,7 @@ export function ReferenceField({
|
|
|
177
177
|
data?: Record<string, unknown>
|
|
178
178
|
slug?: string
|
|
179
179
|
status: string
|
|
180
|
-
|
|
180
|
+
contentTypeName: string
|
|
181
181
|
},
|
|
182
182
|
showRemove = true
|
|
183
183
|
) => (
|
|
@@ -194,7 +194,7 @@ export function ReferenceField({
|
|
|
194
194
|
</p>
|
|
195
195
|
<div className="flex items-center gap-2">
|
|
196
196
|
<span className="text-xs text-muted-foreground">
|
|
197
|
-
{
|
|
197
|
+
{getContentTypeDisplayName(entry.contentTypeName)}
|
|
198
198
|
</span>
|
|
199
199
|
<CmsStatusBadge status={entry.status as ContentStatus} />
|
|
200
200
|
</div>
|
|
@@ -329,7 +329,7 @@ export function ReferenceField({
|
|
|
329
329
|
</p>
|
|
330
330
|
<div className="flex items-center gap-2">
|
|
331
331
|
<span className="text-xs text-muted-foreground">
|
|
332
|
-
{
|
|
332
|
+
{getContentTypeDisplayName(entry.contentTypeName)}
|
|
333
333
|
</span>
|
|
334
334
|
<Badge
|
|
335
335
|
variant="secondary"
|
|
@@ -39,19 +39,11 @@ import { CmsAdminApi } from "~/embed/contexts/ApiContext";
|
|
|
39
39
|
|
|
40
40
|
type ContentType = {
|
|
41
41
|
_id: string;
|
|
42
|
+
name: string;
|
|
42
43
|
displayName: string;
|
|
43
44
|
titleField?: string;
|
|
44
45
|
};
|
|
45
46
|
|
|
46
|
-
type Entry = {
|
|
47
|
-
_id: string;
|
|
48
|
-
contentTypeId: string;
|
|
49
|
-
slug?: string;
|
|
50
|
-
status: string;
|
|
51
|
-
data: Record<string, unknown>;
|
|
52
|
-
_creationTime: number;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
47
|
export interface ContentPageProps {
|
|
56
48
|
api: CmsAdminApi
|
|
57
49
|
navigation: AdminNavigation;
|
|
@@ -70,12 +62,12 @@ export function ContentPage({ api, navigation }: ContentPageProps) {
|
|
|
70
62
|
const isLoadingContentTypes = contentTypesResult === undefined;
|
|
71
63
|
|
|
72
64
|
const entriesResult = useQuery(api.listEntries, {
|
|
73
|
-
|
|
65
|
+
contentTypeName: selectedTypeId || undefined,
|
|
74
66
|
status: selectedStatus || undefined,
|
|
75
67
|
search: searchQuery.trim() || undefined,
|
|
76
68
|
paginationOpts: { numItems: 50, cursor: null },
|
|
77
69
|
});
|
|
78
|
-
const entries
|
|
70
|
+
const entries = entriesResult?.page ?? [];
|
|
79
71
|
const isLoadingEntries = entriesResult === undefined;
|
|
80
72
|
|
|
81
73
|
const isLoading = isLoadingContentTypes || isLoadingEntries;
|
|
@@ -84,16 +76,16 @@ export function ContentPage({ api, navigation }: ContentPageProps) {
|
|
|
84
76
|
navigation.navigateToNewEntry(contentTypeId);
|
|
85
77
|
};
|
|
86
78
|
|
|
87
|
-
const
|
|
88
|
-
const type = contentTypes.find((t) => t.
|
|
89
|
-
return type?.displayName ??
|
|
79
|
+
const getContentTypeDisplayName = (contentTypeName: string) => {
|
|
80
|
+
const type = contentTypes.find((t) => t.name === contentTypeName);
|
|
81
|
+
return type?.displayName ?? contentTypeName;
|
|
90
82
|
};
|
|
91
83
|
|
|
92
84
|
const getEntryTitle = (
|
|
93
85
|
entry: { data: Record<string, unknown> },
|
|
94
|
-
|
|
86
|
+
contentTypeName: string
|
|
95
87
|
) => {
|
|
96
|
-
const type = contentTypes.find((t) => t.
|
|
88
|
+
const type = contentTypes.find((t) => t.name === contentTypeName);
|
|
97
89
|
const titleField = type?.titleField ?? "title";
|
|
98
90
|
const title = entry.data[titleField];
|
|
99
91
|
return typeof title === "string" && title ? title : "Untitled";
|
|
@@ -291,7 +283,7 @@ export function ContentPage({ api, navigation }: ContentPageProps) {
|
|
|
291
283
|
onCheckedChange={(checked) =>
|
|
292
284
|
handleSelectItem(entry._id, checked as boolean)
|
|
293
285
|
}
|
|
294
|
-
aria-label={`Select ${getEntryTitle(entry, entry.
|
|
286
|
+
aria-label={`Select ${getEntryTitle(entry, entry.contentTypeName)}`}
|
|
295
287
|
/>
|
|
296
288
|
</td>
|
|
297
289
|
<td className="p-3">
|
|
@@ -301,7 +293,7 @@ export function ContentPage({ api, navigation }: ContentPageProps) {
|
|
|
301
293
|
className="block text-left"
|
|
302
294
|
>
|
|
303
295
|
<span className="font-medium text-foreground hover:text-primary">
|
|
304
|
-
{getEntryTitle(entry, entry.
|
|
296
|
+
{getEntryTitle(entry, entry.contentTypeName)}
|
|
305
297
|
</span>
|
|
306
298
|
<span className="block text-xs text-muted-foreground">
|
|
307
299
|
{entry.slug}
|
|
@@ -309,7 +301,7 @@ export function ContentPage({ api, navigation }: ContentPageProps) {
|
|
|
309
301
|
</button>
|
|
310
302
|
</td>
|
|
311
303
|
<td className="p-3 text-sm text-muted-foreground">
|
|
312
|
-
{
|
|
304
|
+
{getContentTypeDisplayName(entry.contentTypeName)}
|
|
313
305
|
</td>
|
|
314
306
|
<td className="p-3">
|
|
315
307
|
<CmsStatusBadge status={entry.status as ContentStatus} />
|