convex-cms 0.0.7-alpha.0 → 0.0.8
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 +22 -17
- package/admin/src/components/BreakingChangesWarningDialog.tsx +5 -5
- package/admin/src/components/BulkOperationModal.tsx +14 -14
- package/admin/src/components/ContentEntryEditor.tsx +6 -6
- package/admin/src/components/ContentTypeFormModal.tsx +1 -1
- package/admin/src/components/Header.tsx +5 -2
- package/admin/src/components/SchemaDriftWarning.tsx +126 -0
- package/admin/src/components/TaxonomyEditor.tsx +2 -2
- package/admin/src/components/TermTree.tsx +3 -3
- package/admin/src/components/UploadDropzone.tsx +7 -7
- package/admin/src/components/VersionCompare.tsx +13 -13
- package/admin/src/components/VersionHistory.tsx +2 -2
- package/admin/src/components/VersionRollbackModal.tsx +5 -5
- package/admin/src/components/cmsds/CmsButton.tsx +2 -2
- package/admin/src/components/cmsds/CmsDialog.tsx +4 -1
- package/admin/src/components/cmsds/CmsStatusBadge.tsx +5 -5
- package/admin/src/components/fields/JsonField.tsx +1 -1
- package/admin/src/components/fields/TagField.tsx +1 -1
- package/admin/src/contexts/SettingsConfigContext.tsx +10 -3
- package/admin/src/embed/index.tsx +29 -9
- package/admin/src/embed/pages/ContentTypeEntries.tsx +25 -0
- package/admin/src/embed/pages/Entry.tsx +114 -0
- package/admin/src/embed/pages/Media.tsx +3 -1
- package/admin/src/embed/pages/NewEntry.tsx +83 -0
- package/admin/src/embed/pages/index.ts +3 -0
- package/admin/src/pages/ContentPage.tsx +16 -1
- package/admin/src/pages/ContentTypeEntriesPage.tsx +466 -0
- package/admin/src/pages/ContentTypesPage.tsx +3 -3
- package/admin/src/pages/DashboardPage.tsx +3 -0
- package/admin/src/pages/SettingsPage.tsx +4 -4
- package/admin/src/pages/index.ts +1 -0
- package/admin/src/routes/__root.tsx +10 -10
- package/admin/src/styles/globals.css +31 -5
- package/admin/src/styles/tailwind-config.css +25 -0
- package/admin/src/styles/theme.css +50 -0
- package/admin-dist/nitro.json +1 -1
- package/admin-dist/public/assets/{CmsEmptyState-CXVkI3FZ.js → CmsEmptyState-6-PLaXtD.js} +1 -1
- package/admin-dist/public/assets/{CmsPageHeader-DU9fD34s.js → CmsPageHeader-SoF4Epu9.js} +1 -1
- package/admin-dist/public/assets/CmsStatusBadge-D7kYaohx.js +1 -0
- package/admin-dist/public/assets/{CmsSurface-DF7OcKg_.js → CmsSurface-BvksBm6W.js} +1 -1
- package/admin-dist/public/assets/{CmsToolbar-5S8FQrSx.js → CmsToolbar-DlZPMe2B.js} +1 -1
- package/admin-dist/public/assets/ContentEntryEditor-C6n9xLS9.js +4 -0
- package/admin-dist/public/assets/{TaxonomyFilter-DEN2Q9Lo.js → TaxonomyFilter-CFX1_g8s.js} +1 -1
- package/admin-dist/public/assets/{_contentTypeId-Ba5iowxH.js → _contentTypeId-DTv8UoTp.js} +1 -1
- package/admin-dist/public/assets/_entryId-D3lr5Dvy.js +1 -0
- package/admin-dist/public/assets/alert-BAHTL6ao.js +1 -0
- package/admin-dist/public/assets/badge-oJv4Eai8.js +1 -0
- package/admin-dist/public/assets/{circle-check-big-B7eCOM8r.js → circle-check-big-3OHxNDhO.js} +1 -1
- package/admin-dist/public/assets/{command-BIjzeKOv.js → command-DwgQs69u.js} +1 -1
- package/admin-dist/public/assets/content-CKQ4QwW2.js +1 -0
- package/admin-dist/public/assets/content-types-BrttaLpc.js +1 -0
- package/admin-dist/public/assets/globals-CoCRjt0K.css +1 -0
- package/admin-dist/public/assets/index-DOkgTSx0.js +1 -0
- package/admin-dist/public/assets/{main-BZB1uYTH.js → main-DV6oxWnU.js} +5 -5
- package/admin-dist/public/assets/media-B2i-mCbx.js +1 -0
- package/admin-dist/public/assets/new._contentTypeId-VF63rpic.js +1 -0
- package/admin-dist/public/assets/{pencil-BDQ1ZWRw.js → pencil-CX1CiTDD.js} +1 -1
- package/admin-dist/public/assets/refresh-cw-Cm-YOeFI.js +1 -0
- package/admin-dist/public/assets/{rotate-ccw-BWblSIsl.js → rotate-ccw-B45JsL5f.js} +1 -1
- package/admin-dist/public/assets/{scroll-area-BoaB6x8v.js → scroll-area-b3A1HHR7.js} +1 -1
- package/admin-dist/public/assets/{search-CYMIpd39.js → search-DKKh_DdH.js} +1 -1
- package/admin-dist/public/assets/settings-CGVDEV1r.js +1 -0
- package/admin-dist/public/assets/{switch-DN7TOCa5.js → switch-BTMY8Qnk.js} +1 -1
- package/admin-dist/public/assets/tabs-DUQwUoIb.js +1 -0
- package/admin-dist/public/assets/{tanstack-adapter-DQcKErwf.js → tanstack-adapter-f7AHmQ5L.js} +1 -1
- package/admin-dist/public/assets/taxonomies-DvMppdiD.js +1 -0
- package/admin-dist/public/assets/{trash-Dp_a2mpb.js → trash-D7e0uKd9.js} +1 -1
- package/admin-dist/public/assets/{useBreadcrumbLabel-BQ9dJI6T.js → useBreadcrumbLabel-CF2KYwsw.js} +1 -1
- package/admin-dist/public/assets/{usePermissions-WUBNg_Id.js → usePermissions-DWBImEOW.js} +1 -1
- package/admin-dist/server/_libs/lucide-react.mjs +50 -43
- package/admin-dist/server/_ssr/{CmsEmptyState-DYh_PPQE.mjs → CmsEmptyState-BM8DghTl.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsPageHeader-BcniLh49.mjs → CmsPageHeader-BHUmrIWD.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsStatusBadge-BShWDxwE.mjs → CmsStatusBadge-D0Zb0oRl.mjs} +7 -7
- package/admin-dist/server/_ssr/{CmsSurface-CHEv-Kba.mjs → CmsSurface-B2eBr-47.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsToolbar-Dqqb216_.mjs → CmsToolbar-BCrwg7OL.mjs} +1 -1
- package/admin-dist/server/_ssr/{ContentEntryEditor-DOIAyWME.mjs → ContentEntryEditor-Cjfm0uhr.mjs} +37 -37
- package/admin-dist/server/_ssr/{TaxonomyFilter-BfsPAZ-Y.mjs → TaxonomyFilter-C4pD0kfM.mjs} +3 -3
- package/admin-dist/server/_ssr/{_contentTypeId-CPjmri90.mjs → _contentTypeId-CiDiX-p7.mjs} +11 -11
- package/admin-dist/server/_ssr/{_entryId-D0yu8HuP.mjs → _entryId-9GxatOkL.mjs} +11 -11
- package/admin-dist/server/_ssr/_tanstack-start-manifest_v-CC7UrHKE.mjs +4 -0
- package/admin-dist/server/_ssr/{badge-Cp61aQNc.mjs → badge-EI998zba.mjs} +1 -1
- package/admin-dist/server/_ssr/{command-BfjE1WJf.mjs → command-BLAWQhUw.mjs} +1 -1
- package/admin-dist/server/_ssr/{content-DrODe6sA.mjs → content-BHX39L4D.mjs} +31 -22
- package/admin-dist/server/_ssr/{content-types-BPgMwxiT.mjs → content-types-DCzrBhTH.mjs} +9 -9
- package/admin-dist/server/_ssr/{index-BTHmIC9W.mjs → index-DwM_5VNP.mjs} +92 -6
- package/admin-dist/server/_ssr/index.mjs +2 -2
- package/admin-dist/server/_ssr/{media-DkvBfmD9.mjs → media-CbzgTRRQ.mjs} +9 -9
- package/admin-dist/server/_ssr/{new._contentTypeId-Co_73sDJ.mjs → new._contentTypeId-6Ph-Gtlw.mjs} +10 -10
- package/admin-dist/server/_ssr/{router-CaDgRHfQ.mjs → router-vd1nySeP.mjs} +45 -35
- package/admin-dist/server/_ssr/{scroll-area-D3v-O_jk.mjs → scroll-area--B9snFTJ.mjs} +1 -1
- package/admin-dist/server/_ssr/{settings-MaEXh2Hz.mjs → settings-DlTO2JSj.mjs} +11 -11
- package/admin-dist/server/_ssr/{switch-DmbR03dm.mjs → switch-C05NgNW0.mjs} +1 -1
- package/admin-dist/server/_ssr/{tabs-5oFlAGLz.mjs → tabs-DAk2J5xy.mjs} +8 -8
- package/admin-dist/server/_ssr/{tanstack-adapter-DNaUioIZ.mjs → tanstack-adapter-DWbaPByn.mjs} +15 -1
- package/admin-dist/server/_ssr/{taxonomies-D3xMK23a.mjs → taxonomies-B8nqce6u.mjs} +12 -12
- package/admin-dist/server/_ssr/{trash-CNw1mtF1.mjs → trash-zdlZgpTo.mjs} +7 -7
- package/admin-dist/server/_ssr/{useBreadcrumbLabel-BQGjOTcy.mjs → useBreadcrumbLabel-DpEKyG1h.mjs} +1 -1
- package/admin-dist/server/_ssr/{usePermissions-D0qtvmNi.mjs → usePermissions-olYRd9S9.mjs} +1 -1
- package/admin-dist/server/index.mjs +164 -157
- package/dist/client/admin/contentTypes.d.ts +25 -0
- package/dist/client/admin/contentTypes.d.ts.map +1 -1
- package/dist/client/admin/contentTypes.js +212 -6
- package/dist/client/admin/contentTypes.js.map +1 -1
- package/dist/client/admin/entries.d.ts.map +1 -1
- package/dist/client/admin/entries.js +27 -0
- package/dist/client/admin/entries.js.map +1 -1
- package/dist/client/admin/index.d.ts +4 -0
- package/dist/client/admin/index.d.ts.map +1 -1
- package/dist/client/admin/index.js +16 -0
- package/dist/client/admin/index.js.map +1 -1
- package/dist/client/admin/types.d.ts +4 -0
- package/dist/client/admin/types.d.ts.map +1 -1
- package/dist/client/schema/defineContentType.d.ts.map +1 -1
- package/dist/client/schema/defineContentType.js +99 -80
- package/dist/client/schema/defineContentType.js.map +1 -1
- package/dist/component/contentTypeMutations.d.ts.map +1 -1
- package/dist/component/contentTypeMutations.js +5 -4
- package/dist/component/contentTypeMutations.js.map +1 -1
- package/package.json +2 -2
- package/admin-dist/public/assets/CmsStatusBadge-nZ9TeLBL.js +0 -1
- package/admin-dist/public/assets/ContentEntryEditor-BDb44eTo.js +0 -4
- package/admin-dist/public/assets/_entryId-OY3sLz6O.js +0 -1
- package/admin-dist/public/assets/alert-BbW1Q9CR.js +0 -1
- package/admin-dist/public/assets/badge-DdM8Eua8.js +0 -1
- package/admin-dist/public/assets/content-BV3YeSSW.js +0 -1
- package/admin-dist/public/assets/content-types-Bm4b2tf8.js +0 -1
- package/admin-dist/public/assets/globals-D41WzvyZ.css +0 -1
- package/admin-dist/public/assets/index-DnJ5Twlv.js +0 -1
- package/admin-dist/public/assets/media-BIMN5jXt.js +0 -1
- package/admin-dist/public/assets/new._contentTypeId-DTWb8ZDl.js +0 -1
- package/admin-dist/public/assets/settings-DaNDUtr5.js +0 -1
- package/admin-dist/public/assets/tabs-RN__emeJ.js +0 -1
- package/admin-dist/public/assets/taxonomies-DylY9HE1.js +0 -1
- package/admin-dist/server/_ssr/_tanstack-start-manifest_v-DgCpSt_y.mjs +0 -4
|
@@ -315,7 +315,7 @@ export function TagField({
|
|
|
315
315
|
<div className="border-t px-3 py-1.5 text-xs text-muted-foreground">
|
|
316
316
|
{value?.length ?? 0} tag{(value?.length ?? 0) !== 1 ? 's' : ''}
|
|
317
317
|
{minTags && (value?.length ?? 0) < minTags && (
|
|
318
|
-
<span className="text-
|
|
318
|
+
<span className="text-warning"> (minimum {minTags})</span>
|
|
319
319
|
)}
|
|
320
320
|
{maxTags && <span> / {maxTags} max</span>}
|
|
321
321
|
</div>
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { useQuery } from "convex/react";
|
|
2
2
|
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
3
|
-
import { api } from "../../convex/_generated/api";
|
|
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
|
|
7
|
+
type Settings = NonNullable<typeof localApi.admin.getSettings._returnType>;
|
|
8
|
+
|
|
9
|
+
type SettingsApi = {
|
|
10
|
+
getSettings: typeof localApi.admin.getSettings;
|
|
11
|
+
};
|
|
8
12
|
|
|
9
13
|
interface SettingsConfigContextValue {
|
|
10
14
|
baseConfig: AdminConfig;
|
|
@@ -16,11 +20,14 @@ const SettingsConfigContext = createContext<SettingsConfigContextValue | null>(n
|
|
|
16
20
|
export function SettingsConfigProvider({
|
|
17
21
|
baseConfig,
|
|
18
22
|
children,
|
|
23
|
+
api,
|
|
19
24
|
}: {
|
|
20
25
|
baseConfig: AdminConfig;
|
|
21
26
|
children: ReactNode;
|
|
27
|
+
api?: SettingsApi;
|
|
22
28
|
}) {
|
|
23
|
-
const
|
|
29
|
+
const resolvedApi = api ?? localApi.admin;
|
|
30
|
+
const settings = useQuery(resolvedApi.getSettings);
|
|
24
31
|
|
|
25
32
|
const mergedConfig = useMemo((): AdminConfig => {
|
|
26
33
|
if (!settings) return baseConfig;
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
import { ConvexProvider, ConvexReactClient } from "convex/react";
|
|
32
32
|
import { useMemo, type ReactNode } from "react";
|
|
33
|
-
import {
|
|
33
|
+
import { SettingsConfigProvider } from "../contexts/SettingsConfigContext";
|
|
34
34
|
import {
|
|
35
35
|
AuthProvider,
|
|
36
36
|
type GetUserHook,
|
|
@@ -51,11 +51,14 @@ import { EmbedLayout } from "./components/EmbedLayout";
|
|
|
51
51
|
import {
|
|
52
52
|
EmbedDashboard,
|
|
53
53
|
EmbedContent,
|
|
54
|
+
EmbedContentTypeEntries,
|
|
54
55
|
EmbedContentTypes,
|
|
55
56
|
EmbedMedia,
|
|
56
57
|
EmbedSettings,
|
|
57
58
|
EmbedTrash,
|
|
58
59
|
EmbedTaxonomies,
|
|
60
|
+
EmbedNewEntry,
|
|
61
|
+
EmbedEntry,
|
|
59
62
|
} from "./pages";
|
|
60
63
|
|
|
61
64
|
function adaptAuthConfig(auth: CmsAdminAuthConfig): {
|
|
@@ -85,11 +88,11 @@ function ConvexProviderWrapper({
|
|
|
85
88
|
if (!convex) {
|
|
86
89
|
return (
|
|
87
90
|
<div className="flex min-h-full items-center justify-center bg-background p-6">
|
|
88
|
-
<div className="max-w-lg space-y-4 rounded-lg border
|
|
89
|
-
<h2 className="text-xl font-semibold text-
|
|
91
|
+
<div className="diff-modified max-w-lg space-y-4 rounded-lg border p-6 text-center">
|
|
92
|
+
<h2 className="text-xl font-semibold text-diff-modified">
|
|
90
93
|
Convex Configuration Required
|
|
91
94
|
</h2>
|
|
92
|
-
<p className="text-sm text-
|
|
95
|
+
<p className="text-sm text-diff-modified-foreground">
|
|
93
96
|
Please provide a valid convexUrl prop to the CmsAdmin component.
|
|
94
97
|
</p>
|
|
95
98
|
</div>
|
|
@@ -119,8 +122,21 @@ function EmbedRouter() {
|
|
|
119
122
|
return <EmbedTaxonomies />;
|
|
120
123
|
case "trash":
|
|
121
124
|
return <EmbedTrash />;
|
|
122
|
-
case "entries":
|
|
125
|
+
case "entries": {
|
|
126
|
+
// Handle new entry action
|
|
127
|
+
if (currentRoute.params.action === "new") {
|
|
128
|
+
return <EmbedNewEntry />;
|
|
129
|
+
}
|
|
130
|
+
// Handle existing entry edit
|
|
131
|
+
if (currentRoute.params.entryId) {
|
|
132
|
+
return <EmbedEntry />;
|
|
133
|
+
}
|
|
134
|
+
// Handle content type specific entries
|
|
135
|
+
if (currentRoute.params.contentTypeId) {
|
|
136
|
+
return <EmbedContentTypeEntries contentTypeId={currentRoute.params.contentTypeId} />;
|
|
137
|
+
}
|
|
123
138
|
return <EmbedContent />;
|
|
139
|
+
}
|
|
124
140
|
default:
|
|
125
141
|
return <EmbedDashboard />;
|
|
126
142
|
}
|
|
@@ -144,13 +160,17 @@ export function CmsAdmin({
|
|
|
144
160
|
}) {
|
|
145
161
|
const adminConfig = useMemo(() => resolveAdminConfig(config), [config]);
|
|
146
162
|
const authConfig = useMemo(() => adaptAuthConfig(auth), [auth]);
|
|
163
|
+
const settingsApi = useMemo(
|
|
164
|
+
() => (api.getSettings ? { getSettings: api.getSettings } : undefined),
|
|
165
|
+
[api]
|
|
166
|
+
);
|
|
147
167
|
|
|
148
168
|
return (
|
|
149
169
|
<div className={className}>
|
|
150
170
|
<ApiProvider api={api}>
|
|
151
171
|
<ThemeProvider>
|
|
152
|
-
<
|
|
153
|
-
<
|
|
172
|
+
<ConvexProviderWrapper convexUrl={convexUrl}>
|
|
173
|
+
<SettingsConfigProvider baseConfig={adminConfig} api={settingsApi}>
|
|
154
174
|
<AuthProvider
|
|
155
175
|
getUser={authConfig.getUser}
|
|
156
176
|
getUserRole={authConfig.getUserRole}
|
|
@@ -168,8 +188,8 @@ export function CmsAdmin({
|
|
|
168
188
|
</RouteGuard>
|
|
169
189
|
</EmbedNavigationProvider>
|
|
170
190
|
</AuthProvider>
|
|
171
|
-
</
|
|
172
|
-
</
|
|
191
|
+
</SettingsConfigProvider>
|
|
192
|
+
</ConvexProviderWrapper>
|
|
173
193
|
</ThemeProvider>
|
|
174
194
|
</ApiProvider>
|
|
175
195
|
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embed Content Type Entries Page
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around the shared ContentTypeEntriesPage component.
|
|
5
|
+
* Provides embed navigation and API access.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useApi } from "../contexts/ApiContext";
|
|
9
|
+
import { useEmbedNavigation } from "../navigation";
|
|
10
|
+
import { useEmbedAdapter } from "../../lib/embed-adapter";
|
|
11
|
+
import { ContentTypeEntriesPage } from "../../pages";
|
|
12
|
+
|
|
13
|
+
export function EmbedContentTypeEntries({ contentTypeId }: { contentTypeId: string }) {
|
|
14
|
+
const api = useApi();
|
|
15
|
+
const embedNav = useEmbedNavigation();
|
|
16
|
+
const navigation = useEmbedAdapter(embedNav);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<ContentTypeEntriesPage
|
|
20
|
+
api={api}
|
|
21
|
+
navigation={navigation}
|
|
22
|
+
contentTypeId={contentTypeId}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embed Entry Page
|
|
3
|
+
*
|
|
4
|
+
* Renders the entry editor for editing an existing content entry.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useApi } from "../contexts/ApiContext";
|
|
8
|
+
import { useEmbedNavigation, useEmbedParams } from "../navigation";
|
|
9
|
+
import { useQuery } from "convex/react";
|
|
10
|
+
import { ContentEntryEditor } from "../../components/ContentEntryEditor";
|
|
11
|
+
import type { ContentType, ContentEntry } from "../../components/ContentEntryEditor";
|
|
12
|
+
import { CmsEmptyState } from "../../components/cmsds/CmsEmptyState";
|
|
13
|
+
import { FileText } from "lucide-react";
|
|
14
|
+
import { usePermissions } from "../../hooks";
|
|
15
|
+
|
|
16
|
+
export function EmbedEntry() {
|
|
17
|
+
const api = useApi();
|
|
18
|
+
const params = useEmbedParams();
|
|
19
|
+
const { navigate } = useEmbedNavigation();
|
|
20
|
+
const { canDelete } = usePermissions();
|
|
21
|
+
|
|
22
|
+
const entryId = params.entryId;
|
|
23
|
+
|
|
24
|
+
const entry = useQuery(api.getEntry, entryId ? { id: entryId } : "skip");
|
|
25
|
+
|
|
26
|
+
const contentType = useQuery(
|
|
27
|
+
api.getContentType,
|
|
28
|
+
entry ? { name: entry.contentTypeName } : "skip"
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
if (!entryId) {
|
|
32
|
+
return (
|
|
33
|
+
<div className="space-y-6 p-6">
|
|
34
|
+
<CmsEmptyState
|
|
35
|
+
icon={<FileText className="size-6" />}
|
|
36
|
+
title="No Entry Selected"
|
|
37
|
+
description="Please select an entry to edit."
|
|
38
|
+
action={{
|
|
39
|
+
label: "Go to Content",
|
|
40
|
+
onClick: () => navigate("content"),
|
|
41
|
+
}}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (entry === undefined || (entry && contentType === undefined)) {
|
|
48
|
+
return (
|
|
49
|
+
<div className="space-y-6 p-6">
|
|
50
|
+
<div className="flex flex-col items-center justify-center py-12">
|
|
51
|
+
<div className="size-8 animate-spin rounded-full border-2 border-muted border-t-primary" />
|
|
52
|
+
<p className="mt-4 text-sm text-muted-foreground">Loading entry...</p>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (entry === null) {
|
|
59
|
+
return (
|
|
60
|
+
<div className="space-y-6 p-6">
|
|
61
|
+
<CmsEmptyState
|
|
62
|
+
icon={<FileText className="size-6" />}
|
|
63
|
+
title="Entry Not Found"
|
|
64
|
+
description="The content entry you're looking for doesn't exist or has been deleted."
|
|
65
|
+
action={{
|
|
66
|
+
label: "Back to Content",
|
|
67
|
+
onClick: () => navigate("content"),
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (contentType === null) {
|
|
75
|
+
return (
|
|
76
|
+
<div className="space-y-6 p-6">
|
|
77
|
+
<CmsEmptyState
|
|
78
|
+
icon={<FileText className="size-6" />}
|
|
79
|
+
title="Content Type Not Found"
|
|
80
|
+
description="The content type for this entry doesn't exist or has been deleted."
|
|
81
|
+
action={{
|
|
82
|
+
label: "Back to Content",
|
|
83
|
+
onClick: () => navigate("content"),
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const handleSave = (_savedEntry: ContentEntry) => {
|
|
91
|
+
// Stay on page - ContentEntryEditor handles success feedback
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleCancel = () => {
|
|
95
|
+
navigate("content");
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleDelete = () => {
|
|
99
|
+
navigate("content");
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div className="space-y-6 p-6">
|
|
104
|
+
<ContentEntryEditor
|
|
105
|
+
contentType={contentType as ContentType}
|
|
106
|
+
entry={entry as ContentEntry}
|
|
107
|
+
onSave={handleSave}
|
|
108
|
+
onCancel={handleCancel}
|
|
109
|
+
onDelete={handleDelete}
|
|
110
|
+
canDelete={canDelete("contentEntries")}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
import { useApi } from "../contexts/ApiContext";
|
|
9
9
|
import { useEmbedNavigation } from "../navigation";
|
|
10
10
|
import { useEmbedAdapter } from "../../lib/embed-adapter";
|
|
11
|
+
import { useSettingsConfig } from "../../contexts";
|
|
11
12
|
import { MediaPage } from "../../pages";
|
|
12
13
|
|
|
13
14
|
export function EmbedMedia() {
|
|
14
15
|
const api = useApi();
|
|
15
16
|
const embedNav = useEmbedNavigation();
|
|
16
17
|
const navigation = useEmbedAdapter(embedNav);
|
|
18
|
+
const { settings } = useSettingsConfig();
|
|
17
19
|
|
|
18
|
-
return <MediaPage api={api} navigation={navigation} />;
|
|
20
|
+
return <MediaPage api={api} navigation={navigation} settings={settings} />;
|
|
19
21
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embed New Entry Page
|
|
3
|
+
*
|
|
4
|
+
* Renders the entry editor for creating a new content entry.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useApi } from "../contexts/ApiContext";
|
|
8
|
+
import { useEmbedNavigation, useEmbedParams } from "../navigation";
|
|
9
|
+
import { useQuery } from "convex/react";
|
|
10
|
+
import { ContentEntryEditor } from "../../components/ContentEntryEditor";
|
|
11
|
+
import type { ContentType, ContentEntry } from "../../components/ContentEntryEditor";
|
|
12
|
+
import { CmsEmptyState } from "../../components/cmsds/CmsEmptyState";
|
|
13
|
+
import { FileText } from "lucide-react";
|
|
14
|
+
|
|
15
|
+
export function EmbedNewEntry() {
|
|
16
|
+
const api = useApi();
|
|
17
|
+
const params = useEmbedParams();
|
|
18
|
+
const { navigate, navigateToEntry, navigateToContentType } = useEmbedNavigation();
|
|
19
|
+
|
|
20
|
+
const contentTypeId = params.contentTypeId;
|
|
21
|
+
const contentType = useQuery(api.getContentType, contentTypeId ? { id: contentTypeId } : "skip");
|
|
22
|
+
|
|
23
|
+
if (!contentTypeId) {
|
|
24
|
+
return (
|
|
25
|
+
<div className="space-y-6 p-6">
|
|
26
|
+
<CmsEmptyState
|
|
27
|
+
icon={<FileText className="size-6" />}
|
|
28
|
+
title="No Content Type Selected"
|
|
29
|
+
description="Please select a content type to create an entry."
|
|
30
|
+
action={{
|
|
31
|
+
label: "Go to Content",
|
|
32
|
+
onClick: () => navigate("content"),
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (contentType === undefined) {
|
|
40
|
+
return (
|
|
41
|
+
<div className="space-y-6 p-6">
|
|
42
|
+
<div className="flex flex-col items-center justify-center py-12">
|
|
43
|
+
<div className="size-8 animate-spin rounded-full border-2 border-muted border-t-primary" />
|
|
44
|
+
<p className="mt-4 text-sm text-muted-foreground">Loading content type...</p>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (contentType === null) {
|
|
51
|
+
return (
|
|
52
|
+
<div className="space-y-6 p-6">
|
|
53
|
+
<CmsEmptyState
|
|
54
|
+
icon={<FileText className="size-6" />}
|
|
55
|
+
title="Content Type Not Found"
|
|
56
|
+
description="The content type you're looking for doesn't exist or has been deleted."
|
|
57
|
+
action={{
|
|
58
|
+
label: "Back to Content Types",
|
|
59
|
+
onClick: () => navigate("content-types"),
|
|
60
|
+
}}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const handleSave = (entry: ContentEntry) => {
|
|
67
|
+
navigateToEntry(entry._id);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleCancel = () => {
|
|
71
|
+
navigateToContentType(contentTypeId);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div className="space-y-6 p-6">
|
|
76
|
+
<ContentEntryEditor
|
|
77
|
+
contentType={contentType as ContentType}
|
|
78
|
+
onSave={handleSave}
|
|
79
|
+
onCancel={handleCancel}
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export { EmbedDashboard } from "./Dashboard";
|
|
2
2
|
export { EmbedContent } from "./Content";
|
|
3
|
+
export { EmbedContentTypeEntries } from "./ContentTypeEntries";
|
|
3
4
|
export { EmbedContentTypes } from "./ContentTypes";
|
|
4
5
|
export { EmbedMedia } from "./Media";
|
|
5
6
|
export { EmbedSettings } from "./Settings";
|
|
6
7
|
export { EmbedTrash } from "./Trash";
|
|
7
8
|
export { EmbedTaxonomies } from "./Taxonomies";
|
|
9
|
+
export { EmbedNewEntry } from "./NewEntry";
|
|
10
|
+
export { EmbedEntry } from "./Entry";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Used by both CLI routes and embed pages.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { useState, useCallback } from "react";
|
|
8
|
+
import { useState, useCallback, useEffect } from "react";
|
|
9
9
|
import { useQuery } from "convex/react";
|
|
10
10
|
import { usePermissions } from "~/hooks";
|
|
11
11
|
import { BulkActionBar } from "~/components/BulkActionBar";
|
|
@@ -57,6 +57,21 @@ export function ContentPage({ api, navigation }: ContentPageProps) {
|
|
|
57
57
|
|
|
58
58
|
const { canCreate, canUpdate } = usePermissions();
|
|
59
59
|
|
|
60
|
+
// Sync with navigation params when contentTypeId changes
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const contentTypeId = navigation.params?.contentTypeId;
|
|
63
|
+
if (contentTypeId) {
|
|
64
|
+
// Extract slug from contentTypeId (handles "code:slug" format)
|
|
65
|
+
const slug = contentTypeId.startsWith("code:")
|
|
66
|
+
? contentTypeId.slice(5)
|
|
67
|
+
: contentTypeId;
|
|
68
|
+
setSelectedTypeId(slug);
|
|
69
|
+
} else {
|
|
70
|
+
// Clear selection when navigating to "All Entries"
|
|
71
|
+
setSelectedTypeId("");
|
|
72
|
+
}
|
|
73
|
+
}, [navigation.params?.contentTypeId]);
|
|
74
|
+
|
|
60
75
|
const contentTypesResult = useQuery(api.listContentTypes, { isActive: true });
|
|
61
76
|
const contentTypes: ContentType[] = contentTypesResult?.page ?? [];
|
|
62
77
|
const isLoadingContentTypes = contentTypesResult === undefined;
|