create-vexcms 0.1.0-alpha.2 → 0.1.0-alpha.21

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 (115) hide show
  1. package/README.md +48 -14
  2. package/dist/index.js +1714 -1504
  3. package/package.json +2 -2
  4. package/templates/base-nextjs/README.md +18 -5
  5. package/templates/base-nextjs/convex/auth/index.ts +2 -2
  6. package/templates/base-nextjs/convex/auth/options.ts +27 -26
  7. package/templates/base-nextjs/convex/auth/schema.ts +51 -0
  8. package/templates/base-nextjs/convex/auth/sessions.ts +3 -3
  9. package/templates/base-nextjs/convex/tsconfig.json +2 -2
  10. package/templates/base-nextjs/convex/vex/firstUser.ts +2 -2
  11. package/templates/base-nextjs/convex/vex/globals.ts +3 -2
  12. package/templates/base-nextjs/convex/vex/media.ts +3 -2
  13. package/templates/base-nextjs/convex/vex.schema.ts +1 -0
  14. package/templates/base-nextjs/convex/vex.ts +17 -4
  15. package/templates/base-nextjs/next.config.ts +52 -6
  16. package/templates/base-nextjs/package.json +18 -7
  17. package/templates/base-nextjs/public/favicons/favicon.ico +0 -0
  18. package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +27 -15
  19. package/templates/base-nextjs/src/app/(vexcms)/admin/[[...path]]/page.tsx +2 -2
  20. package/templates/base-nextjs/src/app/(vexcms)/admin/clientProviders.tsx +9 -14
  21. package/templates/base-nextjs/src/app/(vexcms)/admin/layout.tsx +8 -6
  22. package/templates/base-nextjs/src/app/api/vex/revalidate/route.ts +19 -0
  23. package/templates/base-nextjs/src/app/globals.css +10 -9
  24. package/templates/base-nextjs/src/app/layout.tsx +6 -0
  25. package/templates/base-nextjs/src/auth/access.ts +14 -2
  26. package/templates/base-nextjs/src/auth/serverUtils.ts +20 -6
  27. package/templates/base-nextjs/src/components/WelcomePage.tsx +54 -43
  28. package/templates/base-nextjs/src/components/component-example.tsx +1 -1
  29. package/templates/base-nextjs/src/components/providers/server.tsx +16 -8
  30. package/templates/base-nextjs/src/vex.config.server.ts +30 -0
  31. package/templates/base-nextjs/src/vex.config.ts +14 -11
  32. package/templates/base-nextjs/src/vex.types.ts +2 -1
  33. package/templates/base-nextjs/src/vexcms/collections/images.ts +1 -1
  34. package/templates/base-nextjs/src/vexcms/collections/users.ts +3 -1
  35. package/templates/marketing-site/convex/pages.ts +20 -1
  36. package/templates/marketing-site/convex/schema.ts +4 -4
  37. package/templates/marketing-site/convex/seed.ts +515 -70
  38. package/templates/marketing-site/convex/siteSettings.ts +1 -1
  39. package/templates/marketing-site/convex/theme.ts +3 -3
  40. package/templates/marketing-site/convex/vex.schema.ts +75 -38
  41. package/templates/marketing-site/public/favicon.svg +14 -0
  42. package/templates/marketing-site/public/favicons/apple-touch-icon.png +0 -0
  43. package/templates/marketing-site/public/favicons/favicon.ico +0 -0
  44. package/templates/marketing-site/src/app/(frontend)/(site)/PageContent.tsx +64 -0
  45. package/templates/marketing-site/src/app/(frontend)/(site)/SiteLivePreviewProvider.tsx +31 -0
  46. package/templates/marketing-site/src/app/(frontend)/(site)/[slug]/page.tsx +63 -0
  47. package/templates/marketing-site/src/app/(frontend)/(site)/layout.tsx +50 -0
  48. package/templates/marketing-site/src/app/(frontend)/(site)/page.tsx +33 -0
  49. package/templates/marketing-site/src/app/(frontend)/layout.tsx +10 -24
  50. package/templates/marketing-site/src/app/(vexcms)/admin/layout.tsx +32 -0
  51. package/templates/marketing-site/src/app/globals.css +413 -0
  52. package/templates/marketing-site/src/app/layout.tsx +85 -0
  53. package/templates/marketing-site/src/app/robots.ts +13 -0
  54. package/templates/marketing-site/src/app/sitemap.ts +18 -0
  55. package/templates/marketing-site/src/components/BrandMark.tsx +27 -0
  56. package/templates/marketing-site/src/components/CodeHighlightContext.tsx +76 -0
  57. package/templates/marketing-site/src/components/CodePane.tsx +98 -0
  58. package/templates/marketing-site/src/components/Container.tsx +25 -0
  59. package/templates/marketing-site/src/components/CopyButton.tsx +80 -0
  60. package/templates/marketing-site/src/components/FirstAdminBootstrap.tsx +54 -0
  61. package/templates/marketing-site/src/components/InstallCommand.tsx +29 -0
  62. package/templates/marketing-site/src/components/MediaImage.tsx +59 -0
  63. package/templates/marketing-site/src/components/SectionHeader.tsx +51 -0
  64. package/templates/marketing-site/src/components/SiteFooter.tsx +8 -6
  65. package/templates/marketing-site/src/components/SiteHeader.tsx +8 -6
  66. package/templates/marketing-site/src/components/ThemeLive.tsx +1 -2
  67. package/templates/marketing-site/src/components/ThemeStyle.tsx +3 -3
  68. package/templates/marketing-site/src/components/motion-primitives/animated-group.tsx +44 -120
  69. package/templates/marketing-site/src/components/motion-primitives/reveal.module.css +32 -0
  70. package/templates/marketing-site/src/components/motion-primitives/text-effect.tsx +47 -266
  71. package/templates/marketing-site/src/lib/codeHighlightKey.ts +27 -0
  72. package/templates/marketing-site/src/lib/highlight.ts +134 -0
  73. package/templates/marketing-site/src/lib/highlightClient.ts +37 -0
  74. package/templates/marketing-site/src/lib/metadata.ts +61 -33
  75. package/templates/marketing-site/src/lib/resolvePagePath.ts +8 -0
  76. package/templates/marketing-site/src/lib/vex.ts +18 -0
  77. package/templates/marketing-site/src/proxy.ts +179 -0
  78. package/templates/marketing-site/src/vex.config.server.ts +27 -0
  79. package/templates/marketing-site/src/vex.config.ts +27 -9
  80. package/templates/marketing-site/src/vex.types.ts +102 -76
  81. package/templates/marketing-site/src/vexcms/api.ts +2 -2
  82. package/templates/marketing-site/src/vexcms/blocks/CTA/config.ts +3 -2
  83. package/templates/marketing-site/src/vexcms/blocks/CTA/index.tsx +40 -24
  84. package/templates/marketing-site/src/vexcms/blocks/CodeShowcase/config.ts +62 -0
  85. package/templates/marketing-site/src/vexcms/blocks/CodeShowcase/index.tsx +106 -0
  86. package/templates/marketing-site/src/vexcms/blocks/FAQ/config.ts +4 -3
  87. package/templates/marketing-site/src/vexcms/blocks/FAQ/index.tsx +63 -50
  88. package/templates/marketing-site/src/vexcms/blocks/Features/config.ts +1 -0
  89. package/templates/marketing-site/src/vexcms/blocks/Features/index.tsx +43 -58
  90. package/templates/marketing-site/src/vexcms/blocks/Footer/config.ts +6 -3
  91. package/templates/marketing-site/src/vexcms/blocks/Footer/index.tsx +90 -53
  92. package/templates/marketing-site/src/vexcms/blocks/Header/config.ts +10 -3
  93. package/templates/marketing-site/src/vexcms/blocks/Header/index.tsx +205 -138
  94. package/templates/marketing-site/src/vexcms/blocks/Hero/config.ts +27 -12
  95. package/templates/marketing-site/src/vexcms/blocks/Hero/index.tsx +174 -119
  96. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/config.ts +2 -1
  97. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/index.tsx +59 -51
  98. package/templates/marketing-site/src/vexcms/blocks/Roadmap/config.ts +26 -5
  99. package/templates/marketing-site/src/vexcms/blocks/Roadmap/index.tsx +256 -73
  100. package/templates/marketing-site/src/vexcms/blocks/Split/config.ts +62 -0
  101. package/templates/marketing-site/src/vexcms/blocks/Split/index.tsx +186 -0
  102. package/templates/marketing-site/src/vexcms/blocks/Stats/config.ts +35 -0
  103. package/templates/marketing-site/src/vexcms/blocks/Stats/index.tsx +77 -0
  104. package/templates/marketing-site/src/vexcms/blocks/config.ts +27 -11
  105. package/templates/marketing-site/src/vexcms/blocks/constants.ts +3 -0
  106. package/templates/marketing-site/src/vexcms/blocks/index.ts +9 -0
  107. package/templates/marketing-site/src/vexcms/collections/footers.ts +1 -0
  108. package/templates/marketing-site/src/vexcms/collections/headers.ts +1 -0
  109. package/templates/marketing-site/src/vexcms/collections/pages.ts +23 -1
  110. package/templates/marketing-site/src/vexcms/collections/themeColors.ts +14 -14
  111. package/templates/marketing-site/src/vexcms/collections/users.ts +20 -0
  112. package/dist/index.mjs +0 -30976
  113. package/templates/marketing-site/src/app/(frontend)/PageContent.tsx +0 -53
  114. package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +0 -25
  115. package/templates/marketing-site/src/app/(frontend)/page.tsx +0 -21
@@ -0,0 +1,77 @@
1
+ import type { BlockComponentProps } from "@vexcms/react"
2
+
3
+ import { cn } from "@vexcms/react"
4
+
5
+ import type { StatsBlock } from "~/vex.types"
6
+
7
+ import { Container } from "~/components/Container"
8
+ import { SectionHeader } from "~/components/SectionHeader"
9
+
10
+ export { statsBlock } from "./config"
11
+
12
+
13
+ /** Beyond this length the value stops being a headline number and has to
14
+ * step down a size to keep the cell from breaking. */
15
+ const LONG_VALUE_CHARS = 6
16
+
17
+ /**
18
+ * Stats — four numbers on a raised band.
19
+ *
20
+ * Shorter than a standard block on purpose: this is a caption on the hero,
21
+ * not a section. Cells are separated by 1px dividers rather than gaps, and
22
+ * values are top-aligned per cell rather than baseline-aligned to each other,
23
+ * so `"0"`, `"12"` and `"~30s"` all sit correctly next to one another.
24
+ */
25
+ export default function StatsBlock({ block }: BlockComponentProps) {
26
+ const { heading, items, subheading } = block as StatsBlock
27
+ const stats = items ?? []
28
+
29
+ if (stats.length === 0) {return null}
30
+
31
+ return (
32
+ <section className="border-y border-border bg-card py-10 md:py-14 xl:py-16">
33
+ <Container>
34
+ <SectionHeader heading={heading} subheading={subheading} />
35
+
36
+ <dl
37
+ className={cn(
38
+ "grid grid-cols-1 divide-y divide-border md:divide-y-0",
39
+ stats.length > 1 && "md:grid-cols-2 md:divide-x xl:grid-cols-4"
40
+ )}
41
+ >
42
+ {stats.map((stat, index) => (
43
+ <div
44
+ className={cn(
45
+ "flex flex-col items-start py-6 md:px-8 md:py-0",
46
+ // The first cell of each row owns the left edge, so it must not
47
+ // draw a divider; at md that is every other cell, at xl every
48
+ // fourth. Rows after the first take a top rule instead.
49
+ "md:[&:nth-child(odd)]:border-l-0 xl:[&:nth-child(odd)]:border-l xl:[&:nth-child(4n+1)]:border-l-0",
50
+ index >= 2 && "md:border-t md:border-border xl:border-t-0",
51
+ index >= 4 && "xl:border-t xl:border-border"
52
+ )}
53
+ key={`${stat.label}-${index}`}
54
+ >
55
+ <dd
56
+ className={cn(
57
+ "font-extrabold tracking-[-0.04em] tabular-nums text-foreground",
58
+ stat.value.length > LONG_VALUE_CHARS
59
+ ? "text-3xl md:text-4xl"
60
+ : "text-[40px] leading-none md:text-5xl xl:text-[56px]"
61
+ )}
62
+ >
63
+ {stat.value}
64
+ </dd>
65
+ <dt className="mt-3.5 text-[15px] font-semibold text-foreground">{stat.label}</dt>
66
+ {stat.description ? (
67
+ <p className="mt-2 max-w-[30ch] text-[13.5px] leading-[1.55] text-muted-foreground">
68
+ {stat.description}
69
+ </p>
70
+ ) : null}
71
+ </div>
72
+ ))}
73
+ </dl>
74
+ </Container>
75
+ </section>
76
+ )
77
+ }
@@ -1,25 +1,41 @@
1
+ import { codeShowcaseBlock } from "./CodeShowcase/config"
2
+ import { ctaBlock } from "./CTA/config"
3
+ import { faqBlock } from "./FAQ/config"
4
+ import { featuresBlock } from "./Features/config"
5
+ import { footerBlock } from "./Footer/config"
6
+ import { headerBlock } from "./Header/config"
1
7
  // Block configs only — no React/motion dependencies. Safe to import from
2
8
  // collections, vex.config.ts, and convex/seed.ts.
3
9
  import { heroBlock } from "./Hero/config"
4
- import { featuresBlock } from "./Features/config"
5
10
  import { howItWorksBlock } from "./HowItWorks/config"
6
11
  import { roadmapBlock } from "./Roadmap/config"
7
- import { ctaBlock } from "./CTA/config"
8
- import { faqBlock } from "./FAQ/config"
9
- import { headerBlock } from "./Header/config"
10
- import { footerBlock } from "./Footer/config"
11
-
12
- export const pageBlocks = [heroBlock, featuresBlock, howItWorksBlock, roadmapBlock, ctaBlock, faqBlock]
13
- export const headerBlocks = [headerBlock]
14
- export const footerBlocks = [footerBlock]
12
+ import { splitBlock } from "./Split/config"
13
+ import { statsBlock } from "./Stats/config"
15
14
 
16
- export {
15
+ export const pageBlocks = [
17
16
  heroBlock,
17
+ statsBlock,
18
18
  featuresBlock,
19
+ codeShowcaseBlock,
20
+ splitBlock,
19
21
  howItWorksBlock,
20
22
  roadmapBlock,
23
+ faqBlock,
24
+ ctaBlock,
25
+ ]
26
+ export const headerBlocks = [headerBlock]
27
+ export const footerBlocks = [footerBlock]
28
+
29
+ export {
30
+ codeShowcaseBlock,
21
31
  ctaBlock,
22
32
  faqBlock,
23
- headerBlock,
33
+ featuresBlock,
24
34
  footerBlock,
35
+ headerBlock,
36
+ heroBlock,
37
+ howItWorksBlock,
38
+ roadmapBlock,
39
+ splitBlock,
40
+ statsBlock,
25
41
  }
@@ -6,3 +6,6 @@ export const BLOCK_SLUG_CTA = "cta" as const
6
6
  export const BLOCK_SLUG_FAQ = "faq" as const
7
7
  export const BLOCK_SLUG_HEADER = "header" as const
8
8
  export const BLOCK_SLUG_FOOTER = "footer" as const
9
+ export const BLOCK_SLUG_CODE_SHOWCASE = "code_showcase" as const
10
+ export const BLOCK_SLUG_STATS = "stats" as const
11
+ export const BLOCK_SLUG_SPLIT = "split" as const
@@ -1,7 +1,9 @@
1
1
  import type { BlockComponentProps } from "@vexcms/react"
2
2
  import type { ComponentType } from "react"
3
3
 
4
+ import CodeShowcaseBlock from "./CodeShowcase"
4
5
  import {
6
+ BLOCK_SLUG_CODE_SHOWCASE,
5
7
  BLOCK_SLUG_CTA,
6
8
  BLOCK_SLUG_FAQ,
7
9
  BLOCK_SLUG_FEATURES,
@@ -10,6 +12,8 @@ import {
10
12
  BLOCK_SLUG_HERO,
11
13
  BLOCK_SLUG_HOW_IT_WORKS,
12
14
  BLOCK_SLUG_ROADMAP,
15
+ BLOCK_SLUG_SPLIT,
16
+ BLOCK_SLUG_STATS,
13
17
  } from "./constants"
14
18
  import CTABlock from "./CTA"
15
19
  import FAQBlock from "./FAQ"
@@ -19,6 +23,8 @@ import HeaderBlock from "./Header"
19
23
  import HeroBlock from "./Hero"
20
24
  import HowItWorksBlock from "./HowItWorks"
21
25
  import RoadmapBlock from "./Roadmap"
26
+ import SplitBlock from "./Split"
27
+ import StatsBlock from "./Stats"
22
28
 
23
29
  /**
24
30
  * Block component map for `RenderBlocks` (Contract 1). Loosely typed as
@@ -27,6 +33,7 @@ import RoadmapBlock from "./Roadmap"
27
33
  * against this config, so this file can't reference it.
28
34
  */
29
35
  export const blockComponents: Record<string, ComponentType<BlockComponentProps>> = {
36
+ [BLOCK_SLUG_CODE_SHOWCASE]: CodeShowcaseBlock,
30
37
  [BLOCK_SLUG_CTA]: CTABlock,
31
38
  [BLOCK_SLUG_FAQ]: FAQBlock,
32
39
  [BLOCK_SLUG_FEATURES]: FeaturesBlock,
@@ -35,6 +42,8 @@ export const blockComponents: Record<string, ComponentType<BlockComponentProps>>
35
42
  [BLOCK_SLUG_HERO]: HeroBlock,
36
43
  [BLOCK_SLUG_HOW_IT_WORKS]: HowItWorksBlock,
37
44
  [BLOCK_SLUG_ROADMAP]: RoadmapBlock,
45
+ [BLOCK_SLUG_SPLIT]: SplitBlock,
46
+ [BLOCK_SLUG_STATS]: StatsBlock,
38
47
  }
39
48
 
40
49
  export { footerBlocks, headerBlocks, pageBlocks } from "./config"
@@ -7,6 +7,7 @@ export const footers = defineCollection({
7
7
  slug: TABLE_SLUG_FOOTERS,
8
8
  admin: {
9
9
  useAsTitle: "name",
10
+ icon: "PanelBottom",
10
11
  },
11
12
  fields: {
12
13
  name: text({
@@ -7,6 +7,7 @@ export const headers = defineCollection({
7
7
  slug: TABLE_SLUG_HEADERS,
8
8
  admin: {
9
9
  useAsTitle: "name",
10
+ icon: "PanelTop",
10
11
  },
11
12
  fields: {
12
13
  name: text({
@@ -1,5 +1,8 @@
1
- import { blocks, defineCollection, text, upload } from "@vexcms/core"
1
+ import type { DataModel } from "@convex/_generated/dataModel"
2
2
 
3
+ import { blocks, defineCollection, text, textValidator, upload } from "@vexcms/core"
4
+
5
+ import { resolvePagePath } from "~/lib/resolvePagePath"
3
6
  import { TABLE_SLUG_IMAGES, TABLE_SLUG_PAGES } from "~/db/constants"
4
7
  import { pageBlocks } from "~/vexcms/blocks/config"
5
8
 
@@ -8,6 +11,10 @@ export const pages = defineCollection({
8
11
  admin: {
9
12
  table: { defaultColumns: ["title", "slug", "_id"] },
10
13
  useAsTitle: "title",
14
+ icon: "FileText",
15
+ livePreview: {
16
+ url: (doc) => resolvePagePath(doc.slug),
17
+ },
11
18
  },
12
19
  fields: {
13
20
  title: text({
@@ -19,8 +26,23 @@ export const pages = defineCollection({
19
26
  index: "by_slug",
20
27
  label: "Slug",
21
28
  required: true,
29
+ validate: textValidator<typeof TABLE_SLUG_PAGES, DataModel>(
30
+ TABLE_SLUG_PAGES,
31
+ async ({ value, doc, ctx }) => {
32
+ const existing = await ctx.db
33
+ .query("pages")
34
+ .withIndex("by_slug", (q) => q.eq("slug", value))
35
+ .first()
36
+ if (existing && existing._id !== doc._id) {
37
+ return `This slug is already in use by the page '${existing.title}'.`
38
+ }
39
+ },
40
+ ),
22
41
  }),
23
42
  blocks: blocks({
43
+ admin: {
44
+ defaultCollapsed: true,
45
+ },
24
46
  blocks: pageBlocks,
25
47
  interfaceName: "PageBlock",
26
48
  label: "Content",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  color,
3
- THEME_COLOR_TOKENS,
4
3
  type ColorField,
4
+ THEME_COLOR_TOKENS,
5
5
  type ThemeColorTokenKey,
6
6
  } from "@vexcms/core";
7
7
 
@@ -81,19 +81,6 @@ const EMBER: Record<"dark" | "light", Record<ThemeColorTokenKey, string>> = {
81
81
  },
82
82
  };
83
83
 
84
- /**
85
- * Derives a human label from a token key: `"cardForeground"` → `"Card
86
- * Foreground"`, `"chart1"` → `"Chart 1"`.
87
- *
88
- * @param props - Input props.
89
- * @param props.key - The camelCase token key.
90
- * @returns The admin form label.
91
- */
92
- function tokenLabel(props: { key: string }): string {
93
- const spaced = props.key.replace(/([A-Z])/g, " $1").replace(/(\d+)/g, " $1");
94
- return spaced.charAt(0).toUpperCase() + spaced.slice(1);
95
- }
96
-
97
84
  /**
98
85
  * The shadcn design-token set as `color()` fields for one colour scheme.
99
86
  *
@@ -116,3 +103,16 @@ export function themeColors(mode: "dark" | "light"): Record<ThemeColorTokenKey,
116
103
  }
117
104
  return fields;
118
105
  }
106
+
107
+ /**
108
+ * Derives a human label from a token key: `"cardForeground"` → `"Card
109
+ * Foreground"`, `"chart1"` → `"Chart 1"`.
110
+ *
111
+ * @param props - Input props.
112
+ * @param props.key - The camelCase token key.
113
+ * @returns The admin form label.
114
+ */
115
+ function tokenLabel(props: { key: string }): string {
116
+ const spaced = props.key.replace(/([A-Z])/g, " $1").replace(/(\d+)/g, " $1");
117
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
118
+ }
@@ -0,0 +1,20 @@
1
+ import { defineCollection, text } from "@vexcms/core"
2
+
3
+ import { TABLE_SLUG_USERS } from "~/db/constants"
4
+
5
+ export const users = defineCollection({
6
+ slug: TABLE_SLUG_USERS,
7
+ admin: {
8
+ useAsTitle: "name",
9
+ icon: "Users",
10
+ },
11
+ labels: {
12
+ singular: "User",
13
+ plural: "Users",
14
+ },
15
+ fields: {
16
+ name: text({ label: "Name", required: true }),
17
+ // Add custom user fields here — the auth adapter already contributes
18
+ // name/email/roles/etc. from Better Auth's schema.
19
+ },
20
+ })