convex-cms 0.0.9-alpha.6 → 0.0.9-alpha.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/admin/src/contexts/SettingsConfigContext.tsx +67 -11
- package/admin/src/embed/components/EmbedLayout.tsx +6 -2
- package/admin/src/embed/components/EmbedSidebar.tsx +4 -7
- package/admin/src/embed/index.tsx +8 -5
- package/admin/src/embed/types.ts +6 -0
- package/admin/src/pages/SettingsPage.tsx +124 -80
- package/admin/src/routes/__root.tsx +4 -1
- package/admin/src/styles/globals.css +9 -0
- package/admin/src/styles/theme.css +141 -3
- package/admin-dist/nitro.json +1 -1
- package/admin-dist/public/assets/{CmsEmptyState-la_kLGHv.js → CmsEmptyState-DTlpzjOI.js} +1 -1
- package/admin-dist/public/assets/{CmsPageHeader-Cf0SafVG.js → CmsPageHeader-0REGRH4X.js} +1 -1
- package/admin-dist/public/assets/{CmsStatusBadge-BovugIHH.js → CmsStatusBadge-D_n8u8xa.js} +1 -1
- package/admin-dist/public/assets/{CmsSurface-Dp1TegU4.js → CmsSurface-BHmvNai4.js} +1 -1
- package/admin-dist/public/assets/{CmsToolbar-B1zdfMu0.js → CmsToolbar-CY6GV2L8.js} +1 -1
- package/admin-dist/public/assets/{ContentEntryEditor-CB--8SC3.js → ContentEntryEditor-CRgcRkk5.js} +1 -1
- package/admin-dist/public/assets/{TaxonomyFilter-fmZxxbdA.js → TaxonomyFilter-Ohv5Jg9c.js} +1 -1
- package/admin-dist/public/assets/{_contentTypeId-vT09P77i.js → _contentTypeId-C_vJq22X.js} +1 -1
- package/admin-dist/public/assets/{_entryId-Dhq6ybYt.js → _entryId-jPXz4z9T.js} +1 -1
- package/admin-dist/public/assets/{alert-DfdkD-ZZ.js → alert-CG97cMfC.js} +1 -1
- package/admin-dist/public/assets/{badge-Cmc3T9vs.js → badge-C6qt24oj.js} +1 -1
- package/admin-dist/public/assets/{circle-check-big-B8AJAcBi.js → circle-check-big-PltpxuB1.js} +1 -1
- package/admin-dist/public/assets/{command-BsmkQ6_j.js → command-CJ8i86fd.js} +1 -1
- package/admin-dist/public/assets/{content-DdQ1u7T4.js → content-pKaIL2ru.js} +1 -1
- package/admin-dist/public/assets/{content-types-QYcwm0Sy.js → content-types-Bl_8I1Re.js} +1 -1
- package/admin-dist/public/assets/{index-kxLB3e43.js → index-CtHq_P5q.js} +1 -1
- package/admin-dist/public/assets/{main-BrFRroF1.js → main-CA-4LyFT.js} +19 -19
- package/admin-dist/public/assets/{media-Cx9IyZvU.js → media-Bl1tBbJQ.js} +1 -1
- package/admin-dist/public/assets/{new._contentTypeId-2HsDwzy_.js → new._contentTypeId-qsvo01mH.js} +1 -1
- package/admin-dist/public/assets/{pencil-PaJhpDeC.js → pencil-gAL0R34f.js} +1 -1
- package/admin-dist/public/assets/{refresh-cw-D_KNzBUN.js → refresh-cw-sdVUGJNs.js} +1 -1
- package/admin-dist/public/assets/{rotate-ccw-I3wzW1RQ.js → rotate-ccw-6OcXCcxb.js} +1 -1
- package/admin-dist/public/assets/{scroll-area-t72-FBB4.js → scroll-area-CJBhf9pf.js} +1 -1
- package/admin-dist/public/assets/{search-BsuImjd-.js → search-WXp6KxDJ.js} +1 -1
- package/admin-dist/public/assets/settings-D8crrFCn.js +1 -0
- package/admin-dist/public/assets/{switch-DWN_fx2n.js → switch-Ck9ecqEX.js} +1 -1
- package/admin-dist/public/assets/{tabs-BG0vukFH.js → tabs-vQYu8rjC.js} +1 -1
- package/admin-dist/public/assets/{tanstack-adapter-DHsy8Fjs.js → tanstack-adapter-BRt2CUCw.js} +1 -1
- package/admin-dist/public/assets/{taxonomies-WG8YV2pR.js → taxonomies-DvILUNvr.js} +1 -1
- package/admin-dist/public/assets/{trash-C3Lt5m9d.js → trash-YyYaC3L9.js} +1 -1
- package/admin-dist/public/assets/{useBreadcrumbLabel-BC8wl3jQ.js → useBreadcrumbLabel-tlSh7dtO.js} +1 -1
- package/admin-dist/public/assets/{usePermissions-BM1Vv1YJ.js → usePermissions-BTGdTOJS.js} +1 -1
- package/admin-dist/server/_ssr/{CmsEmptyState-NKmyUWD9.mjs → CmsEmptyState-CB6e53i5.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsPageHeader-CngxPIOg.mjs → CmsPageHeader-COUHuECp.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsStatusBadge-D4fiHjJD.mjs → CmsStatusBadge-kMTL6koE.mjs} +2 -2
- package/admin-dist/server/_ssr/{CmsSurface-DN9I2iuX.mjs → CmsSurface-D1HDYjRg.mjs} +1 -1
- package/admin-dist/server/_ssr/{CmsToolbar-ux-veU96.mjs → CmsToolbar-NB014hsd.mjs} +1 -1
- package/admin-dist/server/_ssr/{ContentEntryEditor-BiY9bJr9.mjs → ContentEntryEditor-Bq8FR_uK.mjs} +8 -8
- package/admin-dist/server/_ssr/{TaxonomyFilter-BdtKJie2.mjs → TaxonomyFilter-bm_p4ADg.mjs} +3 -3
- package/admin-dist/server/_ssr/{_contentTypeId-CRo5WQVu.mjs → _contentTypeId-B7obLmi_.mjs} +10 -10
- package/admin-dist/server/_ssr/{_entryId-FnG3uc_Y.mjs → _entryId-B4zhQqFg.mjs} +11 -11
- package/admin-dist/server/_ssr/_tanstack-start-manifest_v-DndoqCo7.mjs +4 -0
- package/admin-dist/server/_ssr/{badge-D3SGS0Jp.mjs → badge-NOEC9bkk.mjs} +1 -1
- package/admin-dist/server/_ssr/{command-2t7uTBKt.mjs → command-h4-OYNBo.mjs} +1 -1
- package/admin-dist/server/_ssr/{content-CCsSzXeb.mjs → content-CShtLuhK.mjs} +8 -8
- package/admin-dist/server/_ssr/{content-types-CuVG3uSt.mjs → content-types-PeyRyfbc.mjs} +6 -6
- package/admin-dist/server/_ssr/{index-CLwCLoco.mjs → index-CplFXpGg.mjs} +3 -3
- package/admin-dist/server/_ssr/index.mjs +2 -2
- package/admin-dist/server/_ssr/{media-D3duVWkk.mjs → media-QAkNdX54.mjs} +9 -9
- package/admin-dist/server/_ssr/{new._contentTypeId-6uKYdgGO.mjs → new._contentTypeId-DEJyMphJ.mjs} +10 -10
- package/admin-dist/server/_ssr/{router-D9Zk56-q.mjs → router-CQXMuGMF.mjs} +59 -16
- package/admin-dist/server/_ssr/{scroll-area-vbjKsfFu.mjs → scroll-area-B7zoNyWB.mjs} +1 -1
- package/admin-dist/server/_ssr/{settings-D7-vwjqD.mjs → settings-CNaqVa4D.mjs} +97 -82
- package/admin-dist/server/_ssr/{switch-CbKuV4Qh.mjs → switch-BKZhvryc.mjs} +1 -1
- package/admin-dist/server/_ssr/{tabs-C_IfqLiu.mjs → tabs-DtIIQxiD.mjs} +1 -1
- package/admin-dist/server/_ssr/{tanstack-adapter-yhyAcBi-.mjs → tanstack-adapter-CLavdbUY.mjs} +1 -1
- package/admin-dist/server/_ssr/{taxonomies-C15s_nvM.mjs → taxonomies-vIZYICzr.mjs} +7 -7
- package/admin-dist/server/_ssr/{trash-BPIjmAUh.mjs → trash-7yGR4-dF.mjs} +7 -7
- package/admin-dist/server/_ssr/{useBreadcrumbLabel-BC7plG0L.mjs → useBreadcrumbLabel-DR5FaAMf.mjs} +1 -1
- package/admin-dist/server/_ssr/{usePermissions-DJ8a7bZU.mjs → usePermissions-DKkpETj_.mjs} +1 -1
- package/admin-dist/server/index.mjs +159 -159
- package/package.json +1 -1
- package/admin-dist/public/assets/settings-DB6TvceQ.js +0 -1
- package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BbSNqRIw.mjs +0 -4
|
@@ -18,8 +18,12 @@ export interface Settings {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
type SettingsApi = {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
getSettings: FunctionReference<
|
|
22
|
+
"query",
|
|
23
|
+
"public",
|
|
24
|
+
Record<string, never>,
|
|
25
|
+
Settings | null
|
|
26
|
+
>;
|
|
23
27
|
};
|
|
24
28
|
|
|
25
29
|
interface SettingsConfigContextValue {
|
|
@@ -27,21 +31,22 @@ interface SettingsConfigContextValue {
|
|
|
27
31
|
settings: Settings | undefined;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
const SettingsConfigContext = createContext<SettingsConfigContextValue | null>(
|
|
34
|
+
const SettingsConfigContext = createContext<SettingsConfigContextValue | null>(
|
|
35
|
+
null
|
|
36
|
+
);
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
// Component that queries settings (only rendered when api is provided)
|
|
39
|
+
function SettingsConfigProviderWithQuery({
|
|
33
40
|
baseConfig,
|
|
34
41
|
children,
|
|
35
42
|
api,
|
|
36
43
|
}: {
|
|
37
44
|
baseConfig: AdminConfig;
|
|
38
45
|
children: ReactNode;
|
|
39
|
-
api
|
|
46
|
+
api: SettingsApi;
|
|
40
47
|
}) {
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
const queryArg = api ? api.getSettings : ("skip" as any);
|
|
44
|
-
const settings = useQuery(queryArg) as Settings | undefined;
|
|
48
|
+
// Proper skip pattern: function ref first, args (or "skip") second
|
|
49
|
+
const settings = useQuery(api.getSettings, {}) ?? undefined;
|
|
45
50
|
|
|
46
51
|
const mergedConfig = useMemo((): AdminConfig => {
|
|
47
52
|
if (!settings) return baseConfig;
|
|
@@ -50,7 +55,8 @@ export function SettingsConfigProvider({
|
|
|
50
55
|
...baseConfig,
|
|
51
56
|
navigation: {
|
|
52
57
|
...baseConfig.navigation,
|
|
53
|
-
showMedia:
|
|
58
|
+
showMedia:
|
|
59
|
+
baseConfig.navigation.showMedia && settings.features.mediaManagement,
|
|
54
60
|
},
|
|
55
61
|
};
|
|
56
62
|
}, [baseConfig, settings]);
|
|
@@ -69,10 +75,60 @@ export function SettingsConfigProvider({
|
|
|
69
75
|
);
|
|
70
76
|
}
|
|
71
77
|
|
|
78
|
+
// Component without query (when api is not provided)
|
|
79
|
+
function SettingsConfigProviderWithoutQuery({
|
|
80
|
+
baseConfig,
|
|
81
|
+
children,
|
|
82
|
+
}: {
|
|
83
|
+
baseConfig: AdminConfig;
|
|
84
|
+
children: ReactNode;
|
|
85
|
+
}) {
|
|
86
|
+
const contextValue = useMemo(
|
|
87
|
+
() => ({ baseConfig, settings: undefined }),
|
|
88
|
+
[baseConfig]
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<SettingsConfigContext.Provider value={contextValue}>
|
|
93
|
+
<AdminConfigProvider config={baseConfig}>{children}</AdminConfigProvider>
|
|
94
|
+
</SettingsConfigContext.Provider>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function SettingsConfigProvider({
|
|
99
|
+
baseConfig,
|
|
100
|
+
children,
|
|
101
|
+
api,
|
|
102
|
+
}: {
|
|
103
|
+
baseConfig: AdminConfig;
|
|
104
|
+
children: ReactNode;
|
|
105
|
+
api?: SettingsApi;
|
|
106
|
+
}) {
|
|
107
|
+
// Use component splitting to avoid calling useQuery without a valid function ref
|
|
108
|
+
if (api) {
|
|
109
|
+
return (
|
|
110
|
+
<SettingsConfigProviderWithQuery
|
|
111
|
+
baseConfig={baseConfig}
|
|
112
|
+
api={api}
|
|
113
|
+
children={children}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<SettingsConfigProviderWithoutQuery
|
|
120
|
+
baseConfig={baseConfig}
|
|
121
|
+
children={children}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
72
126
|
export function useSettingsConfig(): SettingsConfigContextValue {
|
|
73
127
|
const ctx = useContext(SettingsConfigContext);
|
|
74
128
|
if (!ctx) {
|
|
75
|
-
throw new Error(
|
|
129
|
+
throw new Error(
|
|
130
|
+
"useSettingsConfig must be used within SettingsConfigProvider"
|
|
131
|
+
);
|
|
76
132
|
}
|
|
77
133
|
return ctx;
|
|
78
134
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A router-agnostic layout for the embedded admin that uses
|
|
5
5
|
* EmbedSidebar instead of the router-dependent Sidebar.
|
|
6
|
+
* Uses CSS Grid for proper container-relative positioning.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import type { ReactNode } from "react";
|
|
@@ -18,9 +19,12 @@ export function EmbedLayout({ children }: EmbedLayoutProps) {
|
|
|
18
19
|
const { layout } = useAdminConfig();
|
|
19
20
|
|
|
20
21
|
return (
|
|
21
|
-
<div
|
|
22
|
+
<div
|
|
23
|
+
className="grid h-full bg-background"
|
|
24
|
+
style={{ gridTemplateColumns: `${layout.sidebarWidth}px 1fr` }}
|
|
25
|
+
>
|
|
22
26
|
<EmbedSidebar />
|
|
23
|
-
<div className="flex flex-
|
|
27
|
+
<div className="flex flex-col overflow-hidden">
|
|
24
28
|
<EmbedHeader />
|
|
25
29
|
<main className="flex-1 overflow-auto p-6">{children}</main>
|
|
26
30
|
</div>
|
|
@@ -36,7 +36,9 @@ function pathToRoute(path: string): EmbedRoute {
|
|
|
36
36
|
export function EmbedSidebar() {
|
|
37
37
|
const { currentPath, navigate, navigateToContentType } = useEmbedNavigation();
|
|
38
38
|
const config = useAdminConfig();
|
|
39
|
-
const { navItems, branding,
|
|
39
|
+
const { navItems, branding,
|
|
40
|
+
// layout
|
|
41
|
+
} = config;
|
|
40
42
|
const api = useApi();
|
|
41
43
|
|
|
42
44
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
|
@@ -152,14 +154,9 @@ export function EmbedSidebar() {
|
|
|
152
154
|
</Collapsible>
|
|
153
155
|
);
|
|
154
156
|
|
|
155
|
-
const sidebarWidth = layout.sidebarWidth;
|
|
156
|
-
|
|
157
157
|
return (
|
|
158
158
|
<>
|
|
159
|
-
<aside
|
|
160
|
-
className="fixed inset-y-0 left-0 z-50 flex flex-col border-r border-sidebar-border bg-sidebar"
|
|
161
|
-
style={{ width: sidebarWidth }}
|
|
162
|
-
>
|
|
159
|
+
<aside className="sticky top-0 z-40 flex h-full flex-col overflow-hidden border-r border-sidebar-border bg-sidebar">
|
|
163
160
|
<div className="flex h-14 items-center gap-2 border-b border-sidebar-border px-4">
|
|
164
161
|
<button
|
|
165
162
|
type="button"
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
import { ThemeProvider } from "../contexts/ThemeContext";
|
|
47
47
|
import { RouteGuard } from "../components/RouteGuard";
|
|
48
48
|
import { resolveAdminConfig } from "../lib/admin-config";
|
|
49
|
+
import { cn } from "../lib/cn";
|
|
49
50
|
import type { CmsAdminProps, CmsAdminAuthConfig } from "./types";
|
|
50
51
|
import { ApiProvider } from "./contexts/ApiContext";
|
|
51
52
|
import {
|
|
@@ -127,6 +128,7 @@ export function CmsAdmin({
|
|
|
127
128
|
auth,
|
|
128
129
|
basePath = "/admin",
|
|
129
130
|
className,
|
|
131
|
+
themeMode = "isolated",
|
|
130
132
|
initialRoute = "dashboard",
|
|
131
133
|
onNavigate,
|
|
132
134
|
}: CmsAdminProps & {
|
|
@@ -143,7 +145,10 @@ export function CmsAdmin({
|
|
|
143
145
|
|
|
144
146
|
if (!convex) {
|
|
145
147
|
return (
|
|
146
|
-
<div
|
|
148
|
+
<div
|
|
149
|
+
className={cn("flex h-full items-center justify-center bg-background p-6", className)}
|
|
150
|
+
data-cms-admin={themeMode}
|
|
151
|
+
>
|
|
147
152
|
<div className="diff-modified max-w-lg space-y-4 rounded-lg border p-6 text-center">
|
|
148
153
|
<h2 className="text-xl font-semibold text-diff-modified">
|
|
149
154
|
ConvexProvider Required
|
|
@@ -158,7 +163,7 @@ export function CmsAdmin({
|
|
|
158
163
|
}
|
|
159
164
|
|
|
160
165
|
return (
|
|
161
|
-
<div className={className}>
|
|
166
|
+
<div className={cn("h-full", className)} data-cms-admin={themeMode}>
|
|
162
167
|
<ApiProvider api={api}>
|
|
163
168
|
<ThemeProvider>
|
|
164
169
|
<SettingsConfigProvider baseConfig={adminConfig} api={settingsApi}>
|
|
@@ -173,9 +178,7 @@ export function CmsAdmin({
|
|
|
173
178
|
onNavigate={onNavigate}
|
|
174
179
|
>
|
|
175
180
|
<RouteGuard>
|
|
176
|
-
<
|
|
177
|
-
<EmbedRouter />
|
|
178
|
-
</div>
|
|
181
|
+
<EmbedRouter />
|
|
179
182
|
</RouteGuard>
|
|
180
183
|
</EmbedNavigationProvider>
|
|
181
184
|
</AuthProvider>
|
package/admin/src/embed/types.ts
CHANGED
|
@@ -20,4 +20,10 @@ export interface CmsAdminProps {
|
|
|
20
20
|
auth: CmsAdminAuthConfig;
|
|
21
21
|
basePath?: string;
|
|
22
22
|
className?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Theme mode for CSS variable scoping:
|
|
25
|
+
* - 'isolated' (default): Admin uses its own theme, ignores parent app styles
|
|
26
|
+
* - 'inherit': Admin inherits parent app's CSS variables (for shadcn apps)
|
|
27
|
+
*/
|
|
28
|
+
themeMode?: "isolated" | "inherit";
|
|
23
29
|
}
|
|
@@ -126,22 +126,110 @@ export interface SettingsPageProps {
|
|
|
126
126
|
navigation: AdminNavigation;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
// Unconfigured settings page (no useQuery needed)
|
|
130
|
+
function SettingsPageUnconfigured() {
|
|
131
|
+
return (
|
|
132
|
+
<RouteGuard requiredPermission={{ resource: "settings", action: "manage" }}>
|
|
133
|
+
<div className="space-y-6 p-6">
|
|
134
|
+
<CmsPageHeader
|
|
135
|
+
title="Settings"
|
|
136
|
+
description="Configure your CMS settings and preferences."
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
<div className="space-y-6">
|
|
140
|
+
<AppearanceSection />
|
|
141
|
+
|
|
142
|
+
<Alert>
|
|
143
|
+
<Info className="size-4" />
|
|
144
|
+
<AlertDescription>
|
|
145
|
+
<strong>Settings not configured.</strong> To enable CMS settings,
|
|
146
|
+
export{" "}
|
|
147
|
+
<code className="rounded bg-muted px-1 py-0.5 text-xs">
|
|
148
|
+
getSettings
|
|
149
|
+
</code>
|
|
150
|
+
,{" "}
|
|
151
|
+
<code className="rounded bg-muted px-1 py-0.5 text-xs">
|
|
152
|
+
updateSettings
|
|
153
|
+
</code>
|
|
154
|
+
, and{" "}
|
|
155
|
+
<code className="rounded bg-muted px-1 py-0.5 text-xs">
|
|
156
|
+
resetSettings
|
|
157
|
+
</code>{" "}
|
|
158
|
+
from your{" "}
|
|
159
|
+
<code className="rounded bg-muted px-1 py-0.5 text-xs">
|
|
160
|
+
convex/admin.ts
|
|
161
|
+
</code>{" "}
|
|
162
|
+
file.
|
|
163
|
+
</AlertDescription>
|
|
164
|
+
</Alert>
|
|
165
|
+
|
|
166
|
+
<CmsSurface elevation="base" className="p-6">
|
|
167
|
+
<div className="mb-4 flex items-center gap-2">
|
|
168
|
+
<h2 className="text-lg font-semibold text-foreground">Features</h2>
|
|
169
|
+
<Badge variant="secondary" className="gap-1">
|
|
170
|
+
<Lock className="size-3" />
|
|
171
|
+
Default values
|
|
172
|
+
</Badge>
|
|
173
|
+
</div>
|
|
174
|
+
<p className="mb-4 text-sm text-muted-foreground">
|
|
175
|
+
Showing default feature flags. Configure settings in your admin
|
|
176
|
+
API to customize.
|
|
177
|
+
</p>
|
|
178
|
+
<div className="space-y-4">
|
|
179
|
+
{(
|
|
180
|
+
[
|
|
181
|
+
"versioning",
|
|
182
|
+
"scheduling",
|
|
183
|
+
"localization",
|
|
184
|
+
"mediaManagement",
|
|
185
|
+
] as const
|
|
186
|
+
).map((feature) => (
|
|
187
|
+
<div
|
|
188
|
+
key={feature}
|
|
189
|
+
className="flex items-center justify-between opacity-75"
|
|
190
|
+
>
|
|
191
|
+
<div>
|
|
192
|
+
<Label className="text-sm font-medium capitalize">
|
|
193
|
+
{feature}
|
|
194
|
+
</Label>
|
|
195
|
+
</div>
|
|
196
|
+
<Switch checked={DEFAULT_FEATURES[feature]} disabled={true} />
|
|
197
|
+
</div>
|
|
198
|
+
))}
|
|
199
|
+
</div>
|
|
200
|
+
</CmsSurface>
|
|
201
|
+
|
|
202
|
+
<CmsSurface elevation="base" className="p-6">
|
|
203
|
+
<h2 className="mb-4 text-lg font-semibold text-foreground">API</h2>
|
|
204
|
+
<div className="space-y-4">
|
|
205
|
+
<div>
|
|
206
|
+
<Label className="text-sm font-medium">
|
|
207
|
+
Convex Deployment URL
|
|
208
|
+
</Label>
|
|
209
|
+
<code className="mt-1 block rounded-md bg-muted px-3 py-2 text-sm">
|
|
210
|
+
{import.meta.env.VITE_CONVEX_URL || "Not configured"}
|
|
211
|
+
</code>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</CmsSurface>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
</RouteGuard>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Configured settings page with query
|
|
222
|
+
function SettingsPageConfigured({
|
|
130
223
|
api,
|
|
131
|
-
|
|
132
|
-
|
|
224
|
+
}: {
|
|
225
|
+
api: CmsAdminApi & { getSettings: NonNullable<CmsAdminApi["getSettings"]> };
|
|
226
|
+
}) {
|
|
133
227
|
const { canManageSettings } = usePermissions();
|
|
134
228
|
const canEdit = canManageSettings();
|
|
135
229
|
const adminConfig = useAdminConfig();
|
|
136
230
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}, [api]);
|
|
140
|
-
|
|
141
|
-
const settings = useQuery(
|
|
142
|
-
isConfigured ? api.getSettings : ("skip" as unknown as typeof api.getSettings),
|
|
143
|
-
isConfigured ? {} : "skip"
|
|
144
|
-
);
|
|
231
|
+
// Proper skip pattern: valid function ref, args as second param
|
|
232
|
+
const settings = useQuery(api.getSettings, {});
|
|
145
233
|
|
|
146
234
|
const updateSettingsMutation = useMutation(
|
|
147
235
|
api.updateSettings ?? ((() => {}) as unknown as typeof api.updateSettings)
|
|
@@ -197,7 +285,7 @@ export function SettingsPage({
|
|
|
197
285
|
);
|
|
198
286
|
|
|
199
287
|
const handleSave = useCallback(async () => {
|
|
200
|
-
if (!formData || !isDirty || !
|
|
288
|
+
if (!formData || !isDirty || !api.updateSettings) return;
|
|
201
289
|
|
|
202
290
|
setFeedbackStatus("saving");
|
|
203
291
|
setErrorMessage(null);
|
|
@@ -219,10 +307,10 @@ export function SettingsPage({
|
|
|
219
307
|
error instanceof Error ? error.message : "Failed to save settings",
|
|
220
308
|
);
|
|
221
309
|
}
|
|
222
|
-
}, [formData, isDirty,
|
|
310
|
+
}, [formData, isDirty, api.updateSettings, updateSettingsMutation]);
|
|
223
311
|
|
|
224
312
|
const handleReset = useCallback(async () => {
|
|
225
|
-
if (!
|
|
313
|
+
if (!api.resetSettings) return;
|
|
226
314
|
|
|
227
315
|
const confirmed = window.confirm(
|
|
228
316
|
"Are you sure you want to reset all settings to their defaults? This action cannot be undone.",
|
|
@@ -257,7 +345,7 @@ export function SettingsPage({
|
|
|
257
345
|
error instanceof Error ? error.message : "Failed to reset settings",
|
|
258
346
|
);
|
|
259
347
|
}
|
|
260
|
-
}, [
|
|
348
|
+
}, [api.resetSettings, resetSettingsMutation]);
|
|
261
349
|
|
|
262
350
|
const handleDiscard = useCallback(() => {
|
|
263
351
|
if (normalizedSettings) {
|
|
@@ -268,71 +356,6 @@ export function SettingsPage({
|
|
|
268
356
|
}
|
|
269
357
|
}, [normalizedSettings]);
|
|
270
358
|
|
|
271
|
-
if (!isConfigured) {
|
|
272
|
-
return (
|
|
273
|
-
<RouteGuard
|
|
274
|
-
requiredPermission={{ resource: "settings", action: "manage" }}
|
|
275
|
-
>
|
|
276
|
-
<div className="space-y-6 p-6">
|
|
277
|
-
<CmsPageHeader
|
|
278
|
-
title="Settings"
|
|
279
|
-
description="Configure your CMS settings and preferences."
|
|
280
|
-
/>
|
|
281
|
-
|
|
282
|
-
<div className="space-y-6">
|
|
283
|
-
<AppearanceSection />
|
|
284
|
-
|
|
285
|
-
<Alert>
|
|
286
|
-
<Info className="size-4" />
|
|
287
|
-
<AlertDescription>
|
|
288
|
-
<strong>Settings not configured.</strong> To enable CMS settings, export{" "}
|
|
289
|
-
<code className="rounded bg-muted px-1 py-0.5 text-xs">getSettings</code>,{" "}
|
|
290
|
-
<code className="rounded bg-muted px-1 py-0.5 text-xs">updateSettings</code>, and{" "}
|
|
291
|
-
<code className="rounded bg-muted px-1 py-0.5 text-xs">resetSettings</code> from your{" "}
|
|
292
|
-
<code className="rounded bg-muted px-1 py-0.5 text-xs">convex/admin.ts</code> file.
|
|
293
|
-
</AlertDescription>
|
|
294
|
-
</Alert>
|
|
295
|
-
|
|
296
|
-
<CmsSurface elevation="base" className="p-6">
|
|
297
|
-
<div className="mb-4 flex items-center gap-2">
|
|
298
|
-
<h2 className="text-lg font-semibold text-foreground">Features</h2>
|
|
299
|
-
<Badge variant="secondary" className="gap-1">
|
|
300
|
-
<Lock className="size-3" />
|
|
301
|
-
Default values
|
|
302
|
-
</Badge>
|
|
303
|
-
</div>
|
|
304
|
-
<p className="mb-4 text-sm text-muted-foreground">
|
|
305
|
-
Showing default feature flags. Configure settings in your admin API to customize.
|
|
306
|
-
</p>
|
|
307
|
-
<div className="space-y-4">
|
|
308
|
-
{(["versioning", "scheduling", "localization", "mediaManagement"] as const).map((feature) => (
|
|
309
|
-
<div key={feature} className="flex items-center justify-between opacity-75">
|
|
310
|
-
<div>
|
|
311
|
-
<Label className="text-sm font-medium capitalize">{feature}</Label>
|
|
312
|
-
</div>
|
|
313
|
-
<Switch checked={DEFAULT_FEATURES[feature]} disabled={true} />
|
|
314
|
-
</div>
|
|
315
|
-
))}
|
|
316
|
-
</div>
|
|
317
|
-
</CmsSurface>
|
|
318
|
-
|
|
319
|
-
<CmsSurface elevation="base" className="p-6">
|
|
320
|
-
<h2 className="mb-4 text-lg font-semibold text-foreground">API</h2>
|
|
321
|
-
<div className="space-y-4">
|
|
322
|
-
<div>
|
|
323
|
-
<Label className="text-sm font-medium">Convex Deployment URL</Label>
|
|
324
|
-
<code className="mt-1 block rounded-md bg-muted px-3 py-2 text-sm">
|
|
325
|
-
{import.meta.env.VITE_CONVEX_URL || "Not configured"}
|
|
326
|
-
</code>
|
|
327
|
-
</div>
|
|
328
|
-
</div>
|
|
329
|
-
</CmsSurface>
|
|
330
|
-
</div>
|
|
331
|
-
</div>
|
|
332
|
-
</RouteGuard>
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
359
|
if (settings === undefined) {
|
|
337
360
|
return (
|
|
338
361
|
<RouteGuard
|
|
@@ -547,3 +570,24 @@ export function SettingsPage({
|
|
|
547
570
|
</RouteGuard>
|
|
548
571
|
);
|
|
549
572
|
}
|
|
573
|
+
|
|
574
|
+
// Main wrapper that decides which component to render
|
|
575
|
+
export function SettingsPage({
|
|
576
|
+
api,
|
|
577
|
+
navigation: _navigation,
|
|
578
|
+
}: SettingsPageProps) {
|
|
579
|
+
// Check if settings API is configured
|
|
580
|
+
if (api.getSettings) {
|
|
581
|
+
return (
|
|
582
|
+
<SettingsPageConfigured
|
|
583
|
+
api={
|
|
584
|
+
api as CmsAdminApi & {
|
|
585
|
+
getSettings: NonNullable<CmsAdminApi["getSettings"]>;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/>
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return <SettingsPageUnconfigured />;
|
|
593
|
+
}
|
|
@@ -231,7 +231,10 @@ function ConvexProviderWrapper({
|
|
|
231
231
|
return (
|
|
232
232
|
<ConvexProvider client={convex}>
|
|
233
233
|
<ApiProvider api={api.admin}>
|
|
234
|
-
<SettingsConfigProvider
|
|
234
|
+
<SettingsConfigProvider
|
|
235
|
+
baseConfig={adminConfig}
|
|
236
|
+
api={{ getSettings: api.admin.getSettings }}
|
|
237
|
+
>
|
|
235
238
|
{children}
|
|
236
239
|
</SettingsConfigProvider>
|
|
237
240
|
</ApiProvider>
|
|
@@ -12,12 +12,21 @@
|
|
|
12
12
|
@apply border-border;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/* Standalone mode - admin owns the document */
|
|
15
16
|
body {
|
|
16
17
|
@apply bg-background text-foreground;
|
|
17
18
|
font-feature-settings: 'rlig' 1, 'calt' 1;
|
|
18
19
|
-webkit-font-smoothing: antialiased;
|
|
19
20
|
-moz-osx-font-smoothing: grayscale;
|
|
20
21
|
}
|
|
22
|
+
|
|
23
|
+
/* Embedded mode - scope styles to container */
|
|
24
|
+
[data-cms-admin] {
|
|
25
|
+
@apply bg-background text-foreground;
|
|
26
|
+
font-feature-settings: 'rlig' 1, 'calt' 1;
|
|
27
|
+
-webkit-font-smoothing: antialiased;
|
|
28
|
+
-moz-osx-font-smoothing: grayscale;
|
|
29
|
+
}
|
|
21
30
|
}
|
|
22
31
|
|
|
23
32
|
@layer utilities {
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
CMS Admin Theme - CSS Custom Properties
|
|
3
3
|
Professional & Clean (Linear/Notion aesthetic)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Theme modes:
|
|
6
|
+
- Standalone: Uses :root for the entire document
|
|
7
|
+
- Isolated (embedded): Admin uses its own theme via [data-cms-admin="isolated"]
|
|
8
|
+
- Inherit (embedded): Admin inherits parent's colors via [data-cms-admin="inherit"]
|
|
7
9
|
============================================ */
|
|
8
10
|
|
|
11
|
+
/* Standalone mode - admin owns the document */
|
|
9
12
|
:root {
|
|
10
13
|
/* Main colors */
|
|
11
14
|
--background: hsl(0 0% 98%);
|
|
@@ -64,7 +67,106 @@
|
|
|
64
67
|
--info-foreground: hsl(217 91% 30%);
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
/* Isolated mode - forces admin's own theme, ignores parent styles */
|
|
71
|
+
[data-cms-admin="isolated"] {
|
|
72
|
+
/* Main colors */
|
|
73
|
+
--background: hsl(0 0% 98%);
|
|
74
|
+
--foreground: hsl(240 10% 3.9%);
|
|
75
|
+
--card: hsl(0 0% 100%);
|
|
76
|
+
--card-foreground: hsl(240 10% 3.9%);
|
|
77
|
+
--popover: hsl(0 0% 100%);
|
|
78
|
+
--popover-foreground: hsl(240 10% 3.9%);
|
|
79
|
+
--primary: hsl(240 5.9% 10%);
|
|
80
|
+
--primary-foreground: hsl(0 0% 98%);
|
|
81
|
+
--secondary: hsl(240 4.8% 95.9%);
|
|
82
|
+
--secondary-foreground: hsl(240 5.9% 10%);
|
|
83
|
+
--muted: hsl(240 4.8% 95.9%);
|
|
84
|
+
--muted-foreground: hsl(240 3.8% 46.1%);
|
|
85
|
+
--accent: hsl(240 4.8% 95.9%);
|
|
86
|
+
--accent-foreground: hsl(240 5.9% 10%);
|
|
87
|
+
--destructive: hsl(0 84.2% 60.2%);
|
|
88
|
+
--destructive-foreground: hsl(0 0% 98%);
|
|
89
|
+
--border: hsl(240 5.9% 90%);
|
|
90
|
+
--input: hsl(240 5.9% 90%);
|
|
91
|
+
--ring: hsl(240 5.9% 10%);
|
|
92
|
+
|
|
93
|
+
/* Sidebar */
|
|
94
|
+
--sidebar: hsl(0 0% 98%);
|
|
95
|
+
--sidebar-foreground: hsl(240 5.3% 26.1%);
|
|
96
|
+
--sidebar-primary: hsl(240 5.9% 10%);
|
|
97
|
+
--sidebar-primary-foreground: hsl(0 0% 98%);
|
|
98
|
+
--sidebar-accent: hsl(240 4.8% 95.9%);
|
|
99
|
+
--sidebar-accent-foreground: hsl(240 5.9% 10%);
|
|
100
|
+
--sidebar-border: hsl(220 13% 91%);
|
|
101
|
+
--sidebar-ring: hsl(217.2 91.2% 59.8%);
|
|
102
|
+
|
|
103
|
+
/* Semantic: Diff/Change indicators */
|
|
104
|
+
--diff-added: hsl(142 76% 36%);
|
|
105
|
+
--diff-added-bg: hsl(142 76% 95%);
|
|
106
|
+
--diff-added-border: hsl(142 76% 85%);
|
|
107
|
+
--diff-added-foreground: hsl(142 76% 25%);
|
|
108
|
+
|
|
109
|
+
--diff-removed: hsl(0 84% 60%);
|
|
110
|
+
--diff-removed-bg: hsl(0 86% 97%);
|
|
111
|
+
--diff-removed-border: hsl(0 93% 85%);
|
|
112
|
+
--diff-removed-foreground: hsl(0 72% 30%);
|
|
113
|
+
|
|
114
|
+
--diff-modified: hsl(38 92% 50%);
|
|
115
|
+
--diff-modified-bg: hsl(48 96% 97%);
|
|
116
|
+
--diff-modified-border: hsl(48 96% 82%);
|
|
117
|
+
--diff-modified-foreground: hsl(38 92% 25%);
|
|
118
|
+
|
|
119
|
+
/* Semantic: Success/Warning/Info states */
|
|
120
|
+
--success: hsl(142 76% 36%);
|
|
121
|
+
--success-foreground: hsl(0 0% 100%);
|
|
122
|
+
--warning: hsl(38 92% 50%);
|
|
123
|
+
--warning-foreground: hsl(0 0% 100%);
|
|
124
|
+
--info: hsl(217 91% 60%);
|
|
125
|
+
--info-bg: hsl(214 95% 97%);
|
|
126
|
+
--info-foreground: hsl(217 91% 30%);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Inherit mode - uses parent's colors with fallbacks for layout */
|
|
130
|
+
[data-cms-admin="inherit"] {
|
|
131
|
+
/* Sidebar variables with fallbacks (prevents broken layout) */
|
|
132
|
+
--sidebar: var(--sidebar, hsl(0 0% 98%));
|
|
133
|
+
--sidebar-foreground: var(--sidebar-foreground, hsl(240 5.3% 26.1%));
|
|
134
|
+
--sidebar-primary: var(--sidebar-primary, hsl(240 5.9% 10%));
|
|
135
|
+
--sidebar-primary-foreground: var(--sidebar-primary-foreground, hsl(0 0% 98%));
|
|
136
|
+
--sidebar-accent: var(--sidebar-accent, hsl(240 4.8% 95.9%));
|
|
137
|
+
--sidebar-accent-foreground: var(--sidebar-accent-foreground, hsl(240 5.9% 10%));
|
|
138
|
+
--sidebar-border: var(--sidebar-border, hsl(220 13% 91%));
|
|
139
|
+
--sidebar-ring: var(--sidebar-ring, hsl(217.2 91.2% 59.8%));
|
|
140
|
+
|
|
141
|
+
/* Semantic variables with fallbacks */
|
|
142
|
+
--diff-added: var(--diff-added, hsl(142 76% 36%));
|
|
143
|
+
--diff-added-bg: var(--diff-added-bg, hsl(142 76% 95%));
|
|
144
|
+
--diff-added-border: var(--diff-added-border, hsl(142 76% 85%));
|
|
145
|
+
--diff-added-foreground: var(--diff-added-foreground, hsl(142 76% 25%));
|
|
146
|
+
|
|
147
|
+
--diff-removed: var(--diff-removed, hsl(0 84% 60%));
|
|
148
|
+
--diff-removed-bg: var(--diff-removed-bg, hsl(0 86% 97%));
|
|
149
|
+
--diff-removed-border: var(--diff-removed-border, hsl(0 93% 85%));
|
|
150
|
+
--diff-removed-foreground: var(--diff-removed-foreground, hsl(0 72% 30%));
|
|
151
|
+
|
|
152
|
+
--diff-modified: var(--diff-modified, hsl(38 92% 50%));
|
|
153
|
+
--diff-modified-bg: var(--diff-modified-bg, hsl(48 96% 97%));
|
|
154
|
+
--diff-modified-border: var(--diff-modified-border, hsl(48 96% 82%));
|
|
155
|
+
--diff-modified-foreground: var(--diff-modified-foreground, hsl(38 92% 25%));
|
|
156
|
+
|
|
157
|
+
--success: var(--success, hsl(142 76% 36%));
|
|
158
|
+
--success-foreground: var(--success-foreground, hsl(0 0% 100%));
|
|
159
|
+
--warning: var(--warning, hsl(38 92% 50%));
|
|
160
|
+
--warning-foreground: var(--warning-foreground, hsl(0 0% 100%));
|
|
161
|
+
--info: var(--info, hsl(217 91% 60%));
|
|
162
|
+
--info-bg: var(--info-bg, hsl(214 95% 97%));
|
|
163
|
+
--info-foreground: var(--info-foreground, hsl(217 91% 30%));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* Dark mode - standalone and isolated modes */
|
|
167
|
+
.dark,
|
|
168
|
+
[data-cms-admin="isolated"].dark,
|
|
169
|
+
[data-cms-admin="isolated"] .dark {
|
|
68
170
|
/* Main colors */
|
|
69
171
|
--background: hsl(240 10% 3.9%);
|
|
70
172
|
--foreground: hsl(0 0% 98%);
|
|
@@ -121,3 +223,39 @@
|
|
|
121
223
|
--info-bg: hsl(217 50% 15%);
|
|
122
224
|
--info-foreground: hsl(217 80% 80%);
|
|
123
225
|
}
|
|
226
|
+
|
|
227
|
+
/* Dark mode sidebar fallbacks for inherit mode */
|
|
228
|
+
[data-cms-admin="inherit"].dark,
|
|
229
|
+
[data-cms-admin="inherit"] .dark {
|
|
230
|
+
--sidebar: var(--sidebar, hsl(240 5.9% 10%));
|
|
231
|
+
--sidebar-foreground: var(--sidebar-foreground, hsl(240 4.8% 95.9%));
|
|
232
|
+
--sidebar-primary: var(--sidebar-primary, hsl(224.3 76.3% 48%));
|
|
233
|
+
--sidebar-primary-foreground: var(--sidebar-primary-foreground, hsl(0 0% 100%));
|
|
234
|
+
--sidebar-accent: var(--sidebar-accent, hsl(240 3.7% 15.9%));
|
|
235
|
+
--sidebar-accent-foreground: var(--sidebar-accent-foreground, hsl(240 4.8% 95.9%));
|
|
236
|
+
--sidebar-border: var(--sidebar-border, hsl(240 3.7% 15.9%));
|
|
237
|
+
--sidebar-ring: var(--sidebar-ring, hsl(217.2 91.2% 59.8%));
|
|
238
|
+
|
|
239
|
+
--diff-added: var(--diff-added, hsl(142 70% 50%));
|
|
240
|
+
--diff-added-bg: var(--diff-added-bg, hsl(142 50% 15%));
|
|
241
|
+
--diff-added-border: var(--diff-added-border, hsl(142 50% 25%));
|
|
242
|
+
--diff-added-foreground: var(--diff-added-foreground, hsl(142 70% 80%));
|
|
243
|
+
|
|
244
|
+
--diff-removed: var(--diff-removed, hsl(0 70% 55%));
|
|
245
|
+
--diff-removed-bg: var(--diff-removed-bg, hsl(0 50% 15%));
|
|
246
|
+
--diff-removed-border: var(--diff-removed-border, hsl(0 50% 25%));
|
|
247
|
+
--diff-removed-foreground: var(--diff-removed-foreground, hsl(0 70% 80%));
|
|
248
|
+
|
|
249
|
+
--diff-modified: var(--diff-modified, hsl(38 80% 55%));
|
|
250
|
+
--diff-modified-bg: var(--diff-modified-bg, hsl(38 50% 15%));
|
|
251
|
+
--diff-modified-border: var(--diff-modified-border, hsl(38 50% 25%));
|
|
252
|
+
--diff-modified-foreground: var(--diff-modified-foreground, hsl(38 80% 80%));
|
|
253
|
+
|
|
254
|
+
--success: var(--success, hsl(142 70% 50%));
|
|
255
|
+
--success-foreground: var(--success-foreground, hsl(0 0% 100%));
|
|
256
|
+
--warning: var(--warning, hsl(38 80% 55%));
|
|
257
|
+
--warning-foreground: var(--warning-foreground, hsl(0 0% 100%));
|
|
258
|
+
--info: var(--info, hsl(217 80% 60%));
|
|
259
|
+
--info-bg: var(--info-bg, hsl(217 50% 15%));
|
|
260
|
+
--info-foreground: var(--info-foreground, hsl(217 80% 80%));
|
|
261
|
+
}
|
package/admin-dist/nitro.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as e,C as n,h as x}from"./main-
|
|
1
|
+
import{j as e,C as n,h as x}from"./main-CA-4LyFT.js";function d({icon:s,title:m,description:r,action:t,className:l,...a}){return e.jsxs("div",{className:x("flex flex-col items-center justify-center py-12 text-center",l),...a,children:[s&&e.jsx("div",{className:"mb-4 flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground",children:s}),e.jsx("h3",{className:"text-base font-semibold text-foreground",children:m}),r&&e.jsx("p",{className:"mt-1 max-w-sm text-sm text-muted-foreground",children:r}),t&&e.jsx(n,{variant:t.variant??"primary",onClick:t.onClick,className:"mt-4",children:t.label})]})}export{d as C};
|