create-vexcms 0.0.3
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/dist/index.d.ts +1 -0
- package/dist/index.js +2409 -0
- package/package.json +37 -0
- package/templates/base-nextjs/.prettierignore +26 -0
- package/templates/base-nextjs/.prettierrc +7 -0
- package/templates/base-nextjs/README.md +256 -0
- package/templates/base-nextjs/_gitignore +2 -0
- package/templates/base-nextjs/components.json +24 -0
- package/templates/base-nextjs/convex/_generated/api.d.ts +125 -0
- package/templates/base-nextjs/convex/_generated/api.js +23 -0
- package/templates/base-nextjs/convex/_generated/dataModel.d.ts +60 -0
- package/templates/base-nextjs/convex/_generated/server.d.ts +143 -0
- package/templates/base-nextjs/convex/_generated/server.js +93 -0
- package/templates/base-nextjs/convex/auth/adapter/index.ts +230 -0
- package/templates/base-nextjs/convex/auth/adapter/utils.ts +547 -0
- package/templates/base-nextjs/convex/auth/api.ts +8 -0
- package/templates/base-nextjs/convex/auth/config.ts +10 -0
- package/templates/base-nextjs/convex/auth/db.ts +303 -0
- package/templates/base-nextjs/convex/auth/index.ts +14 -0
- package/templates/base-nextjs/convex/auth/options.ts +63 -0
- package/templates/base-nextjs/convex/auth/plugins/index.ts +20 -0
- package/templates/base-nextjs/convex/auth/sessions.ts +60 -0
- package/templates/base-nextjs/convex/auth.config.ts +7 -0
- package/templates/base-nextjs/convex/convex.config.ts +5 -0
- package/templates/base-nextjs/convex/http.ts +28 -0
- package/templates/base-nextjs/convex/schema.ts +3 -0
- package/templates/base-nextjs/convex/vex/auth.ts +38 -0
- package/templates/base-nextjs/convex/vex/collections.ts +321 -0
- package/templates/base-nextjs/convex/vex/firstUser.ts +134 -0
- package/templates/base-nextjs/convex/vex/helpers.ts +33 -0
- package/templates/base-nextjs/convex/vex/impersonation.ts +51 -0
- package/templates/base-nextjs/convex/vex/media.ts +246 -0
- package/templates/base-nextjs/convex/vex/migrate.ts +74 -0
- package/templates/base-nextjs/convex/vex/model/collections.ts +196 -0
- package/templates/base-nextjs/convex/vex/model/media.ts +87 -0
- package/templates/base-nextjs/convex/vex/model/versions.ts +254 -0
- package/templates/base-nextjs/convex/vex/previewSnapshot.ts +53 -0
- package/templates/base-nextjs/convex/vex/versions.ts +588 -0
- package/templates/base-nextjs/eslint.config.mjs +164 -0
- package/templates/base-nextjs/next.config.ts +10 -0
- package/templates/base-nextjs/package.json +67 -0
- package/templates/base-nextjs/postcss.config.mjs +7 -0
- package/templates/base-nextjs/public/favicons/favicon.ico +0 -0
- package/templates/base-nextjs/public/file.svg +1 -0
- package/templates/base-nextjs/public/globe.svg +1 -0
- package/templates/base-nextjs/public/next.svg +1 -0
- package/templates/base-nextjs/public/vercel.svg +1 -0
- package/templates/base-nextjs/public/window.svg +1 -0
- package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/page.tsx +9 -0
- package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +23 -0
- package/templates/base-nextjs/src/app/(frontend)/@auth/default.tsx +3 -0
- package/templates/base-nextjs/src/app/(frontend)/@auth/page.tsx +3 -0
- package/templates/base-nextjs/src/app/(frontend)/auth/[pathname]/page.tsx +9 -0
- package/templates/base-nextjs/src/app/(frontend)/auth/[pathname]/view.tsx +11 -0
- package/templates/base-nextjs/src/app/(frontend)/layout.tsx +14 -0
- package/templates/base-nextjs/src/app/(frontend)/page.tsx +116 -0
- package/templates/base-nextjs/src/app/admin/AdminLayoutWrapper.tsx +33 -0
- package/templates/base-nextjs/src/app/admin/AdminPageWrapper.tsx +38 -0
- package/templates/base-nextjs/src/app/admin/RichTextFieldWithMedia.tsx +101 -0
- package/templates/base-nextjs/src/app/admin/[[...path]]/page.tsx +15 -0
- package/templates/base-nextjs/src/app/admin/layout.tsx +33 -0
- package/templates/base-nextjs/src/app/api/auth/[...all]/route.ts +6 -0
- package/templates/base-nextjs/src/app/favicon.ico +0 -0
- package/templates/base-nextjs/src/app/globals.css +130 -0
- package/templates/base-nextjs/src/app/layout.tsx +30 -0
- package/templates/base-nextjs/src/auth/client.tsx +41 -0
- package/templates/base-nextjs/src/auth/permissions.ts +102 -0
- package/templates/base-nextjs/src/auth/server.ts +16 -0
- package/templates/base-nextjs/src/auth/serverUtils.ts +64 -0
- package/templates/base-nextjs/src/auth/types.ts +3 -0
- package/templates/base-nextjs/src/components/component-example.tsx +474 -0
- package/templates/base-nextjs/src/components/example.tsx +52 -0
- package/templates/base-nextjs/src/components/providers/client.tsx +9 -0
- package/templates/base-nextjs/src/components/providers/convex.tsx +32 -0
- package/templates/base-nextjs/src/components/providers/server.tsx +15 -0
- package/templates/base-nextjs/src/components/providers/theme.tsx +11 -0
- package/templates/base-nextjs/src/components/ui/alert-dialog.tsx +162 -0
- package/templates/base-nextjs/src/components/ui/badge.tsx +52 -0
- package/templates/base-nextjs/src/components/ui/button.tsx +60 -0
- package/templates/base-nextjs/src/components/ui/card.tsx +92 -0
- package/templates/base-nextjs/src/components/ui/combobox.tsx +271 -0
- package/templates/base-nextjs/src/components/ui/dialog.tsx +135 -0
- package/templates/base-nextjs/src/components/ui/dropdown-menu.tsx +246 -0
- package/templates/base-nextjs/src/components/ui/field.tsx +224 -0
- package/templates/base-nextjs/src/components/ui/input-group.tsx +146 -0
- package/templates/base-nextjs/src/components/ui/input.tsx +20 -0
- package/templates/base-nextjs/src/components/ui/label.tsx +20 -0
- package/templates/base-nextjs/src/components/ui/select.tsx +189 -0
- package/templates/base-nextjs/src/components/ui/separator.tsx +21 -0
- package/templates/base-nextjs/src/components/ui/textarea.tsx +18 -0
- package/templates/base-nextjs/src/components/ui/theme-toggle.tsx +67 -0
- package/templates/base-nextjs/src/db/constants/auth.ts +8 -0
- package/templates/base-nextjs/src/db/constants/index.ts +44 -0
- package/templates/base-nextjs/src/db/types.ts +6 -0
- package/templates/base-nextjs/src/env.mjs +48 -0
- package/templates/base-nextjs/src/lib/utils.ts +6 -0
- package/templates/base-nextjs/src/proxy.ts +23 -0
- package/templates/base-nextjs/src/vexcms/access.ts +28 -0
- package/templates/base-nextjs/src/vexcms/auth.ts +4 -0
- package/templates/base-nextjs/src/vexcms/collections/index.ts +1 -0
- package/templates/base-nextjs/src/vexcms/collections/media.ts +15 -0
- package/templates/base-nextjs/src/vexcms/collections/users.ts +46 -0
- package/templates/base-nextjs/tsconfig.json +60 -0
- package/templates/base-nextjs/vex.config.ts +23 -0
- package/templates/marketing-site/convex/pages.ts +46 -0
- package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +58 -0
- package/templates/marketing-site/src/app/(frontend)/preview/[slug]/page.tsx +69 -0
- package/templates/marketing-site/src/app/admin/AdminLayoutWrapper.tsx +71 -0
- package/templates/marketing-site/src/db/constants/index.ts +52 -0
- package/templates/marketing-site/src/vexcms/collections/footers.ts +28 -0
- package/templates/marketing-site/src/vexcms/collections/headers.ts +35 -0
- package/templates/marketing-site/src/vexcms/collections/index.ts +7 -0
- package/templates/marketing-site/src/vexcms/collections/pages.ts +40 -0
- package/templates/marketing-site/src/vexcms/collections/site_settings.ts +31 -0
- package/templates/marketing-site/src/vexcms/collections/themes.ts +37 -0
- package/templates/marketing-site/vex.config.ts +32 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import convexPlugin from "@convex-dev/eslint-plugin"
|
|
2
|
+
import { FlatCompat } from "@eslint/eslintrc"
|
|
3
|
+
import importX from "eslint-plugin-import-x"
|
|
4
|
+
import perfectionist from "eslint-plugin-perfectionist"
|
|
5
|
+
import tseslint from "typescript-eslint"
|
|
6
|
+
|
|
7
|
+
const compat = new FlatCompat({
|
|
8
|
+
baseDirectory: import.meta.dirname,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const convexConfig = convexPlugin.configs.recommended[0]
|
|
12
|
+
const convexRules = convexConfig.rules
|
|
13
|
+
|
|
14
|
+
export const defaultESLintIgnores = [
|
|
15
|
+
"**/.temp",
|
|
16
|
+
"**/.git",
|
|
17
|
+
"**/.hg",
|
|
18
|
+
"**/.pnp.*",
|
|
19
|
+
"**/.svn",
|
|
20
|
+
"**/.next",
|
|
21
|
+
"**/.open-next",
|
|
22
|
+
"**/.sst",
|
|
23
|
+
"**/playwright.config.ts",
|
|
24
|
+
"**/vitest.config.ts",
|
|
25
|
+
"**/tsconfig.tsbuildinfo",
|
|
26
|
+
"**/README.md",
|
|
27
|
+
"**/eslint.config.js",
|
|
28
|
+
"**/dist/",
|
|
29
|
+
"**/.yarn/",
|
|
30
|
+
"**/build/",
|
|
31
|
+
"**/node_modules/",
|
|
32
|
+
"**/temp/",
|
|
33
|
+
"**/registry/",
|
|
34
|
+
"**/public/",
|
|
35
|
+
"**/convex/_generated/",
|
|
36
|
+
"**/.source/",
|
|
37
|
+
"**/sst.config.ts",
|
|
38
|
+
"**/sst.workflow.ts",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
export default tseslint.config(
|
|
42
|
+
{
|
|
43
|
+
ignores: defaultESLintIgnores,
|
|
44
|
+
},
|
|
45
|
+
...compat.extends("next/core-web-vitals"),
|
|
46
|
+
perfectionist.configs["recommended-natural"],
|
|
47
|
+
{
|
|
48
|
+
extends: [
|
|
49
|
+
...tseslint.configs.recommended,
|
|
50
|
+
...tseslint.configs.recommendedTypeChecked,
|
|
51
|
+
...tseslint.configs.stylisticTypeChecked,
|
|
52
|
+
],
|
|
53
|
+
files: ["**/*.ts", "**/*.tsx"],
|
|
54
|
+
plugins: {
|
|
55
|
+
"import-x": importX,
|
|
56
|
+
},
|
|
57
|
+
rules: {
|
|
58
|
+
"@typescript-eslint/array-type": "off",
|
|
59
|
+
"@typescript-eslint/ban-tslint-comment": "off",
|
|
60
|
+
"@typescript-eslint/consistent-type-definitions": "off",
|
|
61
|
+
"@typescript-eslint/consistent-type-imports": [
|
|
62
|
+
"warn",
|
|
63
|
+
{ fixStyle: "inline-type-imports", prefer: "type-imports" },
|
|
64
|
+
],
|
|
65
|
+
"@typescript-eslint/no-misused-promises": [
|
|
66
|
+
"error",
|
|
67
|
+
{ checksVoidReturn: { attributes: false } },
|
|
68
|
+
],
|
|
69
|
+
"@typescript-eslint/no-unused-vars": [
|
|
70
|
+
"warn",
|
|
71
|
+
{
|
|
72
|
+
argsIgnorePattern: "^_",
|
|
73
|
+
caughtErrorsIgnorePattern: "^(_|ignore)",
|
|
74
|
+
destructuredArrayIgnorePattern: "^_",
|
|
75
|
+
varsIgnorePattern: "^_",
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
"@typescript-eslint/require-await": "off",
|
|
79
|
+
"@typescript-eslint/unbound-method": "off",
|
|
80
|
+
|
|
81
|
+
curly: ["warn", "all"],
|
|
82
|
+
"import-x/no-duplicates": "warn",
|
|
83
|
+
"import-x/prefer-default-export": "off",
|
|
84
|
+
"no-console": "off",
|
|
85
|
+
// Allow default exports for Next.js
|
|
86
|
+
"no-restricted-exports": "off",
|
|
87
|
+
"no-underscore-dangle": "off",
|
|
88
|
+
"no-useless-escape": "warn",
|
|
89
|
+
|
|
90
|
+
"object-shorthand": "warn",
|
|
91
|
+
|
|
92
|
+
"perfectionist/sort-imports": "warn",
|
|
93
|
+
"perfectionist/sort-interfaces": "warn",
|
|
94
|
+
"perfectionist/sort-intersection-types": "warn",
|
|
95
|
+
// Perfectionist sorting configuration (from PayloadCMS)
|
|
96
|
+
"perfectionist/sort-jsx-props": "warn",
|
|
97
|
+
"perfectionist/sort-modules": "warn",
|
|
98
|
+
"perfectionist/sort-named-imports": "warn",
|
|
99
|
+
"perfectionist/sort-object-types": "warn",
|
|
100
|
+
"perfectionist/sort-objects": [
|
|
101
|
+
"warn",
|
|
102
|
+
{
|
|
103
|
+
customGroups: [
|
|
104
|
+
{
|
|
105
|
+
elementNamePattern: "^(_id|id|name|slug|type)$",
|
|
106
|
+
groupName: "top",
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
groups: ["top", "unknown"],
|
|
110
|
+
order: "asc",
|
|
111
|
+
partitionByComment: true,
|
|
112
|
+
partitionByNewLine: true,
|
|
113
|
+
type: "natural",
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
"perfectionist/sort-switch-case": "off",
|
|
117
|
+
"perfectionist/sort-union-types": "warn",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
// Disable rules for test files and config files
|
|
122
|
+
files: ["src/test/**/*.js", "src/test/**/*.ts", "tests/**/*.ts", "*.config.js", "*.config.mjs"],
|
|
123
|
+
rules: {
|
|
124
|
+
"import/no-anonymous-default-export": "off",
|
|
125
|
+
},
|
|
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
|
+
{
|
|
148
|
+
languageOptions: {
|
|
149
|
+
parserOptions: {
|
|
150
|
+
ecmaVersion: "latest",
|
|
151
|
+
project: "./tsconfig.json",
|
|
152
|
+
sourceType: "module",
|
|
153
|
+
tsconfigRootDir: import.meta.dirname,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
linterOptions: {
|
|
157
|
+
reportUnusedDisableDirectives: "error",
|
|
158
|
+
},
|
|
159
|
+
settings: {
|
|
160
|
+
// Workaround for ESLint flat config circular reference in diagnostics
|
|
161
|
+
_internalSilentUse: true,
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev --port=3010",
|
|
7
|
+
"vex:dev": "vex dev",
|
|
8
|
+
"vex:generate": "vex dev --once",
|
|
9
|
+
"vex:update": "pnpm add @vexcms/core@latest @vexcms/cli@latest @vexcms/admin-next@latest @vexcms/ui@latest @vexcms/richtext@latest @vexcms/better-auth@latest @vexcms/file-storage-convex@latest",
|
|
10
|
+
"build": "next build",
|
|
11
|
+
"start": "next start",
|
|
12
|
+
"lint": "eslint .",
|
|
13
|
+
"format": "prettier --write .",
|
|
14
|
+
"typecheck": "tsc --noEmit",
|
|
15
|
+
"secret:create": "openssl rand -base64 32 | tr -d '\\n' | tee /dev/stderr | pbcopy && echo '\\n✓ Secret copied to clipboard'"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@base-ui/react": "^1.1.0",
|
|
19
|
+
"@convex-dev/better-auth": "^0.10.10",
|
|
20
|
+
"@convex-dev/react-query": "^0.1.0",
|
|
21
|
+
"@daveyplate/better-auth-ui": "^3.3.15",
|
|
22
|
+
"@t3-oss/env-nextjs": "^0.13.10",
|
|
23
|
+
"@tanstack/react-form": "^1.27.7",
|
|
24
|
+
"@tanstack/react-query": "^5.90.17",
|
|
25
|
+
"@vexcms/admin-next": "^0.0.2",
|
|
26
|
+
"@vexcms/better-auth": "^0.0.2",
|
|
27
|
+
"@vexcms/core": "^0.0.2",
|
|
28
|
+
"@vexcms/richtext": "^0.0.2",
|
|
29
|
+
"@vexcms/ui": "^0.0.2",
|
|
30
|
+
"@vexcms/file-storage-convex": "^0.0.2",
|
|
31
|
+
"better-auth": ">=1.4.9 <1.5.0",
|
|
32
|
+
"class-variance-authority": "^0.7.1",
|
|
33
|
+
"clsx": "^2.1.1",
|
|
34
|
+
"convex": "^1.31.5",
|
|
35
|
+
"convex-helpers": "^0.1.111",
|
|
36
|
+
"lucide-react": "^0.562.0",
|
|
37
|
+
"next": "^16.1.0",
|
|
38
|
+
"next-themes": "^0.4.6",
|
|
39
|
+
"nuqs": "^2.8.8",
|
|
40
|
+
"react": "^19.2.4",
|
|
41
|
+
"react-dom": "^19.2.4",
|
|
42
|
+
"shadcn": "^3.6.3",
|
|
43
|
+
"tailwind-merge": "^3.4.0",
|
|
44
|
+
"tw-animate-css": "^1.4.0",
|
|
45
|
+
"zod": "^4.3.5"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@convex-dev/eslint-plugin": "^1.1.1",
|
|
49
|
+
"@eslint/eslintrc": "^3.3.3",
|
|
50
|
+
"@next/eslint-plugin-next": "^15.4.3",
|
|
51
|
+
"@tailwindcss/postcss": "^4",
|
|
52
|
+
"@types/node": "^20",
|
|
53
|
+
"@types/react": "^19",
|
|
54
|
+
"@types/react-dom": "^19",
|
|
55
|
+
"@vexcms/cli": "^0.0.2",
|
|
56
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
57
|
+
"eslint": "^9",
|
|
58
|
+
"eslint-config-next": "15.5.9",
|
|
59
|
+
"eslint-plugin-import-x": "^4.16.1",
|
|
60
|
+
"eslint-plugin-perfectionist": "^5.3.1",
|
|
61
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
62
|
+
"prettier": "^3.8.1",
|
|
63
|
+
"tailwindcss": "^4",
|
|
64
|
+
"typescript": "^5.9.3",
|
|
65
|
+
"typescript-eslint": "^8.53.0"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AuthView } from "@daveyplate/better-auth-ui"
|
|
2
|
+
import { Activity } from "react"
|
|
3
|
+
|
|
4
|
+
import { Dialog, DialogContent, DialogTitle } from "~/components/ui/dialog"
|
|
5
|
+
|
|
6
|
+
export default function AuthCard({ pathname }: { pathname: string }) {
|
|
7
|
+
return (
|
|
8
|
+
<main>
|
|
9
|
+
<Dialog open>
|
|
10
|
+
<Activity mode="hidden">
|
|
11
|
+
<DialogTitle />
|
|
12
|
+
</Activity>
|
|
13
|
+
<DialogContent
|
|
14
|
+
aria-describedby={undefined}
|
|
15
|
+
className="grid place-items-center border-none bg-transparent shadow-none ring-0"
|
|
16
|
+
showCloseButton={false}
|
|
17
|
+
>
|
|
18
|
+
<AuthView path={pathname} />
|
|
19
|
+
</DialogContent>
|
|
20
|
+
</Dialog>
|
|
21
|
+
</main>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { AuthView } from "@daveyplate/better-auth-ui"
|
|
4
|
+
|
|
5
|
+
export default function AuthCard({ pathname }: { pathname: string }) {
|
|
6
|
+
return (
|
|
7
|
+
<main className="absolute inset-0 grid place-items-center">
|
|
8
|
+
<AuthView path={pathname} />
|
|
9
|
+
</main>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client"
|
|
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
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function Page() {
|
|
20
|
+
const isBootstrapped = useQuery(api.vex.firstUser.isBootstrapped)
|
|
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
|
+
)
|
|
116
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { AdminLayout } from "@vexcms/admin-next"
|
|
4
|
+
import type { PermissionUser } from "@vexcms/admin-next"
|
|
5
|
+
import type { ClientVexConfig } from "@vexcms/core"
|
|
6
|
+
import { access } from "~/vexcms/access"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Client wrapper that provides the access config directly (not through RSC serialization).
|
|
10
|
+
* The access config contains callback functions that can't be serialized through RSC.
|
|
11
|
+
*/
|
|
12
|
+
export function AdminLayoutWrapper({
|
|
13
|
+
config,
|
|
14
|
+
user,
|
|
15
|
+
permissionUser,
|
|
16
|
+
children,
|
|
17
|
+
}: {
|
|
18
|
+
config: ClientVexConfig
|
|
19
|
+
user?: { name: string; email: string; avatar?: string }
|
|
20
|
+
permissionUser?: PermissionUser
|
|
21
|
+
children: React.ReactNode
|
|
22
|
+
}) {
|
|
23
|
+
return (
|
|
24
|
+
<AdminLayout
|
|
25
|
+
config={config}
|
|
26
|
+
user={user}
|
|
27
|
+
permissionUser={permissionUser}
|
|
28
|
+
access={access}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</AdminLayout>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { AdminPage } from "@vexcms/admin-next"
|
|
4
|
+
import type { ClientVexConfig } from "@vexcms/core"
|
|
5
|
+
import { extractLivePreviewConfigs, sanitizeConfigForClient } from "@vexcms/core"
|
|
6
|
+
import { RichTextFieldWithMedia } from "./RichTextFieldWithMedia"
|
|
7
|
+
import originalConfig from "~/../vex.config"
|
|
8
|
+
|
|
9
|
+
const livePreviewConfigs = extractLivePreviewConfigs(originalConfig)
|
|
10
|
+
// Sanitize on the client so component references in admin.components survive
|
|
11
|
+
// (they can't cross the RSC serialization boundary)
|
|
12
|
+
const clientConfig = sanitizeConfigForClient(originalConfig)
|
|
13
|
+
|
|
14
|
+
export function AdminPageWrapper({
|
|
15
|
+
config: _serverConfig,
|
|
16
|
+
path,
|
|
17
|
+
}: {
|
|
18
|
+
config: ClientVexConfig
|
|
19
|
+
path?: string[]
|
|
20
|
+
}) {
|
|
21
|
+
return (
|
|
22
|
+
<AdminPage
|
|
23
|
+
config={clientConfig}
|
|
24
|
+
path={path}
|
|
25
|
+
livePreviewConfigs={livePreviewConfigs}
|
|
26
|
+
renderRichTextField={(props: any) => (
|
|
27
|
+
<RichTextFieldWithMedia
|
|
28
|
+
field={props.field}
|
|
29
|
+
fieldDef={props.fieldDef}
|
|
30
|
+
name={props.name}
|
|
31
|
+
generateUploadUrl={props.generateUploadUrl}
|
|
32
|
+
createMediaDocument={props.createMediaDocument}
|
|
33
|
+
onUploadNew={props.onUploadNew}
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { PlateEditorField } from "@vexcms/richtext/editor"
|
|
4
|
+
import { defaultFeatures } from "@vexcms/richtext/editor"
|
|
5
|
+
import { useMediaPickerState } from "@vexcms/admin-next"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Inner component that always calls useMediaPickerState (no conditional hook).
|
|
9
|
+
*/
|
|
10
|
+
function WithMedia({
|
|
11
|
+
field,
|
|
12
|
+
fieldDef,
|
|
13
|
+
name,
|
|
14
|
+
mediaCollection,
|
|
15
|
+
generateUploadUrl,
|
|
16
|
+
createMediaDocument,
|
|
17
|
+
onUploadNew,
|
|
18
|
+
}: {
|
|
19
|
+
field: any
|
|
20
|
+
fieldDef: any
|
|
21
|
+
name: string
|
|
22
|
+
mediaCollection: string
|
|
23
|
+
generateUploadUrl?: () => Promise<string>
|
|
24
|
+
createMediaDocument?: (props: { collectionSlug: string; fields: Record<string, unknown> }) => Promise<string>
|
|
25
|
+
onUploadNew?: () => void
|
|
26
|
+
}) {
|
|
27
|
+
const mediaPicker = useMediaPickerState({
|
|
28
|
+
collectionSlug: mediaCollection,
|
|
29
|
+
searchField: "filename",
|
|
30
|
+
searchIndexName: "search_filename",
|
|
31
|
+
selectedId: null,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<PlateEditorField
|
|
36
|
+
value={field.state.value}
|
|
37
|
+
onChange={(val: any) => field.handleChange(val)}
|
|
38
|
+
name={name}
|
|
39
|
+
label={fieldDef.label ?? name}
|
|
40
|
+
description={fieldDef.description}
|
|
41
|
+
features={defaultFeatures}
|
|
42
|
+
mediaCollection={mediaCollection}
|
|
43
|
+
mediaResults={mediaPicker.results}
|
|
44
|
+
mediaSearchTerm={mediaPicker.searchTerm}
|
|
45
|
+
onMediaSearchChange={mediaPicker.setSearchTerm}
|
|
46
|
+
mediaCanLoadMore={mediaPicker.canLoadMore}
|
|
47
|
+
onMediaLoadMore={mediaPicker.loadMore}
|
|
48
|
+
mediaIsLoading={mediaPicker.isLoading}
|
|
49
|
+
onUploadNew={onUploadNew}
|
|
50
|
+
generateUploadUrl={generateUploadUrl}
|
|
51
|
+
createMediaDocument={createMediaDocument}
|
|
52
|
+
/>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Wrapper that creates media picker state for a richtext field
|
|
58
|
+
* when mediaCollection is configured.
|
|
59
|
+
*/
|
|
60
|
+
export function RichTextFieldWithMedia({
|
|
61
|
+
field,
|
|
62
|
+
fieldDef,
|
|
63
|
+
name,
|
|
64
|
+
generateUploadUrl,
|
|
65
|
+
createMediaDocument,
|
|
66
|
+
onUploadNew,
|
|
67
|
+
}: {
|
|
68
|
+
field: any
|
|
69
|
+
fieldDef: any
|
|
70
|
+
name: string
|
|
71
|
+
generateUploadUrl?: () => Promise<string>
|
|
72
|
+
createMediaDocument?: (props: { collectionSlug: string; fields: Record<string, unknown> }) => Promise<string>
|
|
73
|
+
onUploadNew?: () => void
|
|
74
|
+
}) {
|
|
75
|
+
const mediaCollection = fieldDef?.mediaCollection as string | undefined
|
|
76
|
+
|
|
77
|
+
if (mediaCollection) {
|
|
78
|
+
return (
|
|
79
|
+
<WithMedia
|
|
80
|
+
field={field}
|
|
81
|
+
fieldDef={fieldDef}
|
|
82
|
+
name={name}
|
|
83
|
+
mediaCollection={mediaCollection}
|
|
84
|
+
generateUploadUrl={generateUploadUrl}
|
|
85
|
+
createMediaDocument={createMediaDocument}
|
|
86
|
+
onUploadNew={onUploadNew}
|
|
87
|
+
/>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<PlateEditorField
|
|
93
|
+
value={field.state.value}
|
|
94
|
+
onChange={(val: any) => field.handleChange(val)}
|
|
95
|
+
name={name}
|
|
96
|
+
label={fieldDef.label ?? name}
|
|
97
|
+
description={fieldDef.description}
|
|
98
|
+
features={defaultFeatures}
|
|
99
|
+
/>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { sanitizeConfigForClient } from "@vexcms/core"
|
|
2
|
+
|
|
3
|
+
import config from "~/../vex.config"
|
|
4
|
+
import { AdminPageWrapper } from "../AdminPageWrapper"
|
|
5
|
+
|
|
6
|
+
const clientConfig = sanitizeConfigForClient(config)
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
params: Promise<{ path?: string[] }>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default async function Page({ params }: Props) {
|
|
13
|
+
const { path } = await params
|
|
14
|
+
return <AdminPageWrapper config={clientConfig} path={path} />
|
|
15
|
+
}
|