create-vexcms 0.0.20 → 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 -25
- 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 +97 -12
- 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/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 -333
- 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
|
@@ -4,15 +4,17 @@ import { cookies } from "next/headers"
|
|
|
4
4
|
|
|
5
5
|
export async function getSessionToken() {
|
|
6
6
|
const cookieStore = await cookies()
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
const sessionTokenCookie = cookieStore.get("better-auth.session_token")?.value
|
|
8
|
+
|
|
9
|
+
if (!sessionTokenCookie) return null
|
|
10
|
+
|
|
11
|
+
// better-auth stores tokens as "<raw_token>.<hmac_signature>" (2 parts, not a JWT)
|
|
12
|
+
// The raw token (first part) is what's indexed in the DB by_token
|
|
13
|
+
if (sessionTokenCookie.includes(".")) {
|
|
14
|
+
return sessionTokenCookie.split(".")[0]
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
return sessionTokenCookie
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/**
|
|
@@ -22,17 +24,12 @@ export async function getSessionToken() {
|
|
|
22
24
|
export const getCurrentUser = async () => {
|
|
23
25
|
try {
|
|
24
26
|
const sessionToken = await getSessionToken()
|
|
25
|
-
|
|
26
|
-
if (!sessionToken) {
|
|
27
|
-
return null
|
|
28
|
-
}
|
|
27
|
+
if (!sessionToken) return null
|
|
29
28
|
|
|
30
29
|
const session = await fetchQuery(api.auth.sessions.getSessionWithUser, {
|
|
31
30
|
sessionToken,
|
|
32
31
|
})
|
|
33
|
-
if (!session?.user)
|
|
34
|
-
return null
|
|
35
|
-
}
|
|
32
|
+
if (!session?.user) return null
|
|
36
33
|
return session.user
|
|
37
34
|
} catch (error) {
|
|
38
35
|
console.error("Error getting current user:", error)
|
|
@@ -46,22 +43,15 @@ export const getCurrentUser = async () => {
|
|
|
46
43
|
export async function getSession() {
|
|
47
44
|
try {
|
|
48
45
|
const sessionToken = await getSessionToken()
|
|
49
|
-
|
|
50
|
-
if (!sessionToken) {
|
|
51
|
-
console.log("no session token")
|
|
52
|
-
return null
|
|
53
|
-
}
|
|
46
|
+
if (!sessionToken) return null
|
|
54
47
|
|
|
55
48
|
const session = await fetchQuery(api.auth.sessions.getSessionWithUser, {
|
|
56
49
|
sessionToken,
|
|
57
50
|
})
|
|
58
|
-
if (!session?.user)
|
|
59
|
-
console.log("no session")
|
|
60
|
-
return null
|
|
61
|
-
}
|
|
51
|
+
if (!session?.user) return null
|
|
62
52
|
return session
|
|
63
53
|
} catch (error) {
|
|
64
|
-
console.error("Error
|
|
54
|
+
console.error("[getSession] Error:", error)
|
|
65
55
|
return null
|
|
66
56
|
}
|
|
67
57
|
}
|
|
@@ -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
|
|
15
|
+
className="opacity-75"
|
|
16
|
+
fill="currentColor"
|
|
17
|
+
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"
|
|
18
|
+
/>
|
|
19
|
+
</svg>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Landing UI for a project that has not been bootstrapped yet (no admin user),
|
|
25
|
+
* and the "Go to Admin" / "Sign in" entry point once it has.
|
|
26
|
+
*
|
|
27
|
+
* Renders directly on `templates/base-nextjs`'s home route (no `pages`
|
|
28
|
+
* collection exists there). The marketing overlay's `PageContent` renders it
|
|
29
|
+
* as a fallback instead, when `pages.getBySlug("home")` returns no document.
|
|
30
|
+
*
|
|
31
|
+
* Promotes the first signed-in user to admin exactly once: `promoteFirstAdmin`
|
|
32
|
+
* is a Convex mutation, so concurrent signups cannot both win — Convex
|
|
33
|
+
* serializes mutations, and the loser observes `isBootstrapped` already true.
|
|
34
|
+
*/
|
|
35
|
+
export function WelcomePage() {
|
|
36
|
+
const isBootstrapped = useQuery(api.vex.firstUser.isBootstrapped)
|
|
37
|
+
const promoteFirstAdmin = useMutation(api.vex.firstUser.promoteFirstAdmin)
|
|
38
|
+
const { data: session } = useSession()
|
|
39
|
+
const router = useRouter()
|
|
40
|
+
const pathname = usePathname()
|
|
41
|
+
const [promoting, setPromoting] = useState(false)
|
|
42
|
+
const [navigating, setNavigating] = useState(false)
|
|
43
|
+
|
|
44
|
+
// Reset navigating state when the user comes back to the home page
|
|
45
|
+
// (e.g. closing the intercepting auth dialog)
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (pathname === "/") {
|
|
48
|
+
setNavigating(false)
|
|
49
|
+
}
|
|
50
|
+
}, [pathname])
|
|
51
|
+
|
|
52
|
+
// If the user is signed in and this is a fresh project, try to promote them
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (session?.user && isBootstrapped === false && !promoting) {
|
|
55
|
+
setPromoting(true)
|
|
56
|
+
setNavigating(true)
|
|
57
|
+
promoteFirstAdmin()
|
|
58
|
+
.then((result) => {
|
|
59
|
+
if (result.promoted) {
|
|
60
|
+
router.push("/admin")
|
|
61
|
+
} else {
|
|
62
|
+
setNavigating(false)
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
.catch(() => {
|
|
66
|
+
setPromoting(false)
|
|
67
|
+
setNavigating(false)
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
}, [session, isBootstrapped, promoting, promoteFirstAdmin, router])
|
|
71
|
+
|
|
72
|
+
// Treat undefined (query still loading) as not bootstrapped — show the welcome page immediately
|
|
73
|
+
const bootstrapped = isBootstrapped === true
|
|
74
|
+
|
|
75
|
+
const buttonClass =
|
|
76
|
+
"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"
|
|
77
|
+
|
|
78
|
+
const handleNavigate = (path: string) => {
|
|
79
|
+
setNavigating(true)
|
|
80
|
+
router.push(path)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div className="flex min-h-screen flex-col items-center justify-center gap-8 p-8">
|
|
85
|
+
<div className="text-center">
|
|
86
|
+
<h1 className="text-4xl font-bold tracking-tight">VexCMS</h1>
|
|
87
|
+
<p className="mt-3 text-lg text-muted-foreground">
|
|
88
|
+
{bootstrapped
|
|
89
|
+
? "Your content management system is ready."
|
|
90
|
+
: "Welcome! Create your admin account to get started."}
|
|
91
|
+
</p>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div className="flex gap-4">
|
|
95
|
+
{session?.user ? (
|
|
96
|
+
<button onClick={() => handleNavigate("/admin")} disabled={navigating} className={buttonClass}>
|
|
97
|
+
{navigating && <LoadingSpinner />}
|
|
98
|
+
{navigating ? "Loading..." : "Go to Admin Panel"}
|
|
99
|
+
</button>
|
|
100
|
+
) : bootstrapped ? (
|
|
101
|
+
<button onClick={() => handleNavigate("/auth/sign-in")} disabled={navigating} className={buttonClass}>
|
|
102
|
+
{navigating && <LoadingSpinner />}
|
|
103
|
+
{navigating ? "Loading..." : "Sign In"}
|
|
104
|
+
</button>
|
|
105
|
+
) : (
|
|
106
|
+
<button onClick={() => handleNavigate("/auth/sign-up")} disabled={navigating} className={buttonClass}>
|
|
107
|
+
{navigating && <LoadingSpinner />}
|
|
108
|
+
{navigating ? "Loading..." : "Create Admin Account"}
|
|
109
|
+
</button>
|
|
110
|
+
)}
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
{promoting && <p className="text-sm text-muted-foreground animate-pulse">Setting up your admin account...</p>}
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
116
|
+
}
|