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.
Files changed (153) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +93 -83
  3. package/dist/index.js +246 -81
  4. package/dist/index.mjs +30976 -0
  5. package/package.json +35 -15
  6. package/templates/base-nextjs/README.md +53 -205
  7. package/templates/base-nextjs/_env.example +7 -9
  8. package/templates/base-nextjs/_gitignore +41 -1
  9. package/templates/base-nextjs/convex/_generated/api.d.ts +6 -62
  10. package/templates/base-nextjs/convex/_generated/server.d.ts +19 -0
  11. package/templates/base-nextjs/convex/_generated/server.js +8 -0
  12. package/templates/base-nextjs/convex/auth/api.ts +49 -3
  13. package/templates/base-nextjs/convex/auth/db.ts +42 -300
  14. package/templates/base-nextjs/convex/auth/index.ts +11 -7
  15. package/templates/base-nextjs/convex/auth/options.ts +5 -29
  16. package/templates/base-nextjs/convex/auth/plugins/index.ts +15 -5
  17. package/templates/base-nextjs/convex/auth/sessions.ts +9 -15
  18. package/templates/base-nextjs/convex/http.ts +18 -3
  19. package/templates/base-nextjs/convex/schema.ts +104 -2
  20. package/templates/base-nextjs/convex/vex/firstUser.ts +10 -84
  21. package/templates/base-nextjs/convex/vex/globals.ts +19 -82
  22. package/templates/base-nextjs/convex/vex/media.ts +19 -246
  23. package/templates/base-nextjs/convex/vex.schema.ts +138 -0
  24. package/templates/base-nextjs/convex/vex.ts +19 -0
  25. package/templates/base-nextjs/convex.json +6 -0
  26. package/templates/base-nextjs/eslint.config.mjs +1 -21
  27. package/templates/base-nextjs/next.config.ts +10 -0
  28. package/templates/base-nextjs/package.json +47 -48
  29. package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +17 -5
  30. package/templates/base-nextjs/src/app/(frontend)/auth/[pathname]/view.tsx +2 -4
  31. package/templates/base-nextjs/src/app/(frontend)/layout.tsx +12 -2
  32. package/templates/base-nextjs/src/app/(frontend)/page.tsx +2 -113
  33. package/templates/base-nextjs/src/app/(vexcms)/admin/[[...path]]/page.tsx +40 -0
  34. package/templates/base-nextjs/src/app/(vexcms)/admin/clientProviders.tsx +22 -0
  35. package/templates/base-nextjs/src/app/(vexcms)/admin/layout.tsx +19 -0
  36. package/templates/base-nextjs/src/app/globals.css +240 -104
  37. package/templates/base-nextjs/src/app/layout.tsx +23 -13
  38. package/templates/base-nextjs/src/app/unauthorized/page.tsx +24 -0
  39. package/templates/base-nextjs/src/auth/access.ts +46 -0
  40. package/templates/base-nextjs/src/auth/client.tsx +17 -5
  41. package/templates/base-nextjs/src/auth/hasPermission.ts +39 -0
  42. package/templates/base-nextjs/src/auth/serverUtils.ts +15 -25
  43. package/templates/base-nextjs/src/components/WelcomePage.tsx +116 -0
  44. package/templates/base-nextjs/src/components/component-example.tsx +56 -464
  45. package/templates/base-nextjs/src/components/providers/auth.tsx +13 -0
  46. package/templates/base-nextjs/src/components/providers/client.tsx +6 -1
  47. package/templates/base-nextjs/src/components/providers/convex.tsx +53 -21
  48. package/templates/base-nextjs/src/components/providers/server.tsx +10 -7
  49. package/templates/{marketing-site → base-nextjs}/src/components/ui/accordion.tsx +30 -32
  50. package/templates/base-nextjs/src/components/ui/badge.tsx +10 -13
  51. package/templates/base-nextjs/src/components/ui/input.tsx +35 -5
  52. package/templates/base-nextjs/src/context/AuthContext.tsx +29 -0
  53. package/templates/base-nextjs/src/db/constants/auth.ts +0 -1
  54. package/templates/base-nextjs/src/db/constants/index.ts +22 -35
  55. package/templates/base-nextjs/src/lib/utils.ts +0 -12
  56. package/templates/base-nextjs/src/proxy.ts +97 -12
  57. package/templates/base-nextjs/src/vex.config.ts +35 -0
  58. package/templates/base-nextjs/src/vex.types.ts +293 -0
  59. package/templates/base-nextjs/src/vexcms/collections/images.ts +15 -0
  60. package/templates/base-nextjs/src/vexcms/collections/index.ts +2 -1
  61. package/templates/base-nextjs/src/vexcms/collections/users.ts +7 -35
  62. package/templates/base-nextjs/tsconfig.json +3 -0
  63. package/templates/marketing-site/convex/_generated/api.d.ts +81 -0
  64. package/templates/marketing-site/convex/_generated/api.js +23 -0
  65. package/templates/marketing-site/convex/_generated/dataModel.d.ts +60 -0
  66. package/templates/marketing-site/convex/_generated/server.d.ts +162 -0
  67. package/templates/marketing-site/convex/_generated/server.js +101 -0
  68. package/templates/marketing-site/convex/footers.ts +11 -7
  69. package/templates/marketing-site/convex/headers.ts +15 -5
  70. package/templates/marketing-site/convex/pages.ts +20 -35
  71. package/templates/marketing-site/convex/schema.ts +119 -0
  72. package/templates/marketing-site/convex/seed.ts +582 -35
  73. package/templates/marketing-site/convex/siteSettings.ts +19 -5
  74. package/templates/marketing-site/convex/theme.ts +55 -61
  75. package/templates/marketing-site/convex/vex.schema.ts +355 -0
  76. package/templates/marketing-site/src/app/(frontend)/PageContent.tsx +27 -160
  77. package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +6 -9
  78. package/templates/marketing-site/src/app/(frontend)/layout.tsx +4 -2
  79. package/templates/marketing-site/src/app/(frontend)/page.tsx +2 -5
  80. package/templates/marketing-site/src/components/SiteFooter.tsx +6 -3
  81. package/templates/marketing-site/src/components/SiteHeader.tsx +6 -3
  82. package/templates/marketing-site/src/components/ThemeLive.tsx +59 -0
  83. package/templates/marketing-site/src/components/ThemeStyle.tsx +45 -89
  84. package/templates/marketing-site/src/lib/metadata.ts +51 -72
  85. package/templates/marketing-site/src/vex.config.ts +35 -0
  86. package/templates/marketing-site/src/vex.types.ts +634 -0
  87. package/templates/marketing-site/src/vexcms/api.ts +23 -0
  88. package/templates/marketing-site/src/vexcms/blocks/CTA/config.ts +3 -3
  89. package/templates/marketing-site/src/vexcms/blocks/CTA/index.tsx +2 -2
  90. package/templates/marketing-site/src/vexcms/blocks/FAQ/config.ts +4 -4
  91. package/templates/marketing-site/src/vexcms/blocks/FAQ/index.tsx +2 -2
  92. package/templates/marketing-site/src/vexcms/blocks/Features/config.ts +4 -8
  93. package/templates/marketing-site/src/vexcms/blocks/Features/index.tsx +2 -2
  94. package/templates/marketing-site/src/vexcms/blocks/Footer/config.ts +7 -11
  95. package/templates/marketing-site/src/vexcms/blocks/Footer/index.tsx +1 -1
  96. package/templates/marketing-site/src/vexcms/blocks/Header/config.ts +9 -9
  97. package/templates/marketing-site/src/vexcms/blocks/Header/index.tsx +4 -5
  98. package/templates/marketing-site/src/vexcms/blocks/Hero/config.ts +3 -4
  99. package/templates/marketing-site/src/vexcms/blocks/Hero/index.tsx +10 -19
  100. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/config.ts +4 -9
  101. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/index.tsx +3 -6
  102. package/templates/marketing-site/src/vexcms/blocks/Roadmap/config.ts +28 -68
  103. package/templates/marketing-site/src/vexcms/blocks/Roadmap/index.tsx +13 -43
  104. package/templates/marketing-site/src/vexcms/blocks/config.ts +7 -22
  105. package/templates/marketing-site/src/vexcms/blocks/constants.ts +2 -3
  106. package/templates/marketing-site/src/vexcms/blocks/index.ts +9 -8
  107. package/templates/marketing-site/src/vexcms/collections/footers.ts +1 -1
  108. package/templates/marketing-site/src/vexcms/collections/headers.ts +1 -1
  109. package/templates/marketing-site/src/vexcms/collections/index.ts +2 -1
  110. package/templates/marketing-site/src/vexcms/collections/pages.ts +12 -21
  111. package/templates/marketing-site/src/vexcms/collections/themeColors.ts +118 -0
  112. package/templates/marketing-site/src/vexcms/collections/themes.ts +29 -111
  113. package/templates/marketing-site/src/vexcms/globals/index.ts +1 -1
  114. package/templates/marketing-site/src/vexcms/globals/siteSettings.ts +43 -27
  115. package/templates/base-nextjs/convex/auth/adapter/index.ts +0 -230
  116. package/templates/base-nextjs/convex/auth/adapter/utils.ts +0 -547
  117. package/templates/base-nextjs/convex/auth/config.ts +0 -10
  118. package/templates/base-nextjs/convex/vex/auth.ts +0 -38
  119. package/templates/base-nextjs/convex/vex/collections.ts +0 -333
  120. package/templates/base-nextjs/convex/vex/helpers.ts +0 -33
  121. package/templates/base-nextjs/convex/vex/impersonation.ts +0 -51
  122. package/templates/base-nextjs/convex/vex/migrate.ts +0 -74
  123. package/templates/base-nextjs/convex/vex/model/collections.ts +0 -196
  124. package/templates/base-nextjs/convex/vex/model/media.ts +0 -87
  125. package/templates/base-nextjs/convex/vex/model/versions.ts +0 -254
  126. package/templates/base-nextjs/convex/vex/previewSnapshot.ts +0 -53
  127. package/templates/base-nextjs/convex/vex/versions.ts +0 -648
  128. package/templates/base-nextjs/src/app/admin/AdminLayoutWrapper.tsx +0 -33
  129. package/templates/base-nextjs/src/app/admin/AdminPageWrapper.tsx +0 -38
  130. package/templates/base-nextjs/src/app/admin/RichTextFieldWithMedia.tsx +0 -101
  131. package/templates/base-nextjs/src/app/admin/[[...path]]/page.tsx +0 -15
  132. package/templates/base-nextjs/src/app/admin/layout.tsx +0 -51
  133. package/templates/base-nextjs/src/app/favicon.ico +0 -0
  134. package/templates/base-nextjs/src/components/providers/theme.tsx +0 -11
  135. package/templates/base-nextjs/src/components/ui/theme-toggle.tsx +0 -67
  136. package/templates/base-nextjs/src/db/types.ts +0 -6
  137. package/templates/base-nextjs/src/vexcms/access.ts +0 -44
  138. package/templates/base-nextjs/src/vexcms/auth.ts +0 -4
  139. package/templates/base-nextjs/src/vexcms/collections/media.ts +0 -15
  140. package/templates/base-nextjs/vex.config.ts +0 -25
  141. package/templates/marketing-site/public/favicons/favicon.ico +0 -0
  142. package/templates/marketing-site/src/app/(frontend)/preview/[slug]/PreviewPageContent.tsx +0 -55
  143. package/templates/marketing-site/src/app/(frontend)/preview/[slug]/page.tsx +0 -27
  144. package/templates/marketing-site/src/app/(frontend)/preview/layout.tsx +0 -18
  145. package/templates/marketing-site/src/app/admin/AdminLayoutWrapper.tsx +0 -71
  146. package/templates/marketing-site/src/components/ThemeInjector.tsx +0 -128
  147. package/templates/marketing-site/src/components/admin/IconPickerField.tsx +0 -134
  148. package/templates/marketing-site/src/components/admin/ThemeImport.tsx +0 -174
  149. package/templates/marketing-site/src/components/admin/ThemeImportField.tsx +0 -33
  150. package/templates/marketing-site/src/db/constants/index.ts +0 -52
  151. package/templates/marketing-site/src/lib/colorConvert.ts +0 -171
  152. package/templates/marketing-site/src/vexcms/access.ts +0 -36
  153. package/templates/marketing-site/vex.config.ts +0 -32
@@ -1,99 +1,78 @@
1
1
  import type { Metadata } from "next"
2
2
 
3
- import { buildSiteMetadata } from "@vexcms/core"
4
3
  import { fetchQuery } from "convex/nextjs"
5
4
 
6
5
  import { api } from "@convex/_generated/api"
7
6
  import { anyApi } from "convex/server"
8
7
 
8
+ const TITLE_SUFFIX = " | Vex CMS"
9
+
10
+ /**
11
+ * Resolves an `upload()` field's stored media id to a public URL via the
12
+ * Convex file storage adapter. Returns `undefined` when the media document
13
+ * no longer exists or the deployment is unreachable.
14
+ */
15
+ async function resolveMediaUrl(mediaId: string): Promise<string | undefined> {
16
+ try {
17
+ const result = (await fetchQuery(anyApi.vex.media.getUrl, {
18
+ adapter: "convex",
19
+ mediaId,
20
+ })) as { url?: string; error?: string }
21
+ return result.url
22
+ } catch {
23
+ return undefined
24
+ }
25
+ }
26
+
9
27
  /**
10
28
  * Generate Next.js Metadata for a page.
11
- * Fetches site settings and optionally a page document,
12
- * merges with buildSiteMetadata, and returns Next.js Metadata format.
29
+ *
30
+ * Fetches site settings and, when a slug is given, the matching page
31
+ * document, then merges them — page-level `metaTitle`/`metaDescription`/
32
+ * `ogImage` win over the site's defaults from `siteSettings`.
13
33
  *
14
34
  * @param props.slug - Optional page slug to fetch per-page SEO overrides
15
35
  */
16
- export async function generatePageMetadata(props: {
17
- slug?: string
18
- }): Promise<Metadata> {
36
+ export async function generatePageMetadata(props: { slug?: string }): Promise<Metadata> {
19
37
  try {
20
- // Fetch site settings
21
- const settings = (await fetchQuery(api.siteSettings.get)) as Record<
22
- string,
23
- unknown
24
- > | null
25
-
38
+ const settings = (await fetchQuery(api.siteSettings.get)) as Record<string, unknown> | null
26
39
  if (!settings) {
27
40
  return { title: "Untitled" }
28
41
  }
29
42
 
30
- // Resolve the ogImage URL from site settings (upload ID → URL)
31
- let siteOgImageUrl: string | undefined
32
- const ogImageId = settings.ogImage as string | undefined
33
- if (ogImageId) {
34
- try {
35
- const mediaDoc = (await fetchQuery(
36
- anyApi.vex.collections.getDocument,
37
- {
38
- collectionSlug: "media",
39
- documentId: ogImageId,
40
- }
41
- )) as Record<string, unknown> | null
42
- siteOgImageUrl = (mediaDoc?.url as string) || undefined
43
- } catch {
44
- // Media document not found — skip ogImage
45
- }
46
- }
47
-
48
- // Fetch page data for per-page overrides
49
- let pageData: Record<string, unknown> | null = null
43
+ let pageData: Record<string, unknown> | undefined
50
44
  if (props.slug) {
51
- pageData = await fetchQuery(api.pages.getBySlug, {
52
- slug: props.slug,
53
- _vexDrafts: false,
54
- })
45
+ const pages = (await fetchQuery(api.pages.getBySlug, { slug: props.slug })) as
46
+ | Record<string, unknown>[]
47
+ | undefined
48
+ pageData = pages?.[0]
55
49
  }
56
50
 
57
- // Build framework-agnostic metadata
58
- const meta = buildSiteMetadata({
59
- site: {
60
- name: settings.name as string | undefined,
61
- metaTitle: settings.metaTitle as string | undefined,
62
- metaDescription: settings.metaDescription as string | undefined,
63
- description: settings.description as string | undefined,
64
- ogImage: siteOgImageUrl,
65
- twitterHandle: settings.twitterHandle as string | undefined,
66
- },
67
- page: pageData
68
- ? {
69
- title: pageData.title as string | undefined,
70
- metaTitle: pageData.metaTitle as string | undefined,
71
- metaDescription: pageData.metaDescription as string | undefined,
72
- ogImage: pageData.ogImage as string | undefined,
73
- }
74
- : undefined,
75
- titleSuffix: " | Vex CMS",
76
- })
51
+ const pageTitle = (pageData?.metaTitle as string | undefined) ?? (pageData?.title as string | undefined)
52
+ const siteName = settings.name as string | undefined
53
+ const title = (pageTitle ?? (settings.metaTitle as string | undefined) ?? siteName ?? "Untitled") + TITLE_SUFFIX
54
+ const description =
55
+ (pageData?.metaDescription as string | undefined) ??
56
+ (settings.metaDescription as string | undefined) ??
57
+ (settings.description as string | undefined)
77
58
 
78
- // Map to Next.js Metadata
79
- const metadata: Metadata = {
80
- title: meta.title,
81
- description: meta.description,
82
- }
59
+ // `upload()` fields always store an array of media ids — the first entry
60
+ // is the selection. Page-level ogImage wins over the site default.
61
+ const pageOgImageId = (pageData?.ogImage as string[] | undefined)?.[0]
62
+ const siteOgImageId = (settings.ogImage as string[] | undefined)?.[0]
63
+ const ogImageId = pageOgImageId ?? siteOgImageId
64
+ const ogImageUrl = ogImageId ? await resolveMediaUrl(ogImageId) : undefined
65
+
66
+ const twitterHandle = settings.twitterHandle as string | undefined
67
+
68
+ const metadata: Metadata = { title, description }
83
69
 
84
- if (meta.ogImage) {
85
- metadata.openGraph = {
86
- title: meta.title,
87
- description: meta.description,
88
- images: [{ url: meta.ogImage }],
89
- }
70
+ if (ogImageUrl) {
71
+ metadata.openGraph = { title, description, images: [{ url: ogImageUrl }] }
90
72
  }
91
73
 
92
- if (meta.twitterHandle) {
93
- metadata.twitter = {
94
- card: "summary_large_image",
95
- site: meta.twitterHandle,
96
- }
74
+ if (twitterHandle) {
75
+ metadata.twitter = { card: "summary_large_image", site: twitterHandle }
97
76
  }
98
77
 
99
78
  return metadata
@@ -0,0 +1,35 @@
1
+ import { betterAuthAdapter } from "@vexcms/better-auth"
2
+ import { defineConfig } from "@vexcms/core"
3
+ import { convexFileStorage } from "@vexcms/file-storage-convex"
4
+
5
+ import { authOptions } from "@convex/auth/options"
6
+
7
+ import { access } from "~/auth/access"
8
+ import { footers, headers, images, pages, themes, users } from "~/vexcms/collections"
9
+ import { siteSettings } from "~/vexcms/globals"
10
+
11
+ /**
12
+ * VexCMS configuration for the marketing site.
13
+ *
14
+ * Replaces `templates/base-nextjs`'s bare config wholesale (overlay copy is
15
+ * file-level, not a merge): carries base's `users`/`images` forward unchanged
16
+ * and adds the four marketing collections plus `siteSettings`. `vex dev`/
17
+ * `vex generate` consume this to produce the Convex schema and TypeScript
18
+ * types.
19
+ */
20
+ const vexConfig = defineConfig({
21
+ access,
22
+ admin: {
23
+ sidebar: {
24
+ side: "right",
25
+ },
26
+ },
27
+ authAdapter: betterAuthAdapter({ config: authOptions }),
28
+ storage: {
29
+ adapters: [convexFileStorage({ mediaCollections: [images] })],
30
+ },
31
+ collections: [users, pages, headers, footers, themes],
32
+ globals: [siteSettings],
33
+ })
34
+
35
+ export default vexConfig