@vexcms/next 0.1.0-alpha.16 → 0.1.0-alpha.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -6
- package/dist/NextAdminLayout.d.ts +14 -24
- package/dist/NextAdminLayout.d.ts.map +1 -1
- package/dist/NextAdminLayout.js +1 -1
- package/dist/NextAdminLayoutClient.d.ts +6 -10
- package/dist/NextAdminLayoutClient.d.ts.map +1 -1
- package/dist/NextAdminLayoutClient.js +0 -1
- package/dist/NextAdminLayoutClient.js.map +1 -1
- package/dist/NextAdminPage.d.ts +7 -5
- package/dist/NextAdminPage.d.ts.map +1 -1
- package/dist/NextAdminPage.js +1 -1
- package/dist/{chunk-YCJNIS6O.js → chunk-K4AAS5B5.js} +1 -4
- package/dist/chunk-K4AAS5B5.js.map +1 -0
- package/dist/{chunk-TR3Z3P6Q.js → chunk-LHJAXWLB.js} +15 -31
- package/dist/chunk-LHJAXWLB.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-TR3Z3P6Q.js.map +0 -1
- package/dist/chunk-YCJNIS6O.js.map +0 -1
package/README.md
CHANGED
|
@@ -10,18 +10,30 @@ pnpm add @vexcms/next@alpha
|
|
|
10
10
|
|
|
11
11
|
## Quick Setup
|
|
12
12
|
|
|
13
|
+
```tsx
|
|
14
|
+
// app/admin/clientProviders.tsx
|
|
15
|
+
"use client"
|
|
16
|
+
import { VexConfigProvider } from "@vexcms/react"
|
|
17
|
+
import config from "@/vex.config"
|
|
18
|
+
|
|
19
|
+
export function ClientProviders({ children }: { children: React.ReactNode }) {
|
|
20
|
+
return <VexConfigProvider config={config}>{children}</VexConfigProvider>
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
13
24
|
```tsx
|
|
14
25
|
// app/admin/layout.tsx
|
|
15
26
|
import { NextAdminLayout } from "@vexcms/next/client"
|
|
16
|
-
import config from "@/vex.config"
|
|
17
27
|
import { getCurrentUser } from "@/auth/serverUtils"
|
|
18
28
|
|
|
29
|
+
import { ClientProviders } from "./clientProviders"
|
|
30
|
+
|
|
19
31
|
export default async function AdminRootLayout({ children }: { children: React.ReactNode }) {
|
|
20
32
|
const user = await getCurrentUser()
|
|
21
33
|
return (
|
|
22
|
-
<
|
|
23
|
-
{children}
|
|
24
|
-
</
|
|
34
|
+
<ClientProviders>
|
|
35
|
+
<NextAdminLayout user={user ?? undefined}>{children}</NextAdminLayout>
|
|
36
|
+
</ClientProviders>
|
|
25
37
|
)
|
|
26
38
|
}
|
|
27
39
|
```
|
|
@@ -29,7 +41,7 @@ export default async function AdminRootLayout({ children }: { children: React.Re
|
|
|
29
41
|
```tsx
|
|
30
42
|
// app/admin/[[...path]]/page.tsx
|
|
31
43
|
import { NextAdminPage } from "@vexcms/next/server"
|
|
32
|
-
import config from "@/vex.config"
|
|
44
|
+
import config from "@/vex.config.server"
|
|
33
45
|
|
|
34
46
|
export default function AdminPage({
|
|
35
47
|
params,
|
|
@@ -62,7 +74,7 @@ Versioning, drafts, and live preview are not implemented yet — both are in pro
|
|
|
62
74
|
|
|
63
75
|
### Access Control
|
|
64
76
|
|
|
65
|
-
- **
|
|
77
|
+
- **Config provider** — `VexConfigProvider` (`@vexcms/react`) supplies the resolved client config — including `access` and `storage.clientUploads` — to the admin panel via React context, mounted once by your app around the admin route
|
|
66
78
|
- **Collection-level permissions** — `usePermission` gates create, read, update, and delete per collection and per global
|
|
67
79
|
- **Field-level permissions** — a per-field access rule hides a read-denied input entirely and renders an update-denied one read-only; edit forms submit only the fields the user actually changed
|
|
68
80
|
- **UI enforcement** — Buttons and actions are disabled or hidden when the current user lacks permission
|
|
@@ -1,44 +1,35 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import type { VexConfig } from "@vexcms/core";
|
|
3
2
|
import type { AdminUser } from "@vexcms/react";
|
|
4
3
|
/**
|
|
5
4
|
* Next.js admin layout shell for VexCMS.
|
|
6
5
|
*
|
|
7
|
-
* A **server component** that
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* server side of that edge. Sanitizing inside the client component would run
|
|
15
|
-
* too late — the raw config would already have failed to serialize.
|
|
16
|
-
*
|
|
17
|
-
* The client leaf handles everything that needs the browser (`usePathname`,
|
|
18
|
-
* `NuqsAdapter`, `NextLink`/`NextImage`); this wrapper does no client work.
|
|
6
|
+
* A **server component** that reads the `sidebar_state` cookie server-side
|
|
7
|
+
* (so the sidebar's open/closed state is correct on first paint, with no
|
|
8
|
+
* client flash) and passes it, plus `user`/`organization`/`children`, to the
|
|
9
|
+
* client leaf `NextAdminLayoutClient`. Config no longer crosses this
|
|
10
|
+
* boundary at all — the admin panel's client tree gets its config from the
|
|
11
|
+
* app's own `VexConfigProvider` mount (`clientProviders.tsx`), not from a
|
|
12
|
+
* prop threaded through this layout.
|
|
19
13
|
*
|
|
20
14
|
* Render it from your admin `layout.tsx`. Because this is a server component,
|
|
21
15
|
* your `layout.tsx` may itself be an `async` server component (e.g. to run auth
|
|
22
|
-
* checks)
|
|
23
|
-
* needed on the application side.
|
|
16
|
+
* checks).
|
|
24
17
|
*
|
|
25
|
-
* @param props - Layout props
|
|
26
|
-
* @param props.
|
|
27
|
-
* @param props.
|
|
28
|
-
* @
|
|
29
|
-
*
|
|
30
|
-
* client boundary ({@link NextAdminLayoutClient}).
|
|
18
|
+
* @param props - Layout props.
|
|
19
|
+
* @param props.children - The page content from `[[...slug]]/page.tsx`.
|
|
20
|
+
* @param props.user - Current user for the admin shell.
|
|
21
|
+
* @returns The rendered admin shell, wrapping `children` in the client boundary
|
|
22
|
+
* ({@link NextAdminLayoutClient}) with the server-read sidebar cookie state.
|
|
31
23
|
* @example
|
|
32
24
|
* ```tsx
|
|
33
25
|
* // app/admin/layout.tsx
|
|
34
26
|
* import { NextAdminLayout } from "@vexcms/next/client";
|
|
35
|
-
* import config from "~/vex.config";
|
|
36
27
|
* import { getCurrentUser } from "~/auth/serverUtils";
|
|
37
28
|
*
|
|
38
29
|
* export default async function AdminLayout({ children }: { children: ReactNode }) {
|
|
39
30
|
* const user = await getCurrentUser();
|
|
40
31
|
* return (
|
|
41
|
-
* <NextAdminLayout
|
|
32
|
+
* <NextAdminLayout user={user ?? undefined}>
|
|
42
33
|
* {children}
|
|
43
34
|
* </NextAdminLayout>
|
|
44
35
|
* );
|
|
@@ -46,7 +37,6 @@ import type { AdminUser } from "@vexcms/react";
|
|
|
46
37
|
* ```
|
|
47
38
|
*/
|
|
48
39
|
export declare function NextAdminLayout(props: {
|
|
49
|
-
config: VexConfig;
|
|
50
40
|
children: ReactNode;
|
|
51
41
|
user?: AdminUser;
|
|
52
42
|
organization?: Record<string, unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NextAdminLayout.d.ts","sourceRoot":"","sources":["../src/NextAdminLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"NextAdminLayout.d.ts","sourceRoot":"","sources":["../src/NextAdminLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAI/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE;IAC3C,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC,wCAaA"}
|
package/dist/NextAdminLayout.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import type { ClientVexConfig } from "@vexcms/core";
|
|
3
2
|
import { type AdminUser } from "@vexcms/react";
|
|
4
3
|
/**
|
|
5
4
|
* Client leaf for the Next.js admin layout.
|
|
@@ -12,24 +11,21 @@ import { type AdminUser } from "@vexcms/react";
|
|
|
12
11
|
* - `NextLink` / `NextImage` passed as framework components so every
|
|
13
12
|
* `VexLink`/`VexImage` uses Next.js routing and image optimisation.
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* instances, functions, React components) cross the server→client boundary.
|
|
14
|
+
* Config reaches `AdminLayout`'s children through the app's own
|
|
15
|
+
* `VexConfigProvider` mount, not through a prop on this component — this
|
|
16
|
+
* leaf never touches config at all.
|
|
19
17
|
*
|
|
20
18
|
* This component is internal to `@vexcms/next` — applications render
|
|
21
19
|
* {@link NextAdminLayout} instead.
|
|
22
20
|
*
|
|
23
|
-
* @param props - Layout props
|
|
24
|
-
* @param props.
|
|
25
|
-
* @param props.
|
|
26
|
-
* @param props.user - Current user for the admin shell
|
|
21
|
+
* @param props - Layout props.
|
|
22
|
+
* @param props.children - The page content from `[[...slug]]/page.tsx`.
|
|
23
|
+
* @param props.user - Current user for the admin shell.
|
|
27
24
|
* @returns The admin shell rendered from `children`, wrapped in `NuqsAdapter`
|
|
28
25
|
* and the shared `AdminLayout`, wired with the active slug/document id derived
|
|
29
26
|
* from `usePathname()` and Next.js `Link`/`Image` components.
|
|
30
27
|
*/
|
|
31
28
|
export declare function NextAdminLayoutClient(props: {
|
|
32
|
-
config: ClientVexConfig;
|
|
33
29
|
children: ReactNode;
|
|
34
30
|
user?: AdminUser;
|
|
35
31
|
organization?: Record<string, unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NextAdminLayoutClient.d.ts","sourceRoot":"","sources":["../src/NextAdminLayoutClient.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"NextAdminLayoutClient.d.ts","sourceRoot":"","sources":["../src/NextAdminLayoutClient.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,+BAuBA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/NextAdminLayoutClient.tsx"],"sourcesContent":["\"use client\";\n// \"use client\" is injected by the tsup build banner for this entry (see\n// tsup.config.ts) so the emitted module carries the client boundary.\nimport type { ReactNode } from \"react\";\nimport
|
|
1
|
+
{"version":3,"sources":["../src/NextAdminLayoutClient.tsx"],"sourcesContent":["\"use client\";\n// \"use client\" is injected by the tsup build banner for this entry (see\n// tsup.config.ts) so the emitted module carries the client boundary.\nimport type { ReactNode } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport NextLink from \"next/link\";\nimport NextImage from \"next/image\";\nimport { NuqsAdapter } from \"nuqs/adapters/next/app\";\nimport { AdminLayout, type AdminUser } from \"@vexcms/react\";\n\n/**\n * Client leaf for the Next.js admin layout.\n *\n * Owns all client-only wiring that cannot run on the server:\n * - `usePathname()` to derive the active collection slug / document id for\n * sidebar highlighting (Next.js layouts do not receive route params).\n * - `NuqsAdapter` (`nuqs/adapters/next/app`) so URL state works inside the\n * admin routes.\n * - `NextLink` / `NextImage` passed as framework components so every\n * `VexLink`/`VexImage` uses Next.js routing and image optimisation.\n *\n * Config reaches `AdminLayout`'s children through the app's own\n * `VexConfigProvider` mount, not through a prop on this component — this\n * leaf never touches config at all.\n *\n * This component is internal to `@vexcms/next` — applications render\n * {@link NextAdminLayout} instead.\n *\n * @param props - Layout props.\n * @param props.children - The page content from `[[...slug]]/page.tsx`.\n * @param props.user - Current user for the admin shell.\n * @returns The admin shell rendered from `children`, wrapped in `NuqsAdapter`\n * and the shared `AdminLayout`, wired with the active slug/document id derived\n * from `usePathname()` and Next.js `Link`/`Image` components.\n */\nexport function NextAdminLayoutClient(props: {\n children: ReactNode;\n user?: AdminUser;\n organization?: Record<string, unknown>;\n sidebarOpen?: boolean;\n}) {\n const pathname = usePathname();\n // pathname: \"/admin\", \"/admin/posts\", \"/admin/posts/123\"\n // Split on \"/\" and take the segment after \"admin\"\n const segments = pathname.split(\"/\").filter(Boolean);\n const activeSlug = segments[1]; // undefined on /admin, \"posts\" on /admin/posts\n const activeDocID = segments[2];\n\n return (\n <NuqsAdapter>\n <AdminLayout\n activeSlug={activeSlug}\n components={{ Link: NextLink, Image: NextImage }}\n pathname={pathname}\n activeDocID={activeDocID}\n user={props.user}\n organization={props.organization}\n sidebarOpen={props.sidebarOpen}\n >\n {props.children}\n </AdminLayout>\n </NuqsAdapter>\n );\n}\n"],"mappings":";;;;AAIA,SAAS,mBAAmB;AAC5B,OAAO,cAAc;AACrB,OAAO,eAAe;AACtB,SAAS,mBAAmB;AAC5B,SAAS,mBAAmC;AA0CtC;AAfC,SAAS,sBAAsB,OAKnC;AACD,QAAM,WAAW,YAAY;AAG7B,QAAM,WAAW,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AACnD,QAAM,aAAa,SAAS,CAAC;AAC7B,QAAM,cAAc,SAAS,CAAC;AAE9B,SACE,oBAAC,eACC;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,YAAY,EAAE,MAAM,UAAU,OAAO,UAAU;AAAA,MAC/C;AAAA,MACA;AAAA,MACA,MAAM,MAAM;AAAA,MACZ,cAAc,MAAM;AAAA,MACpB,aAAa,MAAM;AAAA,MAElB,gBAAM;AAAA;AAAA,EACT,GACF;AAEJ;","names":[]}
|
package/dist/NextAdminPage.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { VexConfig } from "@vexcms/core";
|
|
|
4
4
|
*
|
|
5
5
|
* An `async` server component that routes by the `[[...slug]]` catch-all
|
|
6
6
|
* params, prefetches Convex data via `fetchQuery`, and renders the correct
|
|
7
|
-
* view component. Does **not** include a layout wrapper — `
|
|
7
|
+
* view component. Does **not** include a layout wrapper — `NextAdminLayout`
|
|
8
8
|
* in `app/admin/layout.tsx` owns the persistent shell.
|
|
9
9
|
*
|
|
10
10
|
* **Route mapping:**
|
|
@@ -16,22 +16,24 @@ import type { VexConfig } from "@vexcms/core";
|
|
|
16
16
|
* | `[collectionSlug, documentId]` | `CollectionEditView` with preloaded doc |
|
|
17
17
|
*
|
|
18
18
|
* @param props - Component props
|
|
19
|
-
* @param props.config - The resolved
|
|
19
|
+
* @param props.config - The resolved server `VexConfig`, typically the default export of
|
|
20
|
+
* `vex.config.server.ts` — not the client-safe `vex.config.ts`.
|
|
20
21
|
* @param props.params - Next.js 15 async params `{ path?: string[] }`
|
|
22
|
+
* @param props.token - Optional session token forwarded to `fetchQuery` for authenticated reads.
|
|
21
23
|
* @returns The appropriate admin view for the current URL path.
|
|
22
24
|
*
|
|
23
25
|
* @example
|
|
24
26
|
* ```tsx
|
|
25
27
|
* // app/admin/[[...slug]]/page.tsx
|
|
26
|
-
* import {
|
|
27
|
-
* import config from "../../../../vex.config";
|
|
28
|
+
* import { NextAdminPage } from "@vexcms/next/server";
|
|
29
|
+
* import config from "../../../../vex.config.server";
|
|
28
30
|
*
|
|
29
31
|
* export default function AdminPage({
|
|
30
32
|
* params,
|
|
31
33
|
* }: {
|
|
32
34
|
* params: Promise<{ path?: string[] }>;
|
|
33
35
|
* }) {
|
|
34
|
-
* return <
|
|
36
|
+
* return <NextAdminPage config={config} params={params} />;
|
|
35
37
|
* }
|
|
36
38
|
* ```
|
|
37
39
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NextAdminPage.d.ts","sourceRoot":"","sources":["../src/NextAdminPage.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAoC,SAAS,EAAoB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"NextAdminPage.d.ts","sourceRoot":"","sources":["../src/NextAdminPage.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAoC,SAAS,EAAoB,MAAM,cAAc,CAAC;AAWlG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE;IACzC,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,wCAgHA"}
|
package/dist/NextAdminPage.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
// src/NextAdminLayout.tsx
|
|
2
|
-
import { sanitizeConfigForClient } from "@vexcms/core";
|
|
3
2
|
import { NextAdminLayoutClient } from "./NextAdminLayoutClient";
|
|
4
3
|
import { cookies } from "next/headers";
|
|
5
4
|
import { jsx } from "react/jsx-runtime";
|
|
6
5
|
async function NextAdminLayout(props) {
|
|
7
|
-
const clientConfig = sanitizeConfigForClient(props.config);
|
|
8
6
|
const cookieStore = await cookies();
|
|
9
7
|
const sidebarOpen = String(cookieStore.get("sidebar_state")?.value) === "true";
|
|
10
8
|
return /* @__PURE__ */ jsx(
|
|
11
9
|
NextAdminLayoutClient,
|
|
12
10
|
{
|
|
13
|
-
config: clientConfig,
|
|
14
11
|
user: props.user,
|
|
15
12
|
organization: props.organization,
|
|
16
13
|
sidebarOpen,
|
|
@@ -22,4 +19,4 @@ async function NextAdminLayout(props) {
|
|
|
22
19
|
export {
|
|
23
20
|
NextAdminLayout
|
|
24
21
|
};
|
|
25
|
-
//# sourceMappingURL=chunk-
|
|
22
|
+
//# sourceMappingURL=chunk-K4AAS5B5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/NextAdminLayout.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { AdminUser } from \"@vexcms/react\";\nimport { NextAdminLayoutClient } from \"./NextAdminLayoutClient\";\nimport { cookies } from \"next/headers\";\n\n/**\n * Next.js admin layout shell for VexCMS.\n *\n * A **server component** that reads the `sidebar_state` cookie server-side\n * (so the sidebar's open/closed state is correct on first paint, with no\n * client flash) and passes it, plus `user`/`organization`/`children`, to the\n * client leaf `NextAdminLayoutClient`. Config no longer crosses this\n * boundary at all — the admin panel's client tree gets its config from the\n * app's own `VexConfigProvider` mount (`clientProviders.tsx`), not from a\n * prop threaded through this layout.\n *\n * Render it from your admin `layout.tsx`. Because this is a server component,\n * your `layout.tsx` may itself be an `async` server component (e.g. to run auth\n * checks).\n *\n * @param props - Layout props.\n * @param props.children - The page content from `[[...slug]]/page.tsx`.\n * @param props.user - Current user for the admin shell.\n * @returns The rendered admin shell, wrapping `children` in the client boundary\n * ({@link NextAdminLayoutClient}) with the server-read sidebar cookie state.\n * @example\n * ```tsx\n * // app/admin/layout.tsx\n * import { NextAdminLayout } from \"@vexcms/next/client\";\n * import { getCurrentUser } from \"~/auth/serverUtils\";\n *\n * export default async function AdminLayout({ children }: { children: ReactNode }) {\n * const user = await getCurrentUser();\n * return (\n * <NextAdminLayout user={user ?? undefined}>\n * {children}\n * </NextAdminLayout>\n * );\n * }\n * ```\n */\nexport async function NextAdminLayout(props: {\n children: ReactNode;\n user?: AdminUser;\n organization?: Record<string, unknown>;\n}) {\n const cookieStore = await cookies();\n const sidebarOpen = String(cookieStore.get(\"sidebar_state\")?.value) === \"true\";\n\n return (\n <NextAdminLayoutClient\n user={props.user}\n organization={props.organization}\n sidebarOpen={sidebarOpen}\n >\n {props.children}\n </NextAdminLayoutClient>\n );\n}\n"],"mappings":";AAEA,SAAS,6BAA6B;AACtC,SAAS,eAAe;AA+CpB;AATJ,eAAsB,gBAAgB,OAInC;AACD,QAAM,cAAc,MAAM,QAAQ;AAClC,QAAM,cAAc,OAAO,YAAY,IAAI,eAAe,GAAG,KAAK,MAAM;AAExE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,MAAM;AAAA,MACZ,cAAc,MAAM;AAAA,MACpB;AAAA,MAEC,gBAAM;AAAA;AAAA,EACT;AAEJ;","names":[]}
|
|
@@ -10,20 +10,18 @@ import {
|
|
|
10
10
|
GlobalsListView,
|
|
11
11
|
GlobalEditView
|
|
12
12
|
} from "@vexcms/react";
|
|
13
|
-
import { sanitizeConfigForClient } from "@vexcms/core";
|
|
14
13
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
14
|
async function NextAdminPage(props) {
|
|
16
15
|
const { path = [] } = await props.params;
|
|
17
16
|
const [collectionSlug, documentId] = path;
|
|
18
|
-
const clientConfig = sanitizeConfigForClient(props.config);
|
|
19
17
|
if (!collectionSlug) {
|
|
20
|
-
return /* @__PURE__ */ jsx(DashboardView, {
|
|
18
|
+
return /* @__PURE__ */ jsx(DashboardView, {});
|
|
21
19
|
}
|
|
22
20
|
if (collectionSlug === "globals") {
|
|
23
21
|
if (!documentId) {
|
|
24
|
-
return /* @__PURE__ */ jsx(GlobalsListView, {
|
|
22
|
+
return /* @__PURE__ */ jsx(GlobalsListView, {});
|
|
25
23
|
}
|
|
26
|
-
const globalConfig =
|
|
24
|
+
const globalConfig = props.config.globals.find((g) => g.slug === documentId);
|
|
27
25
|
if (!globalConfig) {
|
|
28
26
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
29
27
|
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground p-6", children: [
|
|
@@ -39,10 +37,10 @@ async function NextAdminPage(props) {
|
|
|
39
37
|
{ slug: globalConfig.slug },
|
|
40
38
|
props.token ? { token: props.token } : void 0
|
|
41
39
|
);
|
|
42
|
-
return /* @__PURE__ */ jsx(GlobalEditView, { global: globalConfig, initialData: global });
|
|
40
|
+
return /* @__PURE__ */ jsx(GlobalEditView, { global: globalConfig.slug, initialData: global });
|
|
43
41
|
}
|
|
44
|
-
const collection =
|
|
45
|
-
const mediaCollection =
|
|
42
|
+
const collection = props.config.collections.find((c) => c.slug === collectionSlug);
|
|
43
|
+
const mediaCollection = props.config.mediaCollections.find((mc) => mc.slug === collectionSlug);
|
|
46
44
|
if (!collection && !mediaCollection) {
|
|
47
45
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
48
46
|
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground p-6", children: [
|
|
@@ -56,16 +54,13 @@ async function NextAdminPage(props) {
|
|
|
56
54
|
if (mediaCollection && documentId) {
|
|
57
55
|
const initialData2 = await fetchQuery(
|
|
58
56
|
vexConvexApi.get,
|
|
59
|
-
{
|
|
60
|
-
id: documentId,
|
|
61
|
-
collection: mediaCollection.slug
|
|
62
|
-
},
|
|
57
|
+
{ id: documentId, collection: mediaCollection.slug },
|
|
63
58
|
props.token ? { token: props.token } : void 0
|
|
64
59
|
);
|
|
65
60
|
return /* @__PURE__ */ jsx(
|
|
66
61
|
MediaCollectionEditView,
|
|
67
62
|
{
|
|
68
|
-
collection: mediaCollection,
|
|
63
|
+
collection: mediaCollection.slug,
|
|
69
64
|
documentId,
|
|
70
65
|
initialData: initialData2
|
|
71
66
|
}
|
|
@@ -80,18 +75,14 @@ async function NextAdminPage(props) {
|
|
|
80
75
|
vexConvexApi.find,
|
|
81
76
|
{
|
|
82
77
|
collection: collectionSlug,
|
|
83
|
-
paginationOpts: {
|
|
84
|
-
numItems: numItems2,
|
|
85
|
-
totalDocs: true,
|
|
86
|
-
cursor: null
|
|
87
|
-
}
|
|
78
|
+
paginationOpts: { numItems: numItems2, totalDocs: true, cursor: null }
|
|
88
79
|
},
|
|
89
80
|
props.token ? { token: props.token } : void 0
|
|
90
81
|
);
|
|
91
82
|
return /* @__PURE__ */ jsx(
|
|
92
83
|
MediaCollectionListView,
|
|
93
84
|
{
|
|
94
|
-
collection: mediaCollection,
|
|
85
|
+
collection: mediaCollection.slug,
|
|
95
86
|
initialData: initialData2
|
|
96
87
|
}
|
|
97
88
|
);
|
|
@@ -99,16 +90,13 @@ async function NextAdminPage(props) {
|
|
|
99
90
|
if (collection && documentId) {
|
|
100
91
|
const initialData2 = await fetchQuery(
|
|
101
92
|
vexConvexApi.get,
|
|
102
|
-
{
|
|
103
|
-
id: documentId,
|
|
104
|
-
collection: collection.slug
|
|
105
|
-
},
|
|
93
|
+
{ id: documentId, collection: collection.slug },
|
|
106
94
|
props.token ? { token: props.token } : void 0
|
|
107
95
|
);
|
|
108
96
|
return /* @__PURE__ */ jsx(
|
|
109
97
|
CollectionEditView,
|
|
110
98
|
{
|
|
111
|
-
collection,
|
|
99
|
+
collection: collection.slug,
|
|
112
100
|
documentId,
|
|
113
101
|
initialData: initialData2
|
|
114
102
|
}
|
|
@@ -126,18 +114,14 @@ async function NextAdminPage(props) {
|
|
|
126
114
|
{
|
|
127
115
|
collection: collectionSlug,
|
|
128
116
|
depth: 1,
|
|
129
|
-
paginationOpts: {
|
|
130
|
-
cursor: null,
|
|
131
|
-
numItems,
|
|
132
|
-
totalDocs: true
|
|
133
|
-
}
|
|
117
|
+
paginationOpts: { cursor: null, numItems, totalDocs: true }
|
|
134
118
|
},
|
|
135
119
|
props.token ? { token: props.token } : void 0
|
|
136
120
|
);
|
|
137
|
-
return /* @__PURE__ */ jsx(CollectionListView, { collection, initialData });
|
|
121
|
+
return /* @__PURE__ */ jsx(CollectionListView, { collection: collection.slug, initialData });
|
|
138
122
|
}
|
|
139
123
|
|
|
140
124
|
export {
|
|
141
125
|
NextAdminPage
|
|
142
126
|
};
|
|
143
|
-
//# sourceMappingURL=chunk-
|
|
127
|
+
//# sourceMappingURL=chunk-LHJAXWLB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/NextAdminPage.tsx"],"sourcesContent":["import { fetchQuery } from \"convex/nextjs\";\nimport { vexConvexApi } from \"@vexcms/core\";\nimport type { CollectionSlug, PaginationResult, VexConfig, VexMediaDocument } from \"@vexcms/core\";\nimport {\n DashboardView,\n CollectionListView,\n CollectionEditView,\n MediaCollectionEditView,\n MediaCollectionListView,\n GlobalsListView,\n GlobalEditView,\n} from \"@vexcms/react\";\n\n/**\n * VexCMS admin page server component for Next.js.\n *\n * An `async` server component that routes by the `[[...slug]]` catch-all\n * params, prefetches Convex data via `fetchQuery`, and renders the correct\n * view component. Does **not** include a layout wrapper — `NextAdminLayout`\n * in `app/admin/layout.tsx` owns the persistent shell.\n *\n * **Route mapping:**\n * | `path` array | View |\n * |---|---|\n * | `[]` or undefined | `DashboardView` |\n * | `[collectionSlug]` | `CollectionListView` with preloaded docs |\n * | `[collectionSlug, \"new\"]` | `CollectionEditView` (empty form) |\n * | `[collectionSlug, documentId]` | `CollectionEditView` with preloaded doc |\n *\n * @param props - Component props\n * @param props.config - The resolved server `VexConfig`, typically the default export of\n * `vex.config.server.ts` — not the client-safe `vex.config.ts`.\n * @param props.params - Next.js 15 async params `{ path?: string[] }`\n * @param props.token - Optional session token forwarded to `fetchQuery` for authenticated reads.\n * @returns The appropriate admin view for the current URL path.\n *\n * @example\n * ```tsx\n * // app/admin/[[...slug]]/page.tsx\n * import { NextAdminPage } from \"@vexcms/next/server\";\n * import config from \"../../../../vex.config.server\";\n *\n * export default function AdminPage({\n * params,\n * }: {\n * params: Promise<{ path?: string[] }>;\n * }) {\n * return <NextAdminPage config={config} params={params} />;\n * }\n * ```\n */\nexport async function NextAdminPage(props: {\n config: VexConfig;\n params: Promise<{ path?: string[] }>;\n token?: string;\n}) {\n const { path = [] } = await props.params;\n const [collectionSlug, documentId] = path;\n\n if (!collectionSlug) {\n return <DashboardView />;\n }\n\n if (collectionSlug === \"globals\") {\n if (!documentId) {\n return <GlobalsListView />;\n }\n const globalConfig = props.config.globals.find((g) => g.slug === documentId);\n if (!globalConfig) {\n return (\n <div>\n <p className=\"text-muted-foreground p-6\">Global "{documentId}" not found.</p>\n <p>TODO: add not found view</p>\n </div>\n );\n }\n const global = await fetchQuery(\n vexConvexApi.globals.get,\n { slug: globalConfig.slug },\n props.token ? { token: props.token } : undefined,\n );\n return <GlobalEditView global={globalConfig.slug} initialData={global} />;\n }\n\n const collection = props.config.collections.find((c) => c.slug === collectionSlug);\n const mediaCollection = props.config.mediaCollections.find((mc) => mc.slug === collectionSlug);\n\n if (!collection && !mediaCollection) {\n return (\n <div>\n <p className=\"text-muted-foreground p-6\">\n Collection "{collectionSlug}" not found.\n </p>\n <p>TODO: add not found view</p>\n </div>\n );\n }\n\n if (mediaCollection && documentId) {\n const initialData = await fetchQuery(\n vexConvexApi.get,\n { id: documentId, collection: mediaCollection.slug },\n props.token ? { token: props.token } : undefined,\n );\n return (\n <MediaCollectionEditView\n collection={mediaCollection.slug}\n documentId={documentId}\n initialData={initialData as VexMediaDocument | null}\n />\n );\n }\n\n if (mediaCollection) {\n const numItems = Math.max(\n mediaCollection.admin.table.serverPageSize,\n mediaCollection.admin.table.defaultPageSize,\n );\n const initialData = await fetchQuery(\n vexConvexApi.find,\n {\n collection: collectionSlug as CollectionSlug,\n paginationOpts: { numItems, totalDocs: true, cursor: null },\n },\n props.token ? { token: props.token } : undefined,\n );\n return (\n <MediaCollectionListView\n collection={mediaCollection.slug}\n initialData={initialData as PaginationResult<VexMediaDocument>}\n />\n );\n }\n\n if (collection && documentId) {\n const initialData = await fetchQuery(\n vexConvexApi.get,\n { id: documentId, collection: collection.slug },\n props.token ? { token: props.token } : undefined,\n );\n return (\n <CollectionEditView\n collection={collection.slug}\n documentId={documentId}\n initialData={initialData}\n />\n );\n }\n\n if (!collection) {\n throw new Error(\"invalid collection slug\");\n }\n\n const numItems = Math.max(\n collection.admin.table.serverPageSize,\n collection.admin.table.defaultPageSize,\n );\n const initialData = await fetchQuery(\n vexConvexApi.findPaginated,\n {\n collection: collectionSlug as CollectionSlug,\n depth: 1,\n paginationOpts: { cursor: null, numItems, totalDocs: true },\n },\n props.token ? { token: props.token } : undefined,\n );\n return <CollectionListView collection={collection.slug} initialData={initialData} />;\n}\n"],"mappings":"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","names":["initialData","numItems"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export * from "./NextAdminPage";
|
|
|
2
2
|
export * from "./NextAdminLayout";
|
|
3
3
|
export { relationship, defineCollection } from "@vexcms/react";
|
|
4
4
|
export { text, number, select, date, url, checkbox, defineConfig, } from "@vexcms/react";
|
|
5
|
-
export type { AdminUser, RelationshipFieldInput, RelationshipField, AdminCollectionConfigInput, CollectionConfigInput, CollectionConfig, TextField, NumberField, SelectField, DateField, UrlField, CheckboxField, AdminField, CollectionSlug, VexDocument, VexConfig,
|
|
5
|
+
export type { AdminUser, RelationshipFieldInput, RelationshipField, AdminCollectionConfigInput, CollectionConfigInput, CollectionConfig, TextField, NumberField, SelectField, DateField, UrlField, CheckboxField, AdminField, CollectionSlug, VexDocument, VexConfig, VexClientConfig, VexClientConfigInput, VexServerConfigInput, RelationshipPreviewProps, } from "@vexcms/react";
|
|
6
6
|
export type { RelationshipKeysOf, TextKeysOf, SortableKeysOf, RelationshipTargetOf, Populated, PopulateShape, } from "@vexcms/core";
|
|
7
7
|
export { createVexServerClient } from "./cache/createVexServerClient";
|
|
8
8
|
export type { VexServerClientOptions, VexServerClient } from "./cache/types";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAyBA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAGlC,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAG/D,OAAO,EACL,IAAI,EACJ,MAAM,EACN,MAAM,EACN,IAAI,EACJ,GAAG,EACH,QAAQ,EACR,YAAY,GACb,MAAM,eAAe,CAAC;AAMvB,YAAY,EACV,SAAS,EAET,sBAAsB,EACtB,iBAAiB,EACjB,0BAA0B,EAC1B,qBAAqB,EACrB,gBAAgB,EAEhB,SAAS,EACT,WAAW,EACX,WAAW,EACX,SAAS,EACT,QAAQ,EACR,aAAa,EAEb,UAAU,EACV,cAAc,EACd,WAAW,EACX,SAAS,EACT,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAyBA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAGlC,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAG/D,OAAO,EACL,IAAI,EACJ,MAAM,EACN,MAAM,EACN,IAAI,EACJ,GAAG,EACH,QAAQ,EACR,YAAY,GACb,MAAM,eAAe,CAAC;AAMvB,YAAY,EACV,SAAS,EAET,sBAAsB,EACtB,iBAAiB,EACjB,0BAA0B,EAC1B,qBAAqB,EACrB,gBAAgB,EAEhB,SAAS,EACT,WAAW,EACX,WAAW,EACX,SAAS,EACT,QAAQ,EACR,aAAa,EAEb,UAAU,EACV,cAAc,EACd,WAAW,EACX,SAAS,EACT,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,eAAe,CAAC;AAYvB,YAAY,EACV,kBAAkB,EAClB,UAAU,EACV,cAAc,EACd,oBAAoB,EACpB,SAAS,EACT,aAAa,GACd,MAAM,cAAc,CAAC;AAKtB,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,YAAY,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,YAAY,EAAE,6BAA6B,EAAE,MAAM,kCAAkC,CAAC;AACtF,YAAY,EACX,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// @vexcms/next v0.1.0-alpha.1\n//\n// Two distinct export surfaces:\n//\n// 1. Server / client component entries (sub-path imports — required because\n// \"use client\" can't be mixed with async server components in one bundle):\n// import { NextAdminPage } from \"@vexcms/next/server\" ← async server component\n// import { NextAdminLayout } from \"@vexcms/next/client\" ← \"use client\" component\n//\n// 2. Config / type re-exports (everything below). Re-exports `@vexcms/react`'s\n// HKT-bound config functions and types transitively so Next consumers can\n// import every config function from a single package:\n//\n// import {\n// defineConfig,\n// defineCollection,\n// relationship,\n// text, number, select, date, url, checkbox,\n// } from \"@vexcms/next\";\n//\n// `admin.components.preview` slots remain typed as React `ComponentType<P>`\n// via `ReactHKT` because @vexcms/react bound it before re-exporting. See\n// spec 22 Decision 15 for the architectural rationale.\n\n// Server / client surface\nexport * from \"./NextAdminPage\";\nexport * from \"./NextAdminLayout\";\n\n// Config functions — HKT-bound wrappers from @vexcms/react\nexport { relationship, defineCollection } from \"@vexcms/react\";\n\n// Field config functions without component slots (pure pass-through)\nexport {\n text,\n number,\n select,\n date,\n url,\n checkbox,\n defineConfig,\n} from \"@vexcms/react\";\n\n// Public types — HKT-bound where applicable.\n// `*FieldInput` core config types intentionally not re-exported (they collide\n// with React `*FieldInput` component names; users get them via inference\n// through the config functions).\nexport type {\n AdminUser,\n // HKT-bound (admin.components.preview = React ComponentType)\n RelationshipFieldInput,\n RelationshipField,\n AdminCollectionConfigInput,\n CollectionConfigInput,\n CollectionConfig,\n // Resolved field types\n TextField,\n NumberField,\n SelectField,\n DateField,\n UrlField,\n CheckboxField,\n // Shared types\n AdminField,\n CollectionSlug,\n VexDocument,\n VexConfig,\n
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// @vexcms/next v0.1.0-alpha.1\n//\n// Two distinct export surfaces:\n//\n// 1. Server / client component entries (sub-path imports — required because\n// \"use client\" can't be mixed with async server components in one bundle):\n// import { NextAdminPage } from \"@vexcms/next/server\" ← async server component\n// import { NextAdminLayout } from \"@vexcms/next/client\" ← \"use client\" component\n//\n// 2. Config / type re-exports (everything below). Re-exports `@vexcms/react`'s\n// HKT-bound config functions and types transitively so Next consumers can\n// import every config function from a single package:\n//\n// import {\n// defineConfig,\n// defineCollection,\n// relationship,\n// text, number, select, date, url, checkbox,\n// } from \"@vexcms/next\";\n//\n// `admin.components.preview` slots remain typed as React `ComponentType<P>`\n// via `ReactHKT` because @vexcms/react bound it before re-exporting. See\n// spec 22 Decision 15 for the architectural rationale.\n\n// Server / client surface\nexport * from \"./NextAdminPage\";\nexport * from \"./NextAdminLayout\";\n\n// Config functions — HKT-bound wrappers from @vexcms/react\nexport { relationship, defineCollection } from \"@vexcms/react\";\n\n// Field config functions without component slots (pure pass-through)\nexport {\n text,\n number,\n select,\n date,\n url,\n checkbox,\n defineConfig,\n} from \"@vexcms/react\";\n\n// Public types — HKT-bound where applicable.\n// `*FieldInput` core config types intentionally not re-exported (they collide\n// with React `*FieldInput` component names; users get them via inference\n// through the config functions).\nexport type {\n AdminUser,\n // HKT-bound (admin.components.preview = React ComponentType)\n RelationshipFieldInput,\n RelationshipField,\n AdminCollectionConfigInput,\n CollectionConfigInput,\n CollectionConfig,\n // Resolved field types\n TextField,\n NumberField,\n SelectField,\n DateField,\n UrlField,\n CheckboxField,\n // Shared types\n AdminField,\n CollectionSlug,\n VexDocument,\n VexConfig,\n VexClientConfig,\n VexClientConfigInput,\n VexServerConfigInput,\n RelationshipPreviewProps,\n} from \"@vexcms/react\";\n\n// API functions are NOT re-exported from this barrel because @vexcms/next's\n// root index.ts is imported by server components (NextAdminPage). Pulling\n// @vexcms/core/client here would transitively import @convex-dev/react-query\n// which uses createContext — crashing RSC.\n//\n// Import directly from the correct environment-specific path instead:\n//\n// import { find, get, search } from \"@vexcms/core/client\"; ← React components\n// import { find, get, search } from \"@vexcms/core/server\"; ← Convex handlers\n\nexport type {\n RelationshipKeysOf,\n TextKeysOf,\n SortableKeysOf,\n RelationshipTargetOf,\n Populated,\n PopulateShape,\n} from \"@vexcms/core\";\n\n// Cache / SEO surface — server-only, no client context, so these are safe in\n// this RSC-imported root barrel. Also reachable via the \"@vexcms/next/cache\"\n// and \"@vexcms/next/seo\" subpaths.\nexport { createVexServerClient } from \"./cache/createVexServerClient\";\nexport type { VexServerClientOptions, VexServerClient } from \"./cache/types\";\nexport { createVexRobots } from \"./seo/createVexRobots\";\nexport { createVexSitemap } from \"./seo/createVexSitemap\";\nexport { vexMetadata } from \"./seo/vexMetadata\";\nexport { vexStaticParams } from \"./seo/vexStaticParams\";\nexport { createVexRevalidateRoute } from \"./cache/createVexRevalidateRoute\";\nexport type { CreateVexRevalidateRouteProps } from \"./cache/createVexRevalidateRoute\";\nexport type {\n} from \"./cache/types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6BA,SAAS,cAAc,wBAAwB;AAG/C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vexcms/next",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.19",
|
|
4
4
|
"description": "Next.js adapter and admin routes for VexCMS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cms",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"lucide-react": "0.577.0",
|
|
69
|
-
"@vexcms/react": "0.1.0-alpha.
|
|
70
|
-
"@vexcms/core": "0.1.0-alpha.
|
|
69
|
+
"@vexcms/react": "0.1.0-alpha.19",
|
|
70
|
+
"@vexcms/core": "0.1.0-alpha.19"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@convex-dev/react-query": "0.1.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/NextAdminPage.tsx"],"sourcesContent":["import { fetchQuery } from \"convex/nextjs\";\nimport { vexConvexApi } from \"@vexcms/core\";\nimport type { CollectionSlug, PaginationResult, VexConfig, VexMediaDocument } from \"@vexcms/core\";\nimport {\n DashboardView,\n CollectionListView,\n CollectionEditView,\n MediaCollectionEditView,\n MediaCollectionListView,\n GlobalsListView,\n GlobalEditView,\n} from \"@vexcms/react\";\nimport { sanitizeConfigForClient } from \"@vexcms/core\";\n\n/**\n * VexCMS admin page server component for Next.js.\n *\n * An `async` server component that routes by the `[[...slug]]` catch-all\n * params, prefetches Convex data via `fetchQuery`, and renders the correct\n * view component. Does **not** include a layout wrapper — `VexAdminLayout`\n * in `app/admin/layout.tsx` owns the persistent shell.\n *\n * **Route mapping:**\n * | `path` array | View |\n * |---|---|\n * | `[]` or undefined | `DashboardView` |\n * | `[collectionSlug]` | `CollectionListView` with preloaded docs |\n * | `[collectionSlug, \"new\"]` | `CollectionEditView` (empty form) |\n * | `[collectionSlug, documentId]` | `CollectionEditView` with preloaded doc |\n *\n * @param props - Component props\n * @param props.config - The resolved VexCMS configuration from `vex.config.ts`\n * @param props.params - Next.js 15 async params `{ path?: string[] }`\n * @returns The appropriate admin view for the current URL path.\n *\n * @example\n * ```tsx\n * // app/admin/[[...slug]]/page.tsx\n * import { VexAdminPage } from \"@vexcms/next\";\n * import config from \"../../../../vex.config\";\n *\n * export default function AdminPage({\n * params,\n * }: {\n * params: Promise<{ path?: string[] }>;\n * }) {\n * return <VexAdminPage config={config} params={params} />;\n * }\n * ```\n */\nexport async function NextAdminPage(props: {\n config: VexConfig;\n params: Promise<{ path?: string[] }>;\n token?: string;\n}) {\n const { path = [] } = await props.params;\n const [collectionSlug, documentId] = path;\n\n // Sanitize config for client components (strips storageAdapters, recursively sanitizes mediaCollections)\n const clientConfig = sanitizeConfigForClient(props.config);\n\n if (!collectionSlug) {\n return <DashboardView config={clientConfig} />;\n }\n\n if (collectionSlug === \"globals\") {\n if (!documentId) {\n return <GlobalsListView config={clientConfig} />;\n }\n // Validate the route param against the registered globals first — the\n // found config's `slug` carries the narrowed GlobalSlug type, so no cast\n // of the raw URL segment is needed downstream.\n const globalConfig = clientConfig.globals.find((g) => g.slug === documentId);\n if (!globalConfig) {\n return (\n <div>\n <p className=\"text-muted-foreground p-6\">Global "{documentId}" not found.</p>\n <p>TODO: add not found view</p>\n </div>\n );\n }\n const global = await fetchQuery(\n vexConvexApi.globals.get,\n { slug: globalConfig.slug },\n props.token ? { token: props.token } : undefined,\n );\n return <GlobalEditView global={globalConfig} initialData={global} />;\n }\n\n const collection = clientConfig.collections.find((c) => c.slug === collectionSlug);\n const mediaCollection = clientConfig.mediaCollections.find((mc) => mc.slug === collectionSlug);\n\n if (!collection && !mediaCollection) {\n return (\n <div>\n <p className=\"text-muted-foreground p-6\">\n Collection "{collectionSlug}" not found.\n </p>\n <p>TODO: add not found view</p>\n </div>\n );\n }\n\n if (mediaCollection && documentId) {\n const initialData = await fetchQuery(\n vexConvexApi.get,\n {\n id: documentId,\n collection: mediaCollection.slug,\n },\n props.token ? { token: props.token } : undefined,\n );\n return (\n <MediaCollectionEditView\n collection={mediaCollection}\n documentId={documentId}\n initialData={initialData as VexMediaDocument | null}\n />\n );\n }\n\n if (mediaCollection) {\n const numItems = Math.max(\n mediaCollection.admin.table.serverPageSize,\n mediaCollection.admin.table.defaultPageSize,\n );\n const initialData = await fetchQuery(\n vexConvexApi.find,\n {\n collection: collectionSlug as CollectionSlug,\n paginationOpts: {\n numItems,\n totalDocs: true,\n cursor: null,\n },\n },\n props.token ? { token: props.token } : undefined,\n );\n return (\n <MediaCollectionListView\n collection={mediaCollection}\n initialData={initialData as PaginationResult<VexMediaDocument>}\n />\n );\n }\n\n if (collection && documentId) {\n const initialData = await fetchQuery(\n vexConvexApi.get,\n {\n id: documentId,\n collection: collection.slug,\n },\n props.token ? { token: props.token } : undefined,\n );\n return (\n <CollectionEditView\n collection={collection}\n documentId={documentId}\n initialData={initialData}\n />\n );\n }\n\n if (!collection) {\n throw new Error(\"invalid collection slug\");\n }\n\n const numItems = Math.max(\n collection.admin.table.serverPageSize,\n collection.admin.table.defaultPageSize,\n );\n const initialData = await fetchQuery(\n vexConvexApi.findPaginated,\n {\n collection: collectionSlug as CollectionSlug,\n depth: 1,\n paginationOpts: {\n cursor: null,\n numItems,\n totalDocs: true,\n },\n },\n props.token ? { token: props.token } : undefined,\n );\n return <CollectionListView collection={collection} initialData={initialData} />;\n}\n"],"mappings":"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","names":["initialData","numItems"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/NextAdminLayout.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { VexConfig } from \"@vexcms/core\";\nimport { sanitizeConfigForClient } from \"@vexcms/core\";\nimport type { AdminUser } from \"@vexcms/react\";\nimport { NextAdminLayoutClient } from \"./NextAdminLayoutClient\";\nimport { cookies } from \"next/headers\";\n\n/**\n * Next.js admin layout shell for VexCMS.\n *\n * A **server component** that sits on the server→client boundary for the admin\n * panel. It sanitizes the resolved `vex.config` with `sanitizeConfigForClient`\n * — stripping storage adapter class instances, functions, and React components\n * — *before* the config is handed to the client leaf `NextAdminLayoutClient`.\n *\n * This placement is the whole point: React serializes props when a server\n * component renders a client component, so the sanitize **must** happen on the\n * server side of that edge. Sanitizing inside the client component would run\n * too late — the raw config would already have failed to serialize.\n *\n * The client leaf handles everything that needs the browser (`usePathname`,\n * `NuqsAdapter`, `NextLink`/`NextImage`); this wrapper does no client work.\n *\n * Render it from your admin `layout.tsx`. Because this is a server component,\n * your `layout.tsx` may itself be an `async` server component (e.g. to run auth\n * checks) and pass the **raw** `vex.config` straight through — no sanitize\n * needed on the application side.\n *\n * @param props - Layout props\n * @param props.config - The resolved VexCMS config from `vex.config.ts` (raw)\n * @param props.children - The page content from `[[...slug]]/page.tsx`\n * @param props.user - Current user for the admin shell\n * @returns The rendered admin shell, wrapping `children` in the sanitized-config\n * client boundary ({@link NextAdminLayoutClient}).\n * @example\n * ```tsx\n * // app/admin/layout.tsx\n * import { NextAdminLayout } from \"@vexcms/next/client\";\n * import config from \"~/vex.config\";\n * import { getCurrentUser } from \"~/auth/serverUtils\";\n *\n * export default async function AdminLayout({ children }: { children: ReactNode }) {\n * const user = await getCurrentUser();\n * return (\n * <NextAdminLayout config={config} user={user ?? undefined}>\n * {children}\n * </NextAdminLayout>\n * );\n * }\n * ```\n */\nexport async function NextAdminLayout(props: {\n config: VexConfig;\n children: ReactNode;\n user?: AdminUser;\n organization?: Record<string, unknown>;\n}) {\n // Sanitize on the SERVER side of the server→client boundary, before the\n // config is serialized as a prop into the client leaf. This removes the\n // storage adapter class instances that would otherwise crash RSC\n // serialization (\"Classes or null prototypes are not supported\").\n const clientConfig = sanitizeConfigForClient(props.config);\n const cookieStore = await cookies();\n const sidebarOpen = String(cookieStore.get(\"sidebar_state\")?.value) === \"true\";\n\n return (\n <NextAdminLayoutClient\n config={clientConfig}\n user={props.user}\n organization={props.organization}\n sidebarOpen={sidebarOpen}\n >\n {props.children}\n </NextAdminLayoutClient>\n );\n}\n"],"mappings":";AAEA,SAAS,+BAA+B;AAExC,SAAS,6BAA6B;AACtC,SAAS,eAAe;AA6DpB;AAfJ,eAAsB,gBAAgB,OAKnC;AAKD,QAAM,eAAe,wBAAwB,MAAM,MAAM;AACzD,QAAM,cAAc,MAAM,QAAQ;AAClC,QAAM,cAAc,OAAO,YAAY,IAAI,eAAe,GAAG,KAAK,MAAM;AAExE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,QAAQ;AAAA,MACR,MAAM,MAAM;AAAA,MACZ,cAAc,MAAM;AAAA,MACpB;AAAA,MAEC,gBAAM;AAAA;AAAA,EACT;AAEJ;","names":[]}
|