create-vexcms 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 +11 -0
- package/dist/index.js +8 -1
- package/package.json +1 -1
- package/templates/base-nextjs/README.md +3 -2
- package/templates/base-nextjs/convex/auth/options.ts +18 -26
- package/templates/base-nextjs/convex/auth/schema.ts +51 -0
- package/templates/base-nextjs/convex/vex/globals.ts +1 -1
- package/templates/base-nextjs/convex/vex/media.ts +1 -1
- package/templates/base-nextjs/convex/vex.ts +1 -1
- package/templates/base-nextjs/package.json +6 -6
- package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +27 -15
- package/templates/base-nextjs/src/app/(vexcms)/admin/[[...path]]/page.tsx +1 -1
- package/templates/base-nextjs/src/app/(vexcms)/admin/clientProviders.tsx +9 -14
- package/templates/base-nextjs/src/app/(vexcms)/admin/layout.tsx +1 -2
- package/templates/base-nextjs/src/app/api/vex/revalidate/route.ts +1 -1
- package/templates/base-nextjs/src/auth/access.ts +14 -2
- package/templates/base-nextjs/src/vex.config.server.ts +30 -0
- package/templates/base-nextjs/src/vex.config.ts +14 -11
- package/templates/base-nextjs/src/vexcms/collections/images.ts +1 -1
- package/templates/marketing-site/convex/seed.ts +14 -11
- package/templates/marketing-site/convex/siteSettings.ts +1 -1
- package/templates/marketing-site/convex/theme.ts +1 -1
- package/templates/marketing-site/src/app/(vexcms)/admin/layout.tsx +1 -4
- package/templates/marketing-site/src/lib/metadata.ts +9 -2
- package/templates/marketing-site/src/vex.config.server.ts +27 -0
- package/templates/marketing-site/src/vex.config.ts +12 -9
- package/templates/marketing-site/src/vexcms/api.ts +1 -1
- package/templates/marketing-site/src/vexcms/blocks/Roadmap/index.tsx +2 -1
package/README.md
CHANGED
|
@@ -100,6 +100,17 @@ site:
|
|
|
100
100
|
- `convex/seed.ts` — an idempotent `init` mutation seeding site settings, a header, a footer, the
|
|
101
101
|
starter palettes, and a complete home page from the blocks' own defaults (`pnpm seed`)
|
|
102
102
|
|
|
103
|
+
### Configuration
|
|
104
|
+
|
|
105
|
+
Every scaffold ships two config files: `src/vex.config.ts` — client-safe, holds collections,
|
|
106
|
+
globals, media collections, access rules, and the auth collections
|
|
107
|
+
`betterAuthCollections(authSchema)` derives from `src/auth/schema.ts` — and
|
|
108
|
+
`src/vex.config.server.ts`, which layers the Better Auth adapter and the storage adapter(s)
|
|
109
|
+
onto it via `defineServerConfig`. Nothing is generated for auth: `src/auth/schema.ts` is the
|
|
110
|
+
one source of truth for model names and fields (organizations on or off, per the `--orgs`
|
|
111
|
+
flag) — the client config calls `betterAuthCollections()` on it directly, and
|
|
112
|
+
`src/auth/options.ts` spreads it into the real Better Auth options.
|
|
113
|
+
|
|
103
114
|
## Getting started
|
|
104
115
|
|
|
105
116
|
After scaffolding:
|
package/dist/index.js
CHANGED
|
@@ -2339,7 +2339,8 @@ var VexFrameworkInstaller = class {
|
|
|
2339
2339
|
}
|
|
2340
2340
|
/**
|
|
2341
2341
|
* Configure the Better Auth organizations plugin.
|
|
2342
|
-
* When enabled, replaces placeholders with organization import
|
|
2342
|
+
* When enabled, replaces placeholders with the organization import, plugin
|
|
2343
|
+
* instance, and schema descriptor,
|
|
2343
2344
|
* and leaves every org-aware file (`createGetAuth` call sites, `access.ts`,
|
|
2344
2345
|
* `AuthContext.tsx`, `hasPermission.ts`, `convex/auth/api.ts`,
|
|
2345
2346
|
* `db/constants/index.ts`) untouched.
|
|
@@ -2369,6 +2370,12 @@ var VexFrameworkInstaller = class {
|
|
|
2369
2370
|
}
|
|
2370
2371
|
await fs5.writeFile(pluginsPath, content);
|
|
2371
2372
|
}
|
|
2373
|
+
const schemaPath = path2.join(this.targetPath, "convex/auth/schema.ts");
|
|
2374
|
+
if (await fs5.pathExists(schemaPath)) {
|
|
2375
|
+
let content = await fs5.readFile(schemaPath, "utf-8");
|
|
2376
|
+
content = enabled ? content.replace(" // {{ORGANIZATIONS_SCHEMA}}", " organization: true,") : content.replace(/.*\/\/ \{\{ORGANIZATIONS_SCHEMA\}\}\n?/, "");
|
|
2377
|
+
await fs5.writeFile(schemaPath, content);
|
|
2378
|
+
}
|
|
2372
2379
|
if (!enabled) {
|
|
2373
2380
|
await this.stripOrganizationWiring();
|
|
2374
2381
|
}
|
package/package.json
CHANGED
|
@@ -77,10 +77,11 @@ see `convex/vex/firstUser.ts`. From there, "Go to Admin Panel" opens `/admin`.
|
|
|
77
77
|
│ ├── auth/ # Auth client + server helpers, access control
|
|
78
78
|
│ ├── components/ # React components
|
|
79
79
|
│ ├── vexcms/collections/ # Collection definitions
|
|
80
|
-
│ ├── vex.config.ts # VexCMS config
|
|
80
|
+
│ ├── vex.config.ts # VexCMS client config (collections, globals, access)
|
|
81
|
+
│ ├── vex.config.server.ts # VexCMS server config (auth adapter, storage adapters)
|
|
81
82
|
│ └── env.mjs # Typed environment variables
|
|
82
83
|
├── convex/ # Convex backend
|
|
83
|
-
│ ├── auth/ # Better Auth config + plugins
|
|
84
|
+
│ ├── auth/ # Better Auth config + plugins (schema.ts, options.ts)
|
|
84
85
|
│ ├── vex/ # Bootstrap, media, globals endpoints
|
|
85
86
|
│ └── schema.ts # Database schema
|
|
86
87
|
└── .env.local # Environment variables (not committed)
|
|
@@ -2,20 +2,28 @@ import type { BetterAuthOptions } from "better-auth"
|
|
|
2
2
|
|
|
3
3
|
import { anonRoleDatabaseHook } from "@vexcms/better-auth"
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
TABLE_SLUG_ACCOUNTS,
|
|
7
|
-
TABLE_SLUG_SESSIONS,
|
|
8
|
-
TABLE_SLUG_USERS,
|
|
9
|
-
TABLE_SLUG_VERIFICATIONS,
|
|
10
|
-
USER_ROLES,
|
|
11
|
-
} from "~/db/constants"
|
|
5
|
+
import { USER_ROLES } from "~/db/constants"
|
|
12
6
|
|
|
13
7
|
import { createPlugins } from "./plugins"
|
|
8
|
+
import { authSchema } from "./schema"
|
|
14
9
|
|
|
10
|
+
// `plugins` on the shared schema is a descriptor map for
|
|
11
|
+
// `betterAuthCollections`, not Better Auth plugin instances — dropped by name
|
|
12
|
+
// so the intent survives a reordering of the object literal below.
|
|
13
|
+
const { plugins: _pluginDescriptors, ...schemaOptions } = authSchema
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Better Auth options for this project.
|
|
17
|
+
*
|
|
18
|
+
* Model names and `user.additionalFields` come from `./schema`, the one module
|
|
19
|
+
* both this file and `src/vex.config.ts` read, so a field added there reaches
|
|
20
|
+
* the database and the admin panel without being written twice. Everything
|
|
21
|
+
* below the spread is server-only.
|
|
22
|
+
*
|
|
23
|
+
* @see ./schema for the shared, client-safe half
|
|
24
|
+
*/
|
|
15
25
|
export const authOptions: BetterAuthOptions = {
|
|
16
|
-
|
|
17
|
-
modelName: TABLE_SLUG_ACCOUNTS,
|
|
18
|
-
},
|
|
26
|
+
...schemaOptions,
|
|
19
27
|
baseURL: process.env.SITE_URL,
|
|
20
28
|
databaseHooks: {
|
|
21
29
|
// Ties Better Auth's anonymous-plugin users to `access.anonRole` (see
|
|
@@ -28,21 +36,5 @@ export const authOptions: BetterAuthOptions = {
|
|
|
28
36
|
// {{OAUTH_PROVIDERS}}
|
|
29
37
|
plugins: createPlugins(),
|
|
30
38
|
secret: process.env.BETTER_AUTH_SECRET,
|
|
31
|
-
session: {
|
|
32
|
-
modelName: TABLE_SLUG_SESSIONS,
|
|
33
|
-
},
|
|
34
39
|
trustedOrigins: [process.env.SITE_URL!],
|
|
35
|
-
user: {
|
|
36
|
-
additionalFields: {
|
|
37
|
-
roles: {
|
|
38
|
-
type: "string[]",
|
|
39
|
-
defaultValue: [USER_ROLES.user],
|
|
40
|
-
required: true,
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
modelName: TABLE_SLUG_USERS,
|
|
44
|
-
},
|
|
45
|
-
verification: {
|
|
46
|
-
modelName: TABLE_SLUG_VERIFICATIONS,
|
|
47
|
-
},
|
|
48
40
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { BetterAuthSchemaInput } from "@vexcms/better-auth/client"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
TABLE_SLUG_ACCOUNTS,
|
|
5
|
+
TABLE_SLUG_SESSIONS,
|
|
6
|
+
TABLE_SLUG_USERS,
|
|
7
|
+
TABLE_SLUG_VERIFICATIONS,
|
|
8
|
+
USER_ROLES,
|
|
9
|
+
} from "~/db/constants"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Everything about your Better Auth setup that determines which collections
|
|
13
|
+
* exist and which fields they carry — and nothing else.
|
|
14
|
+
*
|
|
15
|
+
* This is the one auth module the browser reads: `src/vex.config.ts` passes it
|
|
16
|
+
* to `betterAuthCollections()` to build the admin panel's auth collections,
|
|
17
|
+
* and `./options` spreads it so the same model names and fields reach the
|
|
18
|
+
* database. Add a `user.additionalFields` entry here and it shows up in both.
|
|
19
|
+
*
|
|
20
|
+
* Keep it inert. `./options` reads `process.env` and `./plugins` pulls
|
|
21
|
+
* `better-auth/next-js` (→ `next/headers`), which is why they are separate
|
|
22
|
+
* files — importing either from the client config would drag the server into
|
|
23
|
+
* your browser bundle. Runtime plugin options (admin roles, mailers, OAuth
|
|
24
|
+
* secrets) belong on the real plugin instances in `./plugins`; only options
|
|
25
|
+
* that change which TABLES exist belong here.
|
|
26
|
+
*
|
|
27
|
+
* @see betterAuthCollections in @vexcms/better-auth/client
|
|
28
|
+
* @see ./options for the server-side options that spread this
|
|
29
|
+
*/
|
|
30
|
+
export const authSchema = {
|
|
31
|
+
account: { modelName: TABLE_SLUG_ACCOUNTS },
|
|
32
|
+
session: { modelName: TABLE_SLUG_SESSIONS },
|
|
33
|
+
user: {
|
|
34
|
+
modelName: TABLE_SLUG_USERS,
|
|
35
|
+
additionalFields: {
|
|
36
|
+
roles: {
|
|
37
|
+
type: "string[]",
|
|
38
|
+
defaultValue: [USER_ROLES.user],
|
|
39
|
+
required: true,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
verification: { modelName: TABLE_SLUG_VERIFICATIONS },
|
|
44
|
+
plugins: {
|
|
45
|
+
admin: true,
|
|
46
|
+
anonymous: true,
|
|
47
|
+
apiKey: true,
|
|
48
|
+
convex: true,
|
|
49
|
+
// {{ORGANIZATIONS_SCHEMA}}
|
|
50
|
+
},
|
|
51
|
+
} satisfies BetterAuthSchemaInput
|
|
@@ -2,7 +2,7 @@ import { createGetAuth } from "@vexcms/better-auth";
|
|
|
2
2
|
import { globalsApi } from "@vexcms/core/server";
|
|
3
3
|
|
|
4
4
|
import { TABLE_SLUG_ORGANIZATIONS, TABLE_SLUG_SESSIONS, TABLE_SLUG_USERS } from "~/db/constants";
|
|
5
|
-
import config from "~/vex.config";
|
|
5
|
+
import config from "~/vex.config.server";
|
|
6
6
|
|
|
7
7
|
import { mutation, query } from "../_generated/server";
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@ import { createGetAuth } from "@vexcms/better-auth";
|
|
|
2
2
|
import { mediaApi } from "@vexcms/core";
|
|
3
3
|
|
|
4
4
|
import { TABLE_SLUG_ORGANIZATIONS, TABLE_SLUG_SESSIONS, TABLE_SLUG_USERS } from "~/db/constants";
|
|
5
|
-
import config from "~/vex.config";
|
|
5
|
+
import config from "~/vex.config.server";
|
|
6
6
|
|
|
7
7
|
import { mutation, query } from "../_generated/server";
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@ import { createGetAuth } from "@vexcms/better-auth";
|
|
|
2
2
|
import { collectionsApi } from "@vexcms/core/server";
|
|
3
3
|
|
|
4
4
|
import { TABLE_SLUG_ORGANIZATIONS, TABLE_SLUG_SESSIONS, TABLE_SLUG_USERS } from "~/db/constants";
|
|
5
|
-
import config from "~/vex.config";
|
|
5
|
+
import config from "~/vex.config.server";
|
|
6
6
|
|
|
7
7
|
import { mutation, query } from "./_generated/server";
|
|
8
8
|
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"@t3-oss/env-nextjs": "0.13.11",
|
|
25
25
|
"@tanstack/react-form": "1.33.1",
|
|
26
26
|
"@tanstack/react-query": "5.101.2",
|
|
27
|
-
"@vexcms/better-auth": "~0.1.0-alpha.
|
|
28
|
-
"@vexcms/core": "~0.1.0-alpha.
|
|
29
|
-
"@vexcms/file-storage-convex": "~0.1.0-alpha.
|
|
30
|
-
"@vexcms/next": "~0.1.0-alpha.
|
|
31
|
-
"@vexcms/react": "~0.1.0-alpha.
|
|
27
|
+
"@vexcms/better-auth": "~0.1.0-alpha.19",
|
|
28
|
+
"@vexcms/core": "~0.1.0-alpha.19",
|
|
29
|
+
"@vexcms/file-storage-convex": "~0.1.0-alpha.19",
|
|
30
|
+
"@vexcms/next": "~0.1.0-alpha.19",
|
|
31
|
+
"@vexcms/react": "~0.1.0-alpha.19",
|
|
32
32
|
"better-auth": "1.6.23",
|
|
33
33
|
"class-variance-authority": "0.7.1",
|
|
34
34
|
"clsx": "2.1.1",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@types/node": "20.19.43",
|
|
53
53
|
"@types/react": "19.2.17",
|
|
54
54
|
"@types/react-dom": "19.2.3",
|
|
55
|
-
"@vexcms/cli": "~0.1.0-alpha.
|
|
55
|
+
"@vexcms/cli": "~0.1.0-alpha.19",
|
|
56
56
|
"babel-plugin-react-compiler": "1.0.0",
|
|
57
57
|
"eslint": "9.39.5",
|
|
58
58
|
"eslint-config-next": "15.5.9",
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { AuthView } from "@daveyplate/better-auth-ui";
|
|
4
|
-
import { useSearchParams } from "next/navigation";
|
|
4
|
+
import { useRouter, useSearchParams } from "next/navigation";
|
|
5
5
|
import { Activity } from "react";
|
|
6
6
|
|
|
7
7
|
import { Dialog, DialogContent, DialogTitle } from "~/components/ui/dialog";
|
|
8
8
|
|
|
9
9
|
export default function AuthCard({ pathname }: { pathname: string }) {
|
|
10
|
+
const router = useRouter();
|
|
10
11
|
const searchParams = useSearchParams();
|
|
11
12
|
const isGuardRedirect = searchParams.has("redirectTo");
|
|
12
13
|
if (isGuardRedirect) {
|
|
@@ -16,20 +17,31 @@ export default function AuthCard({ pathname }: { pathname: string }) {
|
|
|
16
17
|
</main>
|
|
17
18
|
);
|
|
18
19
|
}
|
|
20
|
+
// No in-flow wrapper here on purpose. The Dialog portals into <body>, so a
|
|
21
|
+
// wrapping <main> would be an empty element sitting after the footer (the
|
|
22
|
+
// @auth slot renders last in the frontend layout). Next's post-navigation
|
|
23
|
+
// scroll handler targets the new segment's first DOM node and would scroll
|
|
24
|
+
// that empty element into view — i.e. jump the page to the bottom behind
|
|
25
|
+
// the modal. With only a portal rendered there is no node to target.
|
|
19
26
|
return (
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
<Dialog
|
|
28
|
+
onOpenChange={(open) => {
|
|
29
|
+
if (!open) {
|
|
30
|
+
router.back();
|
|
31
|
+
}
|
|
32
|
+
}}
|
|
33
|
+
open
|
|
34
|
+
>
|
|
35
|
+
<Activity mode="hidden">
|
|
36
|
+
<DialogTitle />
|
|
37
|
+
</Activity>
|
|
38
|
+
<DialogContent
|
|
39
|
+
aria-describedby={undefined}
|
|
40
|
+
className="grid place-items-center bg-transparent shadow-none ring-transparent"
|
|
41
|
+
showCloseButton={false}
|
|
42
|
+
>
|
|
43
|
+
<AuthView path={pathname} />
|
|
44
|
+
</DialogContent>
|
|
45
|
+
</Dialog>
|
|
34
46
|
);
|
|
35
47
|
}
|
|
@@ -4,7 +4,7 @@ import { NextAdminPage } from "@vexcms/next/server";
|
|
|
4
4
|
import { redirect } from "next/navigation";
|
|
5
5
|
|
|
6
6
|
import { fetchAuthQuery, getToken } from "~/auth/server";
|
|
7
|
-
import config from "~/vex.config";
|
|
7
|
+
import config from "~/vex.config.server";
|
|
8
8
|
|
|
9
9
|
export const dynamic = "force-dynamic";
|
|
10
10
|
|
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client"
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { StorageAdapterContextProvider, VexAccessProvider } from "@vexcms/react";
|
|
3
|
+
import { VexConfigProvider } from "@vexcms/react"
|
|
5
4
|
|
|
6
|
-
import
|
|
5
|
+
import config from "~/vex.config"
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
|
-
*
|
|
8
|
+
* Mounts the single `VexConfigProvider` for the admin panel's client tree.
|
|
10
9
|
*
|
|
11
|
-
* This is a "use client" component so
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* This is a "use client" component so `vex.config` is imported and owned on
|
|
11
|
+
* the client — it never crosses the RSC boundary as a prop. `access` and
|
|
12
|
+
* `storage.clientUploads` (the two fields that used to need their own
|
|
13
|
+
* providers) are already part of the config this imports.
|
|
15
14
|
*/
|
|
16
15
|
export function ClientProviders({ children }: { children: React.ReactNode }) {
|
|
17
|
-
return
|
|
18
|
-
<StorageAdapterContextProvider adapterClients={{ convex: uploadFile }}>
|
|
19
|
-
<VexAccessProvider access={access}>{children}</VexAccessProvider>
|
|
20
|
-
</StorageAdapterContextProvider>
|
|
21
|
-
);
|
|
16
|
+
return <VexConfigProvider config={config}>{children}</VexConfigProvider>
|
|
22
17
|
}
|
|
@@ -4,7 +4,6 @@ import { NextAdminLayout } from "@vexcms/next/client"
|
|
|
4
4
|
|
|
5
5
|
import { AuthServerProvider } from "~/components/providers/auth"
|
|
6
6
|
import { getCurrentUser } from "~/auth/serverUtils"
|
|
7
|
-
import config from "~/vex.config"
|
|
8
7
|
|
|
9
8
|
import { ClientProviders } from "./clientProviders"
|
|
10
9
|
|
|
@@ -13,7 +12,7 @@ export default async function AdminLayout({ children }: { children: ReactNode })
|
|
|
13
12
|
return (
|
|
14
13
|
<AuthServerProvider>
|
|
15
14
|
<ClientProviders>
|
|
16
|
-
<NextAdminLayout
|
|
15
|
+
<NextAdminLayout user={user ?? undefined}>
|
|
17
16
|
{children}
|
|
18
17
|
</NextAdminLayout>
|
|
19
18
|
</ClientProviders>
|
|
@@ -2,7 +2,7 @@ import { api } from "@convex/_generated/api"
|
|
|
2
2
|
import { createVexRevalidateRoute } from "@vexcms/next/cache"
|
|
3
3
|
|
|
4
4
|
import { fetchAuthQuery, getToken } from "~/auth/server"
|
|
5
|
-
import config from "~/vex.config"
|
|
5
|
+
import config from "~/vex.config.server"
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* `POST /api/vex/revalidate` — session-authorized path purge.
|
|
@@ -11,8 +11,16 @@ import { images, users } from "~/vexcms/collections"
|
|
|
11
11
|
* Access control (RBAC) for the admin panel and every registered collection.
|
|
12
12
|
*
|
|
13
13
|
* `admin` gets unrestricted access. `user` can read and update only their own
|
|
14
|
-
* profile row
|
|
15
|
-
* you register a new collection in `vex.config.ts`.
|
|
14
|
+
* profile row, cannot reach the admin panel, and can read `images`. Add a
|
|
15
|
+
* resource here whenever you register a new collection in `vex.config.ts`.
|
|
16
|
+
*
|
|
17
|
+
* `anonRole: user` also covers every unauthenticated public-site request,
|
|
18
|
+
* including the build-time metadata fetch. `images.read` is what lets the
|
|
19
|
+
* public site show uploaded media at all: `MediaImage` and
|
|
20
|
+
* `src/lib/metadata.ts` resolve uploads through the RBAC-gated
|
|
21
|
+
* `vex/media:getUrl`, so without it anonymous visitors get no images and the
|
|
22
|
+
* page ships no `og:image`. Narrow it with a `constraints` rule if some media
|
|
23
|
+
* must stay private; do not remove it.
|
|
16
24
|
*
|
|
17
25
|
* @see https://vexcms.dev/docs/access-control
|
|
18
26
|
*/
|
|
@@ -32,6 +40,10 @@ export const access = defineAccess({
|
|
|
32
40
|
adminPanel: {
|
|
33
41
|
access: false,
|
|
34
42
|
},
|
|
43
|
+
images: {
|
|
44
|
+
"*": false,
|
|
45
|
+
read: true,
|
|
46
|
+
},
|
|
35
47
|
user: {
|
|
36
48
|
"*": false,
|
|
37
49
|
read: {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { betterAuthAdapter } from "@vexcms/better-auth"
|
|
2
|
+
import { defineServerConfig } from "@vexcms/core"
|
|
3
|
+
import { convexFileStorage } from "@vexcms/file-storage-convex"
|
|
4
|
+
|
|
5
|
+
import { authOptions } from "@convex/auth/options"
|
|
6
|
+
|
|
7
|
+
import vexConfig from "./vex.config"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* VexCMS server-only configuration for this project.
|
|
11
|
+
*
|
|
12
|
+
* Layers the Better Auth adapter and Convex file storage onto the
|
|
13
|
+
* client-safe `vexConfig`. You write this file once at scaffold time and
|
|
14
|
+
* rarely revisit it — day-to-day settings live in `vex.config.ts`.
|
|
15
|
+
*
|
|
16
|
+
* @see defineServerConfig in @vexcms/core
|
|
17
|
+
* @see betterAuthAdapter in @vexcms/better-auth
|
|
18
|
+
* @see ./vex.config for the client-safe half this layers onto
|
|
19
|
+
*/
|
|
20
|
+
const config = defineServerConfig({
|
|
21
|
+
config: vexConfig,
|
|
22
|
+
server: {
|
|
23
|
+
auth: { adapter: betterAuthAdapter({ config: authOptions }) },
|
|
24
|
+
storage: {
|
|
25
|
+
adapters: [convexFileStorage()],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export default config
|
|
@@ -1,35 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { betterAuthCollections } from "@vexcms/better-auth/client"
|
|
2
2
|
import { defineConfig } from "@vexcms/core"
|
|
3
|
-
import {
|
|
3
|
+
import { uploadFile } from "@vexcms/file-storage-convex/client"
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { authSchema } from "@convex/auth/schema"
|
|
6
6
|
|
|
7
7
|
import { access } from "~/auth/access"
|
|
8
8
|
import { images, users } from "~/vexcms/collections"
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* VexCMS configuration for this project.
|
|
11
|
+
* VexCMS client-safe configuration for this project.
|
|
12
12
|
*
|
|
13
|
-
* Registers the admin sidebar layout
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* Registers the admin sidebar layout and every collection, global, and
|
|
14
|
+
* media collection. Add collections/globals here as you define them —
|
|
15
|
+
* `vex dev` / `vex generate` derive the Convex schema and TypeScript types
|
|
16
|
+
* from this file, and the browser imports it directly via
|
|
17
|
+
* `VexConfigProvider`, so nothing here may reach a server SDK or an
|
|
18
|
+
* environment variable.
|
|
17
19
|
*
|
|
18
20
|
* @see defineConfig in @vexcms/core
|
|
19
|
-
* @see
|
|
21
|
+
* @see ./vex.config.server for the auth adapter and storage adapter wiring
|
|
20
22
|
*/
|
|
21
23
|
const vexConfig = defineConfig({
|
|
22
24
|
access,
|
|
25
|
+
authCollections: betterAuthCollections(authSchema),
|
|
23
26
|
admin: {
|
|
24
27
|
sidebar: {
|
|
25
28
|
side: "right",
|
|
26
29
|
},
|
|
27
30
|
},
|
|
28
|
-
authAdapter: betterAuthAdapter({ config: authOptions }),
|
|
29
31
|
storage: {
|
|
30
|
-
|
|
32
|
+
clientUploads: { convex: uploadFile },
|
|
31
33
|
},
|
|
32
34
|
collections: [users],
|
|
35
|
+
mediaCollections: [images],
|
|
33
36
|
})
|
|
34
37
|
|
|
35
38
|
export default vexConfig
|
|
@@ -7,12 +7,15 @@ import {
|
|
|
7
7
|
TABLE_SLUG_PAGES,
|
|
8
8
|
TABLE_SLUG_THEMES,
|
|
9
9
|
} from "~/db/constants"
|
|
10
|
-
import config from "~/vex.config"
|
|
10
|
+
import config from "~/vex.config.server"
|
|
11
11
|
import {
|
|
12
12
|
BLOCK_SLUG_CODE_SHOWCASE,
|
|
13
13
|
BLOCK_SLUG_CTA,
|
|
14
14
|
BLOCK_SLUG_FAQ,
|
|
15
15
|
BLOCK_SLUG_FEATURES,
|
|
16
|
+
BLOCK_SLUG_FOOTER,
|
|
17
|
+
BLOCK_SLUG_HEADER,
|
|
18
|
+
BLOCK_SLUG_HERO,
|
|
16
19
|
BLOCK_SLUG_HOW_IT_WORKS,
|
|
17
20
|
BLOCK_SLUG_ROADMAP,
|
|
18
21
|
BLOCK_SLUG_STATS,
|
|
@@ -389,7 +392,7 @@ export const init = internalMutation({
|
|
|
389
392
|
name: "Main Header",
|
|
390
393
|
content: [
|
|
391
394
|
{
|
|
392
|
-
blockType:
|
|
395
|
+
blockType: BLOCK_SLUG_HEADER,
|
|
393
396
|
blockName: "Site Header",
|
|
394
397
|
id: "main-header",
|
|
395
398
|
logoText: "VexCMS",
|
|
@@ -427,7 +430,7 @@ export const init = internalMutation({
|
|
|
427
430
|
name: "Main Footer",
|
|
428
431
|
content: [
|
|
429
432
|
{
|
|
430
|
-
blockType:
|
|
433
|
+
blockType: BLOCK_SLUG_FOOTER,
|
|
431
434
|
blockName: "Site Footer",
|
|
432
435
|
id: "main-footer",
|
|
433
436
|
logoText: "VexCMS",
|
|
@@ -461,7 +464,7 @@ export const init = internalMutation({
|
|
|
461
464
|
slug: "home",
|
|
462
465
|
blocks: [
|
|
463
466
|
{
|
|
464
|
-
blockType:
|
|
467
|
+
blockType: BLOCK_SLUG_HERO,
|
|
465
468
|
blockName: "Hero",
|
|
466
469
|
id: "home-hero",
|
|
467
470
|
variant: ["full"],
|
|
@@ -507,7 +510,7 @@ export const init = internalMutation({
|
|
|
507
510
|
],
|
|
508
511
|
},
|
|
509
512
|
{
|
|
510
|
-
blockType:
|
|
513
|
+
blockType: BLOCK_SLUG_FEATURES,
|
|
511
514
|
blockName: "Features",
|
|
512
515
|
id: "home-features",
|
|
513
516
|
heading: "Everything comes from one schema.",
|
|
@@ -618,7 +621,7 @@ export const posts = defineTable({
|
|
|
618
621
|
],
|
|
619
622
|
},
|
|
620
623
|
{
|
|
621
|
-
blockType:
|
|
624
|
+
blockType: BLOCK_SLUG_HOW_IT_WORKS,
|
|
622
625
|
blockName: "How It Works",
|
|
623
626
|
id: "home-how-it-works",
|
|
624
627
|
heading: "From zero to a live CMS in four steps.",
|
|
@@ -651,7 +654,7 @@ export const posts = defineTable({
|
|
|
651
654
|
],
|
|
652
655
|
},
|
|
653
656
|
{
|
|
654
|
-
blockType:
|
|
657
|
+
blockType: BLOCK_SLUG_ROADMAP,
|
|
655
658
|
blockName: "Roadmap",
|
|
656
659
|
id: "home-roadmap",
|
|
657
660
|
heading: "Roadmap",
|
|
@@ -769,7 +772,7 @@ export const posts = defineTable({
|
|
|
769
772
|
],
|
|
770
773
|
},
|
|
771
774
|
{
|
|
772
|
-
blockType:
|
|
775
|
+
blockType: BLOCK_SLUG_FAQ,
|
|
773
776
|
blockName: "FAQ",
|
|
774
777
|
id: "home-faq",
|
|
775
778
|
heading: "Questions we keep getting.",
|
|
@@ -809,7 +812,7 @@ export const posts = defineTable({
|
|
|
809
812
|
],
|
|
810
813
|
},
|
|
811
814
|
{
|
|
812
|
-
blockType:
|
|
815
|
+
blockType: BLOCK_SLUG_CTA,
|
|
813
816
|
blockName: "CTA",
|
|
814
817
|
id: "home-cta",
|
|
815
818
|
heading: "Start with a schema. Ship in an hour.",
|
|
@@ -836,7 +839,7 @@ export const posts = defineTable({
|
|
|
836
839
|
slug: "features",
|
|
837
840
|
blocks: [
|
|
838
841
|
{
|
|
839
|
-
blockType:
|
|
842
|
+
blockType: BLOCK_SLUG_HERO,
|
|
840
843
|
blockName: "Hero",
|
|
841
844
|
id: "features-hero",
|
|
842
845
|
variant: ["compact"],
|
|
@@ -937,7 +940,7 @@ export const posts = defineTable({
|
|
|
937
940
|
slug: "roadmap",
|
|
938
941
|
blocks: [
|
|
939
942
|
{
|
|
940
|
-
blockType:
|
|
943
|
+
blockType: BLOCK_SLUG_HERO,
|
|
941
944
|
blockName: "Hero",
|
|
942
945
|
id: "roadmap-hero",
|
|
943
946
|
variant: ["compact"],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getGlobal } from "@vexcms/core/server"
|
|
2
2
|
|
|
3
3
|
import { GLOBAL_SLUG_SITE_SETTINGS, type TABLE_SLUG_THEMES } from "~/db/constants"
|
|
4
|
-
import config from "~/vex.config"
|
|
4
|
+
import config from "~/vex.config.server"
|
|
5
5
|
|
|
6
6
|
import type { Doc, Id } from "./_generated/dataModel"
|
|
7
7
|
import type { QueryCtx } from "./_generated/server"
|
|
@@ -6,7 +6,6 @@ import { AuthServerProvider } from "~/components/providers/auth";
|
|
|
6
6
|
import { getCurrentUser } from "~/auth/serverUtils";
|
|
7
7
|
import { ThemeLive } from "~/components/ThemeLive";
|
|
8
8
|
import { ThemeStyle } from "~/components/ThemeStyle";
|
|
9
|
-
import config from "~/vex.config";
|
|
10
9
|
|
|
11
10
|
import { ClientProviders } from "./clientProviders";
|
|
12
11
|
|
|
@@ -26,9 +25,7 @@ export default async function AdminLayout({ children }: { children: ReactNode })
|
|
|
26
25
|
<ClientProviders>
|
|
27
26
|
<ThemeStyle scope="admin" />
|
|
28
27
|
<ThemeLive scope="admin" />
|
|
29
|
-
<NextAdminLayout
|
|
30
|
-
{children}
|
|
31
|
-
</NextAdminLayout>
|
|
28
|
+
<NextAdminLayout user={user ?? undefined}>{children}</NextAdminLayout>
|
|
32
29
|
</ClientProviders>
|
|
33
30
|
</AuthServerProvider>
|
|
34
31
|
);
|
|
@@ -75,8 +75,15 @@ export async function generatePageMetadata(props: { slug?: string }): Promise<Me
|
|
|
75
75
|
imageUrl: ogImageUrl,
|
|
76
76
|
})
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
// Discord, Slack and X pick the large-image layout from `twitter:card`, not
|
|
79
|
+
// from `og:image` alone — and Next auto-fills `card: "summary"` (a small
|
|
80
|
+
// thumbnail) whenever `twitter` is omitted. So the card type must be emitted
|
|
81
|
+
// whenever an image resolved, independently of whether a handle exists.
|
|
82
|
+
if (ogImageUrl || twitterHandle) {
|
|
83
|
+
metadata.twitter = {
|
|
84
|
+
card: ogImageUrl ? "summary_large_image" : "summary",
|
|
85
|
+
...(twitterHandle ? { site: twitterHandle } : {}),
|
|
86
|
+
}
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
return metadata
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { authOptions } from "@convex/auth/options"
|
|
2
|
+
import { betterAuthAdapter } from "@vexcms/better-auth"
|
|
3
|
+
import { defineServerConfig } from "@vexcms/core"
|
|
4
|
+
import { convexFileStorage } from "@vexcms/file-storage-convex"
|
|
5
|
+
|
|
6
|
+
import vexConfig from "./vex.config"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* VexCMS server-only configuration for the marketing site.
|
|
10
|
+
*
|
|
11
|
+
* Layers the Better Auth adapter and Convex file storage onto the
|
|
12
|
+
* client-safe `vexConfig`. You write this file once at scaffold time and
|
|
13
|
+
* rarely revisit it — day-to-day settings live in `vex.config.ts`.
|
|
14
|
+
*
|
|
15
|
+
* @see ./vex.config for the client-safe half this layers onto
|
|
16
|
+
*/
|
|
17
|
+
const config = defineServerConfig({
|
|
18
|
+
config: vexConfig,
|
|
19
|
+
server: {
|
|
20
|
+
auth: { adapter: betterAuthAdapter({ config: authOptions }) },
|
|
21
|
+
storage: {
|
|
22
|
+
adapters: [convexFileStorage()],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export default config
|
|
@@ -1,33 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { betterAuthAdapter } from "@vexcms/better-auth"
|
|
1
|
+
import { betterAuthCollections } from "@vexcms/better-auth/client"
|
|
3
2
|
import { defineConfig } from "@vexcms/core"
|
|
4
|
-
import {
|
|
3
|
+
import { uploadFile } from "@vexcms/file-storage-convex/client"
|
|
4
|
+
|
|
5
|
+
import { authSchema } from "@convex/auth/schema"
|
|
5
6
|
|
|
6
7
|
import { access } from "~/auth/access"
|
|
7
8
|
import { footers, headers, images, pages, themes, users } from "~/vexcms/collections"
|
|
8
9
|
import { siteSettings } from "~/vexcms/globals"
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
* VexCMS configuration for the marketing site.
|
|
12
|
+
* VexCMS client-safe configuration for the marketing site.
|
|
12
13
|
*
|
|
13
14
|
* Replaces `templates/base-nextjs`'s bare config wholesale (overlay copy is
|
|
14
15
|
* file-level, not a merge): carries base's `users`/`images` forward unchanged
|
|
15
|
-
* and adds the four marketing collections plus `siteSettings`.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* and adds the four marketing collections plus `siteSettings`. Imported
|
|
17
|
+
* directly by the browser via `VexConfigProvider`.
|
|
18
|
+
*
|
|
19
|
+
* @see ./vex.config.server for the auth adapter and storage adapter wiring
|
|
18
20
|
*/
|
|
19
21
|
const vexConfig = defineConfig({
|
|
20
22
|
access,
|
|
23
|
+
authCollections: betterAuthCollections(authSchema),
|
|
21
24
|
admin: {
|
|
22
25
|
sidebar: {
|
|
23
26
|
side: "right",
|
|
24
27
|
},
|
|
25
28
|
},
|
|
26
|
-
authAdapter: betterAuthAdapter({ config: authOptions }),
|
|
27
29
|
storage: {
|
|
28
|
-
|
|
30
|
+
clientUploads: { convex: uploadFile },
|
|
29
31
|
},
|
|
30
32
|
collections: [users, pages, headers, footers, themes],
|
|
33
|
+
mediaCollections: [images],
|
|
31
34
|
globals: [siteSettings],
|
|
32
35
|
routes: {
|
|
33
36
|
map: ({ collection, doc }) => {
|
|
@@ -3,7 +3,7 @@ import { createGetAuth } from "@vexcms/better-auth"
|
|
|
3
3
|
import { vexServerApi } from "@vexcms/core/server"
|
|
4
4
|
|
|
5
5
|
import { TABLE_SLUG_ORGANIZATIONS, TABLE_SLUG_SESSIONS, TABLE_SLUG_USERS } from "~/db/constants"
|
|
6
|
-
import config from "~/vex.config"
|
|
6
|
+
import config from "~/vex.config.server"
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Bound server API — `config`/`getAuth` are threaded once here so
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { BlockComponentProps } from "@vexcms/react"
|
|
4
4
|
|
|
5
|
-
import { cn, ScrollArea, Tabs, TabsList, TabsTrigger, Tooltip, TooltipContent, TooltipTrigger } from "@vexcms/react"
|
|
5
|
+
import { cn, ScrollArea, ScrollBar, Tabs, TabsList, TabsTrigger, Tooltip, TooltipContent, TooltipTrigger } from "@vexcms/react"
|
|
6
6
|
import { Check } from "lucide-react"
|
|
7
7
|
import { useState } from "react"
|
|
8
8
|
|
|
@@ -88,6 +88,7 @@ export default function RoadmapBlock({ block }: BlockComponentProps) {
|
|
|
88
88
|
</TabsTrigger>
|
|
89
89
|
))}
|
|
90
90
|
</TabsList>
|
|
91
|
+
<ScrollBar orientation="horizontal" />
|
|
91
92
|
</ScrollArea>
|
|
92
93
|
</Tabs>
|
|
93
94
|
) : null}
|