convex-cms 0.0.7 → 0.0.9-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/README.md +16 -6
  2. package/admin/src/components/BulkActionBar.tsx +6 -5
  3. package/admin/src/components/ContentEntryEditor.tsx +10 -9
  4. package/admin/src/components/ContentTypeFormModal.tsx +4 -3
  5. package/admin/src/components/Sidebar.tsx +3 -2
  6. package/admin/src/components/TaxonomyEditor.tsx +4 -3
  7. package/admin/src/components/TermTree.tsx +8 -6
  8. package/admin/src/components/VersionCompare.tsx +3 -2
  9. package/admin/src/components/VersionHistory.tsx +4 -3
  10. package/admin/src/components/fields/CategoryField.tsx +3 -2
  11. package/admin/src/components/fields/MediaField.tsx +6 -5
  12. package/admin/src/components/fields/ReferenceField.tsx +6 -5
  13. package/admin/src/components/fields/TagField.tsx +5 -4
  14. package/admin/src/components/filters/TaxonomyFilter.tsx +4 -3
  15. package/admin/src/components/media/MediaAssetEditDialog.tsx +4 -3
  16. package/admin/src/components/media/MediaFolderEditDialog.tsx +3 -2
  17. package/admin/src/components/media/MediaMoveModal.tsx +4 -3
  18. package/admin/src/components/media/MediaTaxonomyPicker.tsx +6 -5
  19. package/admin/src/contexts/SettingsConfigContext.tsx +19 -5
  20. package/admin/src/embed/contexts/ApiContext.tsx +6 -2
  21. package/admin/src/routes/__root.tsx +7 -3
  22. package/admin-dist/nitro.json +1 -1
  23. package/admin-dist/public/assets/{CmsEmptyState-6-PLaXtD.js → CmsEmptyState-BM4e6N83.js} +1 -1
  24. package/admin-dist/public/assets/{CmsPageHeader-SoF4Epu9.js → CmsPageHeader-uor3DPIk.js} +1 -1
  25. package/admin-dist/public/assets/{CmsStatusBadge-D7kYaohx.js → CmsStatusBadge-D8N18LJx.js} +1 -1
  26. package/admin-dist/public/assets/{CmsSurface-BvksBm6W.js → CmsSurface-BEcY-WpI.js} +1 -1
  27. package/admin-dist/public/assets/{CmsToolbar-DlZPMe2B.js → CmsToolbar-DE-bu3W8.js} +1 -1
  28. package/admin-dist/public/assets/ContentEntryEditor-BdkIMCUk.js +4 -0
  29. package/admin-dist/public/assets/TaxonomyFilter-a1-O9DPs.js +1 -0
  30. package/admin-dist/public/assets/{_contentTypeId-DTv8UoTp.js → _contentTypeId-XIkYOLyY.js} +1 -1
  31. package/admin-dist/public/assets/{_entryId-D3lr5Dvy.js → _entryId-DyP15QpI.js} +1 -1
  32. package/admin-dist/public/assets/{alert-BAHTL6ao.js → alert-DHBQuuib.js} +1 -1
  33. package/admin-dist/public/assets/{badge-oJv4Eai8.js → badge-BOhWFWzb.js} +1 -1
  34. package/admin-dist/public/assets/{circle-check-big-3OHxNDhO.js → circle-check-big-DjTNapen.js} +1 -1
  35. package/admin-dist/public/assets/{command-DwgQs69u.js → command-BIc5_8gL.js} +1 -1
  36. package/admin-dist/public/assets/content-C3N8Ugra.js +1 -0
  37. package/admin-dist/public/assets/content-types-D0wh1eUF.js +1 -0
  38. package/admin-dist/public/assets/{index-DOkgTSx0.js → index-B-g3F_ri.js} +1 -1
  39. package/admin-dist/public/assets/main-BapBJgQD.js +102 -0
  40. package/admin-dist/public/assets/media-8uh1MwDi.js +1 -0
  41. package/admin-dist/public/assets/{new._contentTypeId-VF63rpic.js → new._contentTypeId-S96rFbgY.js} +1 -1
  42. package/admin-dist/public/assets/{pencil-CX1CiTDD.js → pencil-DgaZav4e.js} +1 -1
  43. package/admin-dist/public/assets/{refresh-cw-Cm-YOeFI.js → refresh-cw-BBut4hAU.js} +1 -1
  44. package/admin-dist/public/assets/{rotate-ccw-B45JsL5f.js → rotate-ccw-DVCkojZZ.js} +1 -1
  45. package/admin-dist/public/assets/{scroll-area-b3A1HHR7.js → scroll-area-DPC4uXzf.js} +1 -1
  46. package/admin-dist/public/assets/{search-DKKh_DdH.js → search-CSyHHglh.js} +1 -1
  47. package/admin-dist/public/assets/{settings-CGVDEV1r.js → settings-cEqPsoJ0.js} +1 -1
  48. package/admin-dist/public/assets/{switch-BTMY8Qnk.js → switch-O2BviO8Q.js} +1 -1
  49. package/admin-dist/public/assets/{tabs-DUQwUoIb.js → tabs-p1MWhOqY.js} +1 -1
  50. package/admin-dist/public/assets/{tanstack-adapter-f7AHmQ5L.js → tanstack-adapter-CDrxoPZD.js} +1 -1
  51. package/admin-dist/public/assets/taxonomies-DJ9UbjXW.js +1 -0
  52. package/admin-dist/public/assets/{trash-D7e0uKd9.js → trash-RnpP6lXF.js} +1 -1
  53. package/admin-dist/public/assets/{useBreadcrumbLabel-CF2KYwsw.js → useBreadcrumbLabel-zbIWXlkc.js} +1 -1
  54. package/admin-dist/public/assets/{usePermissions-DWBImEOW.js → usePermissions-4CTlK-vU.js} +1 -1
  55. package/admin-dist/server/_chunks/_libs/@floating-ui/core.mjs +71 -65
  56. package/admin-dist/server/_chunks/_libs/@floating-ui/dom.mjs +1 -1
  57. package/admin-dist/server/_chunks/_libs/@floating-ui/utils.mjs +8 -8
  58. package/admin-dist/server/_chunks/_libs/@tanstack/react-router.mjs +12 -30
  59. package/admin-dist/server/_chunks/_libs/@tanstack/react-store.mjs +1 -56
  60. package/admin-dist/server/_chunks/_libs/@tanstack/router-core.mjs +130 -114
  61. package/admin-dist/server/_chunks/_libs/@tanstack/store.mjs +1 -134
  62. package/admin-dist/server/_chunks/_libs/react-dom.mjs +5 -5
  63. package/admin-dist/server/_chunks/_libs/react.mjs +1 -1
  64. package/admin-dist/server/_libs/isbot.mjs +1 -1
  65. package/admin-dist/server/_libs/use-sync-external-store.mjs +1 -76
  66. package/admin-dist/server/_ssr/{CmsEmptyState-BM8DghTl.mjs → CmsEmptyState-BA0Lc5xs.mjs} +1 -1
  67. package/admin-dist/server/_ssr/{CmsPageHeader-BHUmrIWD.mjs → CmsPageHeader-PMyecILZ.mjs} +1 -1
  68. package/admin-dist/server/_ssr/{CmsStatusBadge-D0Zb0oRl.mjs → CmsStatusBadge-CInuN2bZ.mjs} +2 -2
  69. package/admin-dist/server/_ssr/{CmsSurface-B2eBr-47.mjs → CmsSurface-CH1PIfcS.mjs} +1 -1
  70. package/admin-dist/server/_ssr/{CmsToolbar-BCrwg7OL.mjs → CmsToolbar-IuhSA7gR.mjs} +1 -1
  71. package/admin-dist/server/_ssr/{ContentEntryEditor-Cjfm0uhr.mjs → ContentEntryEditor-Bzhir4fQ.mjs} +38 -31
  72. package/admin-dist/server/_ssr/{TaxonomyFilter-C4pD0kfM.mjs → TaxonomyFilter-r4izSMBh.mjs} +6 -5
  73. package/admin-dist/server/_ssr/{_contentTypeId-CiDiX-p7.mjs → _contentTypeId-BWEbjqxY.mjs} +11 -13
  74. package/admin-dist/server/_ssr/{_entryId-9GxatOkL.mjs → _entryId-B5xoXoJf.mjs} +12 -14
  75. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-CBTan6ii.mjs +4 -0
  76. package/admin-dist/server/_ssr/{badge-EI998zba.mjs → badge-DXrjBRqZ.mjs} +1 -1
  77. package/admin-dist/server/_ssr/{command-BLAWQhUw.mjs → command-Cj90OdCX.mjs} +1 -1
  78. package/admin-dist/server/_ssr/config.server-BOr7Jxr4.mjs +0 -3
  79. package/admin-dist/server/_ssr/{content-BHX39L4D.mjs → content-DKRI-YqL.mjs} +14 -15
  80. package/admin-dist/server/_ssr/{content-types-DCzrBhTH.mjs → content-types-BzgRcS8K.mjs} +7 -9
  81. package/admin-dist/server/_ssr/{index-DwM_5VNP.mjs → index-BPf6_agY.mjs} +4 -6
  82. package/admin-dist/server/_ssr/index.mjs +11 -10
  83. package/admin-dist/server/_ssr/{media-CbzgTRRQ.mjs → media-MpjxOZL8.mjs} +23 -21
  84. package/admin-dist/server/_ssr/{new._contentTypeId-6Ph-Gtlw.mjs → new._contentTypeId-DSb4qR9j.mjs} +11 -13
  85. package/admin-dist/server/_ssr/{router-vd1nySeP.mjs → router-Dk9ikPNc.mjs} +61 -47
  86. package/admin-dist/server/_ssr/{scroll-area--B9snFTJ.mjs → scroll-area-JwVD_6MZ.mjs} +1 -1
  87. package/admin-dist/server/_ssr/{settings-DlTO2JSj.mjs → settings-KVJNe0GM.mjs} +8 -10
  88. package/admin-dist/server/_ssr/{switch-C05NgNW0.mjs → switch-DvREvRv4.mjs} +1 -1
  89. package/admin-dist/server/_ssr/{tabs-DAk2J5xy.mjs → tabs-B0h57pFf.mjs} +1 -1
  90. package/admin-dist/server/_ssr/{tanstack-adapter-DWbaPByn.mjs → tanstack-adapter-gmM64LnW.mjs} +1 -1
  91. package/admin-dist/server/_ssr/{taxonomies-B8nqce6u.mjs → taxonomies-BbBNx260.mjs} +17 -16
  92. package/admin-dist/server/_ssr/{trash-zdlZgpTo.mjs → trash-JAzYGh7A.mjs} +8 -10
  93. package/admin-dist/server/_ssr/{useBreadcrumbLabel-DpEKyG1h.mjs → useBreadcrumbLabel-BWIujj97.mjs} +1 -1
  94. package/admin-dist/server/_ssr/{usePermissions-olYRd9S9.mjs → usePermissions-CcLDCSwa.mjs} +1 -1
  95. package/admin-dist/server/index.mjs +162 -162
  96. package/dist/cli/commands/init.d.ts.map +1 -1
  97. package/dist/cli/commands/init.js +18 -29
  98. package/dist/cli/commands/init.js.map +1 -1
  99. package/dist/cli/templates/admin.d.ts +4 -3
  100. package/dist/cli/templates/admin.d.ts.map +1 -1
  101. package/dist/cli/templates/admin.js +152 -195
  102. package/dist/cli/templates/admin.js.map +1 -1
  103. package/dist/cli/templates/cmsClient.d.ts +2 -2
  104. package/dist/cli/templates/cmsClient.d.ts.map +1 -1
  105. package/dist/cli/templates/cmsClient.js +58 -27
  106. package/dist/cli/templates/cmsClient.js.map +1 -1
  107. package/dist/cli/templates/content.d.ts +10 -0
  108. package/dist/cli/templates/content.d.ts.map +1 -0
  109. package/dist/cli/templates/content.js +257 -0
  110. package/dist/cli/templates/content.js.map +1 -0
  111. package/dist/cli/templates/index.d.ts +3 -2
  112. package/dist/cli/templates/index.d.ts.map +1 -1
  113. package/dist/cli/templates/index.js +3 -2
  114. package/dist/cli/templates/index.js.map +1 -1
  115. package/dist/cli/templates/schemas/blog.d.ts +4 -3
  116. package/dist/cli/templates/schemas/blog.d.ts.map +1 -1
  117. package/dist/cli/templates/schemas/blog.js +44 -13
  118. package/dist/cli/templates/schemas/blog.js.map +1 -1
  119. package/dist/cli/templates/schemas/docs.d.ts +4 -3
  120. package/dist/cli/templates/schemas/docs.d.ts.map +1 -1
  121. package/dist/cli/templates/schemas/docs.js +43 -12
  122. package/dist/cli/templates/schemas/docs.js.map +1 -1
  123. package/dist/cli/templates/schemas/index.d.ts +6 -4
  124. package/dist/cli/templates/schemas/index.d.ts.map +1 -1
  125. package/dist/cli/templates/schemas/index.js +6 -4
  126. package/dist/cli/templates/schemas/index.js.map +1 -1
  127. package/dist/cli/templates/schemas/landing.d.ts +4 -3
  128. package/dist/cli/templates/schemas/landing.d.ts.map +1 -1
  129. package/dist/cli/templates/schemas/landing.js +62 -15
  130. package/dist/cli/templates/schemas/landing.js.map +1 -1
  131. package/dist/cli/utils/fileUtils.d.ts.map +1 -1
  132. package/dist/cli/utils/fileUtils.js +6 -5
  133. package/dist/cli/utils/fileUtils.js.map +1 -1
  134. package/dist/client/admin/index.d.ts +2 -1
  135. package/dist/client/admin/index.d.ts.map +1 -1
  136. package/dist/client/admin/index.js.map +1 -1
  137. package/dist/client/admin/types.d.ts +14 -1
  138. package/dist/client/admin/types.d.ts.map +1 -1
  139. package/dist/client/index.d.ts +1 -1
  140. package/dist/client/index.d.ts.map +1 -1
  141. package/dist/client/index.js.map +1 -1
  142. package/dist/react/index.d.ts +176 -0
  143. package/dist/react/index.d.ts.map +1 -1
  144. package/dist/react/index.js +162 -0
  145. package/dist/react/index.js.map +1 -1
  146. package/package.json +2 -2
  147. package/admin-dist/public/assets/ContentEntryEditor-C6n9xLS9.js +0 -4
  148. package/admin-dist/public/assets/TaxonomyFilter-CFX1_g8s.js +0 -1
  149. package/admin-dist/public/assets/content-CKQ4QwW2.js +0 -1
  150. package/admin-dist/public/assets/content-types-BrttaLpc.js +0 -1
  151. package/admin-dist/public/assets/main-DV6oxWnU.js +0 -102
  152. package/admin-dist/public/assets/media-B2i-mCbx.js +0 -1
  153. package/admin-dist/public/assets/taxonomies-DvMppdiD.js +0 -1
  154. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-CC7UrHKE.mjs +0 -4
package/README.md CHANGED
@@ -3,10 +3,14 @@
3
3
  [![npm version](https://badge.fury.io/js/convex-cms.svg)](https://www.npmjs.com/package/convex-cms)
4
4
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
5
 
6
- > **Alpha (v0.0.7)** Actively developed. APIs may change. [Report issues](https://github.com/obkaro/convex-cms/issues).
6
+ > **Alpha (v0.0.8)** Actively developed. APIs may change. [Report issues](https://github.com/obkaro/convex-cms/issues).
7
7
 
8
8
  A headless CMS built as a [Convex Component](https://docs.convex.dev/components). Content management that runs inside your Convex app.
9
9
 
10
+ [![Live Demo](docs/screenshot.png)](https://convex-cms-example.vercel.app)
11
+
12
+ [Live Demo](https://convex-cms-example.vercel.app)
13
+
10
14
  ## Quick Start
11
15
 
12
16
  ### 1. Install
@@ -41,11 +45,11 @@ If you're building on Convex and need content management without the overhead of
41
45
 
42
46
  ### What you get:
43
47
 
44
- - **Typesafe admin API** Admin APIs exported directly from your backend for use in your React queries and mutations
45
- - **Built in admin UI** A well designed admin UI that you can view and edit content from
46
- - **Embeddable content manager** Ability to embed and serve the prebuilt UI as part of you React application
47
- - **Data independence** CMS that lives in your own convex deployment, extendable and customizable with your convex functions
48
- - **Agent-native.** Pre-built tools useful for AI agent integration with `@convex-dev/agent`
48
+ - **Typesafe admin API:** Admin APIs exported directly from your backend for use in your React queries and mutations
49
+ - **Built in admin UI:** A well designed admin UI that you can view and edit content from
50
+ - **Embeddable content manager:** Ability to embed and serve the prebuilt UI as part of you React application
51
+ - **Data independence:** CMS that lives in your own convex deployment, extendable and customizable with your convex functions
52
+ - **Agent-native content management:** Pre-built tools useful for AI agent integration with `@convex-dev/agent`
49
53
 
50
54
  ## Features
51
55
 
@@ -146,3 +150,9 @@ Both modes call the same functions from your `convex/admin.ts`.
146
150
  ## License
147
151
 
148
152
  Apache-2.0
153
+
154
+ ## Support
155
+
156
+ - [GitHub Issues](https://github.com/obkaro/convex-cms/issues): Bug reports and feature requests
157
+ - [Discord Community](https://discord.gg/convex): Chat with other users
158
+ - [Convex Documentation](https://docs.convex.dev): Learn about Convex
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback } from 'react'
2
2
  import { useMutation } from 'convex/react'
3
- import { api } from '../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  import { BulkOperationModal } from './BulkOperationModal'
5
5
  import { CmsButton } from '~/components/cmsds/CmsButton'
6
6
  import { Badge } from '~/components/ui/badge'
@@ -19,6 +19,7 @@ export function BulkActionBar({
19
19
  onClearSelection,
20
20
  onOperationComplete,
21
21
  }: BulkActionBarProps) {
22
+ const api = useApi()
22
23
  const [activeAction, setActiveAction] = useState<BulkAction | null>(null)
23
24
  const [isProcessing, setIsProcessing] = useState(false)
24
25
  const [result, setResult] = useState<{
@@ -27,10 +28,10 @@ export function BulkActionBar({
27
28
  errors?: string[]
28
29
  } | null>(null)
29
30
 
30
- const bulkPublish = useMutation(api.admin.bulkPublish)
31
- const bulkUnpublish = useMutation(api.admin.bulkUnpublish)
32
- const bulkDelete = useMutation(api.admin.bulkDelete)
33
- const bulkUpdate = useMutation(api.admin.bulkUpdate)
31
+ const bulkPublish = useMutation(api.bulkPublish)
32
+ const bulkUnpublish = useMutation(api.bulkUnpublish)
33
+ const bulkDelete = useMutation(api.bulkDelete)
34
+ const bulkUpdate = useMutation(api.bulkUpdate)
34
35
 
35
36
  const handleAction = useCallback((action: BulkAction) => {
36
37
  setActiveAction(action)
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback, useEffect, useRef } from 'react'
2
2
  import { useMutation } from 'convex/react'
3
- import { api } from '../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  import { FieldRenderer } from './fields/FieldRenderer'
5
5
  import { VersionHistory } from './VersionHistory'
6
6
  import type { FieldDefinition, FieldError } from './fields/types'
@@ -188,14 +188,15 @@ export function ContentEntryEditor({
188
188
  const formDataRef = useRef(formData)
189
189
  formDataRef.current = formData
190
190
 
191
- const createEntry = useMutation(api.admin.createEntry)
192
- const updateEntry = useMutation(api.admin.updateEntry)
193
- const publishEntry = useMutation(api.admin.publishEntry)
194
- const unpublishEntry = useMutation(api.admin.unpublishEntry)
195
- const scheduleEntry = useMutation(api.admin.scheduleEntry)
196
- const cancelScheduleEntry = useMutation(api.admin.cancelScheduledEntry)
197
- const deleteEntryMutation = useMutation(api.admin.deleteEntry)
198
- const duplicateEntryMutation = useMutation(api.admin.duplicateEntry)
191
+ const api = useApi()
192
+ const createEntry = useMutation(api.createEntry)
193
+ const updateEntry = useMutation(api.updateEntry)
194
+ const publishEntry = useMutation(api.publishEntry)
195
+ const unpublishEntry = useMutation(api.unpublishEntry)
196
+ const scheduleEntry = useMutation(api.scheduleEntry)
197
+ const cancelScheduleEntry = useMutation(api.cancelScheduledEntry)
198
+ const deleteEntryMutation = useMutation(api.deleteEntry)
199
+ const duplicateEntryMutation = useMutation(api.duplicateEntry)
199
200
 
200
201
  const [showScheduleModal, setShowScheduleModal] = useState(false)
201
202
  const [scheduleDateTime, setScheduleDateTime] = useState<string>(() => {
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback, useMemo, useEffect } from "react";
2
2
  import { useMutation } from "convex/react";
3
- import { api } from "../../convex/_generated/api";
3
+ import { useApi } from "~/embed/contexts/ApiContext";
4
4
  import type { FieldType, ContentType } from "convex-cms/types";
5
5
  import { CmsDialog } from "~/components/cmsds/CmsDialog";
6
6
  import { CmsButton } from "~/components/cmsds/CmsButton";
@@ -191,8 +191,9 @@ export function ContentTypeFormModal({
191
191
  const [showBreakingWarning, setShowBreakingWarning] = useState(false);
192
192
  const [isForceUpdating, setIsForceUpdating] = useState(false);
193
193
 
194
- const createContentType = useMutation(api.admin.createContentType);
195
- const updateContentType = useMutation(api.admin.updateContentType);
194
+ const api = useApi();
195
+ const createContentType = useMutation(api.createContentType);
196
+ const updateContentType = useMutation(api.updateContentType);
196
197
 
197
198
  // Populate form when editing
198
199
  useEffect(() => {
@@ -5,7 +5,7 @@ import { Layers, ChevronDown } from "lucide-react";
5
5
  import { cn } from "~/lib/cn";
6
6
  import { useAdminConfig } from "~/contexts";
7
7
  import { Icon } from "~/lib/icons";
8
- import { api } from "../../convex/_generated/api";
8
+ import { useApi } from "~/embed/contexts/ApiContext";
9
9
  import {
10
10
  Collapsible,
11
11
  CollapsibleTrigger,
@@ -19,10 +19,11 @@ export function Sidebar() {
19
19
  const currentPath = routerState.location.pathname;
20
20
  const config = useAdminConfig();
21
21
  const { navItems, branding, layout } = config;
22
+ const api = useApi();
22
23
 
23
24
  const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
24
25
 
25
- const contentTypesResult = useQuery(api.admin.listContentTypes, {
26
+ const contentTypesResult = useQuery(api.listContentTypes, {
26
27
  isActive: true,
27
28
  });
28
29
  const contentTypes = contentTypesResult?.page ?? [];
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback, useEffect } from 'react'
2
2
  import { useMutation } from 'convex/react'
3
- import { api } from '../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  import { CmsDialog } from '~/components/cmsds/CmsDialog'
5
5
  import { CmsButton } from '~/components/cmsds/CmsButton'
6
6
  import { Input } from '~/components/ui/input'
@@ -49,8 +49,9 @@ export function TaxonomyEditor({
49
49
  const [submitError, setSubmitError] = useState<string | null>(null)
50
50
  const [slugManuallyEdited, setSlugManuallyEdited] = useState(false)
51
51
 
52
- const createTaxonomy = useMutation(api.admin.createTaxonomy)
53
- const updateTaxonomy = useMutation(api.admin.updateTaxonomy)
52
+ const api = useApi()
53
+ const createTaxonomy = useMutation(api.createTaxonomy)
54
+ const updateTaxonomy = useMutation(api.updateTaxonomy)
54
55
 
55
56
  useEffect(() => {
56
57
  if (taxonomy) {
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback } from 'react'
2
2
  import { useQuery, useMutation } from 'convex/react'
3
- import { api } from '../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  import { CmsDialog, CmsConfirmDialog } from '~/components/cmsds/CmsDialog'
5
5
  import { CmsButton } from '~/components/cmsds/CmsButton'
6
6
  import { CmsEmptyState } from '~/components/cmsds/CmsEmptyState'
@@ -34,6 +34,7 @@ export function TermTree({
34
34
  isHierarchical,
35
35
  allowInlineCreation,
36
36
  }: TermTreeProps) {
37
+ const api = useApi()
37
38
  const [expandedTerms, setExpandedTerms] = useState<Set<string>>(new Set())
38
39
  const [editingTerm, setEditingTerm] = useState<Term | null>(null)
39
40
  const [showCreateModal, setShowCreateModal] = useState(false)
@@ -42,10 +43,10 @@ export function TermTree({
42
43
  const [error, setError] = useState<string | null>(null)
43
44
 
44
45
  const termsQuery = isHierarchical
45
- ? useQuery(api.admin.getTermsHierarchy, { taxonomyId })
46
- : useQuery(api.admin.listTerms, { taxonomyId })
46
+ ? useQuery(api.getTermsHierarchy, { taxonomyId })
47
+ : useQuery(api.listTerms, { taxonomyId })
47
48
 
48
- const deleteTerm = useMutation(api.admin.deleteTerm)
49
+ const deleteTerm = useMutation(api.deleteTerm)
49
50
 
50
51
  const terms = (
51
52
  isHierarchical ? termsQuery : (termsQuery as { page?: Term[] })?.page
@@ -304,8 +305,9 @@ function TermEditModal({
304
305
  const [submitError, setSubmitError] = useState<string | null>(null)
305
306
  const [slugManuallyEdited, setSlugManuallyEdited] = useState(false)
306
307
 
307
- const createTerm = useMutation(api.admin.createTerm)
308
- const updateTerm = useMutation(api.admin.updateTerm)
308
+ const api = useApi()
309
+ const createTerm = useMutation(api.createTerm)
310
+ const updateTerm = useMutation(api.updateTerm)
309
311
 
310
312
  const handleChange = useCallback(
311
313
  (field: keyof typeof formData, value: string | number) => {
@@ -1,5 +1,5 @@
1
1
  import { useQuery } from 'convex/react'
2
- import { api } from '../../convex/_generated/api'
2
+ import { useApi } from '~/embed/contexts/ApiContext'
3
3
  import { CmsButton } from '~/components/cmsds/CmsButton'
4
4
  import { Badge } from '~/components/ui/badge'
5
5
  import { ScrollArea } from '~/components/ui/scroll-area'
@@ -47,7 +47,8 @@ export function VersionCompare({
47
47
  onClose,
48
48
  onRollback,
49
49
  }: VersionCompareProps) {
50
- const comparisonQuery = useQuery(api.admin.compareVersions, {
50
+ const api = useApi()
51
+ const comparisonQuery = useQuery(api.compareVersions, {
51
52
  entryId,
52
53
  fromVersionNumber: fromVersion,
53
54
  toVersionNumber: toVersion,
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback } from 'react'
2
2
  import { useQuery, useMutation } from 'convex/react'
3
- import { api } from '../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  import { VersionCompare } from './VersionCompare'
5
5
  import { VersionRollbackModal } from './VersionRollbackModal'
6
6
  import { CmsButton } from '~/components/cmsds/CmsButton'
@@ -34,6 +34,7 @@ export function VersionHistory({
34
34
  onRollbackComplete,
35
35
  onClose,
36
36
  }: VersionHistoryProps) {
37
+ const api = useApi()
37
38
  const [selectedVersions, setSelectedVersions] = useState<
38
39
  [number, number] | null
39
40
  >(null)
@@ -42,12 +43,12 @@ export function VersionHistory({
42
43
  const [rollbackError, setRollbackError] = useState<string | null>(null)
43
44
  const [rollbackSuccess, setRollbackSuccess] = useState(false)
44
45
 
45
- const versionsQuery = useQuery(api.admin.getVersionHistory, {
46
+ const versionsQuery = useQuery(api.getVersionHistory, {
46
47
  entryId,
47
48
  paginationOpts: { numItems: 50, cursor: null },
48
49
  })
49
50
 
50
- const rollbackMutation = useMutation(api.admin.rollbackVersion)
51
+ const rollbackMutation = useMutation(api.rollbackVersion)
51
52
 
52
53
  const versions = (versionsQuery?.page ?? []) as VersionItem[]
53
54
  const isLoading = versionsQuery === undefined
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback, useRef, useEffect } from 'react'
2
2
  import { useQuery } from 'convex/react'
3
- import { api } from '../../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  import { FieldWrapper } from './FieldWrapper'
5
5
  import type { BaseFieldProps } from './types'
6
6
  import { asTaxonomyId } from '../../types'
@@ -41,6 +41,7 @@ export function CategoryField({
41
41
  id,
42
42
  placeholder = 'Select category...',
43
43
  }: CategoryFieldProps) {
44
+ const api = useApi()
44
45
  const fieldId = id || `field-${field.name}`
45
46
  const taxonomyId = field.options?.taxonomyId
46
47
  const allowMultiple = field.options?.allowMultiple ?? false
@@ -52,7 +53,7 @@ export function CategoryField({
52
53
  const containerRef = useRef<HTMLDivElement>(null)
53
54
 
54
55
  const hierarchyResult = useQuery(
55
- api.admin.getTermsHierarchy,
56
+ api.getTermsHierarchy,
56
57
  taxonomyId ? { taxonomyId: asTaxonomyId(taxonomyId) } : 'skip'
57
58
  )
58
59
  const categoryTree = (hierarchyResult ?? []) as CategoryTerm[]
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback } from 'react'
2
2
  import { useQuery } from 'convex/react'
3
- import { api } from '../../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  // IDs are strings when crossing component boundaries
5
5
  import { FieldWrapper } from './FieldWrapper'
6
6
  import type { BaseFieldProps } from './types'
@@ -96,6 +96,7 @@ export function MediaField({
96
96
  id,
97
97
  placeholder = 'Select media...',
98
98
  }: MediaFieldProps) {
99
+ const api = useApi()
99
100
  const fieldId = id || `field-${field.name}`
100
101
  const [showPicker, setShowPicker] = useState(false)
101
102
  const [activeTab, setActiveTab] = useState<string>('browse')
@@ -105,12 +106,12 @@ export function MediaField({
105
106
  const allowedMimeTypes = field.options?.allowedMimeTypes ?? []
106
107
 
107
108
  const selectedAsset = useQuery(
108
- api.admin.getMediaAsset,
109
+ api.getMediaAsset,
109
110
  value ? { id: value } : 'skip'
110
111
  )
111
112
 
112
113
  const assetsResult = useQuery(
113
- api.admin.listMediaAssets,
114
+ api.listMediaAssets,
114
115
  showPicker
115
116
  ? {
116
117
  type: typeFilter
@@ -345,8 +346,8 @@ export function MediaField({
345
346
 
346
347
  <TabsContent value="upload" className="mt-4">
347
348
  <UploadDropzone
348
- generateUploadUrl={api.admin.generateUploadUrl}
349
- createAsset={api.admin.createMediaAsset}
349
+ generateUploadUrl={api.generateUploadUrl}
350
+ createAsset={api.createMediaAsset}
350
351
  onUploadComplete={handleUploadComplete}
351
352
  allowedMimeTypes={allowedMimeTypes}
352
353
  maxFileSize={field.options?.maxFileSize}
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback, useMemo } from 'react'
2
2
  import { useQuery } from 'convex/react'
3
- import { api } from '../../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  import { FieldWrapper } from './FieldWrapper'
5
5
  import type { BaseFieldProps } from './types'
6
6
  import {
@@ -62,6 +62,7 @@ export function ReferenceField({
62
62
  id,
63
63
  placeholder = 'Select content...',
64
64
  }: ReferenceFieldProps) {
65
+ const api = useApi()
65
66
  const fieldId = id || `field-${field.name}`
66
67
  const [showPicker, setShowPicker] = useState(false)
67
68
  const [searchQuery, setSearchQuery] = useState('')
@@ -75,7 +76,7 @@ export function ReferenceField({
75
76
  return Array.isArray(value) ? value : [value]
76
77
  }, [value])
77
78
 
78
- const contentTypes = useQuery(api.admin.listContentTypes, {
79
+ const contentTypes = useQuery(api.listContentTypes, {
79
80
  isActive: true,
80
81
  includeEntryCounts: false,
81
82
  })
@@ -91,12 +92,12 @@ export function ReferenceField({
91
92
  }, [contentTypes?.page, allowedContentTypes])
92
93
 
93
94
  const selectedEntry = useQuery(
94
- api.admin.getEntry,
95
+ api.getEntry,
95
96
  selectedIds.length === 1 ? { id: selectedIds[0] } : 'skip'
96
97
  )
97
98
 
98
99
  const selectedEntries = useQuery(
99
- api.admin.listEntries,
100
+ api.listEntries,
100
101
  selectedIds.length > 1
101
102
  ? {
102
103
  paginationOpts: { numItems: 100, cursor: null },
@@ -110,7 +111,7 @@ export function ReferenceField({
110
111
  }, [selectedEntries?.page, selectedIds])
111
112
 
112
113
  const entriesResult = useQuery(
113
- api.admin.listEntries,
114
+ api.listEntries,
114
115
  showPicker
115
116
  ? {
116
117
  contentTypeName: contentTypeFilter || undefined,
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback, useRef, useEffect, useId } from 'react'
2
2
  import { useQuery, useMutation } from 'convex/react'
3
- import { api } from '../../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  import { FieldWrapper } from './FieldWrapper'
5
5
  import type { BaseFieldProps } from './types'
6
6
  import { asTaxonomyId, asTaxonomyTermIds } from '../../types'
@@ -34,6 +34,7 @@ export function TagField({
34
34
  id,
35
35
  placeholder = 'Add tags...',
36
36
  }: TagFieldProps) {
37
+ const api = useApi()
37
38
  const generatedId = useId()
38
39
  const fieldId = id ?? `field-${field.name}-${generatedId}`
39
40
  const taxonomyId = field.options?.taxonomyId
@@ -51,7 +52,7 @@ export function TagField({
51
52
  const containerRef = useRef<HTMLDivElement>(null)
52
53
 
53
54
  const suggestionsResult = useQuery(
54
- api.admin.suggestTerms,
55
+ api.suggestTerms,
55
56
  taxonomyId
56
57
  ? {
57
58
  taxonomyId: asTaxonomyId(taxonomyId),
@@ -64,7 +65,7 @@ export function TagField({
64
65
  const suggestions = suggestionsResult ?? []
65
66
 
66
67
  const selectedTermsResult = useQuery(
67
- api.admin.listTerms,
68
+ api.listTerms,
68
69
  taxonomyId && value && value.length > 0
69
70
  ? {
70
71
  taxonomyId: asTaxonomyId(taxonomyId),
@@ -82,7 +83,7 @@ export function TagField({
82
83
  }
83
84
  }
84
85
 
85
- const createTermMutation = useMutation(api.admin.createTerm)
86
+ const createTermMutation = useMutation(api.createTerm)
86
87
 
87
88
  useEffect(() => {
88
89
  function handleClickOutside(event: MouseEvent) {
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback, useMemo } from 'react'
2
2
  import { useQuery } from 'convex/react'
3
- import { api } from '../../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  import { CmsButton } from '~/components/cmsds/CmsButton'
5
5
  import {
6
6
  Popover,
@@ -46,10 +46,11 @@ export function TaxonomyFilter({
46
46
  disabled = false,
47
47
  className,
48
48
  }: TaxonomyFilterProps) {
49
+ const api = useApi()
49
50
  const [open, setOpen] = useState(false)
50
51
  const [search, setSearch] = useState('')
51
52
 
52
- const taxonomiesResult = useQuery(api.admin.listTaxonomies, { isActive: true })
53
+ const taxonomiesResult = useQuery(api.listTaxonomies, { isActive: true })
53
54
  const taxonomies = taxonomiesResult?.page ?? []
54
55
 
55
56
  const activeTaxonomy = useMemo(() => {
@@ -62,7 +63,7 @@ export function TaxonomyFilter({
62
63
  const targetTaxonomyId = taxonomyId ?? taxonomies[0]?._id
63
64
 
64
65
  const termsResult = useQuery(
65
- api.admin.listTerms,
66
+ api.listTerms,
66
67
  targetTaxonomyId
67
68
  ? {
68
69
  taxonomyId: targetTaxonomyId,
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback, useEffect } from 'react'
2
2
  import { useMutation, useQuery } from 'convex/react'
3
- import { api } from '../../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  import { CmsDialog } from '~/components/cmsds/CmsDialog'
5
5
  import { CmsButton } from '~/components/cmsds/CmsButton'
6
6
  import { CmsField } from '~/components/cmsds/CmsField'
@@ -30,6 +30,7 @@ export function MediaAssetEditDialog({
30
30
  onOpenChange,
31
31
  onSaved,
32
32
  }: MediaAssetEditDialogProps) {
33
+ const api = useApi()
33
34
  const [name, setName] = useState('')
34
35
  const [title, setTitle] = useState('')
35
36
  const [description, setDescription] = useState('')
@@ -38,9 +39,9 @@ export function MediaAssetEditDialog({
38
39
  const [isSaving, setIsSaving] = useState(false)
39
40
  const [error, setError] = useState('')
40
41
 
41
- const updateAsset = useMutation(api.admin.updateMediaAsset)
42
+ const updateAsset = useMutation(api.updateMediaAsset)
42
43
 
43
- const taxonomiesResult = useQuery(api.admin.listTaxonomies, {
44
+ const taxonomiesResult = useQuery(api.listTaxonomies, {
44
45
  isActive: true,
45
46
  paginationOpts: { numItems: 50, cursor: null },
46
47
  })
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback, useEffect } from 'react'
2
2
  import { useMutation } from 'convex/react'
3
- import { api } from '../../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  import { CmsDialog } from '~/components/cmsds/CmsDialog'
5
5
  import { CmsButton } from '~/components/cmsds/CmsButton'
6
6
  import { CmsField } from '~/components/cmsds/CmsField'
@@ -26,12 +26,13 @@ export function MediaFolderEditDialog({
26
26
  onOpenChange,
27
27
  onSaved,
28
28
  }: MediaFolderEditDialogProps) {
29
+ const api = useApi()
29
30
  const [name, setName] = useState('')
30
31
  const [description, setDescription] = useState('')
31
32
  const [isSaving, setIsSaving] = useState(false)
32
33
  const [error, setError] = useState('')
33
34
 
34
- const updateFolder = useMutation(api.admin.updateMediaFolder)
35
+ const updateFolder = useMutation(api.updateMediaFolder)
35
36
 
36
37
  useEffect(() => {
37
38
  if (folder) {
@@ -1,6 +1,6 @@
1
1
  import { useState, useMemo } from 'react'
2
2
  import { useQuery, useMutation } from 'convex/react'
3
- import { api } from '../../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  import { CmsDialog } from '~/components/cmsds/CmsDialog'
5
5
  import { CmsButton } from '~/components/cmsds/CmsButton'
6
6
  import { cn } from '~/lib/cn'
@@ -28,14 +28,15 @@ export function MediaMoveModal({
28
28
  currentFolderId,
29
29
  onMoved,
30
30
  }: MediaMoveModalProps) {
31
+ const api = useApi()
31
32
  const [selectedFolderId, setSelectedFolderId] = useState<string | undefined>(
32
33
  undefined
33
34
  )
34
35
  const [isMoving, setIsMoving] = useState(false)
35
36
  const [error, setError] = useState('')
36
37
 
37
- const folderTree = useQuery(api.admin.getMediaFolderTree, {})
38
- const moveAssets = useMutation(api.admin.moveMediaAssets)
38
+ const folderTree = useQuery(api.getMediaFolderTree, {})
39
+ const moveAssets = useMutation(api.moveMediaAssets)
39
40
 
40
41
  const sortedFolders = useMemo(() => {
41
42
  if (!folderTree) return []
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback, useRef, useEffect } from 'react'
2
2
  import { useQuery, useMutation } from 'convex/react'
3
- import { api } from '../../../convex/_generated/api'
3
+ import { useApi } from '~/embed/contexts/ApiContext'
4
4
  import { Badge } from '~/components/ui/badge'
5
5
  import { Input } from '~/components/ui/input'
6
6
  import { Button } from '~/components/ui/button'
@@ -32,6 +32,7 @@ export function MediaTaxonomyPicker({
32
32
  disabled = false,
33
33
  className = '',
34
34
  }: MediaTaxonomyPickerProps) {
35
+ const api = useApi()
35
36
  const [inputValue, setInputValue] = useState('')
36
37
  const [showSuggestions, setShowSuggestions] = useState(false)
37
38
  const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(0)
@@ -42,14 +43,14 @@ export function MediaTaxonomyPicker({
42
43
  const suggestionsRef = useRef<HTMLDivElement>(null)
43
44
  const containerRef = useRef<HTMLDivElement>(null)
44
45
 
45
- const currentTerms = useQuery(api.admin.getTermsByMedia, {
46
+ const currentTerms = useQuery(api.getTermsByMedia, {
46
47
  mediaId,
47
48
  taxonomyId,
48
49
  })
49
50
 
50
51
  const selectedTermIds = currentTerms?.map((t: TaxonomyTermDisplay) => t._id) ?? []
51
52
 
52
- const suggestionsResult = useQuery(api.admin.suggestTerms, {
53
+ const suggestionsResult = useQuery(api.suggestTerms, {
53
54
  taxonomyId,
54
55
  query: inputValue,
55
56
  limit: 10,
@@ -57,8 +58,8 @@ export function MediaTaxonomyPicker({
57
58
  })
58
59
  const suggestions = (suggestionsResult ?? []) as TaxonomyTermDisplay[]
59
60
 
60
- const setMediaTermsMutation = useMutation(api.admin.setMediaTerms)
61
- const createTermMutation = useMutation(api.admin.createTermAndAddToMedia)
61
+ const setMediaTermsMutation = useMutation(api.setMediaTerms)
62
+ const createTermMutation = useMutation(api.createTermAndAddToMedia)
62
63
 
63
64
  useEffect(() => {
64
65
  function handleClickOutside(event: MouseEvent) {
@@ -1,13 +1,25 @@
1
1
  import { useQuery } from "convex/react";
2
+ import type { FunctionReference } from "convex/server";
2
3
  import { createContext, useContext, useMemo, type ReactNode } from "react";
3
- import { api as localApi } from "../../convex/_generated/api";
4
4
  import type { AdminConfig } from "~/lib/admin-config";
5
5
  import { AdminConfigProvider } from "./AdminConfigContext";
6
6
 
7
- type Settings = NonNullable<typeof localApi.admin.getSettings._returnType>;
7
+ /**
8
+ * Settings returned by the getSettings query.
9
+ * This type is defined explicitly to avoid importing from convex/_generated.
10
+ */
11
+ export interface Settings {
12
+ features: {
13
+ versioning: boolean;
14
+ scheduling: boolean;
15
+ localization: boolean;
16
+ mediaManagement: boolean;
17
+ };
18
+ }
8
19
 
9
20
  type SettingsApi = {
10
- getSettings: typeof localApi.admin.getSettings;
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ getSettings: FunctionReference<"query", "public", Record<string, never>, Settings | null>;
11
23
  };
12
24
 
13
25
  interface SettingsConfigContextValue {
@@ -26,8 +38,10 @@ export function SettingsConfigProvider({
26
38
  children: ReactNode;
27
39
  api?: SettingsApi;
28
40
  }) {
29
- const resolvedApi = api ?? localApi.admin;
30
- const settings = useQuery(resolvedApi.getSettings);
41
+ // Use skip pattern when api is not provided
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ const queryArg = api ? api.getSettings : ("skip" as any);
44
+ const settings = useQuery(queryArg) as Settings | undefined;
31
45
 
32
46
  const mergedConfig = useMemo((): AdminConfig => {
33
47
  if (!settings) return baseConfig;
@@ -7,9 +7,13 @@
7
7
  */
8
8
 
9
9
  import { createContext, useContext, type ReactNode } from "react";
10
- import { api } from "../../../convex/_generated/api";
10
+ import type { BaseAdminAPI } from "convex-cms";
11
11
 
12
- export type CmsAdminApi = typeof api["admin"];
12
+ // CmsAdminApi accepts BaseAdminAPI with optional namespaced exports.
13
+ // This allows the API to work with Convex's FilterApi which removes
14
+ // non-FunctionReference types (the namespaced objects get filtered out).
15
+ type NamespacedKeys = "stats" | "settings" | "contentTypes" | "entries" | "bulk" | "trash" | "contentLock" | "versions" | "media" | "taxonomies";
16
+ export type CmsAdminApi = Omit<BaseAdminAPI, NamespacedKeys> & Partial<Pick<BaseAdminAPI, NamespacedKeys>>;
13
17
 
14
18
  const ApiContext = createContext<CmsAdminApi | null>(null);
15
19