convex-cms 0.0.9-alpha.5 → 0.0.9-alpha.6

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 (72) hide show
  1. package/admin/src/embed/index.tsx +57 -67
  2. package/admin/src/embed/pages/Entry.tsx +4 -5
  3. package/admin/src/embed/pages/NewEntry.tsx +2 -1
  4. package/admin/src/embed/types.ts +0 -1
  5. package/admin-dist/nitro.json +1 -1
  6. package/admin-dist/public/assets/{CmsEmptyState-BM4e6N83.js → CmsEmptyState-la_kLGHv.js} +1 -1
  7. package/admin-dist/public/assets/{CmsPageHeader-uor3DPIk.js → CmsPageHeader-Cf0SafVG.js} +1 -1
  8. package/admin-dist/public/assets/{CmsStatusBadge-D8N18LJx.js → CmsStatusBadge-BovugIHH.js} +1 -1
  9. package/admin-dist/public/assets/{CmsSurface-BEcY-WpI.js → CmsSurface-Dp1TegU4.js} +1 -1
  10. package/admin-dist/public/assets/{CmsToolbar-DE-bu3W8.js → CmsToolbar-B1zdfMu0.js} +1 -1
  11. package/admin-dist/public/assets/{ContentEntryEditor-BdkIMCUk.js → ContentEntryEditor-CB--8SC3.js} +1 -1
  12. package/admin-dist/public/assets/{TaxonomyFilter-a1-O9DPs.js → TaxonomyFilter-fmZxxbdA.js} +1 -1
  13. package/admin-dist/public/assets/{_contentTypeId-XIkYOLyY.js → _contentTypeId-vT09P77i.js} +1 -1
  14. package/admin-dist/public/assets/{_entryId-DyP15QpI.js → _entryId-Dhq6ybYt.js} +1 -1
  15. package/admin-dist/public/assets/{alert-DHBQuuib.js → alert-DfdkD-ZZ.js} +1 -1
  16. package/admin-dist/public/assets/{badge-BOhWFWzb.js → badge-Cmc3T9vs.js} +1 -1
  17. package/admin-dist/public/assets/{circle-check-big-DjTNapen.js → circle-check-big-B8AJAcBi.js} +1 -1
  18. package/admin-dist/public/assets/{command-BIc5_8gL.js → command-BsmkQ6_j.js} +1 -1
  19. package/admin-dist/public/assets/{content-C3N8Ugra.js → content-DdQ1u7T4.js} +1 -1
  20. package/admin-dist/public/assets/{content-types-D0wh1eUF.js → content-types-QYcwm0Sy.js} +1 -1
  21. package/admin-dist/public/assets/{index-B-g3F_ri.js → index-kxLB3e43.js} +1 -1
  22. package/admin-dist/public/assets/main-BrFRroF1.js +107 -0
  23. package/admin-dist/public/assets/{media-8uh1MwDi.js → media-Cx9IyZvU.js} +1 -1
  24. package/admin-dist/public/assets/{new._contentTypeId-S96rFbgY.js → new._contentTypeId-2HsDwzy_.js} +1 -1
  25. package/admin-dist/public/assets/{pencil-DgaZav4e.js → pencil-PaJhpDeC.js} +1 -1
  26. package/admin-dist/public/assets/{refresh-cw-BBut4hAU.js → refresh-cw-D_KNzBUN.js} +1 -1
  27. package/admin-dist/public/assets/{rotate-ccw-DVCkojZZ.js → rotate-ccw-I3wzW1RQ.js} +1 -1
  28. package/admin-dist/public/assets/{scroll-area-DPC4uXzf.js → scroll-area-t72-FBB4.js} +1 -1
  29. package/admin-dist/public/assets/{search-CSyHHglh.js → search-BsuImjd-.js} +1 -1
  30. package/admin-dist/public/assets/{settings-cEqPsoJ0.js → settings-DB6TvceQ.js} +1 -1
  31. package/admin-dist/public/assets/{switch-O2BviO8Q.js → switch-DWN_fx2n.js} +1 -1
  32. package/admin-dist/public/assets/{tabs-p1MWhOqY.js → tabs-BG0vukFH.js} +1 -1
  33. package/admin-dist/public/assets/{tanstack-adapter-CDrxoPZD.js → tanstack-adapter-DHsy8Fjs.js} +1 -1
  34. package/admin-dist/public/assets/{taxonomies-DJ9UbjXW.js → taxonomies-WG8YV2pR.js} +1 -1
  35. package/admin-dist/public/assets/{trash-RnpP6lXF.js → trash-C3Lt5m9d.js} +1 -1
  36. package/admin-dist/public/assets/{useBreadcrumbLabel-zbIWXlkc.js → useBreadcrumbLabel-BC8wl3jQ.js} +1 -1
  37. package/admin-dist/public/assets/{usePermissions-4CTlK-vU.js → usePermissions-BM1Vv1YJ.js} +1 -1
  38. package/admin-dist/server/_libs/convex-helpers.mjs +37 -0
  39. package/admin-dist/server/_libs/convex.mjs +1357 -168
  40. package/admin-dist/server/_ssr/{CmsEmptyState-BA0Lc5xs.mjs → CmsEmptyState-NKmyUWD9.mjs} +1 -1
  41. package/admin-dist/server/_ssr/{CmsPageHeader-PMyecILZ.mjs → CmsPageHeader-CngxPIOg.mjs} +1 -1
  42. package/admin-dist/server/_ssr/{CmsStatusBadge-CInuN2bZ.mjs → CmsStatusBadge-D4fiHjJD.mjs} +2 -2
  43. package/admin-dist/server/_ssr/{CmsSurface-CH1PIfcS.mjs → CmsSurface-DN9I2iuX.mjs} +1 -1
  44. package/admin-dist/server/_ssr/{CmsToolbar-IuhSA7gR.mjs → CmsToolbar-ux-veU96.mjs} +1 -1
  45. package/admin-dist/server/_ssr/{ContentEntryEditor-Bzhir4fQ.mjs → ContentEntryEditor-BiY9bJr9.mjs} +9 -9
  46. package/admin-dist/server/_ssr/{TaxonomyFilter-r4izSMBh.mjs → TaxonomyFilter-BdtKJie2.mjs} +3 -3
  47. package/admin-dist/server/_ssr/{_contentTypeId-BWEbjqxY.mjs → _contentTypeId-CRo5WQVu.mjs} +12 -11
  48. package/admin-dist/server/_ssr/{_entryId-B5xoXoJf.mjs → _entryId-FnG3uc_Y.mjs} +12 -11
  49. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BbSNqRIw.mjs +4 -0
  50. package/admin-dist/server/_ssr/{badge-DXrjBRqZ.mjs → badge-D3SGS0Jp.mjs} +1 -1
  51. package/admin-dist/server/_ssr/{command-Cj90OdCX.mjs → command-2t7uTBKt.mjs} +1 -1
  52. package/admin-dist/server/_ssr/{content-DKRI-YqL.mjs → content-CCsSzXeb.mjs} +10 -9
  53. package/admin-dist/server/_ssr/{content-types-BzgRcS8K.mjs → content-types-CuVG3uSt.mjs} +7 -6
  54. package/admin-dist/server/_ssr/{index-BPf6_agY.mjs → index-CLwCLoco.mjs} +5 -4
  55. package/admin-dist/server/_ssr/index.mjs +2 -2
  56. package/admin-dist/server/_ssr/{media-MpjxOZL8.mjs → media-D3duVWkk.mjs} +11 -10
  57. package/admin-dist/server/_ssr/{new._contentTypeId-DSb4qR9j.mjs → new._contentTypeId-6uKYdgGO.mjs} +11 -10
  58. package/admin-dist/server/_ssr/router-D9Zk56-q.mjs +7979 -0
  59. package/admin-dist/server/_ssr/{scroll-area-JwVD_6MZ.mjs → scroll-area-vbjKsfFu.mjs} +1 -1
  60. package/admin-dist/server/_ssr/{settings-KVJNe0GM.mjs → settings-D7-vwjqD.mjs} +9 -8
  61. package/admin-dist/server/_ssr/{switch-DvREvRv4.mjs → switch-CbKuV4Qh.mjs} +1 -1
  62. package/admin-dist/server/_ssr/{tabs-B0h57pFf.mjs → tabs-C_IfqLiu.mjs} +2 -2
  63. package/admin-dist/server/_ssr/{tanstack-adapter-gmM64LnW.mjs → tanstack-adapter-yhyAcBi-.mjs} +1 -1
  64. package/admin-dist/server/_ssr/{taxonomies-BbBNx260.mjs → taxonomies-C15s_nvM.mjs} +9 -8
  65. package/admin-dist/server/_ssr/{trash-JAzYGh7A.mjs → trash-BPIjmAUh.mjs} +9 -8
  66. package/admin-dist/server/_ssr/{useBreadcrumbLabel-BWIujj97.mjs → useBreadcrumbLabel-BC7plG0L.mjs} +1 -1
  67. package/admin-dist/server/_ssr/{usePermissions-CcLDCSwa.mjs → usePermissions-DJ8a7bZU.mjs} +1 -1
  68. package/admin-dist/server/index.mjs +157 -157
  69. package/package.json +2 -1
  70. package/admin-dist/public/assets/main-BapBJgQD.js +0 -102
  71. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-CBTan6ii.mjs +0 -4
  72. package/admin-dist/server/_ssr/router-Dk9ikPNc.mjs +0 -3055
@@ -1,3055 +0,0 @@
1
- import { c as createRouter, a as createRootRoute, b as createFileRoute, l as lazyRouteComponent, O as Outlet, H as HeadContent, S as Scripts, u as useRouterState, L as Link, d as useNavigate } from "../_chunks/_libs/@tanstack/react-router.mjs";
2
- import { r as reactExports, j as jsxRuntimeExports } from "../_chunks/_libs/react.mjs";
3
- import { c as clsx } from "../_libs/clsx.mjs";
4
- import { t as twMerge } from "../_libs/tailwind-merge.mjs";
5
- import { R as Root$1, C as CollapsibleTrigger$1, a as CollapsibleContent$1 } from "../_chunks/_libs/@radix-ui/react-collapsible.mjs";
6
- import { R as Root$3, C as Content, a as Close, T as Title, D as Description, P as Portal$2, O as Overlay } from "../_chunks/_libs/@radix-ui/react-dialog.mjs";
7
- import { S as Slot } from "../_chunks/_libs/@radix-ui/react-slot.mjs";
8
- import { c as cva } from "../_libs/class-variance-authority.mjs";
9
- import { R as Root$2 } from "../_chunks/_libs/@radix-ui/react-label.mjs";
10
- import { C as Checkbox$1, a as CheckboxIndicator } from "../_chunks/_libs/@radix-ui/react-checkbox.mjs";
11
- import { R as Root2$2, T as Trigger$2, I as Icon$1, V as Value, P as Portal$1, C as Content2$2, a as Viewport, b as Item, c as ItemIndicator, d as ItemText, S as ScrollUpButton, e as ScrollDownButton } from "../_chunks/_libs/@radix-ui/react-select.mjs";
12
- import { R as Root2$1, T as Trigger$1, P as Portal2, C as Content2$1, L as Label2, S as Separator2, I as Item2 } from "../_chunks/_libs/@radix-ui/react-dropdown-menu.mjs";
13
- import { R as Root, I as Image, F as Fallback } from "../_chunks/_libs/@radix-ui/react-avatar.mjs";
14
- import { R as Root2, T as Trigger, P as Portal, C as Content2 } from "../_chunks/_libs/@radix-ui/react-popover.mjs";
15
- import { c as createServerFn, T as TSS_SERVER_FUNCTION, g as getServerFnById } from "./index.mjs";
16
- import { v, c as componentsGeneric, C as ConvexReactClient, a as ConvexProvider, b as anyApi, u as useQuery, d as useMutation } from "../_libs/convex.mjs";
17
- import { L as LoaderCircle, a as Layers, H as House, B as Bell, C as CircleQuestionMark, b as Book, c as Code, M as MessageSquare, E as ExternalLink, d as ChevronDown, U as User, e as LogOut, f as CodeXml, P as Plus, g as ChevronUp, X, h as ChevronRight, i as Check, j as CircleAlert, k as Eye, S as SquarePen, A as Archive, l as Bookmark, F as Flag, m as Heart, n as Star, o as Package, p as Phone, q as Mail, D as DollarSign, r as MapPin, s as Folder, t as Braces, u as SquareCheckBig, T as ToggleLeft, v as Hash, w as Link$1, x as Clock, y as Calendar, G as Globe, z as Lock, I as Trash2, J as Settings, K as FileCode, N as Tags, O as Image$1, Q as FileText, R as LayoutDashboard, V as FolderOpen, W as Tag, Y as List, Z as Link2, _ as FileType, $ as TextAlignStart, a0 as TriangleAlert } from "../_libs/lucide-react.mjs";
18
- import { o as object, b as boolean, s as string, _ as _enum, n as number, a as array, r as record } from "../_libs/zod.mjs";
19
- import "../_libs/tiny-warning.mjs";
20
- import "../_chunks/_libs/@tanstack/router-core.mjs";
21
- import "../_chunks/_libs/@tanstack/history.mjs";
22
- import "../_libs/tiny-invariant.mjs";
23
- import "node:stream/web";
24
- import "node:stream";
25
- import "../_chunks/_libs/react-dom.mjs";
26
- import "util";
27
- import "crypto";
28
- import "async_hooks";
29
- import "stream";
30
- import "../_libs/isbot.mjs";
31
- import "../_chunks/_libs/@radix-ui/primitive.mjs";
32
- import "../_chunks/_libs/@radix-ui/react-context.mjs";
33
- import "../_chunks/_libs/@radix-ui/react-use-controllable-state.mjs";
34
- import "../_chunks/_libs/@radix-ui/react-use-layout-effect.mjs";
35
- import "../_chunks/_libs/@radix-ui/react-compose-refs.mjs";
36
- import "../_chunks/_libs/@radix-ui/react-primitive.mjs";
37
- import "../_chunks/_libs/@radix-ui/react-presence.mjs";
38
- import "../_chunks/_libs/@radix-ui/react-id.mjs";
39
- import "../_chunks/_libs/@radix-ui/react-dismissable-layer.mjs";
40
- import "../_chunks/_libs/@radix-ui/react-use-callback-ref.mjs";
41
- import "../_chunks/_libs/@radix-ui/react-use-escape-keydown.mjs";
42
- import "../_chunks/_libs/@radix-ui/react-focus-scope.mjs";
43
- import "../_chunks/_libs/@radix-ui/react-portal.mjs";
44
- import "../_chunks/_libs/@radix-ui/react-focus-guards.mjs";
45
- import "../_libs/react-remove-scroll.mjs";
46
- import "../_libs/tslib.mjs";
47
- import "../_libs/react-remove-scroll-bar.mjs";
48
- import "../_libs/react-style-singleton.mjs";
49
- import "../_libs/get-nonce.mjs";
50
- import "../_libs/use-sidecar.mjs";
51
- import "../_libs/use-callback-ref.mjs";
52
- import "../_libs/aria-hidden.mjs";
53
- import "../_chunks/_libs/@radix-ui/react-use-previous.mjs";
54
- import "../_chunks/_libs/@radix-ui/react-use-size.mjs";
55
- import "../_chunks/_libs/@radix-ui/number.mjs";
56
- import "../_chunks/_libs/@radix-ui/react-collection.mjs";
57
- import "../_chunks/_libs/@radix-ui/react-direction.mjs";
58
- import "../_chunks/_libs/@radix-ui/react-popper.mjs";
59
- import "../_chunks/_libs/@floating-ui/react-dom.mjs";
60
- import "../_chunks/_libs/@floating-ui/dom.mjs";
61
- import "../_chunks/_libs/@floating-ui/core.mjs";
62
- import "../_chunks/_libs/@floating-ui/utils.mjs";
63
- import "../_chunks/_libs/@radix-ui/react-arrow.mjs";
64
- import "../_chunks/_libs/@radix-ui/react-visually-hidden.mjs";
65
- import "../_chunks/_libs/@radix-ui/react-menu.mjs";
66
- import "../_chunks/_libs/@radix-ui/react-roving-focus.mjs";
67
- import "../_chunks/_libs/@radix-ui/react-use-is-hydrated.mjs";
68
- import "../_libs/use-sync-external-store.mjs";
69
- import "node:async_hooks";
70
- const globalsCss = "/assets/globals-CoCRjt0K.css";
71
- function cn(...inputs) {
72
- return twMerge(clsx(inputs));
73
- }
74
- v.union(
75
- v.literal("admin"),
76
- v.literal("editor"),
77
- v.literal("author"),
78
- v.literal("viewer")
79
- );
80
- const resourceValidator = v.union(
81
- v.literal("contentTypes"),
82
- v.literal("contentEntries"),
83
- v.literal("mediaItems"),
84
- v.literal("settings")
85
- );
86
- const actionValidator = v.union(
87
- v.literal("create"),
88
- v.literal("read"),
89
- v.literal("update"),
90
- v.literal("delete"),
91
- v.literal("publish"),
92
- v.literal("unpublish"),
93
- v.literal("restore"),
94
- v.literal("manage"),
95
- v.literal("move")
96
- );
97
- v.object({
98
- resource: resourceValidator,
99
- action: actionValidator,
100
- scope: v.optional(v.union(v.literal("all"), v.literal("own")))
101
- });
102
- function fullCrud(resource, scope = "all") {
103
- return [
104
- { resource, action: "create", scope },
105
- { resource, action: "read", scope },
106
- { resource, action: "update", scope },
107
- { resource, action: "delete", scope }
108
- ];
109
- }
110
- function readOnly(resource, scope = "all") {
111
- return [{ resource, action: "read", scope }];
112
- }
113
- function publishPermissions(scope = "all") {
114
- return [
115
- { resource: "contentEntries", action: "publish", scope },
116
- { resource: "contentEntries", action: "unpublish", scope }
117
- ];
118
- }
119
- const ADMIN_ROLE = {
120
- name: "admin",
121
- displayName: "Administrator",
122
- description: "Full access to all CMS features including settings and content type management",
123
- isSystem: true,
124
- permissions: [
125
- // Content types - full management
126
- ...fullCrud("contentTypes"),
127
- // Content entries - full CRUD + publish
128
- ...fullCrud("contentEntries"),
129
- ...publishPermissions(),
130
- { resource: "contentEntries", action: "restore" },
131
- // Media - full management
132
- ...fullCrud("mediaItems"),
133
- // Settings - full access
134
- { resource: "settings", action: "manage" },
135
- ...readOnly("settings")
136
- ]
137
- };
138
- const EDITOR_ROLE = {
139
- name: "editor",
140
- displayName: "Editor",
141
- description: "Can manage all content and media, but cannot modify settings or content types",
142
- isSystem: true,
143
- permissions: [
144
- // Content types - read only
145
- ...readOnly("contentTypes"),
146
- // Content entries - full CRUD + publish
147
- ...fullCrud("contentEntries"),
148
- ...publishPermissions(),
149
- { resource: "contentEntries", action: "restore" },
150
- // Media - full management
151
- ...fullCrud("mediaItems")
152
- ]
153
- };
154
- const AUTHOR_ROLE = {
155
- name: "author",
156
- displayName: "Author",
157
- description: "Can create and manage own content and media",
158
- isSystem: true,
159
- permissions: [
160
- // Content types - read only
161
- ...readOnly("contentTypes"),
162
- // Content entries - own content only
163
- { resource: "contentEntries", action: "create" },
164
- { resource: "contentEntries", action: "read", scope: "own" },
165
- { resource: "contentEntries", action: "update", scope: "own" },
166
- { resource: "contentEntries", action: "delete", scope: "own" },
167
- // Authors can publish/unpublish their own content
168
- { resource: "contentEntries", action: "publish", scope: "own" },
169
- { resource: "contentEntries", action: "unpublish", scope: "own" },
170
- // Media - can create and manage own, read all (for embedding)
171
- { resource: "mediaItems", action: "create" },
172
- { resource: "mediaItems", action: "read", scope: "all" },
173
- // Can read all for embedding
174
- { resource: "mediaItems", action: "update", scope: "own" },
175
- { resource: "mediaItems", action: "delete", scope: "own" }
176
- ]
177
- };
178
- const VIEWER_ROLE = {
179
- name: "viewer",
180
- displayName: "Viewer",
181
- description: "Read-only access to published content and media",
182
- isSystem: true,
183
- permissions: [
184
- // Content types - read only
185
- ...readOnly("contentTypes"),
186
- // Content entries - read published only (scope: "all" means all published)
187
- ...readOnly("contentEntries"),
188
- // Media - read only
189
- ...readOnly("mediaItems")
190
- ]
191
- };
192
- const DEFAULT_ROLES = {
193
- admin: ADMIN_ROLE,
194
- editor: EDITOR_ROLE,
195
- author: AUTHOR_ROLE,
196
- viewer: VIEWER_ROLE
197
- };
198
- function permissionMatches(granted, requested) {
199
- if (granted.resource !== requested.resource || granted.action !== requested.action) {
200
- return false;
201
- }
202
- const grantedScope = granted.scope ?? "all";
203
- const requestedScope = requested.scope ?? "all";
204
- if (grantedScope === "all") {
205
- return true;
206
- }
207
- return requestedScope === "own";
208
- }
209
- function hasPermission(roleName, permission, customRoles) {
210
- const role = DEFAULT_ROLES[roleName] ?? customRoles?.[roleName];
211
- if (!role) {
212
- return false;
213
- }
214
- return role.permissions.some((p) => permissionMatches(p, permission));
215
- }
216
- function getRolePermissions(roleName, customRoles) {
217
- const role = DEFAULT_ROLES[roleName] ?? customRoles?.[roleName];
218
- return role?.permissions ?? [];
219
- }
220
- function getRole(roleName, customRoles) {
221
- return DEFAULT_ROLES[roleName] ?? customRoles?.[roleName];
222
- }
223
- function getResourcePermissions(roleName, resource, customRoles) {
224
- return getRolePermissions(roleName, customRoles).filter(
225
- (p) => p.resource === resource
226
- );
227
- }
228
- function canAccessResource(roleName, resource, customRoles) {
229
- return getResourcePermissions(roleName, resource, customRoles).length > 0;
230
- }
231
- const AuthContext = reactExports.createContext(null);
232
- function AuthProvider({
233
- children,
234
- getUser,
235
- getUserRole,
236
- onLogout,
237
- autoRedirectToLogin = false,
238
- loginUrl = "/login"
239
- }) {
240
- const [user, setUser] = reactExports.useState(null);
241
- const [role, setRole] = reactExports.useState(null);
242
- const [authState, setAuthState] = reactExports.useState("loading");
243
- const [error, setError] = reactExports.useState(null);
244
- const loadAuth = reactExports.useCallback(async () => {
245
- setAuthState("loading");
246
- setError(null);
247
- try {
248
- const currentUser = await getUser();
249
- if (!currentUser) {
250
- setUser(null);
251
- setRole(null);
252
- setAuthState("unauthenticated");
253
- if (autoRedirectToLogin && typeof window !== "undefined") {
254
- window.location.href = loginUrl;
255
- }
256
- return;
257
- }
258
- const userRole = await getUserRole({ userId: currentUser.id });
259
- setUser(currentUser);
260
- setRole(userRole);
261
- setAuthState("authenticated");
262
- } catch (err) {
263
- console.error("Authentication error:", err);
264
- setUser(null);
265
- setRole(null);
266
- setError(err instanceof Error ? err.message : "Authentication failed");
267
- setAuthState("error");
268
- }
269
- }, [getUser, getUserRole, autoRedirectToLogin, loginUrl]);
270
- reactExports.useEffect(() => {
271
- loadAuth();
272
- }, [loadAuth]);
273
- const checkPermission = reactExports.useCallback(
274
- (permission) => {
275
- if (!role) {
276
- return false;
277
- }
278
- return hasPermission(role, permission);
279
- },
280
- [role]
281
- );
282
- const logout = reactExports.useCallback(async () => {
283
- try {
284
- if (onLogout) {
285
- await onLogout();
286
- }
287
- setUser(null);
288
- setRole(null);
289
- setAuthState("unauthenticated");
290
- if (autoRedirectToLogin && typeof window !== "undefined") {
291
- window.location.href = loginUrl;
292
- }
293
- } catch (err) {
294
- console.error("Logout error:", err);
295
- setError(err instanceof Error ? err.message : "Logout failed");
296
- }
297
- }, [onLogout, autoRedirectToLogin, loginUrl]);
298
- const refresh = reactExports.useCallback(async () => {
299
- await loadAuth();
300
- }, [loadAuth]);
301
- const value = {
302
- user,
303
- role,
304
- authState,
305
- isLoading: authState === "loading",
306
- isAuthenticated: authState === "authenticated",
307
- error,
308
- checkPermission,
309
- logout,
310
- refresh
311
- };
312
- return /* @__PURE__ */ jsxRuntimeExports.jsx(AuthContext.Provider, { value, children });
313
- }
314
- function useAuth() {
315
- const context = reactExports.useContext(AuthContext);
316
- if (!context) {
317
- throw new Error("useAuth must be used within an AuthProvider");
318
- }
319
- return context;
320
- }
321
- const ThemeContext = reactExports.createContext(null);
322
- const STORAGE_KEY = "convex-cms-theme";
323
- function getSystemTheme() {
324
- if (typeof window === "undefined") return "light";
325
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
326
- }
327
- function getStoredTheme() {
328
- if (typeof window === "undefined") return "system";
329
- const stored = localStorage.getItem(STORAGE_KEY);
330
- if (stored === "light" || stored === "dark" || stored === "system") {
331
- return stored;
332
- }
333
- return "system";
334
- }
335
- function ThemeProvider({ children }) {
336
- const [theme, setThemeState] = reactExports.useState(() => getStoredTheme());
337
- const [resolvedTheme, setResolvedTheme] = reactExports.useState(() => {
338
- const stored = getStoredTheme();
339
- return stored === "system" ? getSystemTheme() : stored;
340
- });
341
- const applyTheme = reactExports.useCallback((newTheme) => {
342
- const resolved = newTheme === "system" ? getSystemTheme() : newTheme;
343
- setResolvedTheme(resolved);
344
- const root = document.documentElement;
345
- root.classList.remove("light", "dark");
346
- root.classList.add(resolved);
347
- }, []);
348
- const setTheme = reactExports.useCallback(
349
- (newTheme) => {
350
- setThemeState(newTheme);
351
- localStorage.setItem(STORAGE_KEY, newTheme);
352
- applyTheme(newTheme);
353
- },
354
- [applyTheme]
355
- );
356
- reactExports.useEffect(() => {
357
- applyTheme(theme);
358
- }, [theme, applyTheme]);
359
- reactExports.useEffect(() => {
360
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
361
- const handleChange = () => {
362
- if (theme === "system") {
363
- applyTheme("system");
364
- }
365
- };
366
- mediaQuery.addEventListener("change", handleChange);
367
- return () => mediaQuery.removeEventListener("change", handleChange);
368
- }, [theme, applyTheme]);
369
- return /* @__PURE__ */ jsxRuntimeExports.jsx(ThemeContext.Provider, { value: { theme, resolvedTheme, setTheme }, children });
370
- }
371
- function useTheme() {
372
- const context = reactExports.useContext(ThemeContext);
373
- if (!context) {
374
- throw new Error("useTheme must be used within a ThemeProvider");
375
- }
376
- return context;
377
- }
378
- const navItemSchema = object({
379
- id: string(),
380
- path: string(),
381
- label: string(),
382
- icon: string(),
383
- visible: boolean().default(true),
384
- section: _enum(["main", "config"]).default("main"),
385
- badge: string().optional(),
386
- exact: boolean().optional()
387
- });
388
- const brandingSchema = object({
389
- appName: string().default("Convex CMS"),
390
- logo: string().optional(),
391
- favicon: string().optional()
392
- });
393
- const layoutSchema = object({
394
- sidebarWidth: number().min(200).max(400).default(256),
395
- sidebarCollapsible: boolean().default(false)
396
- });
397
- const navigationSchema = object({
398
- showDashboard: boolean().default(true),
399
- showContent: boolean().default(true),
400
- showMedia: boolean().default(true),
401
- showTaxonomies: boolean().default(true),
402
- showContentTypes: boolean().default(true),
403
- showTrash: boolean().default(true),
404
- showSettings: boolean().default(true),
405
- customItems: array(navItemSchema).default([])
406
- });
407
- const themeSchema = object({
408
- mode: _enum(["light", "dark", "system"]).default("system"),
409
- allowModeSwitch: boolean().default(true),
410
- tokens: record(string(), string()).optional()
411
- });
412
- const adminConfigSchema = object({
413
- branding: brandingSchema.default(() => brandingSchema.parse({})),
414
- layout: layoutSchema.default(() => layoutSchema.parse({})),
415
- navigation: navigationSchema.default(() => navigationSchema.parse({})),
416
- theme: themeSchema.default(() => themeSchema.parse({}))
417
- });
418
- const DEFAULT_NAV_ITEMS = [
419
- {
420
- id: "dashboard",
421
- path: "/",
422
- label: "Dashboard",
423
- icon: "LayoutDashboard",
424
- section: "main",
425
- exact: true
426
- },
427
- { id: "content", path: "/content", label: "Content", icon: "FileText", section: "main" },
428
- { id: "media", path: "/media", label: "Media", icon: "Image", section: "main" },
429
- { id: "taxonomies", path: "/taxonomies", label: "Taxonomies", icon: "Tags", section: "main" },
430
- {
431
- id: "content-types",
432
- path: "/content-types",
433
- label: "Content Types",
434
- icon: "Layers",
435
- section: "config"
436
- },
437
- { id: "trash", path: "/trash", label: "Trash", icon: "Trash2", section: "config" },
438
- { id: "settings", path: "/settings", label: "Settings", icon: "Settings", section: "config" }
439
- ];
440
- function resolveAdminConfig(input) {
441
- return adminConfigSchema.parse(input ?? {});
442
- }
443
- function getVisibleNavItems(config) {
444
- const visibilityMap = {
445
- dashboard: config.navigation.showDashboard,
446
- content: config.navigation.showContent,
447
- media: config.navigation.showMedia,
448
- taxonomies: config.navigation.showTaxonomies,
449
- "content-types": config.navigation.showContentTypes,
450
- trash: config.navigation.showTrash,
451
- settings: config.navigation.showSettings
452
- };
453
- const filtered = DEFAULT_NAV_ITEMS.filter((item) => visibilityMap[item.id] !== false);
454
- const allItems = [...filtered, ...config.navigation.customItems.filter((i) => i.visible !== false)];
455
- return {
456
- main: allItems.filter((i) => i.section === "main"),
457
- config: allItems.filter((i) => i.section === "config")
458
- };
459
- }
460
- const AdminConfigContext = reactExports.createContext(null);
461
- function AdminConfigProvider({
462
- config,
463
- children
464
- }) {
465
- const navItems = getVisibleNavItems(config);
466
- const value = { ...config, navItems };
467
- return /* @__PURE__ */ jsxRuntimeExports.jsx(AdminConfigContext.Provider, { value, children });
468
- }
469
- function useAdminConfig() {
470
- const config = reactExports.useContext(AdminConfigContext);
471
- if (!config) {
472
- throw new Error("useAdminConfig must be used within AdminConfigProvider");
473
- }
474
- return config;
475
- }
476
- const SettingsConfigContext = reactExports.createContext(null);
477
- function SettingsConfigProvider({
478
- baseConfig,
479
- children,
480
- api: api2
481
- }) {
482
- const settings = useQuery(api2?.getSettings ?? "skip");
483
- const mergedConfig = reactExports.useMemo(() => {
484
- if (!settings) return baseConfig;
485
- return {
486
- ...baseConfig,
487
- navigation: {
488
- ...baseConfig.navigation,
489
- showMedia: baseConfig.navigation.showMedia && settings.features.mediaManagement
490
- }
491
- };
492
- }, [baseConfig, settings]);
493
- const contextValue = reactExports.useMemo(
494
- () => ({ baseConfig, settings }),
495
- [baseConfig, settings]
496
- );
497
- return /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsConfigContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntimeExports.jsx(AdminConfigProvider, { config: mergedConfig, children }) });
498
- }
499
- function useSettingsConfig() {
500
- const ctx = reactExports.useContext(SettingsConfigContext);
501
- if (!ctx) {
502
- throw new Error("useSettingsConfig must be used within SettingsConfigProvider");
503
- }
504
- return ctx;
505
- }
506
- const BreadcrumbContext = reactExports.createContext(null);
507
- function BreadcrumbProvider({ children }) {
508
- const [overrides, setOverrides] = reactExports.useState(/* @__PURE__ */ new Map());
509
- const setOverride = reactExports.useCallback((path, label) => {
510
- setOverrides((prev) => {
511
- const next = new Map(prev);
512
- next.set(path, label);
513
- return next;
514
- });
515
- }, []);
516
- const clearOverride = reactExports.useCallback((path) => {
517
- setOverrides((prev) => {
518
- const next = new Map(prev);
519
- next.delete(path);
520
- return next;
521
- });
522
- }, []);
523
- const value = reactExports.useMemo(
524
- () => ({ overrides, setOverride, clearOverride }),
525
- [overrides, setOverride, clearOverride]
526
- );
527
- return /* @__PURE__ */ jsxRuntimeExports.jsx(BreadcrumbContext.Provider, { value, children });
528
- }
529
- function useBreadcrumbContext() {
530
- const context = reactExports.useContext(BreadcrumbContext);
531
- if (!context) {
532
- throw new Error("useBreadcrumbContext must be used within a BreadcrumbProvider");
533
- }
534
- return context;
535
- }
536
- const iconRegistry = {
537
- LayoutDashboard,
538
- FileText,
539
- Image: Image$1,
540
- Layers,
541
- Tags,
542
- FileCode,
543
- Settings,
544
- Trash2,
545
- HelpCircle: CircleQuestionMark,
546
- Home: House,
547
- User,
548
- Bell,
549
- Lock,
550
- Globe,
551
- Calendar,
552
- Clock,
553
- Link: Link$1,
554
- Hash,
555
- ToggleLeft,
556
- ChevronDown,
557
- CheckSquare: SquareCheckBig,
558
- Braces,
559
- Folder,
560
- MapPin,
561
- DollarSign,
562
- Mail,
563
- Phone,
564
- Package,
565
- Star,
566
- Heart,
567
- Flag,
568
- Bookmark,
569
- Archive,
570
- Edit: SquarePen,
571
- Eye,
572
- AlertCircle: CircleAlert
573
- };
574
- function Icon({
575
- name,
576
- className = "size-5"
577
- }) {
578
- const IconComponent = iconRegistry[name];
579
- if (!IconComponent) {
580
- return null;
581
- }
582
- return /* @__PURE__ */ jsxRuntimeExports.jsx(IconComponent, { className });
583
- }
584
- const ApiContext = reactExports.createContext(null);
585
- function ApiProvider({
586
- api: api2,
587
- children
588
- }) {
589
- return /* @__PURE__ */ jsxRuntimeExports.jsx(ApiContext.Provider, { value: api2, children });
590
- }
591
- function useApi() {
592
- const api2 = reactExports.useContext(ApiContext);
593
- if (!api2) {
594
- throw new Error("useApi must be used within ApiProvider");
595
- }
596
- return api2;
597
- }
598
- const Collapsible = Root$1;
599
- const CollapsibleTrigger = CollapsibleTrigger$1;
600
- const CollapsibleContent = CollapsibleContent$1;
601
- function Dialog({
602
- ...props
603
- }) {
604
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Root$3, { "data-slot": "dialog", ...props });
605
- }
606
- function DialogPortal({
607
- ...props
608
- }) {
609
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$2, { "data-slot": "dialog-portal", ...props });
610
- }
611
- function DialogOverlay({
612
- className,
613
- ...props
614
- }) {
615
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
616
- Overlay,
617
- {
618
- "data-slot": "dialog-overlay",
619
- className: cn(
620
- "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
621
- className
622
- ),
623
- ...props
624
- }
625
- );
626
- }
627
- function DialogContent({
628
- className,
629
- children,
630
- showCloseButton = true,
631
- ...props
632
- }) {
633
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [
634
- /* @__PURE__ */ jsxRuntimeExports.jsx(DialogOverlay, {}),
635
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
636
- Content,
637
- {
638
- "data-slot": "dialog-content",
639
- className: cn(
640
- "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
641
- className
642
- ),
643
- ...props,
644
- children: [
645
- children,
646
- showCloseButton && /* @__PURE__ */ jsxRuntimeExports.jsxs(
647
- Close,
648
- {
649
- "data-slot": "dialog-close",
650
- className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
651
- children: [
652
- /* @__PURE__ */ jsxRuntimeExports.jsx(X, {}),
653
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sr-only", children: "Close" })
654
- ]
655
- }
656
- )
657
- ]
658
- }
659
- )
660
- ] });
661
- }
662
- function DialogHeader({ className, ...props }) {
663
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
664
- "div",
665
- {
666
- "data-slot": "dialog-header",
667
- className: cn("flex flex-col gap-2 text-center sm:text-left", className),
668
- ...props
669
- }
670
- );
671
- }
672
- function DialogFooter({ className, ...props }) {
673
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
674
- "div",
675
- {
676
- "data-slot": "dialog-footer",
677
- className: cn(
678
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
679
- className
680
- ),
681
- ...props
682
- }
683
- );
684
- }
685
- function DialogTitle({
686
- className,
687
- ...props
688
- }) {
689
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
690
- Title,
691
- {
692
- "data-slot": "dialog-title",
693
- className: cn("text-lg leading-none font-semibold", className),
694
- ...props
695
- }
696
- );
697
- }
698
- function DialogDescription({
699
- className,
700
- ...props
701
- }) {
702
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
703
- Description,
704
- {
705
- "data-slot": "dialog-description",
706
- className: cn("text-muted-foreground text-sm", className),
707
- ...props
708
- }
709
- );
710
- }
711
- const buttonVariants = cva(
712
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
713
- {
714
- variants: {
715
- variant: {
716
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
717
- destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
718
- outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
719
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
720
- ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
721
- link: "text-primary underline-offset-4 hover:underline"
722
- },
723
- size: {
724
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
725
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
726
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
727
- icon: "size-9",
728
- "icon-sm": "size-8",
729
- "icon-lg": "size-10"
730
- }
731
- },
732
- defaultVariants: {
733
- variant: "default",
734
- size: "default"
735
- }
736
- }
737
- );
738
- function Button({
739
- className,
740
- variant = "default",
741
- size = "default",
742
- asChild = false,
743
- ...props
744
- }) {
745
- const Comp = asChild ? Slot : "button";
746
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
747
- Comp,
748
- {
749
- "data-slot": "button",
750
- "data-variant": variant,
751
- "data-size": size,
752
- className: cn(buttonVariants({ variant, size, className })),
753
- ...props
754
- }
755
- );
756
- }
757
- const motion = {
758
- fast: "duration-100 ease-out"
759
- };
760
- const variantMap = {
761
- primary: "default",
762
- secondary: "secondary",
763
- danger: "destructive",
764
- ghost: "ghost",
765
- outline: "outline",
766
- link: "link",
767
- success: "default",
768
- warning: "default"
769
- };
770
- const customVariantClasses = {
771
- success: "bg-success text-success-foreground hover:bg-success/90 focus-visible:ring-success",
772
- warning: "bg-warning text-warning-foreground hover:bg-warning/90 focus-visible:ring-warning"
773
- };
774
- const LoadingSpinner = () => /* @__PURE__ */ jsxRuntimeExports.jsxs(
775
- "svg",
776
- {
777
- className: "size-4 animate-spin",
778
- xmlns: "http://www.w3.org/2000/svg",
779
- fill: "none",
780
- viewBox: "0 0 24 24",
781
- children: [
782
- /* @__PURE__ */ jsxRuntimeExports.jsx(
783
- "circle",
784
- {
785
- className: "opacity-25",
786
- cx: "12",
787
- cy: "12",
788
- r: "10",
789
- stroke: "currentColor",
790
- strokeWidth: "4"
791
- }
792
- ),
793
- /* @__PURE__ */ jsxRuntimeExports.jsx(
794
- "path",
795
- {
796
- className: "opacity-75",
797
- fill: "currentColor",
798
- d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
799
- }
800
- )
801
- ]
802
- }
803
- );
804
- function CmsButton({
805
- variant = "primary",
806
- loading,
807
- disabled,
808
- children,
809
- className,
810
- asChild,
811
- ...props
812
- }) {
813
- const mappedVariant = variantMap[variant];
814
- const customClass = customVariantClasses[variant];
815
- if (asChild) {
816
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
817
- Button,
818
- {
819
- variant: mappedVariant,
820
- disabled: disabled || loading,
821
- className: cn(motion.fast, customClass, className),
822
- asChild: true,
823
- ...props,
824
- children
825
- }
826
- );
827
- }
828
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
829
- Button,
830
- {
831
- variant: mappedVariant,
832
- disabled: disabled || loading,
833
- className: cn(motion.fast, customClass, className),
834
- ...props,
835
- children: [
836
- loading && /* @__PURE__ */ jsxRuntimeExports.jsx(LoadingSpinner, {}),
837
- children
838
- ]
839
- }
840
- );
841
- }
842
- const sizeClasses = {
843
- sm: "max-w-sm",
844
- md: "max-w-md",
845
- lg: "max-w-lg",
846
- xl: "max-w-xl",
847
- "2xl": "max-w-2xl"
848
- };
849
- function CmsDialog({
850
- open,
851
- onOpenChange,
852
- title,
853
- description,
854
- children,
855
- footer,
856
- size = "md",
857
- className
858
- }) {
859
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
860
- DialogContent,
861
- {
862
- className: cn(
863
- "flex max-h-[85vh] flex-col overflow-hidden",
864
- sizeClasses[size],
865
- className
866
- ),
867
- children: [
868
- /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogHeader, { className: "shrink-0", children: [
869
- /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: title }),
870
- description && /* @__PURE__ */ jsxRuntimeExports.jsx(DialogDescription, { children: description })
871
- ] }),
872
- /* @__PURE__ */ jsxRuntimeExports.jsx(
873
- "div",
874
- {
875
- className: "min-h-0 flex-1 overflow-y-auto py-4 [&::-webkit-scrollbar]:hidden",
876
- style: { scrollbarWidth: "none", msOverflowStyle: "none" },
877
- children
878
- }
879
- ),
880
- footer && /* @__PURE__ */ jsxRuntimeExports.jsx(DialogFooter, { className: "shrink-0 border-t pt-4", children: footer })
881
- ]
882
- }
883
- ) });
884
- }
885
- function CmsConfirmDialog({
886
- open,
887
- onOpenChange,
888
- title,
889
- description,
890
- confirmLabel = "Confirm",
891
- cancelLabel = "Cancel",
892
- onConfirm,
893
- onCancel,
894
- variant = "default",
895
- loading,
896
- isLoading,
897
- error
898
- }) {
899
- const isLoadingState = loading ?? isLoading ?? false;
900
- const handleCancel = () => {
901
- onCancel?.();
902
- onOpenChange(false);
903
- };
904
- const handleConfirm = () => {
905
- onConfirm();
906
- };
907
- const getButtonVariant = () => {
908
- if (variant === "danger") return "danger";
909
- if (variant === "warning") return "warning";
910
- return "primary";
911
- };
912
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
913
- CmsDialog,
914
- {
915
- open,
916
- onOpenChange,
917
- title,
918
- size: "sm",
919
- footer: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
920
- /* @__PURE__ */ jsxRuntimeExports.jsx(CmsButton, { variant: "outline", onClick: handleCancel, disabled: isLoadingState, children: cancelLabel }),
921
- /* @__PURE__ */ jsxRuntimeExports.jsx(
922
- CmsButton,
923
- {
924
- variant: getButtonVariant(),
925
- onClick: handleConfirm,
926
- loading: isLoadingState,
927
- children: confirmLabel
928
- }
929
- )
930
- ] }),
931
- children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
932
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-muted-foreground", children: description }),
933
- error && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-destructive", children: error })
934
- ] })
935
- }
936
- );
937
- }
938
- function Input({ className, type, ...props }) {
939
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
940
- "input",
941
- {
942
- type,
943
- "data-slot": "input",
944
- className: cn(
945
- "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
946
- "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
947
- "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
948
- className
949
- ),
950
- ...props
951
- }
952
- );
953
- }
954
- function Label({
955
- className,
956
- ...props
957
- }) {
958
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
959
- Root$2,
960
- {
961
- "data-slot": "label",
962
- className: cn(
963
- "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
964
- className
965
- ),
966
- ...props
967
- }
968
- );
969
- }
970
- function Textarea({ className, ...props }) {
971
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
972
- "textarea",
973
- {
974
- "data-slot": "textarea",
975
- className: cn(
976
- "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
977
- className
978
- ),
979
- ...props
980
- }
981
- );
982
- }
983
- function Checkbox({
984
- className,
985
- ...props
986
- }) {
987
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
988
- Checkbox$1,
989
- {
990
- "data-slot": "checkbox",
991
- className: cn(
992
- "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
993
- className
994
- ),
995
- ...props,
996
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
997
- CheckboxIndicator,
998
- {
999
- "data-slot": "checkbox-indicator",
1000
- className: "grid place-content-center text-current transition-none",
1001
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5" })
1002
- }
1003
- )
1004
- }
1005
- );
1006
- }
1007
- function Select({
1008
- ...props
1009
- }) {
1010
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Root2$2, { "data-slot": "select", ...props });
1011
- }
1012
- function SelectValue({
1013
- ...props
1014
- }) {
1015
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Value, { "data-slot": "select-value", ...props });
1016
- }
1017
- function SelectTrigger({
1018
- className,
1019
- size = "default",
1020
- children,
1021
- ...props
1022
- }) {
1023
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1024
- Trigger$2,
1025
- {
1026
- "data-slot": "select-trigger",
1027
- "data-size": size,
1028
- className: cn(
1029
- "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
1030
- className
1031
- ),
1032
- ...props,
1033
- children: [
1034
- children,
1035
- /* @__PURE__ */ jsxRuntimeExports.jsx(Icon$1, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4 opacity-50" }) })
1036
- ]
1037
- }
1038
- );
1039
- }
1040
- function SelectContent({
1041
- className,
1042
- children,
1043
- position = "item-aligned",
1044
- align = "center",
1045
- ...props
1046
- }) {
1047
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$1, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1048
- Content2$2,
1049
- {
1050
- "data-slot": "select-content",
1051
- className: cn(
1052
- "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
1053
- position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
1054
- className
1055
- ),
1056
- position,
1057
- align,
1058
- ...props,
1059
- children: [
1060
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectScrollUpButton, {}),
1061
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1062
- Viewport,
1063
- {
1064
- className: cn(
1065
- "p-1",
1066
- position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
1067
- ),
1068
- children
1069
- }
1070
- ),
1071
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectScrollDownButton, {})
1072
- ]
1073
- }
1074
- ) });
1075
- }
1076
- function SelectItem({
1077
- className,
1078
- children,
1079
- ...props
1080
- }) {
1081
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1082
- Item,
1083
- {
1084
- "data-slot": "select-item",
1085
- className: cn(
1086
- "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
1087
- className
1088
- ),
1089
- ...props,
1090
- children: [
1091
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1092
- "span",
1093
- {
1094
- "data-slot": "select-item-indicator",
1095
- className: "absolute right-2 flex size-3.5 items-center justify-center",
1096
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ItemIndicator, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-4" }) })
1097
- }
1098
- ),
1099
- /* @__PURE__ */ jsxRuntimeExports.jsx(ItemText, { children })
1100
- ]
1101
- }
1102
- );
1103
- }
1104
- function SelectScrollUpButton({
1105
- className,
1106
- ...props
1107
- }) {
1108
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
1109
- ScrollUpButton,
1110
- {
1111
- "data-slot": "select-scroll-up-button",
1112
- className: cn(
1113
- "flex cursor-default items-center justify-center py-1",
1114
- className
1115
- ),
1116
- ...props,
1117
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { className: "size-4" })
1118
- }
1119
- );
1120
- }
1121
- function SelectScrollDownButton({
1122
- className,
1123
- ...props
1124
- }) {
1125
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
1126
- ScrollDownButton,
1127
- {
1128
- "data-slot": "select-scroll-down-button",
1129
- className: cn(
1130
- "flex cursor-default items-center justify-center py-1",
1131
- className
1132
- ),
1133
- ...props,
1134
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4" })
1135
- }
1136
- );
1137
- }
1138
- function BreakingChangesWarningDialog({
1139
- isOpen,
1140
- onClose,
1141
- breakingChanges,
1142
- onForceUpdate,
1143
- onCancel,
1144
- isLoading
1145
- }) {
1146
- const handleCancel = () => {
1147
- onCancel();
1148
- onClose();
1149
- };
1150
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
1151
- CmsDialog,
1152
- {
1153
- open: isOpen,
1154
- onOpenChange: (open) => !open && !isLoading && handleCancel(),
1155
- title: "Breaking Changes Detected",
1156
- size: "lg",
1157
- footer: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1158
- /* @__PURE__ */ jsxRuntimeExports.jsx(CmsButton, { variant: "outline", onClick: handleCancel, disabled: isLoading, children: "Cancel" }),
1159
- /* @__PURE__ */ jsxRuntimeExports.jsx(CmsButton, { variant: "danger", onClick: onForceUpdate, loading: isLoading, children: "Force Update" })
1160
- ] }),
1161
- children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
1162
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "diff-modified flex items-start gap-3 rounded-lg border p-3", children: [
1163
- /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "mt-0.5 size-5 shrink-0 text-diff-modified" }),
1164
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
1165
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-medium text-diff-modified", children: "These changes may affect existing content" }),
1166
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-diff-modified-foreground", children: "The following changes could cause data loss or validation errors for existing entries. Review carefully before proceeding." })
1167
- ] })
1168
- ] }),
1169
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1170
- /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-sm font-medium text-foreground", children: [
1171
- breakingChanges.length,
1172
- " breaking change",
1173
- breakingChanges.length !== 1 ? "s" : "",
1174
- " detected:"
1175
- ] }),
1176
- /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { className: "space-y-2", children: breakingChanges.map((change, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
1177
- "li",
1178
- {
1179
- className: "flex items-start gap-2 rounded-md border bg-muted/30 px-3 py-2 text-sm",
1180
- children: [
1181
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mt-0.5 size-1.5 shrink-0 rounded-full bg-warning" }),
1182
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground", children: change })
1183
- ]
1184
- },
1185
- index
1186
- )) })
1187
- ] }),
1188
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: 'Click "Force Update" to apply these changes anyway, or "Cancel" to go back and modify your changes.' })
1189
- ] })
1190
- }
1191
- );
1192
- }
1193
- const FIELD_TYPE_INFO = {
1194
- text: {
1195
- label: "Text",
1196
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TextAlignStart, { className: "size-4" }),
1197
- description: "Single line text input"
1198
- },
1199
- richText: {
1200
- label: "Rich Text",
1201
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(FileType, { className: "size-4" }),
1202
- description: "Multi-line formatted text"
1203
- },
1204
- number: {
1205
- label: "Number",
1206
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Hash, { className: "size-4" }),
1207
- description: "Numeric value"
1208
- },
1209
- boolean: {
1210
- label: "Boolean",
1211
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleLeft, { className: "size-4" }),
1212
- description: "True/false toggle"
1213
- },
1214
- date: {
1215
- label: "Date",
1216
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Calendar, { className: "size-4" }),
1217
- description: "Date picker"
1218
- },
1219
- datetime: {
1220
- label: "Date & Time",
1221
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Calendar, { className: "size-4" }),
1222
- description: "Date and time picker"
1223
- },
1224
- reference: {
1225
- label: "Reference",
1226
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Link2, { className: "size-4" }),
1227
- description: "Link to another content entry"
1228
- },
1229
- media: {
1230
- label: "Media",
1231
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Image$1, { className: "size-4" }),
1232
- description: "Image, video, or file"
1233
- },
1234
- json: {
1235
- label: "JSON",
1236
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Braces, { className: "size-4" }),
1237
- description: "Custom JSON data"
1238
- },
1239
- select: {
1240
- label: "Select",
1241
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4" }),
1242
- description: "Dropdown selection"
1243
- },
1244
- multiSelect: {
1245
- label: "Multi-Select",
1246
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(List, { className: "size-4" }),
1247
- description: "Multiple selections"
1248
- },
1249
- tags: {
1250
- label: "Tags",
1251
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Tag, { className: "size-4" }),
1252
- description: "Free-form tag list"
1253
- },
1254
- category: {
1255
- label: "Category",
1256
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(FolderOpen, { className: "size-4" }),
1257
- description: "Taxonomy category selection"
1258
- }
1259
- };
1260
- function generateMachineName(displayName) {
1261
- return displayName.toLowerCase().trim().replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, "_").replace(/^[0-9]/, "_$&").slice(0, 64);
1262
- }
1263
- function isValidMachineName(name) {
1264
- return /^[a-z][a-z0-9_]{0,63}$/.test(name);
1265
- }
1266
- function ContentTypeFormModal({
1267
- isOpen,
1268
- onClose,
1269
- onCreated,
1270
- onUpdated,
1271
- contentType
1272
- }) {
1273
- const isEditing = !!contentType;
1274
- const isCodeDefined = contentType?.source === "code";
1275
- const isReadOnly = isCodeDefined;
1276
- const [displayName, setDisplayName] = reactExports.useState("");
1277
- const [machineName, setMachineName] = reactExports.useState("");
1278
- const [machineNameManuallyEdited, setMachineNameManuallyEdited] = reactExports.useState(
1279
- false
1280
- );
1281
- const [description, setDescription] = reactExports.useState("");
1282
- const [singleton, setSingleton] = reactExports.useState(false);
1283
- const [fields, setFields] = reactExports.useState([
1284
- { name: "title", label: "Title", type: "text", required: true }
1285
- ]);
1286
- const [titleField, setTitleField] = reactExports.useState("title");
1287
- const [slugField, setSlugField] = reactExports.useState("title");
1288
- const [isSubmitting, setIsSubmitting] = reactExports.useState(false);
1289
- const [submitError, setSubmitError] = reactExports.useState(null);
1290
- const [activeFieldIndex, setActiveFieldIndex] = reactExports.useState(null);
1291
- const [showFieldEditor, setShowFieldEditor] = reactExports.useState(false);
1292
- const [breakingChanges, setBreakingChanges] = reactExports.useState([]);
1293
- const [showBreakingWarning, setShowBreakingWarning] = reactExports.useState(false);
1294
- const [isForceUpdating, setIsForceUpdating] = reactExports.useState(false);
1295
- const api2 = useApi();
1296
- const createContentType = useMutation(api2.createContentType);
1297
- const updateContentType = useMutation(api2.updateContentType);
1298
- reactExports.useEffect(() => {
1299
- if (contentType && isOpen) {
1300
- setDisplayName(contentType.displayName);
1301
- setMachineName(contentType.name);
1302
- setMachineNameManuallyEdited(true);
1303
- setDescription(contentType.description || "");
1304
- setSingleton(contentType.singleton || false);
1305
- setFields(contentType.fields);
1306
- setTitleField(contentType.titleField || "");
1307
- setSlugField(contentType.slugField || "");
1308
- }
1309
- }, [contentType, isOpen]);
1310
- const resetForm = reactExports.useCallback(() => {
1311
- setDisplayName("");
1312
- setMachineName("");
1313
- setMachineNameManuallyEdited(false);
1314
- setDescription("");
1315
- setSingleton(false);
1316
- setFields([
1317
- { name: "title", label: "Title", type: "text", required: true }
1318
- ]);
1319
- setTitleField("title");
1320
- setSlugField("title");
1321
- setIsSubmitting(false);
1322
- setSubmitError(null);
1323
- setActiveFieldIndex(null);
1324
- setShowFieldEditor(false);
1325
- setBreakingChanges([]);
1326
- setShowBreakingWarning(false);
1327
- setIsForceUpdating(false);
1328
- }, []);
1329
- const handleDisplayNameChange = reactExports.useCallback(
1330
- (value) => {
1331
- setDisplayName(value);
1332
- if (!machineNameManuallyEdited) {
1333
- setMachineName(generateMachineName(value));
1334
- }
1335
- },
1336
- [machineNameManuallyEdited]
1337
- );
1338
- const handleMachineNameChange = reactExports.useCallback((value) => {
1339
- setMachineNameManuallyEdited(true);
1340
- setMachineName(value.toLowerCase().replace(/[^a-z0-9_]/g, ""));
1341
- }, []);
1342
- const addField = reactExports.useCallback(() => {
1343
- const newFieldName = `field_${fields.length + 1}`;
1344
- setFields((prev) => [
1345
- ...prev,
1346
- {
1347
- name: newFieldName,
1348
- label: `Field ${prev.length + 1}`,
1349
- type: "text",
1350
- required: false
1351
- }
1352
- ]);
1353
- setActiveFieldIndex(fields.length);
1354
- setShowFieldEditor(true);
1355
- }, [fields.length]);
1356
- const removeField = reactExports.useCallback(
1357
- (index) => {
1358
- const fieldToRemove = fields[index];
1359
- setFields((prev) => prev.filter((_, i) => i !== index));
1360
- if (titleField === fieldToRemove.name) {
1361
- const firstTextField = fields.find(
1362
- (f, i) => i !== index && f.type === "text"
1363
- );
1364
- setTitleField(firstTextField?.name || "");
1365
- }
1366
- if (slugField === fieldToRemove.name) {
1367
- const firstTextField = fields.find(
1368
- (f, i) => i !== index && f.type === "text"
1369
- );
1370
- setSlugField(firstTextField?.name || "");
1371
- }
1372
- if (activeFieldIndex === index) {
1373
- setActiveFieldIndex(null);
1374
- setShowFieldEditor(false);
1375
- } else if (activeFieldIndex !== null && activeFieldIndex > index) {
1376
- setActiveFieldIndex(activeFieldIndex - 1);
1377
- }
1378
- },
1379
- [fields, activeFieldIndex, titleField, slugField]
1380
- );
1381
- const updateField = reactExports.useCallback(
1382
- (index, updates) => {
1383
- setFields(
1384
- (prev) => prev.map(
1385
- (field, i) => i === index ? { ...field, ...updates } : field
1386
- )
1387
- );
1388
- },
1389
- []
1390
- );
1391
- const moveField = reactExports.useCallback((fromIndex, toIndex) => {
1392
- setFields((prev) => {
1393
- const newFields = [...prev];
1394
- const [movedField] = newFields.splice(fromIndex, 1);
1395
- newFields.splice(toIndex, 0, movedField);
1396
- return newFields;
1397
- });
1398
- setActiveFieldIndex(toIndex);
1399
- }, []);
1400
- const validationErrors = reactExports.useMemo(() => {
1401
- const errors = [];
1402
- if (!displayName.trim()) {
1403
- errors.push("Display name is required");
1404
- }
1405
- if (!machineName.trim()) {
1406
- errors.push("System Name is required");
1407
- } else if (!isValidMachineName(machineName)) {
1408
- errors.push(
1409
- "System Name must start with a letter and contain only lowercase letters, numbers, and underscores"
1410
- );
1411
- }
1412
- if (fields.length === 0) {
1413
- errors.push("At least one field is required");
1414
- }
1415
- const fieldNames = fields.map((f) => f.name);
1416
- const duplicates = fieldNames.filter(
1417
- (name, index) => fieldNames.indexOf(name) !== index
1418
- );
1419
- if (duplicates.length > 0) {
1420
- errors.push(
1421
- `Duplicate field names: ${[...new Set(duplicates)].join(", ")}`
1422
- );
1423
- }
1424
- for (const field of fields) {
1425
- if (!field.name.trim()) {
1426
- errors.push(`Field "${field.label}" has an empty name`);
1427
- } else if (!/^[a-z][a-z0-9_]{0,63}$/.test(field.name)) {
1428
- errors.push(`Field "${field.name}" has an invalid name format`);
1429
- }
1430
- if (!field.label.trim()) {
1431
- errors.push(`Field with name "${field.name}" has an empty label`);
1432
- }
1433
- if ((field.type === "select" || field.type === "multiSelect") && (!field.options?.options || field.options.options.length === 0)) {
1434
- errors.push(
1435
- `${field.type} field "${field.label}" requires at least one option`
1436
- );
1437
- }
1438
- }
1439
- return errors;
1440
- }, [displayName, machineName, fields]);
1441
- const textFields = reactExports.useMemo(() => fields.filter((f) => f.type === "text"), [
1442
- fields
1443
- ]);
1444
- const parseBreakingChanges = (errorMessage) => {
1445
- const lines = errorMessage.split("\n");
1446
- return lines.filter((line) => line.trim().startsWith("-")).map((line) => line.trim().substring(2));
1447
- };
1448
- const handleSubmit = reactExports.useCallback(
1449
- async (e, force = false) => {
1450
- e.preventDefault();
1451
- if (validationErrors.length > 0) {
1452
- setSubmitError(validationErrors.join(". "));
1453
- return;
1454
- }
1455
- setIsSubmitting(true);
1456
- setSubmitError(null);
1457
- try {
1458
- if (isEditing && contentType) {
1459
- const result = await updateContentType({
1460
- id: contentType._id,
1461
- displayName: displayName.trim(),
1462
- description: description.trim() || void 0,
1463
- fields,
1464
- singleton,
1465
- titleField: titleField || void 0,
1466
- slugField: slugField || void 0,
1467
- force
1468
- });
1469
- onUpdated?.(result);
1470
- resetForm();
1471
- onClose();
1472
- } else {
1473
- const result = await createContentType({
1474
- name: machineName,
1475
- displayName: displayName.trim(),
1476
- description: description.trim() || void 0,
1477
- fields,
1478
- singleton,
1479
- titleField: titleField || void 0,
1480
- slugField: slugField || void 0
1481
- });
1482
- onCreated?.(result);
1483
- resetForm();
1484
- onClose();
1485
- }
1486
- } catch (error) {
1487
- const message = error instanceof Error ? error.message : isEditing ? "Failed to update content type" : "Failed to create content type";
1488
- if (isEditing && !force && message.includes("breaking change")) {
1489
- const changes = parseBreakingChanges(message);
1490
- setBreakingChanges(changes);
1491
- setShowBreakingWarning(true);
1492
- } else {
1493
- setSubmitError(message);
1494
- }
1495
- } finally {
1496
- setIsSubmitting(false);
1497
- }
1498
- },
1499
- [
1500
- validationErrors,
1501
- isEditing,
1502
- contentType,
1503
- createContentType,
1504
- updateContentType,
1505
- machineName,
1506
- displayName,
1507
- description,
1508
- fields,
1509
- singleton,
1510
- titleField,
1511
- slugField,
1512
- onCreated,
1513
- onUpdated,
1514
- resetForm,
1515
- onClose
1516
- ]
1517
- );
1518
- const handleForceUpdate = reactExports.useCallback(async () => {
1519
- setIsForceUpdating(true);
1520
- try {
1521
- if (contentType) {
1522
- const result = await updateContentType({
1523
- id: contentType._id,
1524
- displayName: displayName.trim(),
1525
- description: description.trim() || void 0,
1526
- fields,
1527
- singleton,
1528
- titleField: titleField || void 0,
1529
- slugField: slugField || void 0,
1530
- force: true
1531
- });
1532
- onUpdated?.(result);
1533
- resetForm();
1534
- setShowBreakingWarning(false);
1535
- onClose();
1536
- }
1537
- } catch (error) {
1538
- const message = error instanceof Error ? error.message : "Failed to update content type";
1539
- setSubmitError(message);
1540
- setShowBreakingWarning(false);
1541
- } finally {
1542
- setIsForceUpdating(false);
1543
- }
1544
- }, [
1545
- contentType,
1546
- updateContentType,
1547
- displayName,
1548
- description,
1549
- fields,
1550
- singleton,
1551
- titleField,
1552
- slugField,
1553
- onUpdated,
1554
- resetForm,
1555
- onClose
1556
- ]);
1557
- const handleClose = reactExports.useCallback(() => {
1558
- if (isSubmitting) return;
1559
- resetForm();
1560
- onClose();
1561
- }, [isSubmitting, resetForm, onClose]);
1562
- if (!isOpen) return null;
1563
- const activeField = activeFieldIndex !== null ? fields[activeFieldIndex] : null;
1564
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1565
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1566
- CmsDialog,
1567
- {
1568
- open: isOpen,
1569
- onOpenChange: (open) => !open && handleClose(),
1570
- title: isCodeDefined ? "View Content Type" : isEditing ? "Edit Content Type" : "Create Content Type",
1571
- size: "2xl",
1572
- footer: isReadOnly ? /* @__PURE__ */ jsxRuntimeExports.jsx(CmsButton, { variant: "outline", onClick: handleClose, children: "Close" }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1573
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1574
- CmsButton,
1575
- {
1576
- variant: "outline",
1577
- onClick: handleClose,
1578
- disabled: isSubmitting,
1579
- children: "Cancel"
1580
- }
1581
- ),
1582
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1583
- CmsButton,
1584
- {
1585
- variant: "primary",
1586
- onClick: handleSubmit,
1587
- disabled: validationErrors.length > 0,
1588
- loading: isSubmitting,
1589
- "data-testid": isEditing ? "update-content-type-submit" : "create-content-type-submit",
1590
- children: isEditing ? "Save Changes" : "Create Content Type"
1591
- }
1592
- )
1593
- ] }),
1594
- children: /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleSubmit, className: "space-y-6", children: [
1595
- isCodeDefined && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-violet-200 bg-violet-50 p-3", children: [
1596
- /* @__PURE__ */ jsxRuntimeExports.jsx(CodeXml, { className: "mt-0.5 size-5 shrink-0 text-violet-600" }),
1597
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
1598
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-medium text-violet-900", children: "Managed by Code" }),
1599
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-violet-700", children: "This content type is defined in your codebase and cannot be edited through the admin interface. To make changes, update the definition in your code." })
1600
- ] })
1601
- ] }),
1602
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
1603
- /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-sm font-semibold text-foreground", children: "Basic Information" }),
1604
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1605
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { htmlFor: "displayName", children: [
1606
- "Display Name ",
1607
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
1608
- ] }),
1609
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1610
- Input,
1611
- {
1612
- id: "displayName",
1613
- value: displayName,
1614
- onChange: (e) => handleDisplayNameChange(e.target.value),
1615
- placeholder: "e.g., Blog Post",
1616
- disabled: isSubmitting || isReadOnly,
1617
- autoFocus: !isReadOnly,
1618
- "data-testid": "display-name-input"
1619
- }
1620
- )
1621
- ] }),
1622
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1623
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { htmlFor: "machineName", children: [
1624
- "System Name ",
1625
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
1626
- ] }),
1627
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1628
- Input,
1629
- {
1630
- id: "machineName",
1631
- value: machineName,
1632
- onChange: (e) => handleMachineNameChange(e.target.value),
1633
- placeholder: "e.g., blog_post",
1634
- disabled: isSubmitting || isEditing || isReadOnly,
1635
- className: cn(
1636
- !isValidMachineName(machineName) && machineName && "border-destructive"
1637
- ),
1638
- "data-testid": "machine-name-input"
1639
- }
1640
- ),
1641
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: isEditing ? "System name cannot be changed after creation" : "Lowercase letters, numbers, and underscores only. Used in API queries." })
1642
- ] }),
1643
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1644
- /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "description", children: "Description" }),
1645
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1646
- Textarea,
1647
- {
1648
- id: "description",
1649
- value: description,
1650
- onChange: (e) => setDescription(e.target.value),
1651
- placeholder: "Optional description of this content type",
1652
- disabled: isSubmitting || isReadOnly,
1653
- rows: 2
1654
- }
1655
- )
1656
- ] }),
1657
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1658
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1659
- Checkbox,
1660
- {
1661
- id: "singleton",
1662
- checked: singleton,
1663
- onCheckedChange: (checked) => setSingleton(checked),
1664
- disabled: isSubmitting || isReadOnly
1665
- }
1666
- ),
1667
- /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "singleton", className: "cursor-pointer", children: "Singleton (only one entry allowed)" })
1668
- ] })
1669
- ] }),
1670
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
1671
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
1672
- /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-sm font-semibold text-foreground", children: "Fields" }),
1673
- !isReadOnly && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1674
- CmsButton,
1675
- {
1676
- type: "button",
1677
- variant: "secondary",
1678
- size: "sm",
1679
- onClick: addField,
1680
- disabled: isSubmitting,
1681
- "data-testid": "add-field-button",
1682
- children: [
1683
- /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-3.5" }),
1684
- "Add Field"
1685
- ]
1686
- }
1687
- )
1688
- ] }),
1689
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: fields.map((field, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
1690
- "div",
1691
- {
1692
- className: cn(
1693
- "flex items-center gap-2 rounded-lg border p-2 transition-colors",
1694
- !isReadOnly && "cursor-pointer hover:bg-muted/50",
1695
- activeFieldIndex === index && "border-primary bg-primary/5"
1696
- ),
1697
- onClick: () => {
1698
- if (!isReadOnly) {
1699
- setActiveFieldIndex(index);
1700
- setShowFieldEditor(true);
1701
- }
1702
- },
1703
- "data-testid": `field-item-${index}`,
1704
- children: [
1705
- !isReadOnly && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-0.5", children: [
1706
- index > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
1707
- "button",
1708
- {
1709
- type: "button",
1710
- onClick: (e) => {
1711
- e.stopPropagation();
1712
- moveField(index, index - 1);
1713
- },
1714
- className: "rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
1715
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { className: "size-3" })
1716
- }
1717
- ),
1718
- index < fields.length - 1 && /* @__PURE__ */ jsxRuntimeExports.jsx(
1719
- "button",
1720
- {
1721
- type: "button",
1722
- onClick: (e) => {
1723
- e.stopPropagation();
1724
- moveField(index, index + 1);
1725
- },
1726
- className: "rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
1727
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3" })
1728
- }
1729
- )
1730
- ] }),
1731
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex size-8 items-center justify-center rounded bg-muted text-muted-foreground", children: FIELD_TYPE_INFO[field.type].icon }),
1732
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 flex-1", children: [
1733
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "truncate text-sm font-medium", children: field.label }),
1734
- /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-muted-foreground", children: [
1735
- FIELD_TYPE_INFO[field.type].label,
1736
- field.required && " *"
1737
- ] })
1738
- ] }),
1739
- !isReadOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
1740
- "button",
1741
- {
1742
- type: "button",
1743
- onClick: (e) => {
1744
- e.stopPropagation();
1745
- removeField(index);
1746
- },
1747
- disabled: isSubmitting || fields.length === 1,
1748
- className: "rounded p-1 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive disabled:opacity-50",
1749
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-4" })
1750
- }
1751
- )
1752
- ]
1753
- },
1754
- index
1755
- )) }),
1756
- !isReadOnly && showFieldEditor && activeField && activeFieldIndex !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1757
- "div",
1758
- {
1759
- className: "rounded-lg border bg-muted/30 p-4",
1760
- "data-testid": "field-editor",
1761
- children: [
1762
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4 flex items-center justify-between", children: [
1763
- /* @__PURE__ */ jsxRuntimeExports.jsxs("h5", { className: "font-medium", children: [
1764
- "Edit Field: ",
1765
- activeField.label
1766
- ] }),
1767
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1768
- "button",
1769
- {
1770
- type: "button",
1771
- onClick: () => {
1772
- setShowFieldEditor(false);
1773
- setActiveFieldIndex(null);
1774
- },
1775
- className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
1776
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-4" })
1777
- }
1778
- )
1779
- ] }),
1780
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
1781
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1782
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { htmlFor: "fieldLabel", children: [
1783
- "Label ",
1784
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
1785
- ] }),
1786
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1787
- Input,
1788
- {
1789
- id: "fieldLabel",
1790
- value: activeField.label,
1791
- onChange: (e) => updateField(activeFieldIndex, {
1792
- label: e.target.value,
1793
- name: machineNameManuallyEdited ? activeField.name : generateMachineName(e.target.value) || activeField.name
1794
- }),
1795
- disabled: isSubmitting,
1796
- "data-testid": "field-label-input"
1797
- }
1798
- )
1799
- ] }),
1800
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1801
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { htmlFor: "fieldName", children: [
1802
- "Name ",
1803
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
1804
- ] }),
1805
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1806
- Input,
1807
- {
1808
- id: "fieldName",
1809
- value: activeField.name,
1810
- onChange: (e) => updateField(activeFieldIndex, {
1811
- name: e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, "")
1812
- }),
1813
- disabled: isSubmitting,
1814
- "data-testid": "field-name-input"
1815
- }
1816
- )
1817
- ] }),
1818
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1819
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { htmlFor: "fieldType", children: [
1820
- "Type ",
1821
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
1822
- ] }),
1823
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1824
- Select,
1825
- {
1826
- value: activeField.type,
1827
- onValueChange: (value) => updateField(activeFieldIndex, {
1828
- type: value,
1829
- options: void 0
1830
- }),
1831
- disabled: isSubmitting,
1832
- children: [
1833
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { "data-testid": "field-type-select", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, {}) }),
1834
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectContent, { children: Object.entries(FIELD_TYPE_INFO).map(([type, info]) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: type, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1835
- info.icon,
1836
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: info.label }),
1837
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground", children: [
1838
- "- ",
1839
- info.description
1840
- ] })
1841
- ] }) }, type)) })
1842
- ]
1843
- }
1844
- )
1845
- ] }),
1846
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1847
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1848
- Checkbox,
1849
- {
1850
- id: "fieldRequired",
1851
- checked: activeField.required,
1852
- onCheckedChange: (checked) => updateField(activeFieldIndex, {
1853
- required: checked
1854
- }),
1855
- disabled: isSubmitting
1856
- }
1857
- ),
1858
- /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "fieldRequired", className: "cursor-pointer", children: "Required" })
1859
- ] }),
1860
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1861
- /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "fieldDescription", children: "Help Text" }),
1862
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1863
- Input,
1864
- {
1865
- id: "fieldDescription",
1866
- value: activeField.description || "",
1867
- onChange: (e) => updateField(activeFieldIndex, {
1868
- description: e.target.value || void 0
1869
- }),
1870
- placeholder: "Help text shown below the field",
1871
- disabled: isSubmitting
1872
- }
1873
- )
1874
- ] }),
1875
- (activeField.type === "select" || activeField.type === "multiSelect") && /* @__PURE__ */ jsxRuntimeExports.jsx(
1876
- SelectOptionsEditor,
1877
- {
1878
- options: activeField.options?.options || [],
1879
- onChange: (options) => updateField(activeFieldIndex, {
1880
- options: { ...activeField.options, options }
1881
- }),
1882
- disabled: isSubmitting
1883
- }
1884
- )
1885
- ] })
1886
- ]
1887
- }
1888
- )
1889
- ] }),
1890
- textFields.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
1891
- /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-sm font-semibold text-foreground", children: "Display Settings" }),
1892
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1893
- /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "titleField", children: "Title Field" }),
1894
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1895
- Select,
1896
- {
1897
- value: titleField || "none",
1898
- onValueChange: (v2) => setTitleField(v2 === "none" ? "" : v2),
1899
- disabled: isSubmitting,
1900
- children: [
1901
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: "None" }) }),
1902
- /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [
1903
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "none", children: "None" }),
1904
- textFields.map((field) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: field.name, children: field.label }, field.name))
1905
- ] })
1906
- ]
1907
- }
1908
- ),
1909
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Field to display as the entry title in lists" })
1910
- ] }),
1911
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1912
- /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "slugField", children: "Slug Field" }),
1913
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1914
- Select,
1915
- {
1916
- value: slugField || "none",
1917
- onValueChange: (v2) => setSlugField(v2 === "none" ? "" : v2),
1918
- disabled: isSubmitting,
1919
- children: [
1920
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: "None (auto-generate)" }) }),
1921
- /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [
1922
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "none", children: "None (auto-generate)" }),
1923
- textFields.map((field) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: field.name, children: field.label }, field.name))
1924
- ] })
1925
- ]
1926
- }
1927
- ),
1928
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Field to use for generating URL-friendly slugs" })
1929
- ] })
1930
- ] }),
1931
- submitError && /* @__PURE__ */ jsxRuntimeExports.jsx(
1932
- "div",
1933
- {
1934
- className: "diff-removed rounded-lg border px-3 py-2 text-sm",
1935
- role: "alert",
1936
- "data-testid": "submit-error",
1937
- children: submitError
1938
- }
1939
- )
1940
- ] })
1941
- }
1942
- ),
1943
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1944
- BreakingChangesWarningDialog,
1945
- {
1946
- isOpen: showBreakingWarning,
1947
- onClose: () => setShowBreakingWarning(false),
1948
- breakingChanges,
1949
- onForceUpdate: handleForceUpdate,
1950
- onCancel: () => {
1951
- setShowBreakingWarning(false);
1952
- setBreakingChanges([]);
1953
- },
1954
- isLoading: isForceUpdating
1955
- }
1956
- )
1957
- ] });
1958
- }
1959
- function SelectOptionsEditor({
1960
- options,
1961
- onChange,
1962
- disabled
1963
- }) {
1964
- const addOption = () => {
1965
- onChange([
1966
- ...options,
1967
- { value: `option_${options.length + 1}`, label: "" }
1968
- ]);
1969
- };
1970
- const removeOption = (index) => {
1971
- onChange(options.filter((_, i) => i !== index));
1972
- };
1973
- const updateOption = (index, updates) => {
1974
- onChange(
1975
- options.map((opt, i) => i === index ? { ...opt, ...updates } : opt)
1976
- );
1977
- };
1978
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1979
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { children: [
1980
- "Options ",
1981
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
1982
- ] }),
1983
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: options.map((option, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1984
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1985
- Input,
1986
- {
1987
- value: option.label,
1988
- onChange: (e) => {
1989
- const label = e.target.value;
1990
- const value = label.toLowerCase().replace(/[^a-z0-9]/g, "_").replace(/^_+|_+$/g, "");
1991
- updateOption(index, { label, value });
1992
- },
1993
- placeholder: "Option label",
1994
- disabled,
1995
- className: "flex-1"
1996
- }
1997
- ),
1998
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1999
- Input,
2000
- {
2001
- value: option.value,
2002
- onChange: (e) => updateOption(index, {
2003
- value: e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, "")
2004
- }),
2005
- placeholder: "value",
2006
- disabled,
2007
- className: "w-32"
2008
- }
2009
- ),
2010
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2011
- "button",
2012
- {
2013
- type: "button",
2014
- onClick: () => removeOption(index),
2015
- disabled,
2016
- className: "rounded p-1 text-muted-foreground hover:bg-destructive/10 hover:text-destructive",
2017
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-4" })
2018
- }
2019
- )
2020
- ] }, index)) }),
2021
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
2022
- CmsButton,
2023
- {
2024
- type: "button",
2025
- variant: "secondary",
2026
- size: "sm",
2027
- onClick: addOption,
2028
- disabled,
2029
- children: [
2030
- /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-3.5" }),
2031
- "Add Option"
2032
- ]
2033
- }
2034
- )
2035
- ] });
2036
- }
2037
- function Sidebar() {
2038
- const routerState = useRouterState();
2039
- const currentPath = routerState.location.pathname;
2040
- const config = useAdminConfig();
2041
- const { navItems, branding, layout } = config;
2042
- const api2 = useApi();
2043
- const [isCreateModalOpen, setIsCreateModalOpen] = reactExports.useState(false);
2044
- const contentTypesResult = useQuery(api2.listContentTypes, {
2045
- isActive: true
2046
- });
2047
- const contentTypes = contentTypesResult?.page ?? [];
2048
- const isActive = (to, exact) => {
2049
- if (exact) {
2050
- return currentPath === to;
2051
- }
2052
- return currentPath.startsWith(to);
2053
- };
2054
- const isContentActive = currentPath === "/content" || currentPath.startsWith("/entries/type/") || currentPath.startsWith("/entries/new/") || currentPath.startsWith("/entries/");
2055
- const renderNavItem = (item) => {
2056
- if (item.id === "content") {
2057
- return renderContentMenu(item);
2058
- }
2059
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2060
- Link,
2061
- {
2062
- to: item.path,
2063
- className: cn(
2064
- "flex items-center gap-3 rounded-md px-2 py-2 text-sm font-medium transition-colors",
2065
- isActive(item.path, item.exact) ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-sidebar-foreground hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground"
2066
- ),
2067
- children: [
2068
- /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: item.icon, className: "size-5" }),
2069
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1", children: item.label }),
2070
- item.badge && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "rounded-full bg-sidebar-primary px-2 py-0.5 text-xs text-sidebar-primary-foreground", children: item.badge })
2071
- ]
2072
- },
2073
- item.id
2074
- );
2075
- };
2076
- const renderContentMenu = (item) => /* @__PURE__ */ jsxRuntimeExports.jsxs(Collapsible, { defaultOpen: isContentActive, children: [
2077
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
2078
- CollapsibleTrigger,
2079
- {
2080
- className: cn(
2081
- "flex w-full items-center gap-3 rounded-md px-2 py-2 text-sm font-medium transition-colors",
2082
- isContentActive ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-sidebar-foreground hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground",
2083
- "group"
2084
- ),
2085
- children: [
2086
- /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: item.icon, className: "size-5" }),
2087
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 text-left", children: item.label }),
2088
- /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4 transition-transform duration-200 group-data-[state=open]:rotate-180" })
2089
- ]
2090
- }
2091
- ),
2092
- /* @__PURE__ */ jsxRuntimeExports.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ml-5 mt-1 space-y-1 border-l border-sidebar-border pl-3", children: [
2093
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2094
- Link,
2095
- {
2096
- to: "/content",
2097
- className: cn(
2098
- "flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
2099
- currentPath === "/content" ? "bg-sidebar-accent/60 text-sidebar-accent-foreground" : "text-sidebar-foreground/80 hover:bg-sidebar-accent/30 hover:text-sidebar-accent-foreground"
2100
- ),
2101
- children: "All Entries"
2102
- }
2103
- ),
2104
- contentTypes.map((type) => /* @__PURE__ */ jsxRuntimeExports.jsx(
2105
- Link,
2106
- {
2107
- to: "/entries/type/$contentTypeId",
2108
- params: { contentTypeId: type._id },
2109
- className: cn(
2110
- "flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
2111
- currentPath === `/entries/type/${type._id}` ? "bg-sidebar-accent/60 text-sidebar-accent-foreground" : "text-sidebar-foreground/80 hover:bg-sidebar-accent/30 hover:text-sidebar-accent-foreground"
2112
- ),
2113
- children: type.displayName
2114
- },
2115
- type._id
2116
- )),
2117
- contentTypes.length === 0 && contentTypesResult !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
2118
- "button",
2119
- {
2120
- type: "button",
2121
- onClick: () => setIsCreateModalOpen(true),
2122
- className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm text-sidebar-foreground/60 hover:bg-sidebar-accent/30 hover:text-sidebar-accent-foreground",
2123
- children: "+ Create content type"
2124
- }
2125
- )
2126
- ] }) })
2127
- ] }, item.id);
2128
- const sidebarWidth = layout.sidebarWidth;
2129
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2130
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
2131
- "aside",
2132
- {
2133
- className: "fixed inset-y-0 left-0 z-50 flex flex-col border-r border-sidebar-border bg-sidebar",
2134
- style: { width: sidebarWidth },
2135
- children: [
2136
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex h-14 items-center gap-2 border-b border-sidebar-border px-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Link, { to: "/", className: "flex items-center gap-2 font-semibold text-sidebar-foreground", children: [
2137
- branding.logo ? /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: branding.logo, alt: branding.appName, className: "size-8" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { className: "size-4" }) }),
2138
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-base", children: branding.appName })
2139
- ] }) }),
2140
- /* @__PURE__ */ jsxRuntimeExports.jsxs("nav", { className: "flex-1 space-y-6 overflow-y-auto p-4", children: [
2141
- navItems.main.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
2142
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-2 text-xs font-medium uppercase tracking-wider text-sidebar-foreground/60", children: "Main" }),
2143
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 pt-2", children: navItems.main.map(renderNavItem) })
2144
- ] }),
2145
- navItems.config.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
2146
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-2 text-xs font-medium uppercase tracking-wider text-sidebar-foreground/60", children: "Configuration" }),
2147
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 pt-2", children: navItems.config.map(renderNavItem) })
2148
- ] })
2149
- ] }),
2150
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t border-sidebar-border p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between text-xs text-sidebar-foreground/60", children: [
2151
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Version" }),
2152
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: "0.1.0" })
2153
- ] }) })
2154
- ]
2155
- }
2156
- ),
2157
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2158
- ContentTypeFormModal,
2159
- {
2160
- isOpen: isCreateModalOpen,
2161
- onClose: () => setIsCreateModalOpen(false)
2162
- }
2163
- )
2164
- ] });
2165
- }
2166
- function Breadcrumb({ ...props }) {
2167
- return /* @__PURE__ */ jsxRuntimeExports.jsx("nav", { "aria-label": "breadcrumb", "data-slot": "breadcrumb", ...props });
2168
- }
2169
- function BreadcrumbList({ className, ...props }) {
2170
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2171
- "ol",
2172
- {
2173
- "data-slot": "breadcrumb-list",
2174
- className: cn(
2175
- "text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
2176
- className
2177
- ),
2178
- ...props
2179
- }
2180
- );
2181
- }
2182
- function BreadcrumbItem({ className, ...props }) {
2183
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2184
- "li",
2185
- {
2186
- "data-slot": "breadcrumb-item",
2187
- className: cn("inline-flex items-center gap-1.5", className),
2188
- ...props
2189
- }
2190
- );
2191
- }
2192
- function BreadcrumbLink({
2193
- asChild,
2194
- className,
2195
- ...props
2196
- }) {
2197
- const Comp = asChild ? Slot : "a";
2198
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2199
- Comp,
2200
- {
2201
- "data-slot": "breadcrumb-link",
2202
- className: cn("hover:text-foreground transition-colors", className),
2203
- ...props
2204
- }
2205
- );
2206
- }
2207
- function BreadcrumbPage({ className, ...props }) {
2208
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2209
- "span",
2210
- {
2211
- "data-slot": "breadcrumb-page",
2212
- role: "link",
2213
- "aria-disabled": "true",
2214
- "aria-current": "page",
2215
- className: cn("text-foreground font-normal", className),
2216
- ...props
2217
- }
2218
- );
2219
- }
2220
- function BreadcrumbSeparator({
2221
- children,
2222
- className,
2223
- ...props
2224
- }) {
2225
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2226
- "li",
2227
- {
2228
- "data-slot": "breadcrumb-separator",
2229
- role: "presentation",
2230
- "aria-hidden": "true",
2231
- className: cn("[&>svg]:size-3.5", className),
2232
- ...props,
2233
- children: children ?? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, {})
2234
- }
2235
- );
2236
- }
2237
- function DropdownMenu({
2238
- ...props
2239
- }) {
2240
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Root2$1, { "data-slot": "dropdown-menu", ...props });
2241
- }
2242
- function DropdownMenuTrigger({
2243
- ...props
2244
- }) {
2245
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2246
- Trigger$1,
2247
- {
2248
- "data-slot": "dropdown-menu-trigger",
2249
- ...props
2250
- }
2251
- );
2252
- }
2253
- function DropdownMenuContent({
2254
- className,
2255
- sideOffset = 4,
2256
- ...props
2257
- }) {
2258
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal2, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2259
- Content2$1,
2260
- {
2261
- "data-slot": "dropdown-menu-content",
2262
- sideOffset,
2263
- className: cn(
2264
- "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
2265
- className
2266
- ),
2267
- ...props
2268
- }
2269
- ) });
2270
- }
2271
- function DropdownMenuItem({
2272
- className,
2273
- inset,
2274
- variant = "default",
2275
- ...props
2276
- }) {
2277
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2278
- Item2,
2279
- {
2280
- "data-slot": "dropdown-menu-item",
2281
- "data-inset": inset,
2282
- "data-variant": variant,
2283
- className: cn(
2284
- "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
2285
- className
2286
- ),
2287
- ...props
2288
- }
2289
- );
2290
- }
2291
- function DropdownMenuLabel({
2292
- className,
2293
- inset,
2294
- ...props
2295
- }) {
2296
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2297
- Label2,
2298
- {
2299
- "data-slot": "dropdown-menu-label",
2300
- "data-inset": inset,
2301
- className: cn(
2302
- "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
2303
- className
2304
- ),
2305
- ...props
2306
- }
2307
- );
2308
- }
2309
- function DropdownMenuSeparator({
2310
- className,
2311
- ...props
2312
- }) {
2313
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2314
- Separator2,
2315
- {
2316
- "data-slot": "dropdown-menu-separator",
2317
- className: cn("bg-border -mx-1 my-1 h-px", className),
2318
- ...props
2319
- }
2320
- );
2321
- }
2322
- function Avatar({
2323
- className,
2324
- ...props
2325
- }) {
2326
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2327
- Root,
2328
- {
2329
- "data-slot": "avatar",
2330
- className: cn(
2331
- "relative flex size-8 shrink-0 overflow-hidden rounded-full",
2332
- className
2333
- ),
2334
- ...props
2335
- }
2336
- );
2337
- }
2338
- function AvatarImage({
2339
- className,
2340
- ...props
2341
- }) {
2342
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2343
- Image,
2344
- {
2345
- "data-slot": "avatar-image",
2346
- className: cn("aspect-square size-full", className),
2347
- ...props
2348
- }
2349
- );
2350
- }
2351
- function AvatarFallback({
2352
- className,
2353
- ...props
2354
- }) {
2355
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2356
- Fallback,
2357
- {
2358
- "data-slot": "avatar-fallback",
2359
- className: cn(
2360
- "bg-muted flex size-full items-center justify-center rounded-full",
2361
- className
2362
- ),
2363
- ...props
2364
- }
2365
- );
2366
- }
2367
- function Popover({
2368
- ...props
2369
- }) {
2370
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Root2, { "data-slot": "popover", ...props });
2371
- }
2372
- function PopoverTrigger({
2373
- ...props
2374
- }) {
2375
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger, { "data-slot": "popover-trigger", ...props });
2376
- }
2377
- function PopoverContent({
2378
- className,
2379
- align = "center",
2380
- sideOffset = 4,
2381
- ...props
2382
- }) {
2383
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2384
- Content2,
2385
- {
2386
- "data-slot": "popover-content",
2387
- align,
2388
- sideOffset,
2389
- className: cn(
2390
- "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
2391
- className
2392
- ),
2393
- ...props
2394
- }
2395
- ) });
2396
- }
2397
- const KEYBOARD_SHORTCUTS = [
2398
- { keys: ["⌘", "S"], description: "Save entry" },
2399
- { keys: ["⌘", "⇧", "P"], description: "Publish entry" },
2400
- { keys: ["⌘", "K"], description: "Quick search" },
2401
- { keys: ["Esc"], description: "Close modal/panel" }
2402
- ];
2403
- const HELP_RESOURCES = [
2404
- { label: "Documentation", url: "https://docs.convex.dev", icon: Book },
2405
- { label: "API Reference", url: "https://docs.convex.dev/api", icon: Code },
2406
- { label: "Community Discord", url: "https://discord.gg/convex", icon: MessageSquare }
2407
- ];
2408
- const routeLabels = {
2409
- "/": "Dashboard",
2410
- "/content": "Content",
2411
- "/media": "Media Library",
2412
- "/content-types": "Content Types",
2413
- "/settings": "Settings",
2414
- "/taxonomies": "Taxonomies",
2415
- "/trash": "Trash",
2416
- "/entries": "Entries",
2417
- "/entries/type": "Content Types",
2418
- "/entries/new": "New Entry"
2419
- };
2420
- function getBreadcrumbs(pathname, appName, overrides) {
2421
- const breadcrumbs = [{ label: appName, to: "/" }];
2422
- const decodedPathname = decodeURIComponent(pathname);
2423
- if (decodedPathname === "/") {
2424
- return breadcrumbs;
2425
- }
2426
- const segments = decodedPathname.split("/").filter(Boolean);
2427
- let currentPath = "";
2428
- segments.forEach((segment, index) => {
2429
- currentPath += `/${segment}`;
2430
- const isLast = index === segments.length - 1;
2431
- let label = overrides.get(currentPath) ?? routeLabels[currentPath];
2432
- if (!label) {
2433
- label = segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, " ");
2434
- }
2435
- if (isLast) {
2436
- breadcrumbs.push({ label });
2437
- } else {
2438
- breadcrumbs.push({ label, to: currentPath });
2439
- }
2440
- });
2441
- return breadcrumbs;
2442
- }
2443
- function Header() {
2444
- const routerState = useRouterState();
2445
- const navigate = useNavigate();
2446
- const { branding } = useAdminConfig();
2447
- let overrides = /* @__PURE__ */ new Map();
2448
- try {
2449
- const breadcrumbContext = useBreadcrumbContext();
2450
- overrides = breadcrumbContext.overrides;
2451
- } catch {
2452
- }
2453
- const breadcrumbs = getBreadcrumbs(routerState.location.pathname, branding.appName, overrides);
2454
- let user = null;
2455
- let role = null;
2456
- let logout = async () => {
2457
- };
2458
- let isAuthenticated = false;
2459
- try {
2460
- const auth = useAuth();
2461
- user = auth.user;
2462
- role = auth.role;
2463
- logout = auth.logout;
2464
- isAuthenticated = auth.isAuthenticated;
2465
- } catch {
2466
- }
2467
- const roleDefinition = role ? getRole(role) : null;
2468
- const roleDisplayName = roleDefinition?.displayName ?? role ?? "No Role";
2469
- const userDisplayName = user?.name ?? user?.email ?? "User";
2470
- const getInitials = (name) => {
2471
- const parts = name.split(" ");
2472
- if (parts.length >= 2) {
2473
- return `${parts[0][0]}${parts[1][0]}`.toUpperCase();
2474
- }
2475
- return name.slice(0, 2).toUpperCase();
2476
- };
2477
- const userInitials = user?.name ? getInitials(user.name) : "U";
2478
- const handleLogout = async () => {
2479
- await logout();
2480
- };
2481
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("header", { className: "sticky top-0 z-40 flex h-14 items-center justify-between border-b border-border bg-background/95 px-6 backdrop-blur supports-[backdrop-filter]:bg-background/60", children: [
2482
- /* @__PURE__ */ jsxRuntimeExports.jsx(Breadcrumb, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(BreadcrumbList, { children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs(reactExports.Fragment, { children: [
2483
- index > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(BreadcrumbSeparator, {}),
2484
- /* @__PURE__ */ jsxRuntimeExports.jsx(BreadcrumbItem, { children: crumb.to ? /* @__PURE__ */ jsxRuntimeExports.jsx(BreadcrumbLink, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Link, { to: crumb.to, className: "flex items-center gap-1.5", children: [
2485
- index === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(House, { className: "size-3.5" }),
2486
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: crumb.label })
2487
- ] }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(BreadcrumbPage, { children: crumb.label }) })
2488
- ] }, index)) }) }),
2489
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
2490
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Popover, { children: [
2491
- /* @__PURE__ */ jsxRuntimeExports.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "ghost", size: "icon", className: "size-9", children: [
2492
- /* @__PURE__ */ jsxRuntimeExports.jsx(Bell, { className: "size-4" }),
2493
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sr-only", children: "Notifications" })
2494
- ] }) }),
2495
- /* @__PURE__ */ jsxRuntimeExports.jsxs(PopoverContent, { align: "end", className: "w-80", children: [
2496
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-between pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-medium", children: "Notifications" }) }),
2497
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
2498
- /* @__PURE__ */ jsxRuntimeExports.jsx(Bell, { className: "size-8 text-muted-foreground/50" }),
2499
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-2 text-sm font-medium", children: "No notifications yet" }),
2500
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "You're all caught up!" })
2501
- ] })
2502
- ] })
2503
- ] }),
2504
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Popover, { children: [
2505
- /* @__PURE__ */ jsxRuntimeExports.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "ghost", size: "icon", className: "size-9", children: [
2506
- /* @__PURE__ */ jsxRuntimeExports.jsx(CircleQuestionMark, { className: "size-4" }),
2507
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sr-only", children: "Help" })
2508
- ] }) }),
2509
- /* @__PURE__ */ jsxRuntimeExports.jsxs(PopoverContent, { align: "end", className: "w-72", children: [
2510
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-medium", children: "Help & Resources" }) }),
2511
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
2512
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
2513
- /* @__PURE__ */ jsxRuntimeExports.jsx("h5", { className: "mb-2 text-xs font-medium text-muted-foreground", children: "Keyboard Shortcuts" }),
2514
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1", children: KEYBOARD_SHORTCUTS.map((shortcut, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between text-sm", children: [
2515
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground", children: shortcut.description }),
2516
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-0.5", children: shortcut.keys.map((key, keyIndex) => /* @__PURE__ */ jsxRuntimeExports.jsx(
2517
- "kbd",
2518
- {
2519
- className: "rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-xs",
2520
- children: key
2521
- },
2522
- keyIndex
2523
- )) })
2524
- ] }, index)) })
2525
- ] }),
2526
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
2527
- /* @__PURE__ */ jsxRuntimeExports.jsx("h5", { className: "mb-2 text-xs font-medium text-muted-foreground", children: "Resources" }),
2528
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1", children: HELP_RESOURCES.map((resource) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
2529
- "a",
2530
- {
2531
- href: resource.url,
2532
- target: "_blank",
2533
- rel: "noopener noreferrer",
2534
- className: "flex items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent",
2535
- children: [
2536
- /* @__PURE__ */ jsxRuntimeExports.jsx(resource.icon, { className: "size-4 text-muted-foreground" }),
2537
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1", children: resource.label }),
2538
- /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "size-3 text-muted-foreground" })
2539
- ]
2540
- },
2541
- resource.label
2542
- )) })
2543
- ] })
2544
- ] })
2545
- ] })
2546
- ] }),
2547
- /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenu, { children: [
2548
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "ghost", className: "h-9 gap-2 pl-2 pr-3", children: [
2549
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Avatar, { className: "size-6", children: [
2550
- /* @__PURE__ */ jsxRuntimeExports.jsx(AvatarImage, { src: user?.avatarUrl, alt: userDisplayName }),
2551
- /* @__PURE__ */ jsxRuntimeExports.jsx(AvatarFallback, { className: "text-xs", children: userInitials })
2552
- ] }),
2553
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-medium", children: userDisplayName }),
2554
- /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3.5 text-muted-foreground" })
2555
- ] }) }),
2556
- /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [
2557
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuLabel, { className: "font-normal", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col space-y-1", children: [
2558
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-medium", children: userDisplayName }),
2559
- user?.email && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: user.email }),
2560
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: roleDisplayName })
2561
- ] }) }),
2562
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuSeparator, {}),
2563
- /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenuItem, { onClick: () => navigate({ to: "/settings" }), children: [
2564
- /* @__PURE__ */ jsxRuntimeExports.jsx(User, { className: "mr-2 size-4" }),
2565
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Profile & Settings" })
2566
- ] }),
2567
- isAuthenticated && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2568
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuSeparator, {}),
2569
- /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenuItem, { onClick: handleLogout, className: "text-destructive focus:text-destructive", children: [
2570
- /* @__PURE__ */ jsxRuntimeExports.jsx(LogOut, { className: "mr-2 size-4" }),
2571
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Logout" })
2572
- ] })
2573
- ] })
2574
- ] })
2575
- ] })
2576
- ] })
2577
- ] });
2578
- }
2579
- function AdminLayout({ children }) {
2580
- const { layout } = useAdminConfig();
2581
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-h-screen bg-background", children: [
2582
- /* @__PURE__ */ jsxRuntimeExports.jsx(Sidebar, {}),
2583
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 flex-col", style: { marginLeft: layout.sidebarWidth }, children: [
2584
- /* @__PURE__ */ jsxRuntimeExports.jsx(Header, {}),
2585
- /* @__PURE__ */ jsxRuntimeExports.jsx("main", { className: "flex-1 overflow-auto p-6", children })
2586
- ] })
2587
- ] });
2588
- }
2589
- function DefaultLoading() {
2590
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "route-guard route-guard--loading bg-background flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-8 w-8 animate-spin text-muted-foreground" }) });
2591
- }
2592
- function DefaultUnauthenticated() {
2593
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "route-guard route-guard--unauthenticated", children: [
2594
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "route-guard-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2595
- "svg",
2596
- {
2597
- xmlns: "http://www.w3.org/2000/svg",
2598
- width: "48",
2599
- height: "48",
2600
- viewBox: "0 0 24 24",
2601
- fill: "none",
2602
- stroke: "currentColor",
2603
- strokeWidth: "2",
2604
- strokeLinecap: "round",
2605
- strokeLinejoin: "round",
2606
- children: [
2607
- /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }),
2608
- /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })
2609
- ]
2610
- }
2611
- ) }),
2612
- /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { children: "Authentication Required" }),
2613
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Please log in to access the admin panel." })
2614
- ] });
2615
- }
2616
- function DefaultUnauthorized({
2617
- requiredRole,
2618
- requiredPermission
2619
- }) {
2620
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "route-guard route-guard--unauthorized", children: [
2621
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "route-guard-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2622
- "svg",
2623
- {
2624
- xmlns: "http://www.w3.org/2000/svg",
2625
- width: "48",
2626
- height: "48",
2627
- viewBox: "0 0 24 24",
2628
- fill: "none",
2629
- stroke: "currentColor",
2630
- strokeWidth: "2",
2631
- strokeLinecap: "round",
2632
- strokeLinejoin: "round",
2633
- children: [
2634
- /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "10" }),
2635
- /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "4.93", y1: "4.93", x2: "19.07", y2: "19.07" })
2636
- ]
2637
- }
2638
- ) }),
2639
- /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { children: "Access Denied" }),
2640
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "You don't have permission to access this page." }),
2641
- requiredRole && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "route-guard-detail", children: [
2642
- "Required role: ",
2643
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: requiredRole })
2644
- ] }),
2645
- requiredPermission && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "route-guard-detail", children: [
2646
- "Required permission:",
2647
- " ",
2648
- /* @__PURE__ */ jsxRuntimeExports.jsxs("strong", { children: [
2649
- requiredPermission.action,
2650
- " on ",
2651
- requiredPermission.resource
2652
- ] })
2653
- ] })
2654
- ] });
2655
- }
2656
- function DefaultError({ error }) {
2657
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "route-guard route-guard--error", children: [
2658
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "route-guard-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2659
- "svg",
2660
- {
2661
- xmlns: "http://www.w3.org/2000/svg",
2662
- width: "48",
2663
- height: "48",
2664
- viewBox: "0 0 24 24",
2665
- fill: "none",
2666
- stroke: "currentColor",
2667
- strokeWidth: "2",
2668
- strokeLinecap: "round",
2669
- strokeLinejoin: "round",
2670
- children: [
2671
- /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "10" }),
2672
- /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
2673
- /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
2674
- ]
2675
- }
2676
- ) }),
2677
- /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { children: "Authentication Error" }),
2678
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: error })
2679
- ] });
2680
- }
2681
- function RouteGuard({
2682
- children,
2683
- requiredPermission,
2684
- requiredRole,
2685
- loadingComponent,
2686
- unauthenticatedComponent,
2687
- unauthorizedComponent,
2688
- onUnauthenticated,
2689
- onUnauthorized
2690
- }) {
2691
- const { authState, role, checkPermission, error } = useAuth();
2692
- if (authState === "loading") {
2693
- return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: loadingComponent ?? /* @__PURE__ */ jsxRuntimeExports.jsx(DefaultLoading, {}) });
2694
- }
2695
- if (authState === "error") {
2696
- return /* @__PURE__ */ jsxRuntimeExports.jsx(DefaultError, { error: error ?? "An error occurred" });
2697
- }
2698
- if (authState === "unauthenticated") {
2699
- onUnauthenticated?.();
2700
- return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: unauthenticatedComponent ?? /* @__PURE__ */ jsxRuntimeExports.jsx(DefaultUnauthenticated, {}) });
2701
- }
2702
- if (!role) {
2703
- onUnauthorized?.();
2704
- return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: unauthorizedComponent ?? /* @__PURE__ */ jsxRuntimeExports.jsx(
2705
- DefaultUnauthorized,
2706
- {
2707
- requiredRole,
2708
- requiredPermission
2709
- }
2710
- ) });
2711
- }
2712
- if (requiredRole && role !== requiredRole) {
2713
- onUnauthorized?.();
2714
- return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: unauthorizedComponent ?? /* @__PURE__ */ jsxRuntimeExports.jsx(
2715
- DefaultUnauthorized,
2716
- {
2717
- requiredRole,
2718
- requiredPermission
2719
- }
2720
- ) });
2721
- }
2722
- if (requiredPermission && !checkPermission(requiredPermission)) {
2723
- onUnauthorized?.();
2724
- return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: unauthorizedComponent ?? /* @__PURE__ */ jsxRuntimeExports.jsx(
2725
- DefaultUnauthorized,
2726
- {
2727
- requiredRole,
2728
- requiredPermission
2729
- }
2730
- ) });
2731
- }
2732
- return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children });
2733
- }
2734
- const createSsrRpc = (functionId, importer) => {
2735
- const url = "/_serverFn/" + functionId;
2736
- const serverFnMeta = { id: functionId };
2737
- const fn = async (...args) => {
2738
- const serverFn = await getServerFnById(functionId);
2739
- return serverFn(...args);
2740
- };
2741
- return Object.assign(fn, {
2742
- url,
2743
- serverFnMeta,
2744
- [TSS_SERVER_FUNCTION]: true
2745
- });
2746
- };
2747
- const getServerConfig = createServerFn({
2748
- method: "GET"
2749
- }).handler(createSsrRpc("dff4e5b7f7b29b6a323200a2df0a5335d739cf583e83c9e514598af6b5ade819"));
2750
- const api = anyApi;
2751
- componentsGeneric();
2752
- const mockGetUser = () => {
2753
- return {
2754
- id: "mock_user_123",
2755
- name: "Demo Admin",
2756
- email: "admin@example.com"
2757
- };
2758
- };
2759
- const mockGetUserRole = () => {
2760
- return "admin";
2761
- };
2762
- const mockLogout = () => {
2763
- console.log("Logout called (mock mode)");
2764
- };
2765
- const noAuthGetUser = () => null;
2766
- const noAuthGetUserRole = () => null;
2767
- const noAuthLogout = () => {
2768
- };
2769
- function getAuthConfig(authMode) {
2770
- switch (authMode) {
2771
- case "mock":
2772
- case "demo":
2773
- return {
2774
- getUser: mockGetUser,
2775
- getUserRole: mockGetUserRole,
2776
- onLogout: mockLogout
2777
- };
2778
- case "none":
2779
- case "disabled":
2780
- return {
2781
- getUser: noAuthGetUser,
2782
- getUserRole: noAuthGetUserRole,
2783
- onLogout: noAuthLogout
2784
- };
2785
- default:
2786
- return {
2787
- getUser: mockGetUser,
2788
- getUserRole: mockGetUserRole,
2789
- onLogout: mockLogout
2790
- };
2791
- }
2792
- }
2793
- const Route$a = createRootRoute({
2794
- head: () => ({
2795
- meta: [
2796
- {
2797
- charSet: "utf-8"
2798
- },
2799
- {
2800
- name: "viewport",
2801
- content: "width=device-width, initial-scale=1"
2802
- },
2803
- {
2804
- title: "Convex CMS Admin"
2805
- },
2806
- {
2807
- name: "description",
2808
- content: "Admin interface for Convex CMS - manage content, media, and publishing workflows"
2809
- }
2810
- ],
2811
- links: [
2812
- { rel: "stylesheet", href: globalsCss },
2813
- { rel: "icon", href: "/favicon.ico" }
2814
- ]
2815
- }),
2816
- // Load server config at route initialization
2817
- loader: async () => {
2818
- const config = await getServerConfig();
2819
- return { config };
2820
- },
2821
- component: RootComponent
2822
- });
2823
- function RootComponent() {
2824
- const { config } = Route$a.useLoaderData();
2825
- const authConfig = reactExports.useMemo(() => getAuthConfig(config.authMode), [config.authMode]);
2826
- const adminConfig = reactExports.useMemo(
2827
- () => resolveAdminConfig(config.adminConfig),
2828
- [config.adminConfig]
2829
- );
2830
- return /* @__PURE__ */ jsxRuntimeExports.jsx(RootDocument, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(ThemeProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(BreadcrumbProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(ConvexProviderWrapper, { config, adminConfig, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2831
- AuthProvider,
2832
- {
2833
- getUser: authConfig.getUser,
2834
- getUserRole: authConfig.getUserRole,
2835
- onLogout: authConfig.onLogout,
2836
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(RouteGuard, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(AdminLayout, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Outlet, {}) }) })
2837
- }
2838
- ) }) }) }) });
2839
- }
2840
- function ConvexProviderWrapper({
2841
- children,
2842
- config,
2843
- adminConfig
2844
- }) {
2845
- const convex = reactExports.useMemo(() => {
2846
- if (!config.convexUrl) return null;
2847
- return new ConvexReactClient(config.convexUrl);
2848
- }, [config.convexUrl]);
2849
- if (!convex) {
2850
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex min-h-screen items-center justify-center bg-background p-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "diff-modified max-w-lg space-y-4 rounded-lg border p-6 text-center", children: [
2851
- /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-semibold text-diff-modified", children: "Convex Configuration Required" }),
2852
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-diff-modified-foreground", children: "Please provide a Convex deployment URL to connect to your backend." }),
2853
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 text-left text-sm text-diff-modified-foreground", children: [
2854
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-medium", children: "Options:" }),
2855
- /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "list-inside list-disc space-y-1", children: [
2856
- /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
2857
- "Run with URL:",
2858
- " ",
2859
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "rounded bg-diff-modified-bg/50 px-1", children: "npx convex-cms admin --url YOUR_URL" })
2860
- ] }),
2861
- /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
2862
- "Set environment variable:",
2863
- " ",
2864
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "rounded bg-diff-modified-bg/50 px-1", children: "CONVEX_URL=YOUR_URL" })
2865
- ] }),
2866
- /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
2867
- "Add to",
2868
- " ",
2869
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "rounded bg-diff-modified-bg/50 px-1", children: ".env.local" }),
2870
- ":",
2871
- " ",
2872
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "rounded bg-diff-modified-bg/50 px-1", children: "CONVEX_URL=YOUR_URL" })
2873
- ] })
2874
- ] })
2875
- ] }),
2876
- /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-sm text-diff-modified-foreground", children: [
2877
- "Run",
2878
- " ",
2879
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "rounded bg-diff-modified-bg/50 px-1", children: "npx convex dev" }),
2880
- " to start Convex and get your URL."
2881
- ] })
2882
- ] }) });
2883
- }
2884
- return /* @__PURE__ */ jsxRuntimeExports.jsx(ConvexProvider, { client: convex, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ApiProvider, { api: api.admin, children: /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsConfigProvider, { baseConfig: adminConfig, children }) }) });
2885
- }
2886
- function RootDocument({ children }) {
2887
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("html", { lang: "en", children: [
2888
- /* @__PURE__ */ jsxRuntimeExports.jsx("head", { children: /* @__PURE__ */ jsxRuntimeExports.jsx(HeadContent, {}) }),
2889
- /* @__PURE__ */ jsxRuntimeExports.jsxs("body", { children: [
2890
- children,
2891
- /* @__PURE__ */ jsxRuntimeExports.jsx(Scripts, {})
2892
- ] })
2893
- ] });
2894
- }
2895
- const $$splitComponentImporter$9 = () => import("./trash-JAzYGh7A.mjs");
2896
- const Route$9 = createFileRoute("/trash")({
2897
- component: lazyRouteComponent($$splitComponentImporter$9, "component")
2898
- });
2899
- const $$splitComponentImporter$8 = () => import("./taxonomies-BbBNx260.mjs");
2900
- const Route$8 = createFileRoute("/taxonomies")({
2901
- component: lazyRouteComponent($$splitComponentImporter$8, "component")
2902
- });
2903
- const $$splitComponentImporter$7 = () => import("./settings-KVJNe0GM.mjs");
2904
- const Route$7 = createFileRoute("/settings")({
2905
- component: lazyRouteComponent($$splitComponentImporter$7, "component")
2906
- });
2907
- const $$splitComponentImporter$6 = () => import("./media-MpjxOZL8.mjs");
2908
- const Route$6 = createFileRoute("/media")({
2909
- component: lazyRouteComponent($$splitComponentImporter$6, "component")
2910
- });
2911
- const $$splitComponentImporter$5 = () => import("./content-types-BzgRcS8K.mjs");
2912
- const Route$5 = createFileRoute("/content-types")({
2913
- component: lazyRouteComponent($$splitComponentImporter$5, "component")
2914
- });
2915
- const $$splitComponentImporter$4 = () => import("./content-DKRI-YqL.mjs");
2916
- const Route$4 = createFileRoute("/content")({
2917
- component: lazyRouteComponent($$splitComponentImporter$4, "component")
2918
- });
2919
- const $$splitComponentImporter$3 = () => import("./index-BPf6_agY.mjs");
2920
- const Route$3 = createFileRoute("/")({
2921
- component: lazyRouteComponent($$splitComponentImporter$3, "component")
2922
- });
2923
- const $$splitComponentImporter$2 = () => import("./_entryId-B5xoXoJf.mjs");
2924
- const Route$2 = createFileRoute("/entries/$entryId")({
2925
- component: lazyRouteComponent($$splitComponentImporter$2, "component")
2926
- });
2927
- const $$splitComponentImporter$1 = () => import("./_contentTypeId-BWEbjqxY.mjs");
2928
- const Route$1 = createFileRoute("/entries/type/$contentTypeId")({
2929
- component: lazyRouteComponent($$splitComponentImporter$1, "component")
2930
- });
2931
- const $$splitComponentImporter = () => import("./new._contentTypeId-DSb4qR9j.mjs");
2932
- const Route = createFileRoute("/entries/new/$contentTypeId")({
2933
- component: lazyRouteComponent($$splitComponentImporter, "component")
2934
- });
2935
- const TrashRoute = Route$9.update({
2936
- id: "/trash",
2937
- path: "/trash",
2938
- getParentRoute: () => Route$a
2939
- });
2940
- const TaxonomiesRoute = Route$8.update({
2941
- id: "/taxonomies",
2942
- path: "/taxonomies",
2943
- getParentRoute: () => Route$a
2944
- });
2945
- const SettingsRoute = Route$7.update({
2946
- id: "/settings",
2947
- path: "/settings",
2948
- getParentRoute: () => Route$a
2949
- });
2950
- const MediaRoute = Route$6.update({
2951
- id: "/media",
2952
- path: "/media",
2953
- getParentRoute: () => Route$a
2954
- });
2955
- const ContentTypesRoute = Route$5.update({
2956
- id: "/content-types",
2957
- path: "/content-types",
2958
- getParentRoute: () => Route$a
2959
- });
2960
- const ContentRoute = Route$4.update({
2961
- id: "/content",
2962
- path: "/content",
2963
- getParentRoute: () => Route$a
2964
- });
2965
- const IndexRoute = Route$3.update({
2966
- id: "/",
2967
- path: "/",
2968
- getParentRoute: () => Route$a
2969
- });
2970
- const EntriesEntryIdRoute = Route$2.update({
2971
- id: "/entries/$entryId",
2972
- path: "/entries/$entryId",
2973
- getParentRoute: () => Route$a
2974
- });
2975
- const EntriesTypeContentTypeIdRoute = Route$1.update({
2976
- id: "/entries/type/$contentTypeId",
2977
- path: "/entries/type/$contentTypeId",
2978
- getParentRoute: () => Route$a
2979
- });
2980
- const EntriesNewContentTypeIdRoute = Route.update({
2981
- id: "/entries/new/$contentTypeId",
2982
- path: "/entries/new/$contentTypeId",
2983
- getParentRoute: () => Route$a
2984
- });
2985
- const rootRouteChildren = {
2986
- IndexRoute,
2987
- ContentRoute,
2988
- ContentTypesRoute,
2989
- MediaRoute,
2990
- SettingsRoute,
2991
- TaxonomiesRoute,
2992
- TrashRoute,
2993
- EntriesEntryIdRoute,
2994
- EntriesNewContentTypeIdRoute,
2995
- EntriesTypeContentTypeIdRoute
2996
- };
2997
- const routeTree = Route$a._addFileChildren(rootRouteChildren)._addFileTypes();
2998
- function getRouter() {
2999
- const router2 = createRouter({
3000
- routeTree,
3001
- scrollRestoration: true,
3002
- defaultPreload: "intent"
3003
- });
3004
- return router2;
3005
- }
3006
- const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
3007
- __proto__: null,
3008
- getRouter
3009
- }, Symbol.toStringTag, { value: "Module" }));
3010
- export {
3011
- PopoverContent as A,
3012
- Button as B,
3013
- CmsButton as C,
3014
- Dialog as D,
3015
- useAuth as E,
3016
- getResourcePermissions as F,
3017
- canAccessResource as G,
3018
- hasPermission as H,
3019
- Input as I,
3020
- Route as J,
3021
- buttonVariants as K,
3022
- Label as L,
3023
- useBreadcrumbContext as M,
3024
- router as N,
3025
- Popover as P,
3026
- RouteGuard as R,
3027
- Select as S,
3028
- Textarea as T,
3029
- SelectTrigger as a,
3030
- SelectValue as b,
3031
- SelectContent as c,
3032
- SelectItem as d,
3033
- Checkbox as e,
3034
- cn as f,
3035
- CmsConfirmDialog as g,
3036
- api as h,
3037
- CmsDialog as i,
3038
- useAdminConfig as j,
3039
- useTheme as k,
3040
- DialogContent as l,
3041
- DialogTitle as m,
3042
- DropdownMenu as n,
3043
- DropdownMenuTrigger as o,
3044
- DropdownMenuContent as p,
3045
- DropdownMenuItem as q,
3046
- DropdownMenuSeparator as r,
3047
- DialogHeader as s,
3048
- DialogFooter as t,
3049
- useApi as u,
3050
- useSettingsConfig as v,
3051
- ContentTypeFormModal as w,
3052
- Route$2 as x,
3053
- Route$1 as y,
3054
- PopoverTrigger as z
3055
- };