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,17 +1,328 @@
1
+ import { getGlobal, upsertGlobal } from "@vexcms/core/server"
2
+
3
+ import {
4
+ GLOBAL_SLUG_SITE_SETTINGS,
5
+ TABLE_SLUG_FOOTERS,
6
+ TABLE_SLUG_HEADERS,
7
+ TABLE_SLUG_PAGES,
8
+ TABLE_SLUG_THEMES,
9
+ } from "~/db/constants"
10
+ import config from "~/vex.config"
11
+
1
12
  import { internalMutation } from "./_generated/server"
2
13
 
3
14
  /**
4
- * Initialize the site with default data.
5
- *
6
- * Run from terminal:
7
- * npx convex run seed:init
15
+ * The 4 tweakcn theme presets, lifted verbatim from
16
+ * `apps/test/convex/seed.ts`'s `THEME_PRESETS` (32-token light/dark palettes
17
+ * matching `themeColors.ts`'s `ThemeColorTokenKey` set).
18
+ */
19
+ const THEME_PRESETS = [
20
+ {
21
+ name: "Stark × Ember",
22
+ fontFamily: "Geist, Inter, system-ui, sans-serif",
23
+ radius: "4px",
24
+ light: {
25
+ background: "oklch(96.1% 0 0)",
26
+ foreground: "oklch(13.7% 0 0)",
27
+ card: "oklch(100% 0 0)",
28
+ cardForeground: "oklch(13.7% 0 0)",
29
+ popover: "oklch(100% 0 0)",
30
+ popoverForeground: "oklch(13.7% 0 0)",
31
+ primary: "oklch(60.5% 0.175 42)",
32
+ primaryForeground: "oklch(100% 0 0)",
33
+ secondary: "oklch(98% 0 0)",
34
+ secondaryForeground: "oklch(13.7% 0 0)",
35
+ muted: "oklch(98% 0 0)",
36
+ mutedForeground: "oklch(50.5% 0 0)",
37
+ accent: "oklch(96% 0.025 42)",
38
+ accentForeground: "oklch(52% 0.180 40)",
39
+ destructive: "oklch(57.7% 0.198 27)",
40
+ destructiveForeground: "oklch(98% 0 0)",
41
+ border: "oklch(85% 0 0)",
42
+ input: "oklch(54.6% 0 0)",
43
+ ring: "oklch(60.5% 0.175 42)",
44
+ chart1: "oklch(60.5% 0.175 42)",
45
+ chart2: "oklch(45% 0 0)",
46
+ chart3: "oklch(72% 0.100 60)",
47
+ chart4: "oklch(60% 0.040 30)",
48
+ chart5: "oklch(78% 0 0)",
49
+ sidebar: "oklch(98% 0 0)",
50
+ sidebarForeground: "oklch(13.7% 0 0)",
51
+ sidebarPrimary: "oklch(60.5% 0.175 42)",
52
+ sidebarPrimaryForeground: "oklch(100% 0 0)",
53
+ sidebarAccent: "oklch(96.1% 0 0)",
54
+ sidebarAccentForeground: "oklch(13.7% 0 0)",
55
+ sidebarBorder: "oklch(85% 0 0)",
56
+ sidebarRing: "oklch(60.5% 0.175 42)",
57
+ },
58
+ dark: {
59
+ background: "oklch(13.7% 0 0)",
60
+ foreground: "oklch(95% 0 0)",
61
+ card: "oklch(17.4% 0 0)",
62
+ cardForeground: "oklch(95% 0 0)",
63
+ popover: "oklch(17.4% 0 0)",
64
+ popoverForeground: "oklch(95% 0 0)",
65
+ primary: "oklch(72% 0.175 50)",
66
+ primaryForeground: "oklch(13.7% 0 0)",
67
+ secondary: "oklch(20% 0 0)",
68
+ secondaryForeground: "oklch(95% 0 0)",
69
+ muted: "oklch(20% 0 0)",
70
+ mutedForeground: "oklch(70% 0 0)",
71
+ accent: "oklch(72% 0.175 50 / 0.12)",
72
+ accentForeground: "oklch(72% 0.175 50)",
73
+ destructive: "oklch(63% 0.210 27)",
74
+ destructiveForeground: "oklch(95% 0 0)",
75
+ border: "oklch(25% 0 0)",
76
+ input: "oklch(40% 0 0)",
77
+ ring: "oklch(72% 0.175 50)",
78
+ chart1: "oklch(72% 0.175 50)",
79
+ chart2: "oklch(78% 0 0)",
80
+ chart3: "oklch(78% 0.120 65)",
81
+ chart4: "oklch(60% 0.060 30)",
82
+ chart5: "oklch(45% 0 0)",
83
+ sidebar: "oklch(7% 0 0)",
84
+ sidebarForeground: "oklch(95% 0 0)",
85
+ sidebarPrimary: "oklch(72% 0.175 50)",
86
+ sidebarPrimaryForeground: "oklch(13.7% 0 0)",
87
+ sidebarAccent: "oklch(20% 0 0)",
88
+ sidebarAccentForeground: "oklch(95% 0 0)",
89
+ sidebarBorder: "oklch(25% 0 0)",
90
+ sidebarRing: "oklch(72% 0.175 50)",
91
+ },
92
+ },
93
+ {
94
+ name: "Modern Minimal",
95
+ fontFamily: "Inter, sans-serif",
96
+ radius: "0.375rem",
97
+ light: {
98
+ background: "oklch(100% 0 0)",
99
+ foreground: "oklch(32.11% 0 0)",
100
+ card: "oklch(100% 0 0)",
101
+ cardForeground: "oklch(32.11% 0 0)",
102
+ popover: "oklch(100% 0 0)",
103
+ popoverForeground: "oklch(32.11% 0 0)",
104
+ primary: "oklch(62.31% 0.18801 259.81)",
105
+ primaryForeground: "oklch(100% 0 0)",
106
+ secondary: "oklch(96.7% 0.00287 264.54)",
107
+ secondaryForeground: "oklch(44.61% 0.02631 256.8)",
108
+ muted: "oklch(98.46% 0.00171 247.84)",
109
+ mutedForeground: "oklch(55.1% 0.02336 264.36)",
110
+ accent: "oklch(95.14% 0.02503 236.82)",
111
+ accentForeground: "oklch(37.91% 0.13776 265.52)",
112
+ destructive: "oklch(63.68% 0.20785 25.33)",
113
+ destructiveForeground: "oklch(100% 0 0)",
114
+ border: "oklch(92.76% 0.00581 264.53)",
115
+ input: "oklch(92.76% 0.00581 264.53)",
116
+ ring: "oklch(62.31% 0.18801 259.81)",
117
+ chart1: "oklch(62.31% 0.18801 259.81)",
118
+ chart2: "oklch(54.61% 0.21521 262.88)",
119
+ chart3: "oklch(48.82% 0.21717 264.38)",
120
+ chart4: "oklch(42.44% 0.18087 265.64)",
121
+ chart5: "oklch(37.91% 0.13776 265.52)",
122
+ sidebar: "oklch(98.46% 0.00171 247.84)",
123
+ sidebarForeground: "oklch(32.11% 0 0)",
124
+ sidebarPrimary: "oklch(62.31% 0.18801 259.81)",
125
+ sidebarPrimaryForeground: "oklch(100% 0 0)",
126
+ sidebarAccent: "oklch(95.14% 0.02503 236.82)",
127
+ sidebarAccentForeground: "oklch(37.91% 0.13776 265.52)",
128
+ sidebarBorder: "oklch(92.76% 0.00581 264.53)",
129
+ sidebarRing: "oklch(62.31% 0.18801 259.81)",
130
+ },
131
+ dark: {
132
+ background: "oklch(20.46% 0 0)",
133
+ foreground: "oklch(92.19% 0 0)",
134
+ card: "oklch(26.86% 0 0)",
135
+ cardForeground: "oklch(92.19% 0 0)",
136
+ popover: "oklch(26.86% 0 0)",
137
+ popoverForeground: "oklch(92.19% 0 0)",
138
+ primary: "oklch(62.31% 0.18801 259.81)",
139
+ primaryForeground: "oklch(100% 0 0)",
140
+ secondary: "oklch(26.86% 0 0)",
141
+ secondaryForeground: "oklch(92.19% 0 0)",
142
+ muted: "oklch(23.93% 0 0)",
143
+ mutedForeground: "oklch(71.55% 0 0)",
144
+ accent: "oklch(37.91% 0.13776 265.52)",
145
+ accentForeground: "oklch(88.23% 0.05706 254.13)",
146
+ destructive: "oklch(63.68% 0.20785 25.33)",
147
+ destructiveForeground: "oklch(100% 0 0)",
148
+ border: "oklch(37.15% 0 0)",
149
+ input: "oklch(37.15% 0 0)",
150
+ ring: "oklch(62.31% 0.18801 259.81)",
151
+ chart1: "oklch(71.37% 0.14338 254.62)",
152
+ chart2: "oklch(62.31% 0.18801 259.81)",
153
+ chart3: "oklch(54.61% 0.21521 262.88)",
154
+ chart4: "oklch(48.82% 0.21717 264.38)",
155
+ chart5: "oklch(42.44% 0.18087 265.64)",
156
+ sidebar: "oklch(20.46% 0 0)",
157
+ sidebarForeground: "oklch(92.19% 0 0)",
158
+ sidebarPrimary: "oklch(62.31% 0.18801 259.81)",
159
+ sidebarPrimaryForeground: "oklch(100% 0 0)",
160
+ sidebarAccent: "oklch(37.91% 0.13776 265.52)",
161
+ sidebarAccentForeground: "oklch(88.23% 0.05706 254.13)",
162
+ sidebarBorder: "oklch(37.15% 0 0)",
163
+ sidebarRing: "oklch(62.31% 0.18801 259.81)",
164
+ },
165
+ },
166
+ {
167
+ name: "Violet Bloom",
168
+ fontFamily: "Plus Jakarta Sans, sans-serif",
169
+ radius: "1.4rem",
170
+ light: {
171
+ background: "oklch(99.4% 0 0)",
172
+ foreground: "oklch(0% 0 0)",
173
+ card: "oklch(99.4% 0 0)",
174
+ cardForeground: "oklch(0% 0 0)",
175
+ popover: "oklch(99.11% 0 0)",
176
+ popoverForeground: "oklch(0% 0 0)",
177
+ primary: "oklch(53.93% 0.27129 286.75)",
178
+ primaryForeground: "oklch(100% 0 0)",
179
+ secondary: "oklch(95.4% 0.00626 255.48)",
180
+ secondaryForeground: "oklch(13.44% 0 0)",
181
+ muted: "oklch(97.02% 0 0)",
182
+ mutedForeground: "oklch(43.86% 0 0)",
183
+ accent: "oklch(93.93% 0.02876 266.37)",
184
+ accentForeground: "oklch(54.45% 0.19034 259.48)",
185
+ destructive: "oklch(62.9% 0.19024 23.07)",
186
+ destructiveForeground: "oklch(100% 0 0)",
187
+ border: "oklch(93% 0.00939 286.22)",
188
+ input: "oklch(94.01% 0 0)",
189
+ ring: "oklch(0% 0 0)",
190
+ chart1: "oklch(74.59% 0.14834 156.45)",
191
+ chart2: "oklch(53.93% 0.27129 286.75)",
192
+ chart3: "oklch(73.36% 0.17578 50.55)",
193
+ chart4: "oklch(58.28% 0.18094 259.73)",
194
+ chart5: "oklch(55.9% 0 0)",
195
+ sidebar: "oklch(97.77% 0.00513 247.88)",
196
+ sidebarForeground: "oklch(0% 0 0)",
197
+ sidebarPrimary: "oklch(0% 0 0)",
198
+ sidebarPrimaryForeground: "oklch(100% 0 0)",
199
+ sidebarAccent: "oklch(94.01% 0 0)",
200
+ sidebarAccentForeground: "oklch(0% 0 0)",
201
+ sidebarBorder: "oklch(94.01% 0 0)",
202
+ sidebarRing: "oklch(0% 0 0)",
203
+ },
204
+ dark: {
205
+ background: "oklch(22.23% 0.00601 271.14)",
206
+ foreground: "oklch(95.51% 0 0)",
207
+ card: "oklch(25.68% 0.00762 274.65)",
208
+ cardForeground: "oklch(95.51% 0 0)",
209
+ popover: "oklch(25.68% 0.00762 274.65)",
210
+ popoverForeground: "oklch(95.51% 0 0)",
211
+ primary: "oklch(61.32% 0.22941 291.74)",
212
+ primaryForeground: "oklch(100% 0 0)",
213
+ secondary: "oklch(29.4% 0.01301 272.93)",
214
+ secondaryForeground: "oklch(95.51% 0 0)",
215
+ muted: "oklch(29.4% 0.01301 272.93)",
216
+ mutedForeground: "oklch(70.58% 0 0)",
217
+ accent: "oklch(27.95% 0.03685 260.03)",
218
+ accentForeground: "oklch(78.57% 0.11535 246.66)",
219
+ destructive: "oklch(71.06% 0.16615 22.22)",
220
+ destructiveForeground: "oklch(100% 0 0)",
221
+ border: "oklch(32.89% 0.00922 268.38)",
222
+ input: "oklch(32.89% 0.00922 268.38)",
223
+ ring: "oklch(61.32% 0.22941 291.74)",
224
+ chart1: "oklch(80.03% 0.18206 151.71)",
225
+ chart2: "oklch(61.32% 0.22941 291.74)",
226
+ chart3: "oklch(80.77% 0.10349 19.57)",
227
+ chart4: "oklch(66.91% 0.15686 260.11)",
228
+ chart5: "oklch(70.58% 0 0)",
229
+ sidebar: "oklch(20.11% 0.00394 286.04)",
230
+ sidebarForeground: "oklch(95.51% 0 0)",
231
+ sidebarPrimary: "oklch(61.32% 0.22941 291.74)",
232
+ sidebarPrimaryForeground: "oklch(100% 0 0)",
233
+ sidebarAccent: "oklch(29.4% 0.01301 272.93)",
234
+ sidebarAccentForeground: "oklch(61.32% 0.22941 291.74)",
235
+ sidebarBorder: "oklch(32.89% 0.00922 268.38)",
236
+ sidebarRing: "oklch(61.32% 0.22941 291.74)",
237
+ },
238
+ },
239
+ {
240
+ name: "T3 Chat",
241
+ fontFamily: "Geist, Inter, system-ui, sans-serif",
242
+ radius: "0.5rem",
243
+ light: {
244
+ background: "oklch(97.54% 0.00844 325.64)",
245
+ foreground: "oklch(32.57% 0.11612 325.04)",
246
+ card: "oklch(97.54% 0.00844 325.64)",
247
+ cardForeground: "oklch(32.57% 0.11612 325.04)",
248
+ popover: "oklch(100% 0 0)",
249
+ popoverForeground: "oklch(32.57% 0.11612 325.04)",
250
+ primary: "oklch(53.16% 0.14089 355.2)",
251
+ primaryForeground: "oklch(100% 0 0)",
252
+ secondary: "oklch(86.96% 0.06751 334.9)",
253
+ secondaryForeground: "oklch(44.48% 0.13406 324.8)",
254
+ muted: "oklch(93.95% 0.02604 331.55)",
255
+ mutedForeground: "oklch(49.24% 0.12445 324.45)",
256
+ accent: "oklch(86.96% 0.06751 334.9)",
257
+ accentForeground: "oklch(44.48% 0.13406 324.8)",
258
+ destructive: "oklch(52.48% 0.13678 20.83)",
259
+ destructiveForeground: "oklch(100% 0 0)",
260
+ border: "oklch(85.68% 0.08288 328.91)",
261
+ input: "oklch(85.17% 0.05582 336.6)",
262
+ ring: "oklch(59.16% 0.21798 0.58)",
263
+ chart1: "oklch(60.38% 0.23628 344.47)",
264
+ chart2: "oklch(44.45% 0.22507 300.62)",
265
+ chart3: "oklch(37.9% 0.04376 226.15)",
266
+ chart4: "oklch(83.3% 0.11852 88.35)",
267
+ chart5: "oklch(78.43% 0.12563 59)",
268
+ sidebar: "oklch(93.6% 0.02881 320.58)",
269
+ sidebarForeground: "oklch(49.48% 0.19094 354.54)",
270
+ sidebarPrimary: "oklch(39.63% 0.02513 285.2)",
271
+ sidebarPrimaryForeground: "oklch(96.68% 0.01243 337.52)",
272
+ sidebarAccent: "oklch(97.89% 0.00132 106.42)",
273
+ sidebarAccentForeground: "oklch(39.63% 0.02513 285.2)",
274
+ sidebarBorder: "oklch(93.83% 0.00255 48.72)",
275
+ sidebarRing: "oklch(59.16% 0.21798 0.58)",
276
+ },
277
+ dark: {
278
+ background: "oklch(24.09% 0.0201 307.53)",
279
+ foreground: "oklch(83.98% 0.03874 309.54)",
280
+ card: "oklch(28.03% 0.02323 307.54)",
281
+ cardForeground: "oklch(84.56% 0.03016 341.46)",
282
+ popover: "oklch(15.48% 0.01316 338.9)",
283
+ popoverForeground: "oklch(96.47% 0.00914 341.8)",
284
+ primary: "oklch(46.07% 0.18535 4.1)",
285
+ primaryForeground: "oklch(85.6% 0.06185 346.37)",
286
+ secondary: "oklch(31.37% 0.03057 310.06)",
287
+ secondaryForeground: "oklch(84.83% 0.03825 307.96)",
288
+ muted: "oklch(26.34% 0.02189 309.47)",
289
+ mutedForeground: "oklch(79.4% 0.0372 307.1)",
290
+ accent: "oklch(36.49% 0.05079 308.49)",
291
+ accentForeground: "oklch(96.47% 0.00914 341.8)",
292
+ destructive: "oklch(22.58% 0.05243 12.61)",
293
+ destructiveForeground: "oklch(100% 0 0)",
294
+ border: "oklch(32.86% 0.01535 343.45)",
295
+ input: "oklch(33.87% 0.0195 332.83)",
296
+ ring: "oklch(59.16% 0.21798 0.58)",
297
+ chart1: "oklch(53.16% 0.14089 355.2)",
298
+ chart2: "oklch(56.33% 0.19123 306.86)",
299
+ chart3: "oklch(72.27% 0.1502 60.58)",
300
+ chart4: "oklch(61.93% 0.20294 312.74)",
301
+ chart5: "oklch(61.18% 0.2093 6.14)",
302
+ sidebar: "oklch(18.93% 0.01632 331.05)",
303
+ sidebarForeground: "oklch(86.07% 0.02927 343.66)",
304
+ sidebarPrimary: "oklch(48.82% 0.21717 264.38)",
305
+ sidebarPrimaryForeground: "oklch(100% 0 0)",
306
+ sidebarAccent: "oklch(23.37% 0.02608 338.2)",
307
+ sidebarAccentForeground: "oklch(96.74% 0.00133 286.38)",
308
+ sidebarBorder: "oklch(0% 0 0)",
309
+ sidebarRing: "oklch(59.16% 0.21798 0.58)",
310
+ },
311
+ },
312
+ ]
313
+
314
+ /**
315
+ * Initialize a fresh scaffold with a complete marketing site: the 4 theme
316
+ * presets ("Stark × Ember" active by default), site settings, a header, a
317
+ * footer, and a fully assembled home page built from every block's shipped
318
+ * defaults.
8
319
  *
9
- * Creates:
10
- * - Site settings global (required for theme and SEO)
11
- * - A default header
12
- * - A default footer
320
+ * Safe to run repeatedly — every insert is guarded by an existence check
321
+ * keyed on a natural identifier (theme/header/footer name, page slug, or the
322
+ * `siteSettings` singleton), so re-running after hand-edits in the admin
323
+ * panel never duplicates or clobbers them.
13
324
  *
14
- * Safe to run multiple times — skips items that already exist.
325
+ * Run from terminal: `npx convex run seed:init`
15
326
  */
16
327
  export const init = internalMutation({
17
328
  args: {},
@@ -19,44 +330,69 @@ export const init = internalMutation({
19
330
  const created: string[] = []
20
331
  const skipped: string[] = []
21
332
 
22
- // Site Settings
23
- const existingSettings = await ctx.db.query("site_settings").first()
333
+ let activeThemeId: string | null = null
334
+ for (const preset of THEME_PRESETS) {
335
+ const existing = await ctx.db
336
+ .query(TABLE_SLUG_THEMES)
337
+ .withIndex("by_name", (q) => q.eq("name", preset.name))
338
+ .first()
339
+ if (existing) {
340
+ skipped.push(`theme:${preset.name}`)
341
+ if (preset.name === "Stark × Ember") activeThemeId = existing._id
342
+ continue
343
+ }
344
+ const id = await ctx.db.insert(TABLE_SLUG_THEMES, preset)
345
+ created.push(`theme:${preset.name}`)
346
+ if (preset.name === "Stark × Ember") activeThemeId = id
347
+ }
348
+
349
+ const existingSettings = await getGlobal({
350
+ ctx,
351
+ config,
352
+ slug: GLOBAL_SLUG_SITE_SETTINGS,
353
+ access: { bypass: true },
354
+ })
24
355
  if (existingSettings) {
25
- skipped.push("site_settings")
356
+ skipped.push("siteSettings")
26
357
  } else {
27
- await ctx.db.insert("site_settings", {
28
- name: "My Site",
29
- vex_status: "published",
30
- vex_version: 1,
31
- vex_publishedAt: Date.now(),
358
+ await upsertGlobal({
359
+ ctx,
360
+ config,
361
+ slug: GLOBAL_SLUG_SITE_SETTINGS,
362
+ data: {
363
+ name: "My Site",
364
+ description: "Built with Vex CMS.",
365
+ activeTheme: activeThemeId ? [activeThemeId] : [],
366
+ },
367
+ access: { bypass: true },
32
368
  })
33
- created.push("site_settings")
369
+ created.push("siteSettings")
34
370
  }
35
371
 
36
- // Default Header
37
372
  const existingHeader = await ctx.db
38
- .query("headers")
373
+ .query(TABLE_SLUG_HEADERS)
39
374
  .withIndex("by_name", (q) => q.eq("name", "Main Header"))
40
375
  .first()
41
376
  if (existingHeader) {
42
377
  skipped.push("header")
43
378
  } else {
44
- await ctx.db.insert("headers", {
379
+ await ctx.db.insert(TABLE_SLUG_HEADERS, {
45
380
  name: "Main Header",
46
- vex_status: "published",
47
381
  content: [
48
382
  {
49
383
  blockType: "header",
50
384
  blockName: "Site Header",
51
- _key: "main-header",
385
+ id: "main-header",
52
386
  logoText: "My Site",
53
387
  logoHref: "/",
54
388
  menuItems: [
55
389
  { label: "Features", href: "/features" },
390
+ { label: "Roadmap", href: "/roadmap" },
56
391
  { label: "Docs", href: "/docs" },
57
392
  ],
58
393
  actionButtons: [
59
- { label: "Get Started", href: "/docs", variant: "default" as const },
394
+ { label: "GitHub", href: "https://github.com/vexcms/vex", variant: ["ghost"] },
395
+ { label: "Get Started", href: "/docs", variant: ["default"] },
60
396
  ],
61
397
  },
62
398
  ],
@@ -64,38 +400,249 @@ export const init = internalMutation({
64
400
  created.push("header")
65
401
  }
66
402
 
67
- // Default Footer
68
403
  const existingFooter = await ctx.db
69
- .query("footers")
404
+ .query(TABLE_SLUG_FOOTERS)
70
405
  .withIndex("by_name", (q) => q.eq("name", "Main Footer"))
71
406
  .first()
72
407
  if (existingFooter) {
73
408
  skipped.push("footer")
74
409
  } else {
75
- await ctx.db.insert("footers", {
410
+ await ctx.db.insert(TABLE_SLUG_FOOTERS, {
76
411
  name: "Main Footer",
77
- vex_status: "published",
78
412
  content: [
79
413
  {
80
414
  blockType: "footer",
81
415
  blockName: "Site Footer",
82
- _key: "main-footer",
416
+ id: "main-footer",
83
417
  logoText: "My Site",
84
418
  copyright: "My Site. All rights reserved.",
85
419
  links: [
86
- { label: "Home", href: "/" },
420
+ { label: "Features", href: "/features" },
421
+ { label: "Roadmap", href: "/roadmap" },
422
+ { label: "Documentation", href: "/docs" },
87
423
  ],
88
- socialLinks: [],
424
+ socialLinks: [{ platform: "GitHub", href: "https://github.com/vexcms/vex", icon: "Github" }],
89
425
  },
90
426
  ],
91
427
  })
92
428
  created.push("footer")
93
429
  }
94
430
 
95
- return {
96
- created,
97
- skipped,
98
- message: `Initialized ${created.length} items. Skipped ${skipped.length} (already exist).`,
431
+ const existingHome = await ctx.db
432
+ .query(TABLE_SLUG_PAGES)
433
+ .withIndex("by_slug", (q) => q.eq("slug", "home"))
434
+ .first()
435
+ if (existingHome) {
436
+ skipped.push("page:home")
437
+ } else {
438
+ await ctx.db.insert(TABLE_SLUG_PAGES, {
439
+ title: "Home",
440
+ slug: "home",
441
+ blocks: [
442
+ {
443
+ blockType: "hero",
444
+ blockName: "Hero",
445
+ id: "home-hero",
446
+ badgeText: "Now in public beta",
447
+ badgeLink: "/docs",
448
+ heading: "The CMS built for Convex",
449
+ subheading:
450
+ "Vex CMS gives you a full-featured content management system powered by Convex — real-time data, type-safe schemas, and a beautiful admin panel out of the box.",
451
+ primaryCtaLabel: "Get Started",
452
+ primaryCtaHref: "/docs",
453
+ secondaryCtaLabel: "View on GitHub",
454
+ secondaryCtaHref: "https://github.com/vexcms/vex",
455
+ },
456
+ {
457
+ blockType: "features",
458
+ blockName: "Features",
459
+ id: "home-features",
460
+ heading: "Everything you need to manage content",
461
+ subheading:
462
+ "Built on Convex's real-time infrastructure with a developer experience that doesn't compromise on power.",
463
+ features: [
464
+ {
465
+ title: "Real-Time by Default",
466
+ description:
467
+ "Every query is live. Content updates appear instantly across all connected clients — no polling, no webhooks.",
468
+ icon: "Zap",
469
+ },
470
+ {
471
+ title: "Type-Safe Schemas",
472
+ description:
473
+ "Define your collections with TypeScript. Vex generates Convex schemas, Zod validators, and typed queries automatically.",
474
+ icon: "Shield",
475
+ },
476
+ {
477
+ title: "Developer First",
478
+ description:
479
+ "Code-first configuration, CLI tooling, and a clean API. Build with the tools you already know and love.",
480
+ icon: "Code",
481
+ },
482
+ ],
483
+ },
484
+ {
485
+ blockType: "how_it_works",
486
+ blockName: "How It Works",
487
+ id: "home-how-it-works",
488
+ heading: "Get started in minutes",
489
+ subheading: "From zero to a fully functional CMS in four steps. No boilerplate, no config files to wrestle with.",
490
+ steps: [
491
+ {
492
+ icon: "Terminal",
493
+ title: "Scaffold your project",
494
+ description:
495
+ "Run npx create-vexcms@latest to get a Next.js app with Convex, authentication, and the admin panel pre-configured.",
496
+ },
497
+ {
498
+ icon: "Code",
499
+ title: "Define your schema",
500
+ description:
501
+ "Use defineCollection() and field helpers to declare your content model in TypeScript. Vex generates your Convex schema, types, and queries automatically.",
502
+ },
503
+ {
504
+ icon: "LayoutGrid",
505
+ title: "Build with blocks",
506
+ description:
507
+ "Compose pages from reusable content blocks. Each block is a React component with a typed config — drag, drop, and edit inline from the admin panel.",
508
+ },
509
+ {
510
+ icon: "Rocket",
511
+ title: "Deploy and go live",
512
+ description:
513
+ "Push to Convex and deploy your Next.js app. Real-time content updates flow to every connected client instantly.",
514
+ },
515
+ ],
516
+ },
517
+ {
518
+ blockType: "roadmap",
519
+ blockName: "Roadmap",
520
+ id: "home-roadmap",
521
+ heading: "Roadmap",
522
+ subheading:
523
+ "What we've shipped and what's coming next. Vex CMS is actively developed — here's where we're headed.",
524
+ items: [
525
+ {
526
+ feature: "12 Field Types",
527
+ description:
528
+ "text, url, color, number, checkbox, date, select, relationship, array, group, blocks, and upload — no richtext, json, or tabs yet.",
529
+ status: ["shipped"],
530
+ },
531
+ {
532
+ feature: "Convex Schema Codegen",
533
+ description:
534
+ "vex dev / vex generate write your Convex schema, TypeScript types, and Zod validators from defineCollection() — no hand-written schema.ts.",
535
+ status: ["shipped"],
536
+ },
537
+ {
538
+ feature: "Real-Time Admin Panel",
539
+ description:
540
+ "DataTable with pagination, live totalDocs, and bulk operations — every list view is a Convex subscription.",
541
+ status: ["shipped"],
542
+ },
543
+ {
544
+ feature: "Media Library",
545
+ description:
546
+ "Convex file storage adapter with a searchable, paginated media picker built into every upload field.",
547
+ status: ["shipped"],
548
+ },
549
+ {
550
+ feature: "RBAC & Access Control",
551
+ description:
552
+ "Document-level access rules, indexed constraints that compile to withIndex ranges, per-call access.action/bypass overrides, and an anonRole fallback for public reads.",
553
+ status: ["shipped"],
554
+ },
555
+ {
556
+ feature: "Custom Theme System",
557
+ description:
558
+ "Database-driven themes with light/dark mode, 32 shadcn tokens per mode, and OKLCH color support — live-updates with zero page reload.",
559
+ status: ["shipped"],
560
+ },
561
+ {
562
+ feature: "Better Auth Integration",
563
+ description: "Email/password and OAuth out of the box, with organizations and API keys as opt-in plugins.",
564
+ status: ["shipped"],
565
+ },
566
+ {
567
+ feature: "CLI & Scaffolder",
568
+ description:
569
+ "vex dev, vex generate, and create-vexcms for instant project setup — bare or full marketing-site templates.",
570
+ status: ["shipped"],
571
+ },
572
+ {
573
+ feature: "Versioning & Drafts",
574
+ description: "Draft/publish workflow with live preview — in active development.",
575
+ status: ["coming-soon"],
576
+ },
577
+ {
578
+ feature: "Richtext, JSON, Email & Textarea Fields",
579
+ description: "Plate.js-powered rich text, plus structured JSON, email, and multi-line text inputs.",
580
+ status: ["planned"],
581
+ },
582
+ {
583
+ feature: "Form Builder & Lifecycle Hooks",
584
+ description:
585
+ "Composable form fields beyond content editing, plus beforeChange/afterChange hooks for custom side effects.",
586
+ status: ["planned"],
587
+ },
588
+ {
589
+ feature: "Team Management & API Keys",
590
+ description: "Invite users, assign roles, and issue scoped read-only API tokens for external integrations.",
591
+ status: ["planned"],
592
+ },
593
+ ],
594
+ },
595
+ {
596
+ blockType: "faq",
597
+ blockName: "FAQ",
598
+ id: "home-faq",
599
+ heading: "Frequently Asked Questions",
600
+ subheading: "Everything you need to know about Vex CMS and building with Convex.",
601
+ supportLink: "https://github.com/vexcms/vex/issues",
602
+ items: [
603
+ {
604
+ question: "What is Vex CMS?",
605
+ answer:
606
+ "Vex CMS is a headless content management system built on Convex. It provides real-time data, type-safe schemas, RBAC, and a beautiful admin panel — all configured with TypeScript.",
607
+ },
608
+ {
609
+ question: "How is Vex different from other headless CMS platforms?",
610
+ answer:
611
+ "Vex is powered by Convex's real-time database, so content updates are instant across all clients — no polling, no webhooks. Your schema is defined in code, giving you full type safety from database to frontend.",
612
+ },
613
+ {
614
+ question: "Do I need to know Convex to use Vex CMS?",
615
+ answer:
616
+ "Basic familiarity helps, but Vex handles most of the complexity for you. The CLI generates your Convex schema, queries, and types automatically from your collection definitions.",
617
+ },
618
+ {
619
+ question: "Is Vex CMS free?",
620
+ answer:
621
+ "Yes — Vex CMS is open source and free to use. You only pay for your Convex usage, which has a generous free tier for most projects.",
622
+ },
623
+ {
624
+ question: "How do I get started?",
625
+ answer:
626
+ "Run npx create-vexcms@latest to scaffold a new project with Vex CMS pre-configured — Next.js app, Convex backend, authentication, and admin panel in under a minute.",
627
+ },
628
+ ],
629
+ },
630
+ {
631
+ blockType: "cta",
632
+ blockName: "CTA",
633
+ id: "home-cta",
634
+ heading: "Ready to build with Vex CMS?",
635
+ subheading: "Get started in minutes with create-vexcms. Real-time content management powered by Convex.",
636
+ actions: [
637
+ { label: "Get Started", href: "/docs" },
638
+ { label: "View on GitHub", href: "https://github.com/vexcms/vex" },
639
+ ],
640
+ },
641
+ ],
642
+ })
643
+ created.push("page:home")
99
644
  }
645
+
646
+ return { created, skipped }
100
647
  },
101
648
  })