@vexcms/next 0.1.0-alpha.2 → 0.1.0-alpha.21
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 +59 -48
- package/dist/NextAdminLayout.d.ts +14 -23
- package/dist/NextAdminLayout.d.ts.map +1 -1
- package/dist/NextAdminLayout.js +3 -20
- package/dist/NextAdminLayout.js.map +1 -1
- package/dist/NextAdminLayoutClient.d.ts +10 -11
- package/dist/NextAdminLayoutClient.d.ts.map +1 -1
- package/dist/NextAdminLayoutClient.js +6 -7
- 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 +2 -137
- package/dist/NextAdminPage.js.map +1 -1
- package/dist/cache/createVexRevalidateRoute.d.ts +88 -0
- package/dist/cache/createVexRevalidateRoute.d.ts.map +1 -0
- package/dist/cache/createVexServerClient.d.ts +31 -0
- package/dist/cache/createVexServerClient.d.ts.map +1 -0
- package/dist/cache/index.d.ts +5 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +9 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/types.d.ts +36 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/chunk-3J7LCKFC.js +129 -0
- package/dist/chunk-3J7LCKFC.js.map +1 -0
- package/dist/chunk-K4AAS5B5.js +22 -0
- package/dist/chunk-K4AAS5B5.js.map +1 -0
- package/dist/chunk-KIRSFMPI.js +78 -0
- package/dist/chunk-KIRSFMPI.js.map +1 -0
- package/dist/chunk-L6N3D6L5.js +155 -0
- package/dist/chunk-L6N3D6L5.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/seo/createVexRobots.d.ts +22 -0
- package/dist/seo/createVexRobots.d.ts.map +1 -0
- package/dist/seo/createVexSitemap.d.ts +41 -0
- package/dist/seo/createVexSitemap.d.ts.map +1 -0
- package/dist/seo/index.d.ts +5 -0
- package/dist/seo/index.d.ts.map +1 -0
- package/dist/seo/index.js +13 -0
- package/dist/seo/index.js.map +1 -0
- package/dist/seo/vexMetadata.d.ts +30 -0
- package/dist/seo/vexMetadata.d.ts.map +1 -0
- package/dist/seo/vexStaticParams.d.ts +25 -0
- package/dist/seo/vexStaticParams.d.ts.map +1 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +3 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +25 -5
package/README.md
CHANGED
|
@@ -1,87 +1,98 @@
|
|
|
1
|
-
# @vexcms/
|
|
1
|
+
# @vexcms/next
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The Next.js adapter for [VEX CMS](https://github.com/ianyimi/vex). Provides the server and client components that render the admin panel — collection CRUD, media management, and role-based access control — plus config functions re-exported from `@vexcms/react`.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
pnpm add @vexcms/
|
|
8
|
+
pnpm add @vexcms/next@alpha
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Quick Setup
|
|
12
12
|
|
|
13
13
|
```tsx
|
|
14
|
-
// app/admin/
|
|
15
|
-
|
|
14
|
+
// app/admin/clientProviders.tsx
|
|
15
|
+
"use client"
|
|
16
|
+
import { VexConfigProvider } from "@vexcms/react"
|
|
16
17
|
import config from "@/vex.config"
|
|
17
18
|
|
|
18
|
-
export
|
|
19
|
+
export function ClientProviders({ children }: { children: React.ReactNode }) {
|
|
20
|
+
return <VexConfigProvider config={config}>{children}</VexConfigProvider>
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
// app/admin/layout.tsx
|
|
26
|
+
import { NextAdminLayout } from "@vexcms/next/client"
|
|
27
|
+
import { getCurrentUser } from "@/auth/serverUtils"
|
|
28
|
+
|
|
29
|
+
import { ClientProviders } from "./clientProviders"
|
|
30
|
+
|
|
31
|
+
export default async function AdminRootLayout({ children }: { children: React.ReactNode }) {
|
|
32
|
+
const user = await getCurrentUser()
|
|
19
33
|
return (
|
|
20
|
-
<
|
|
21
|
-
<
|
|
22
|
-
</
|
|
34
|
+
<ClientProviders>
|
|
35
|
+
<NextAdminLayout user={user ?? undefined}>{children}</NextAdminLayout>
|
|
36
|
+
</ClientProviders>
|
|
23
37
|
)
|
|
24
38
|
}
|
|
25
39
|
```
|
|
26
40
|
|
|
41
|
+
```tsx
|
|
42
|
+
// app/admin/[[...path]]/page.tsx
|
|
43
|
+
import { NextAdminPage } from "@vexcms/next/server"
|
|
44
|
+
import config from "@/vex.config.server"
|
|
45
|
+
|
|
46
|
+
export default function AdminPage({
|
|
47
|
+
params,
|
|
48
|
+
}: {
|
|
49
|
+
params: Promise<{ path?: string[] }>
|
|
50
|
+
}) {
|
|
51
|
+
return <NextAdminPage config={config} params={params} />
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`NextAdminPage` and `NextAdminLayout` are exported from separate subpaths (`/server` and `/client`) rather than the package root, because an async server component and a `"use client"` component cannot share one bundle.
|
|
56
|
+
|
|
27
57
|
## Features
|
|
28
58
|
|
|
29
59
|
### Collection Management
|
|
30
60
|
|
|
31
|
-
- **List view** —
|
|
32
|
-
- **Edit view** — Auto-generated forms from collection schema
|
|
33
|
-
- **Create
|
|
34
|
-
- **Bidirectional pagination** — Instant access to first and last pages
|
|
61
|
+
- **List view** — Data table with Load More pagination, row selection, and bulk delete
|
|
62
|
+
- **Edit view** — Auto-generated forms from the collection schema, submitted via TanStack React Form
|
|
63
|
+
- **Create dialog** — Modal for document creation, gated by a `create` permission check
|
|
35
64
|
|
|
36
65
|
### Media Management
|
|
37
66
|
|
|
38
67
|
- **Grid/list views** — Browse media files with thumbnails
|
|
39
|
-
- **Upload** — Drag-and-drop upload
|
|
40
|
-
- **Media picker** — Inline picker for upload
|
|
41
|
-
- **Image dimensions** — Auto-detected on upload
|
|
42
|
-
|
|
43
|
-
### Versioning & Drafts
|
|
68
|
+
- **Upload** — Drag-and-drop or click-to-upload through the collection's configured storage adapter
|
|
69
|
+
- **Media picker** — Inline picker for upload fields, with a client-side search filter and Load More pagination
|
|
44
70
|
|
|
45
|
-
|
|
46
|
-
- **Version history** — Dropdown to view, restore, or delete previous versions
|
|
47
|
-
- **Autosave** — Configurable auto-save interval for draft documents
|
|
71
|
+
### Roadmap note
|
|
48
72
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
- **Side-by-side panel** — Preview content changes in an iframe alongside the editor
|
|
52
|
-
- **Responsive breakpoints** — Test at different screen sizes
|
|
53
|
-
- **Snapshot system** — Transient preview data without saving to database
|
|
73
|
+
Versioning, drafts, and live preview are not implemented yet — both are in progress on the [roadmap](https://docs.vexcms.dev). A global's `versions.drafts` option parses and is stored on the resolved config, but it is not enforced: every read still returns the live document. See [docs.vexcms.dev](https://docs.vexcms.dev) for current status.
|
|
54
74
|
|
|
55
75
|
### Access Control
|
|
56
76
|
|
|
57
|
-
- **
|
|
58
|
-
- **
|
|
59
|
-
- **
|
|
60
|
-
- **UI enforcement** — Buttons and actions
|
|
61
|
-
|
|
62
|
-
### User Impersonation
|
|
63
|
-
|
|
64
|
-
- **Impersonation banner** — Shows when an admin is viewing as another user
|
|
65
|
-
- **User switching** — Select from impersonatable users list
|
|
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
|
|
78
|
+
- **Collection-level permissions** — `usePermission` gates create, read, update, and delete per collection and per global
|
|
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
|
|
80
|
+
- **UI enforcement** — Buttons and actions are disabled or hidden when the current user lacks permission
|
|
66
81
|
|
|
67
82
|
### Admin Layout
|
|
68
83
|
|
|
69
|
-
- **Sidebar navigation** —
|
|
70
|
-
- **Theme support** — Light
|
|
71
|
-
- **User menu** — Profile, sign out, impersonation controls
|
|
84
|
+
- **Sidebar navigation** — Links grouped into Collections, Globals, and Media sections
|
|
85
|
+
- **Theme support** — Light, dark, and system modes via `ThemeToggle`
|
|
72
86
|
|
|
73
87
|
## Exports
|
|
74
88
|
|
|
75
|
-
| Export | Description |
|
|
76
|
-
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
| `usePermissionContext` | Hook to access the full permission context |
|
|
83
|
-
| `ImpersonationBanner` | Banner component for admin impersonation |
|
|
84
|
-
| `useMediaPickerState` | Hook for media picker state management |
|
|
89
|
+
| Export | From | Description |
|
|
90
|
+
|--------|------|-------------|
|
|
91
|
+
| `NextAdminPage` | `@vexcms/next/server` | Async server component that routes the admin panel by URL path (dashboard, list, edit) |
|
|
92
|
+
| `NextAdminLayout` | `@vexcms/next/client` | Client component rendering the admin shell (sidebar, theme, breadcrumbs) around page content |
|
|
93
|
+
| `defineConfig`, `defineCollection`, `relationship`, `text`, `number`, `select`, `date`, `url`, `checkbox` | `@vexcms/next` | Config functions re-exported from `@vexcms/react` |
|
|
94
|
+
|
|
95
|
+
The compiled stylesheet is available at `@vexcms/next/styles`.
|
|
85
96
|
|
|
86
97
|
## Peer Dependencies
|
|
87
98
|
|
|
@@ -1,43 +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.
|
|
24
|
-
*
|
|
25
|
-
* @param props - Layout props
|
|
26
|
-
* @param props.config - The resolved VexCMS config from `vex.config.ts` (raw)
|
|
27
|
-
* @param props.children - The page content from `[[...slug]]/page.tsx`
|
|
28
|
-
* @param props.user - Current user for the admin shell
|
|
16
|
+
* checks).
|
|
29
17
|
*
|
|
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.
|
|
30
23
|
* @example
|
|
31
24
|
* ```tsx
|
|
32
25
|
* // app/admin/layout.tsx
|
|
33
26
|
* import { NextAdminLayout } from "@vexcms/next/client";
|
|
34
|
-
* import config from "~/vex.config";
|
|
35
27
|
* import { getCurrentUser } from "~/auth/serverUtils";
|
|
36
28
|
*
|
|
37
29
|
* export default async function AdminLayout({ children }: { children: ReactNode }) {
|
|
38
30
|
* const user = await getCurrentUser();
|
|
39
31
|
* return (
|
|
40
|
-
* <NextAdminLayout
|
|
32
|
+
* <NextAdminLayout user={user ?? undefined}>
|
|
41
33
|
* {children}
|
|
42
34
|
* </NextAdminLayout>
|
|
43
35
|
* );
|
|
@@ -45,7 +37,6 @@ import type { AdminUser } from "@vexcms/react";
|
|
|
45
37
|
* ```
|
|
46
38
|
*/
|
|
47
39
|
export declare function NextAdminLayout(props: {
|
|
48
|
-
config: VexConfig;
|
|
49
40
|
children: ReactNode;
|
|
50
41
|
user?: AdminUser;
|
|
51
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,23 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { cookies } from "next/headers";
|
|
5
|
-
import { jsx } from "react/jsx-runtime";
|
|
6
|
-
async function NextAdminLayout(props) {
|
|
7
|
-
const clientConfig = sanitizeConfigForClient(props.config);
|
|
8
|
-
const cookieStore = await cookies();
|
|
9
|
-
const sidebarOpen = String(cookieStore.get("sidebar_state")?.value) === "true";
|
|
10
|
-
return /* @__PURE__ */ jsx(
|
|
11
|
-
NextAdminLayoutClient,
|
|
12
|
-
{
|
|
13
|
-
config: clientConfig,
|
|
14
|
-
user: props.user,
|
|
15
|
-
organization: props.organization,
|
|
16
|
-
sidebarOpen,
|
|
17
|
-
children: props.children
|
|
18
|
-
}
|
|
19
|
-
);
|
|
20
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
NextAdminLayout
|
|
3
|
+
} from "./chunk-K4AAS5B5.js";
|
|
21
4
|
export {
|
|
22
5
|
NextAdminLayout
|
|
23
6
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -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,24 @@ 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
|
-
* @
|
|
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.
|
|
24
|
+
* @returns The admin shell rendered from `children`, wrapped in `NuqsAdapter`
|
|
25
|
+
* and the shared `AdminLayout`, wired with the active slug/document id derived
|
|
26
|
+
* from `usePathname()` and Next.js `Link`/`Image` components.
|
|
27
27
|
*/
|
|
28
28
|
export declare function NextAdminLayoutClient(props: {
|
|
29
|
-
config: ClientVexConfig;
|
|
30
29
|
children: ReactNode;
|
|
31
|
-
user?: AdminUser;
|
|
32
30
|
organization?: Record<string, unknown>;
|
|
33
31
|
sidebarOpen?: boolean;
|
|
32
|
+
user?: AdminUser;
|
|
34
33
|
}): import("react").JSX.Element;
|
|
35
34
|
//# sourceMappingURL=NextAdminLayoutClient.d.ts.map
|
|
@@ -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;AAEvC,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAM5D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,QAAQ,EAAE,SAAS,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB,+BAuBA"}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
4
|
// src/NextAdminLayoutClient.tsx
|
|
5
|
-
import {
|
|
6
|
-
import NextLink from "next/link";
|
|
5
|
+
import { AdminLayout } from "@vexcms/react";
|
|
7
6
|
import NextImage from "next/image";
|
|
7
|
+
import NextLink from "next/link";
|
|
8
|
+
import { usePathname } from "next/navigation";
|
|
8
9
|
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
|
9
|
-
import { AdminLayout } from "@vexcms/react";
|
|
10
10
|
import { jsx } from "react/jsx-runtime";
|
|
11
11
|
function NextAdminLayoutClient(props) {
|
|
12
12
|
const pathname = usePathname();
|
|
@@ -16,14 +16,13 @@ function NextAdminLayoutClient(props) {
|
|
|
16
16
|
return /* @__PURE__ */ jsx(NuqsAdapter, { children: /* @__PURE__ */ jsx(
|
|
17
17
|
AdminLayout,
|
|
18
18
|
{
|
|
19
|
-
|
|
19
|
+
activeDocID,
|
|
20
20
|
activeSlug,
|
|
21
21
|
components: { Link: NextLink, Image: NextImage },
|
|
22
|
-
pathname,
|
|
23
|
-
activeDocID,
|
|
24
|
-
user: props.user,
|
|
25
22
|
organization: props.organization,
|
|
23
|
+
pathname,
|
|
26
24
|
sidebarOpen: props.sidebarOpen,
|
|
25
|
+
user: props.user,
|
|
27
26
|
children: props.children
|
|
28
27
|
}
|
|
29
28
|
) });
|
|
@@ -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 type
|
|
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\";\n\nimport { AdminLayout, type AdminUser } from \"@vexcms/react\";\nimport NextImage from \"next/image\";\nimport NextLink from \"next/link\";\nimport { usePathname } from \"next/navigation\";\nimport { NuqsAdapter } from \"nuqs/adapters/next/app\";\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 organization?: Record<string, unknown>;\n sidebarOpen?: boolean;\n user?: AdminUser;\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 activeDocID={activeDocID}\n activeSlug={activeSlug}\n components={{ Link: NextLink, Image: NextImage }}\n organization={props.organization}\n pathname={pathname}\n sidebarOpen={props.sidebarOpen}\n user={props.user}\n >\n {props.children}\n </AdminLayout>\n </NuqsAdapter>\n );\n}\n"],"mappings":";;;;AAKA,SAAS,mBAAmC;AAC5C,OAAO,eAAe;AACtB,OAAO,cAAc;AACrB,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AA0CtB;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;AAAA,MACA,YAAY,EAAE,MAAM,UAAU,OAAO,UAAU;AAAA,MAC/C,cAAc,MAAM;AAAA,MACpB;AAAA,MACA,aAAa,MAAM;AAAA,MACnB,MAAM,MAAM;AAAA,MAEX,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":"
|
|
1
|
+
{"version":3,"file":"NextAdminPage.d.ts","sourceRoot":"","sources":["../src/NextAdminPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoC,SAAS,EAAoB,MAAM,cAAc,CAAC;AAqBlG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,wCAwIA"}
|
package/dist/NextAdminPage.js
CHANGED
|
@@ -1,141 +1,6 @@
|
|
|
1
|
-
// src/NextAdminPage.tsx
|
|
2
|
-
import { fetchQuery } from "convex/nextjs";
|
|
3
|
-
import { vexConvexApi } from "@vexcms/core";
|
|
4
1
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
CollectionEditView,
|
|
8
|
-
MediaCollectionEditView,
|
|
9
|
-
MediaCollectionListView,
|
|
10
|
-
GlobalsListView,
|
|
11
|
-
GlobalEditView
|
|
12
|
-
} from "@vexcms/react";
|
|
13
|
-
import { sanitizeConfigForClient } from "@vexcms/core";
|
|
14
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
-
async function NextAdminPage(props) {
|
|
16
|
-
const { path = [] } = await props.params;
|
|
17
|
-
const [collectionSlug, documentId] = path;
|
|
18
|
-
const clientConfig = sanitizeConfigForClient(props.config);
|
|
19
|
-
if (!collectionSlug) {
|
|
20
|
-
return /* @__PURE__ */ jsx(DashboardView, { config: clientConfig });
|
|
21
|
-
}
|
|
22
|
-
if (collectionSlug === "globals") {
|
|
23
|
-
if (!documentId) {
|
|
24
|
-
return /* @__PURE__ */ jsx(GlobalsListView, { config: clientConfig });
|
|
25
|
-
}
|
|
26
|
-
const globalConfig = clientConfig.globals.find((g) => g.slug === documentId);
|
|
27
|
-
if (!globalConfig) {
|
|
28
|
-
return /* @__PURE__ */ jsxs("div", { children: [
|
|
29
|
-
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground p-6", children: [
|
|
30
|
-
'Global "',
|
|
31
|
-
documentId,
|
|
32
|
-
'" not found.'
|
|
33
|
-
] }),
|
|
34
|
-
/* @__PURE__ */ jsx("p", { children: "TODO: add not found view" })
|
|
35
|
-
] });
|
|
36
|
-
}
|
|
37
|
-
const global = await fetchQuery(
|
|
38
|
-
vexConvexApi.globals.get,
|
|
39
|
-
{ slug: globalConfig.slug },
|
|
40
|
-
props.token ? { token: props.token } : void 0
|
|
41
|
-
);
|
|
42
|
-
return /* @__PURE__ */ jsx(GlobalEditView, { global: globalConfig, initialData: global });
|
|
43
|
-
}
|
|
44
|
-
const collection = clientConfig.collections.find((c) => c.slug === collectionSlug);
|
|
45
|
-
const mediaCollection = clientConfig.mediaCollections.find((mc) => mc.slug === collectionSlug);
|
|
46
|
-
if (!collection && !mediaCollection) {
|
|
47
|
-
return /* @__PURE__ */ jsxs("div", { children: [
|
|
48
|
-
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground p-6", children: [
|
|
49
|
-
'Collection "',
|
|
50
|
-
collectionSlug,
|
|
51
|
-
'" not found.'
|
|
52
|
-
] }),
|
|
53
|
-
/* @__PURE__ */ jsx("p", { children: "TODO: add not found view" })
|
|
54
|
-
] });
|
|
55
|
-
}
|
|
56
|
-
if (mediaCollection && documentId) {
|
|
57
|
-
const initialData2 = await fetchQuery(
|
|
58
|
-
vexConvexApi.get,
|
|
59
|
-
{
|
|
60
|
-
id: documentId,
|
|
61
|
-
collection: mediaCollection.slug
|
|
62
|
-
},
|
|
63
|
-
props.token ? { token: props.token } : void 0
|
|
64
|
-
);
|
|
65
|
-
return /* @__PURE__ */ jsx(
|
|
66
|
-
MediaCollectionEditView,
|
|
67
|
-
{
|
|
68
|
-
collection: mediaCollection,
|
|
69
|
-
documentId,
|
|
70
|
-
initialData: initialData2
|
|
71
|
-
}
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
if (mediaCollection) {
|
|
75
|
-
const numItems2 = Math.max(
|
|
76
|
-
mediaCollection.admin.table.serverPageSize,
|
|
77
|
-
mediaCollection.admin.table.defaultPageSize
|
|
78
|
-
);
|
|
79
|
-
const initialData2 = await fetchQuery(
|
|
80
|
-
vexConvexApi.find,
|
|
81
|
-
{
|
|
82
|
-
collection: collectionSlug,
|
|
83
|
-
paginationOpts: {
|
|
84
|
-
numItems: numItems2,
|
|
85
|
-
totalDocs: true,
|
|
86
|
-
cursor: null
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
props.token ? { token: props.token } : void 0
|
|
90
|
-
);
|
|
91
|
-
return /* @__PURE__ */ jsx(
|
|
92
|
-
MediaCollectionListView,
|
|
93
|
-
{
|
|
94
|
-
collection: mediaCollection,
|
|
95
|
-
initialData: initialData2
|
|
96
|
-
}
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
if (collection && documentId) {
|
|
100
|
-
const initialData2 = await fetchQuery(
|
|
101
|
-
vexConvexApi.get,
|
|
102
|
-
{
|
|
103
|
-
id: documentId,
|
|
104
|
-
collection: collection.slug
|
|
105
|
-
},
|
|
106
|
-
props.token ? { token: props.token } : void 0
|
|
107
|
-
);
|
|
108
|
-
return /* @__PURE__ */ jsx(
|
|
109
|
-
CollectionEditView,
|
|
110
|
-
{
|
|
111
|
-
collection,
|
|
112
|
-
documentId,
|
|
113
|
-
initialData: initialData2
|
|
114
|
-
}
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
if (!collection) {
|
|
118
|
-
throw new Error("invalid collection slug");
|
|
119
|
-
}
|
|
120
|
-
const numItems = Math.max(
|
|
121
|
-
collection.admin.table.serverPageSize,
|
|
122
|
-
collection.admin.table.defaultPageSize
|
|
123
|
-
);
|
|
124
|
-
const initialData = await fetchQuery(
|
|
125
|
-
vexConvexApi.findPaginated,
|
|
126
|
-
{
|
|
127
|
-
collection: collectionSlug,
|
|
128
|
-
depth: 1,
|
|
129
|
-
paginationOpts: {
|
|
130
|
-
cursor: null,
|
|
131
|
-
numItems,
|
|
132
|
-
totalDocs: true
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
props.token ? { token: props.token } : void 0
|
|
136
|
-
);
|
|
137
|
-
return /* @__PURE__ */ jsx(CollectionListView, { collection, initialData });
|
|
138
|
-
}
|
|
2
|
+
NextAdminPage
|
|
3
|
+
} from "./chunk-L6N3D6L5.js";
|
|
139
4
|
export {
|
|
140
5
|
NextAdminPage
|
|
141
6
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|