create-vexcms 0.0.19 → 0.1.0-alpha.2
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/LICENSE +202 -0
- package/README.md +93 -83
- package/dist/index.js +246 -81
- package/dist/index.mjs +30976 -0
- package/package.json +35 -15
- package/templates/base-nextjs/README.md +53 -205
- package/templates/base-nextjs/_env.example +7 -9
- package/templates/base-nextjs/_gitignore +41 -1
- package/templates/base-nextjs/convex/_generated/api.d.ts +6 -62
- package/templates/base-nextjs/convex/_generated/server.d.ts +19 -0
- package/templates/base-nextjs/convex/_generated/server.js +8 -0
- package/templates/base-nextjs/convex/auth/api.ts +49 -3
- package/templates/base-nextjs/convex/auth/db.ts +42 -300
- package/templates/base-nextjs/convex/auth/index.ts +11 -7
- package/templates/base-nextjs/convex/auth/options.ts +5 -29
- package/templates/base-nextjs/convex/auth/plugins/index.ts +15 -5
- package/templates/base-nextjs/convex/auth/sessions.ts +9 -15
- package/templates/base-nextjs/convex/http.ts +18 -3
- package/templates/base-nextjs/convex/schema.ts +104 -2
- package/templates/base-nextjs/convex/vex/firstUser.ts +10 -84
- package/templates/base-nextjs/convex/vex/globals.ts +19 -82
- package/templates/base-nextjs/convex/vex/media.ts +19 -246
- package/templates/base-nextjs/convex/vex.schema.ts +138 -0
- package/templates/base-nextjs/convex/vex.ts +19 -0
- package/templates/base-nextjs/convex.json +6 -0
- package/templates/base-nextjs/eslint.config.mjs +1 -21
- package/templates/base-nextjs/next.config.ts +10 -0
- package/templates/base-nextjs/package.json +47 -48
- package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +17 -5
- package/templates/base-nextjs/src/app/(frontend)/auth/[pathname]/view.tsx +2 -4
- package/templates/base-nextjs/src/app/(frontend)/layout.tsx +12 -2
- package/templates/base-nextjs/src/app/(frontend)/page.tsx +2 -113
- package/templates/base-nextjs/src/app/(vexcms)/admin/[[...path]]/page.tsx +40 -0
- package/templates/base-nextjs/src/app/(vexcms)/admin/clientProviders.tsx +22 -0
- package/templates/base-nextjs/src/app/(vexcms)/admin/layout.tsx +19 -0
- package/templates/base-nextjs/src/app/globals.css +240 -104
- package/templates/base-nextjs/src/app/layout.tsx +23 -13
- package/templates/base-nextjs/src/app/unauthorized/page.tsx +24 -0
- package/templates/base-nextjs/src/auth/access.ts +46 -0
- package/templates/base-nextjs/src/auth/client.tsx +17 -5
- package/templates/base-nextjs/src/auth/hasPermission.ts +39 -0
- package/templates/base-nextjs/src/auth/serverUtils.ts +15 -22
- package/templates/base-nextjs/src/components/WelcomePage.tsx +116 -0
- package/templates/base-nextjs/src/components/component-example.tsx +56 -464
- package/templates/base-nextjs/src/components/providers/auth.tsx +13 -0
- package/templates/base-nextjs/src/components/providers/client.tsx +6 -1
- package/templates/base-nextjs/src/components/providers/convex.tsx +53 -21
- package/templates/base-nextjs/src/components/providers/server.tsx +10 -7
- package/templates/{marketing-site → base-nextjs}/src/components/ui/accordion.tsx +30 -32
- package/templates/base-nextjs/src/components/ui/badge.tsx +10 -13
- package/templates/base-nextjs/src/components/ui/input.tsx +35 -5
- package/templates/base-nextjs/src/context/AuthContext.tsx +29 -0
- package/templates/base-nextjs/src/db/constants/auth.ts +0 -1
- package/templates/base-nextjs/src/db/constants/index.ts +22 -35
- package/templates/base-nextjs/src/lib/utils.ts +0 -12
- package/templates/base-nextjs/src/proxy.ts +98 -11
- package/templates/base-nextjs/src/vex.config.ts +35 -0
- package/templates/base-nextjs/src/vex.types.ts +293 -0
- package/templates/base-nextjs/src/vexcms/collections/images.ts +15 -0
- package/templates/base-nextjs/src/vexcms/collections/index.ts +2 -1
- package/templates/base-nextjs/src/vexcms/collections/users.ts +7 -35
- package/templates/base-nextjs/tsconfig.json +3 -0
- package/templates/marketing-site/convex/_generated/api.d.ts +81 -0
- package/templates/marketing-site/convex/_generated/api.js +23 -0
- package/templates/marketing-site/convex/_generated/dataModel.d.ts +60 -0
- package/templates/marketing-site/convex/_generated/server.d.ts +162 -0
- package/templates/marketing-site/convex/_generated/server.js +101 -0
- package/templates/marketing-site/convex/footers.ts +11 -7
- package/templates/marketing-site/convex/headers.ts +15 -5
- package/templates/marketing-site/convex/pages.ts +20 -35
- package/templates/marketing-site/convex/schema.ts +119 -0
- package/templates/marketing-site/convex/seed.ts +582 -35
- package/templates/marketing-site/convex/siteSettings.ts +19 -5
- package/templates/marketing-site/convex/theme.ts +55 -61
- package/templates/marketing-site/convex/vex.schema.ts +355 -0
- package/templates/marketing-site/src/app/(frontend)/PageContent.tsx +27 -160
- package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +6 -9
- package/templates/marketing-site/src/app/(frontend)/layout.tsx +4 -2
- package/templates/marketing-site/src/app/(frontend)/page.tsx +2 -5
- package/templates/marketing-site/src/components/SiteFooter.tsx +6 -3
- package/templates/marketing-site/src/components/SiteHeader.tsx +6 -3
- package/templates/marketing-site/src/components/ThemeLive.tsx +59 -0
- package/templates/marketing-site/src/components/ThemeStyle.tsx +45 -89
- package/templates/marketing-site/src/components/motion-primitives/animated-group.tsx +2 -2
- package/templates/marketing-site/src/lib/metadata.ts +51 -72
- package/templates/marketing-site/src/vex.config.ts +35 -0
- package/templates/marketing-site/src/vex.types.ts +634 -0
- package/templates/marketing-site/src/vexcms/api.ts +23 -0
- package/templates/marketing-site/src/vexcms/blocks/CTA/config.ts +3 -3
- package/templates/marketing-site/src/vexcms/blocks/CTA/index.tsx +2 -2
- package/templates/marketing-site/src/vexcms/blocks/FAQ/config.ts +4 -4
- package/templates/marketing-site/src/vexcms/blocks/FAQ/index.tsx +2 -2
- package/templates/marketing-site/src/vexcms/blocks/Features/config.ts +4 -8
- package/templates/marketing-site/src/vexcms/blocks/Features/index.tsx +2 -2
- package/templates/marketing-site/src/vexcms/blocks/Footer/config.ts +7 -11
- package/templates/marketing-site/src/vexcms/blocks/Footer/index.tsx +1 -1
- package/templates/marketing-site/src/vexcms/blocks/Header/config.ts +9 -9
- package/templates/marketing-site/src/vexcms/blocks/Header/index.tsx +4 -5
- package/templates/marketing-site/src/vexcms/blocks/Hero/config.ts +3 -4
- package/templates/marketing-site/src/vexcms/blocks/Hero/index.tsx +10 -19
- package/templates/marketing-site/src/vexcms/blocks/HowItWorks/config.ts +4 -9
- package/templates/marketing-site/src/vexcms/blocks/HowItWorks/index.tsx +3 -6
- package/templates/marketing-site/src/vexcms/blocks/Roadmap/config.ts +28 -68
- package/templates/marketing-site/src/vexcms/blocks/Roadmap/index.tsx +13 -43
- package/templates/marketing-site/src/vexcms/blocks/config.ts +7 -22
- package/templates/marketing-site/src/vexcms/blocks/constants.ts +2 -3
- package/templates/marketing-site/src/vexcms/blocks/index.ts +9 -8
- package/templates/marketing-site/src/vexcms/collections/footers.ts +1 -1
- package/templates/marketing-site/src/vexcms/collections/headers.ts +1 -1
- package/templates/marketing-site/src/vexcms/collections/index.ts +2 -1
- package/templates/marketing-site/src/vexcms/collections/pages.ts +12 -21
- package/templates/marketing-site/src/vexcms/collections/themeColors.ts +118 -0
- package/templates/marketing-site/src/vexcms/collections/themes.ts +29 -111
- package/templates/marketing-site/src/vexcms/globals/index.ts +1 -1
- package/templates/marketing-site/src/vexcms/globals/siteSettings.ts +43 -27
- package/templates/base-nextjs/convex/auth/adapter/index.ts +0 -230
- package/templates/base-nextjs/convex/auth/adapter/utils.ts +0 -547
- package/templates/base-nextjs/convex/auth/config.ts +0 -10
- package/templates/base-nextjs/convex/vex/auth.ts +0 -38
- package/templates/base-nextjs/convex/vex/collections.ts +0 -321
- package/templates/base-nextjs/convex/vex/helpers.ts +0 -33
- package/templates/base-nextjs/convex/vex/impersonation.ts +0 -51
- package/templates/base-nextjs/convex/vex/migrate.ts +0 -74
- package/templates/base-nextjs/convex/vex/model/collections.ts +0 -196
- package/templates/base-nextjs/convex/vex/model/media.ts +0 -87
- package/templates/base-nextjs/convex/vex/model/versions.ts +0 -254
- package/templates/base-nextjs/convex/vex/previewSnapshot.ts +0 -53
- package/templates/base-nextjs/convex/vex/versions.ts +0 -648
- package/templates/base-nextjs/src/app/admin/AdminLayoutWrapper.tsx +0 -33
- package/templates/base-nextjs/src/app/admin/AdminPageWrapper.tsx +0 -38
- package/templates/base-nextjs/src/app/admin/RichTextFieldWithMedia.tsx +0 -101
- package/templates/base-nextjs/src/app/admin/[[...path]]/page.tsx +0 -15
- package/templates/base-nextjs/src/app/admin/layout.tsx +0 -51
- package/templates/base-nextjs/src/app/favicon.ico +0 -0
- package/templates/base-nextjs/src/components/providers/theme.tsx +0 -11
- package/templates/base-nextjs/src/components/ui/theme-toggle.tsx +0 -67
- package/templates/base-nextjs/src/db/types.ts +0 -6
- package/templates/base-nextjs/src/vexcms/access.ts +0 -44
- package/templates/base-nextjs/src/vexcms/auth.ts +0 -4
- package/templates/base-nextjs/src/vexcms/collections/media.ts +0 -15
- package/templates/base-nextjs/vex.config.ts +0 -25
- package/templates/marketing-site/public/favicons/favicon.ico +0 -0
- package/templates/marketing-site/src/app/(frontend)/preview/[slug]/PreviewPageContent.tsx +0 -55
- package/templates/marketing-site/src/app/(frontend)/preview/[slug]/page.tsx +0 -27
- package/templates/marketing-site/src/app/(frontend)/preview/layout.tsx +0 -18
- package/templates/marketing-site/src/app/admin/AdminLayoutWrapper.tsx +0 -71
- package/templates/marketing-site/src/components/ThemeInjector.tsx +0 -128
- package/templates/marketing-site/src/components/admin/IconPickerField.tsx +0 -134
- package/templates/marketing-site/src/components/admin/ThemeImport.tsx +0 -174
- package/templates/marketing-site/src/components/admin/ThemeImportField.tsx +0 -33
- package/templates/marketing-site/src/db/constants/index.ts +0 -52
- package/templates/marketing-site/src/lib/colorConvert.ts +0 -171
- package/templates/marketing-site/src/vexcms/access.ts +0 -36
- package/templates/marketing-site/vex.config.ts +0 -32
|
@@ -98,7 +98,7 @@ export default tseslint.config(
|
|
|
98
98
|
"perfectionist/sort-named-imports": "warn",
|
|
99
99
|
"perfectionist/sort-object-types": "warn",
|
|
100
100
|
"perfectionist/sort-objects": [
|
|
101
|
-
"
|
|
101
|
+
"off",
|
|
102
102
|
{
|
|
103
103
|
customGroups: [
|
|
104
104
|
{
|
|
@@ -124,26 +124,6 @@ export default tseslint.config(
|
|
|
124
124
|
"import/no-anonymous-default-export": "off",
|
|
125
125
|
},
|
|
126
126
|
},
|
|
127
|
-
{
|
|
128
|
-
// Disable object sorting in VEX collection/config files — field order determines
|
|
129
|
-
// the order of fields in admin panel forms
|
|
130
|
-
files: [
|
|
131
|
-
"**/vexcms/collections/**/*.ts",
|
|
132
|
-
"**/vexcms/access.*",
|
|
133
|
-
"**/vex.config.*",
|
|
134
|
-
"**/db/constants/**/*.ts",
|
|
135
|
-
],
|
|
136
|
-
rules: {
|
|
137
|
-
"perfectionist/sort-objects": "off",
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
files: ["**/src/convex/**/*.ts"],
|
|
142
|
-
plugins: {
|
|
143
|
-
"@convex-dev": convexPlugin,
|
|
144
|
-
},
|
|
145
|
-
rules: convexRules,
|
|
146
|
-
},
|
|
147
127
|
{
|
|
148
128
|
languageOptions: {
|
|
149
129
|
parserOptions: {
|
|
@@ -3,8 +3,18 @@ import type { NextConfig } from "next"
|
|
|
3
3
|
import "./src/env.mjs"
|
|
4
4
|
|
|
5
5
|
const nextConfig: NextConfig = {
|
|
6
|
+
devIndicators: {
|
|
7
|
+
position: "bottom-right",
|
|
8
|
+
},
|
|
6
9
|
allowedDevOrigins: ["127.0.01", "localhost"],
|
|
7
10
|
reactCompiler: true,
|
|
11
|
+
images: {
|
|
12
|
+
remotePatterns: [
|
|
13
|
+
// Add your Convex deployment's hostname here once `npx convex dev` has
|
|
14
|
+
// run — Convex file storage URLs are served from `<deployment>.convex.cloud`,
|
|
15
|
+
// e.g. { hostname: "your-deployment-575.convex.cloud" }.
|
|
16
|
+
],
|
|
17
|
+
},
|
|
8
18
|
}
|
|
9
19
|
|
|
10
20
|
export default nextConfig
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
"dev": "next dev --port=3010",
|
|
7
7
|
"vex:dev": "vex dev",
|
|
8
8
|
"vex:generate": "vex dev --once",
|
|
9
|
-
"vex:update": "pnpm add @vexcms/core@latest @vexcms/
|
|
9
|
+
"vex:update": "pnpm add @vexcms/core@latest @vexcms/react@latest @vexcms/next@latest @vexcms/better-auth@latest @vexcms/file-storage-convex@latest @vexcms/cli@latest",
|
|
10
|
+
"seed": "convex run seed:init",
|
|
10
11
|
"build": "next build",
|
|
11
12
|
"start": "next start",
|
|
12
13
|
"lint": "eslint .",
|
|
@@ -15,55 +16,53 @@
|
|
|
15
16
|
"secret:create": "openssl rand -base64 32 | tr -d '\\n' | tee /dev/stderr | pbcopy && echo '\\n✓ Secret copied to clipboard'"
|
|
16
17
|
},
|
|
17
18
|
"dependencies": {
|
|
18
|
-
"@base-ui/react": "
|
|
19
|
-
"@
|
|
20
|
-
"@convex-dev/
|
|
21
|
-
"@
|
|
22
|
-
"@
|
|
23
|
-
"@
|
|
24
|
-
"@tanstack/react-
|
|
25
|
-
"@
|
|
26
|
-
"@vexcms/better-auth": "~0.0.
|
|
27
|
-
"@vexcms/core": "~0.0.
|
|
28
|
-
"@vexcms/
|
|
29
|
-
"@vexcms/
|
|
30
|
-
"@vexcms/
|
|
31
|
-
"better-auth": "
|
|
32
|
-
"class-variance-authority": "
|
|
33
|
-
"clsx": "
|
|
34
|
-
"convex": "
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"tw-animate-css": "^1.4.0",
|
|
47
|
-
"zod": "^4.3.5"
|
|
19
|
+
"@base-ui/react": "1.2.0",
|
|
20
|
+
"@better-auth/api-key": "1.6.23",
|
|
21
|
+
"@convex-dev/better-auth": "0.11.5",
|
|
22
|
+
"@convex-dev/react-query": "0.1.0",
|
|
23
|
+
"@daveyplate/better-auth-ui": "3.4.0",
|
|
24
|
+
"@t3-oss/env-nextjs": "0.13.11",
|
|
25
|
+
"@tanstack/react-form": "1.33.1",
|
|
26
|
+
"@tanstack/react-query": "5.101.2",
|
|
27
|
+
"@vexcms/better-auth": "~0.1.0-alpha.2",
|
|
28
|
+
"@vexcms/core": "~0.1.0-alpha.2",
|
|
29
|
+
"@vexcms/file-storage-convex": "~0.1.0-alpha.2",
|
|
30
|
+
"@vexcms/next": "~0.1.0-alpha.2",
|
|
31
|
+
"@vexcms/react": "~0.1.0-alpha.2",
|
|
32
|
+
"better-auth": "1.6.23",
|
|
33
|
+
"class-variance-authority": "0.7.1",
|
|
34
|
+
"clsx": "2.1.1",
|
|
35
|
+
"convex": "1.44.0",
|
|
36
|
+
"convex-helpers": "0.1.120",
|
|
37
|
+
"lucide-react": "0.577.0",
|
|
38
|
+
"motion": "13.1.1",
|
|
39
|
+
"next": "16.3.3",
|
|
40
|
+
"nuqs": "2.9.0",
|
|
41
|
+
"react": "19.2.7",
|
|
42
|
+
"react-dom": "19.2.7",
|
|
43
|
+
"shadcn": "3.6.3",
|
|
44
|
+
"tailwind-merge": "3.5.0",
|
|
45
|
+
"tw-animate-css": "1.4.0",
|
|
46
|
+
"zod": "4.4.3"
|
|
48
47
|
},
|
|
49
48
|
"devDependencies": {
|
|
50
|
-
"@convex-dev/eslint-plugin": "
|
|
51
|
-
"@eslint/eslintrc": "
|
|
52
|
-
"@next/eslint-plugin-next": "
|
|
53
|
-
"@tailwindcss/postcss": "
|
|
54
|
-
"@types/node": "
|
|
55
|
-
"@types/react": "
|
|
56
|
-
"@types/react-dom": "
|
|
57
|
-
"@vexcms/cli": "~0.0.
|
|
58
|
-
"babel-plugin-react-compiler": "
|
|
59
|
-
"eslint": "
|
|
49
|
+
"@convex-dev/eslint-plugin": "1.2.2",
|
|
50
|
+
"@eslint/eslintrc": "3.3.6",
|
|
51
|
+
"@next/eslint-plugin-next": "15.5.20",
|
|
52
|
+
"@tailwindcss/postcss": "4.3.2",
|
|
53
|
+
"@types/node": "20.19.43",
|
|
54
|
+
"@types/react": "19.2.17",
|
|
55
|
+
"@types/react-dom": "19.2.3",
|
|
56
|
+
"@vexcms/cli": "~0.1.0-alpha.2",
|
|
57
|
+
"babel-plugin-react-compiler": "1.0.0",
|
|
58
|
+
"eslint": "9.39.5",
|
|
60
59
|
"eslint-config-next": "15.5.9",
|
|
61
|
-
"eslint-plugin-import-x": "
|
|
62
|
-
"eslint-plugin-perfectionist": "
|
|
63
|
-
"eslint-plugin-react-hooks": "
|
|
64
|
-
"prettier": "
|
|
65
|
-
"tailwindcss": "
|
|
66
|
-
"typescript": "
|
|
67
|
-
"typescript-eslint": "
|
|
60
|
+
"eslint-plugin-import-x": "4.17.1",
|
|
61
|
+
"eslint-plugin-perfectionist": "5.10.0",
|
|
62
|
+
"eslint-plugin-react-hooks": "7.1.1",
|
|
63
|
+
"prettier": "3.9.5",
|
|
64
|
+
"tailwindcss": "4.3.2",
|
|
65
|
+
"typescript": "6.0.3",
|
|
66
|
+
"typescript-eslint": "8.63.0"
|
|
68
67
|
}
|
|
69
68
|
}
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
import { Activity } from "react"
|
|
1
|
+
"use client";
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import { AuthView } from "@daveyplate/better-auth-ui";
|
|
4
|
+
import { useSearchParams } from "next/navigation";
|
|
5
|
+
import { Activity } from "react";
|
|
6
|
+
|
|
7
|
+
import { Dialog, DialogContent, DialogTitle } from "~/components/ui/dialog";
|
|
5
8
|
|
|
6
9
|
export default function AuthCard({ pathname }: { pathname: string }) {
|
|
10
|
+
const searchParams = useSearchParams();
|
|
11
|
+
const isGuardRedirect = searchParams.has("redirectTo");
|
|
12
|
+
if (isGuardRedirect) {
|
|
13
|
+
return (
|
|
14
|
+
<main className="absolute inset-0 grid place-items-center">
|
|
15
|
+
<AuthView path={pathname} />
|
|
16
|
+
</main>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
7
19
|
return (
|
|
8
20
|
<main>
|
|
9
21
|
<Dialog open>
|
|
@@ -12,12 +24,12 @@ export default function AuthCard({ pathname }: { pathname: string }) {
|
|
|
12
24
|
</Activity>
|
|
13
25
|
<DialogContent
|
|
14
26
|
aria-describedby={undefined}
|
|
15
|
-
className="grid place-items-center
|
|
27
|
+
className="grid place-items-center bg-transparent shadow-none ring-transparent"
|
|
16
28
|
showCloseButton={false}
|
|
17
29
|
>
|
|
18
30
|
<AuthView path={pathname} />
|
|
19
31
|
</DialogContent>
|
|
20
32
|
</Dialog>
|
|
21
33
|
</main>
|
|
22
|
-
)
|
|
34
|
+
);
|
|
23
35
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { AuthView } from "@daveyplate/better-auth-ui"
|
|
1
|
+
import { AuthView } from "@daveyplate/better-auth-ui";
|
|
4
2
|
|
|
5
3
|
export default function AuthCard({ pathname }: { pathname: string }) {
|
|
6
4
|
return (
|
|
7
5
|
<main className="absolute inset-0 grid place-items-center">
|
|
8
6
|
<AuthView path={pathname} />
|
|
9
7
|
</main>
|
|
10
|
-
)
|
|
8
|
+
);
|
|
11
9
|
}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public-site layout.
|
|
5
|
+
*
|
|
6
|
+
* Exists so the `@auth` parallel slot is received by the route group that
|
|
7
|
+
* declares it, rather than leaking up to the root layout — which is also shared
|
|
8
|
+
* with `/admin`, where an auth modal slot means nothing. Restores the structure
|
|
9
|
+
* the marketing template and the pre-rebuild app both used.
|
|
10
|
+
*/
|
|
1
11
|
export default function FrontendLayout({
|
|
2
12
|
auth,
|
|
3
13
|
children,
|
|
4
14
|
}: Readonly<{
|
|
5
|
-
auth:
|
|
6
|
-
children:
|
|
15
|
+
auth: ReactNode
|
|
16
|
+
children: ReactNode
|
|
7
17
|
}>) {
|
|
8
18
|
return (
|
|
9
19
|
<>
|
|
@@ -1,116 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { useMutation, useQuery } from "convex/react"
|
|
4
|
-
import { usePathname, useRouter } from "next/navigation"
|
|
5
|
-
import { useEffect, useState } from "react"
|
|
6
|
-
|
|
7
|
-
import { api } from "@convex/_generated/api"
|
|
8
|
-
import { useSession } from "~/auth/client"
|
|
9
|
-
|
|
10
|
-
function LoadingSpinner() {
|
|
11
|
-
return (
|
|
12
|
-
<svg className="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
13
|
-
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
14
|
-
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
|
15
|
-
</svg>
|
|
16
|
-
)
|
|
17
|
-
}
|
|
1
|
+
import { WelcomePage } from "~/components/WelcomePage"
|
|
18
2
|
|
|
19
3
|
export default function Page() {
|
|
20
|
-
|
|
21
|
-
const promoteFirstAdmin = useMutation(api.vex.firstUser.promoteFirstAdmin)
|
|
22
|
-
const { data: session } = useSession()
|
|
23
|
-
const router = useRouter()
|
|
24
|
-
const pathname = usePathname()
|
|
25
|
-
const [promoting, setPromoting] = useState(false)
|
|
26
|
-
const [navigating, setNavigating] = useState(false)
|
|
27
|
-
|
|
28
|
-
// Reset navigating state when the user comes back to the home page
|
|
29
|
-
// (e.g. closing the intercepting auth dialog)
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
if (pathname === "/") {
|
|
32
|
-
setNavigating(false)
|
|
33
|
-
}
|
|
34
|
-
}, [pathname])
|
|
35
|
-
|
|
36
|
-
// If user is signed in and this is a fresh project, try to promote them
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
if (session?.user && isBootstrapped === false && !promoting) {
|
|
39
|
-
setPromoting(true)
|
|
40
|
-
setNavigating(true)
|
|
41
|
-
promoteFirstAdmin()
|
|
42
|
-
.then((result) => {
|
|
43
|
-
if (result.promoted) {
|
|
44
|
-
router.push("/admin")
|
|
45
|
-
} else {
|
|
46
|
-
setNavigating(false)
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
.catch(() => {
|
|
50
|
-
setPromoting(false)
|
|
51
|
-
setNavigating(false)
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
}, [session, isBootstrapped, promoting, promoteFirstAdmin, router])
|
|
55
|
-
|
|
56
|
-
// Treat undefined (query still loading) as not bootstrapped — show the welcome page immediately
|
|
57
|
-
const bootstrapped = isBootstrapped === true
|
|
58
|
-
|
|
59
|
-
const buttonClass =
|
|
60
|
-
"inline-flex items-center justify-center gap-2 rounded-md bg-primary px-6 py-3 text-sm font-medium text-primary-foreground shadow hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-opacity"
|
|
61
|
-
|
|
62
|
-
const handleNavigate = (path: string) => {
|
|
63
|
-
setNavigating(true)
|
|
64
|
-
router.push(path)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<div className="flex min-h-screen flex-col items-center justify-center gap-8 p-8">
|
|
69
|
-
<div className="text-center">
|
|
70
|
-
<h1 className="text-4xl font-bold tracking-tight">VEX CMS</h1>
|
|
71
|
-
<p className="mt-3 text-lg text-muted-foreground">
|
|
72
|
-
{bootstrapped
|
|
73
|
-
? "Your content management system is ready."
|
|
74
|
-
: "Welcome! Create your admin account to get started."}
|
|
75
|
-
</p>
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
|
-
<div className="flex gap-4">
|
|
79
|
-
{session?.user ? (
|
|
80
|
-
<button
|
|
81
|
-
onClick={() => handleNavigate("/admin")}
|
|
82
|
-
disabled={navigating}
|
|
83
|
-
className={buttonClass}
|
|
84
|
-
>
|
|
85
|
-
{navigating && <LoadingSpinner />}
|
|
86
|
-
{navigating ? "Loading..." : "Go to Admin Panel"}
|
|
87
|
-
</button>
|
|
88
|
-
) : bootstrapped ? (
|
|
89
|
-
<button
|
|
90
|
-
onClick={() => handleNavigate("/auth/sign-in")}
|
|
91
|
-
disabled={navigating}
|
|
92
|
-
className={buttonClass}
|
|
93
|
-
>
|
|
94
|
-
{navigating && <LoadingSpinner />}
|
|
95
|
-
{navigating ? "Loading..." : "Sign In"}
|
|
96
|
-
</button>
|
|
97
|
-
) : (
|
|
98
|
-
<button
|
|
99
|
-
onClick={() => handleNavigate("/auth/sign-up")}
|
|
100
|
-
disabled={navigating}
|
|
101
|
-
className={buttonClass}
|
|
102
|
-
>
|
|
103
|
-
{navigating && <LoadingSpinner />}
|
|
104
|
-
{navigating ? "Loading..." : "Create Admin Account"}
|
|
105
|
-
</button>
|
|
106
|
-
)}
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
{promoting && (
|
|
110
|
-
<p className="text-sm text-muted-foreground animate-pulse">
|
|
111
|
-
Setting up your admin account...
|
|
112
|
-
</p>
|
|
113
|
-
)}
|
|
114
|
-
</div>
|
|
115
|
-
)
|
|
4
|
+
return <WelcomePage />
|
|
116
5
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { api } from "@convex/_generated/api";
|
|
2
|
+
import { NextAdminPage } from "@vexcms/next/server";
|
|
3
|
+
import { canAccessAdminPanel } from "@vexcms/core";
|
|
4
|
+
import { redirect } from "next/navigation";
|
|
5
|
+
|
|
6
|
+
import { fetchAuthQuery, getToken } from "~/auth/server";
|
|
7
|
+
import config from "~/vex.config";
|
|
8
|
+
|
|
9
|
+
export const dynamic = "force-dynamic";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Admin panel route. Gates on two separate things, in order:
|
|
13
|
+
*
|
|
14
|
+
* 1. **Authentication** — no session token means sign in first.
|
|
15
|
+
* 2. **Authorization** — a valid session is not enough; the caller must also
|
|
16
|
+
* satisfy `adminPanel.access` in the access matrix. Without this second check
|
|
17
|
+
* any authenticated user reaches the full panel regardless of their roles.
|
|
18
|
+
*
|
|
19
|
+
* Both run server-side, before the panel renders, so an unauthorized caller
|
|
20
|
+
* never receives the admin shell (which would leak collection and global names).
|
|
21
|
+
*/
|
|
22
|
+
export default async function AdminPage({ params }: { params: Promise<{ path?: string[] }> }) {
|
|
23
|
+
const token = await getToken();
|
|
24
|
+
if (!token) {
|
|
25
|
+
redirect("/auth/sign-in?redirectTo=/admin");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const auth = await fetchAuthQuery(api.auth.api.getUserOrg, {});
|
|
29
|
+
if (
|
|
30
|
+
!canAccessAdminPanel({
|
|
31
|
+
access: config.access,
|
|
32
|
+
user: auth.user,
|
|
33
|
+
organization: auth.organization,
|
|
34
|
+
})
|
|
35
|
+
) {
|
|
36
|
+
redirect("/unauthorized");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return <NextAdminPage config={config} params={params} token={token} />;
|
|
40
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { uploadFile } from "@vexcms/file-storage-convex";
|
|
4
|
+
import { StorageAdapterContextProvider, VexAccessProvider } from "@vexcms/react";
|
|
5
|
+
|
|
6
|
+
import { access } from "~/auth/access";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Provides client-side upload functions for VexCMS storage adapters.
|
|
10
|
+
*
|
|
11
|
+
* This is a "use client" component so that uploadFile is imported and
|
|
12
|
+
* owned on the client — it never crosses the RSC boundary as a prop.
|
|
13
|
+
*
|
|
14
|
+
* Add a new entry here when registering an additional storage adapter.
|
|
15
|
+
*/
|
|
16
|
+
export function ClientProviders({ children }: { children: React.ReactNode }) {
|
|
17
|
+
return (
|
|
18
|
+
<StorageAdapterContextProvider adapterClients={{ convex: uploadFile }}>
|
|
19
|
+
<VexAccessProvider access={access}>{children}</VexAccessProvider>
|
|
20
|
+
</StorageAdapterContextProvider>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
|
|
3
|
+
import { NextAdminLayout } from "@vexcms/next/client"
|
|
4
|
+
|
|
5
|
+
import { getCurrentUser } from "~/auth/serverUtils"
|
|
6
|
+
import config from "~/vex.config"
|
|
7
|
+
|
|
8
|
+
import { ClientProviders } from "./clientProviders"
|
|
9
|
+
|
|
10
|
+
export default async function AdminLayout({ children }: { children: ReactNode }) {
|
|
11
|
+
const user = await getCurrentUser()
|
|
12
|
+
return (
|
|
13
|
+
<ClientProviders>
|
|
14
|
+
<NextAdminLayout config={config} user={user ?? undefined}>
|
|
15
|
+
{children}
|
|
16
|
+
</NextAdminLayout>
|
|
17
|
+
</ClientProviders>
|
|
18
|
+
)
|
|
19
|
+
}
|