create-vexcms 0.0.8 → 0.0.10

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 (63) hide show
  1. package/package.json +1 -1
  2. package/templates/base-nextjs/convex/vex/globals.ts +82 -0
  3. package/templates/base-nextjs/convex/vex/versions.ts +66 -6
  4. package/templates/base-nextjs/package.json +7 -7
  5. package/templates/base-nextjs/src/app/admin/layout.tsx +29 -11
  6. package/templates/base-nextjs/src/db/constants/auth.ts +1 -3
  7. package/templates/base-nextjs/src/vexcms/access.ts +29 -13
  8. package/templates/base-nextjs/src/vexcms/collections/users.ts +2 -2
  9. package/templates/base-nextjs/vex.config.ts +2 -0
  10. package/templates/marketing-site/convex/footers.ts +14 -0
  11. package/templates/marketing-site/convex/headers.ts +14 -0
  12. package/templates/marketing-site/convex/siteSettings.ts +14 -0
  13. package/templates/marketing-site/convex/theme.ts +75 -0
  14. package/templates/marketing-site/public/favicons/favicon.ico +0 -0
  15. package/templates/marketing-site/src/app/(frontend)/PageContent.tsx +74 -0
  16. package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +19 -51
  17. package/templates/marketing-site/src/app/(frontend)/layout.tsx +37 -0
  18. package/templates/marketing-site/src/app/(frontend)/page.tsx +24 -0
  19. package/templates/marketing-site/src/app/(frontend)/preview/[slug]/PreviewPageContent.tsx +55 -0
  20. package/templates/marketing-site/src/app/(frontend)/preview/[slug]/page.tsx +19 -63
  21. package/templates/marketing-site/src/app/(frontend)/preview/layout.tsx +18 -0
  22. package/templates/marketing-site/src/components/SiteFooter.tsx +28 -0
  23. package/templates/marketing-site/src/components/SiteHeader.tsx +28 -0
  24. package/templates/marketing-site/src/components/ThemeInjector.tsx +128 -0
  25. package/templates/marketing-site/src/components/ThemeStyle.tsx +104 -0
  26. package/templates/marketing-site/src/components/admin/IconPickerField.tsx +134 -0
  27. package/templates/marketing-site/src/components/admin/ThemeImport.tsx +174 -0
  28. package/templates/marketing-site/src/components/admin/ThemeImportField.tsx +33 -0
  29. package/templates/marketing-site/src/components/motion-primitives/animated-group.tsx +138 -0
  30. package/templates/marketing-site/src/components/motion-primitives/text-effect.tsx +292 -0
  31. package/templates/marketing-site/src/components/ui/accordion.tsx +74 -0
  32. package/templates/marketing-site/src/lib/colorConvert.ts +171 -0
  33. package/templates/marketing-site/src/lib/metadata.ts +104 -0
  34. package/templates/marketing-site/src/vexcms/access.ts +28 -0
  35. package/templates/marketing-site/src/vexcms/blocks/CTA/config.ts +36 -0
  36. package/templates/marketing-site/src/vexcms/blocks/CTA/index.tsx +46 -0
  37. package/templates/marketing-site/src/vexcms/blocks/FAQ/config.ts +70 -0
  38. package/templates/marketing-site/src/vexcms/blocks/FAQ/index.tsx +71 -0
  39. package/templates/marketing-site/src/vexcms/blocks/Features/config.ts +61 -0
  40. package/templates/marketing-site/src/vexcms/blocks/Features/index.tsx +73 -0
  41. package/templates/marketing-site/src/vexcms/blocks/Footer/config.ts +65 -0
  42. package/templates/marketing-site/src/vexcms/blocks/Footer/index.tsx +71 -0
  43. package/templates/marketing-site/src/vexcms/blocks/Header/config.ts +65 -0
  44. package/templates/marketing-site/src/vexcms/blocks/Header/index.tsx +174 -0
  45. package/templates/marketing-site/src/vexcms/blocks/Hero/config.ts +52 -0
  46. package/templates/marketing-site/src/vexcms/blocks/Hero/index.tsx +152 -0
  47. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/config.ts +68 -0
  48. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/index.tsx +74 -0
  49. package/templates/marketing-site/src/vexcms/blocks/Roadmap/config.ts +154 -0
  50. package/templates/marketing-site/src/vexcms/blocks/Roadmap/index.tsx +122 -0
  51. package/templates/marketing-site/src/vexcms/blocks/config.ts +40 -0
  52. package/templates/marketing-site/src/vexcms/blocks/constants.ts +9 -0
  53. package/templates/marketing-site/src/vexcms/blocks/index.ts +39 -0
  54. package/templates/marketing-site/src/vexcms/collections/footers.ts +11 -8
  55. package/templates/marketing-site/src/vexcms/collections/headers.ts +10 -14
  56. package/templates/marketing-site/src/vexcms/collections/index.ts +0 -1
  57. package/templates/marketing-site/src/vexcms/collections/pages.ts +30 -4
  58. package/templates/marketing-site/src/vexcms/collections/themes.ts +107 -14
  59. package/templates/marketing-site/src/vexcms/globals/index.ts +1 -0
  60. package/templates/marketing-site/src/vexcms/globals/siteSettings.ts +57 -0
  61. package/templates/marketing-site/vex.config.ts +5 -5
  62. package/templates/base-nextjs/src/auth/permissions.ts +0 -102
  63. package/templates/marketing-site/src/vexcms/collections/site_settings.ts +0 -31
@@ -1,33 +1,126 @@
1
- import { defineCollection, text } from "@vexcms/core"
1
+ import { color, defineCollection, tabs, text, ui } from "@vexcms/core"
2
2
 
3
+ import ThemeImportField from "~/components/admin/ThemeImportField"
3
4
  import { TABLE_SLUG_THEMES } from "~/db/constants"
4
5
 
6
+ /**
7
+ * Helper to create a set of shadcn/Tailwind color fields for a theme mode.
8
+ */
9
+ function themeColorFields() {
10
+ return {
11
+ background: color({ label: "Background", defaultValue: "#ffffff" }),
12
+ foreground: color({ label: "Foreground", defaultValue: "#0a0a0a" }),
13
+ card: color({ label: "Card", defaultValue: "#ffffff" }),
14
+ cardForeground: color({ label: "Card Foreground", defaultValue: "#0a0a0a" }),
15
+ popover: color({ label: "Popover", defaultValue: "#ffffff" }),
16
+ popoverForeground: color({ label: "Popover Foreground", defaultValue: "#0a0a0a" }),
17
+ primary: color({ label: "Primary", defaultValue: "#171717" }),
18
+ primaryForeground: color({ label: "Primary Foreground", defaultValue: "#fafafa" }),
19
+ secondary: color({ label: "Secondary", defaultValue: "#f5f5f5" }),
20
+ secondaryForeground: color({ label: "Secondary Foreground", defaultValue: "#171717" }),
21
+ muted: color({ label: "Muted", defaultValue: "#f5f5f5" }),
22
+ mutedForeground: color({ label: "Muted Foreground", defaultValue: "#737373" }),
23
+ accent: color({ label: "Accent", defaultValue: "#f5f5f5" }),
24
+ accentForeground: color({ label: "Accent Foreground", defaultValue: "#171717" }),
25
+ destructive: color({ label: "Destructive", defaultValue: "#ef4444" }),
26
+ destructiveForeground: color({ label: "Destructive Foreground", defaultValue: "#fafafa" }),
27
+ border: color({ label: "Border", defaultValue: "#e5e5e5" }),
28
+ input: color({ label: "Input", defaultValue: "#e5e5e5" }),
29
+ ring: color({ label: "Ring", defaultValue: "#0a0a0a" }),
30
+ chart1: color({ label: "Chart 1", defaultValue: "#e76e50" }),
31
+ chart2: color({ label: "Chart 2", defaultValue: "#2a9d90" }),
32
+ chart3: color({ label: "Chart 3", defaultValue: "#264653" }),
33
+ chart4: color({ label: "Chart 4", defaultValue: "#e8c468" }),
34
+ chart5: color({ label: "Chart 5", defaultValue: "#f4a261" }),
35
+ sidebar: color({ label: "Sidebar", defaultValue: "#fafafa" }),
36
+ sidebarForeground: color({ label: "Sidebar Foreground", defaultValue: "#0a0a0a" }),
37
+ sidebarPrimary: color({ label: "Sidebar Primary", defaultValue: "#171717" }),
38
+ sidebarPrimaryForeground: color({ label: "Sidebar Primary FG", defaultValue: "#fafafa" }),
39
+ sidebarAccent: color({ label: "Sidebar Accent", defaultValue: "#f5f5f5" }),
40
+ sidebarAccentForeground: color({ label: "Sidebar Accent FG", defaultValue: "#171717" }),
41
+ sidebarBorder: color({ label: "Sidebar Border", defaultValue: "#e5e5e5" }),
42
+ sidebarRing: color({ label: "Sidebar Ring", defaultValue: "#0a0a0a" }),
43
+ } as const
44
+ }
45
+
5
46
  export const themes = defineCollection({
6
47
  slug: TABLE_SLUG_THEMES,
7
48
  admin: {
49
+ defaultColumns: ["name", "_id", "fontFamily"],
8
50
  group: "Site Builder",
9
51
  useAsTitle: "name",
52
+ livePreview: {
53
+ url: "/preview/home",
54
+ },
55
+ },
56
+ versions: {
57
+ drafts: true,
10
58
  },
11
59
  fields: {
12
60
  name: text({
13
- label: "Name",
61
+ label: "Theme Name",
14
62
  required: true,
15
63
  }),
16
- backgroundColor: text({
17
- defaultValue: "#ffffff",
18
- label: "Background Color",
64
+ importTheme: ui({
65
+ admin: {
66
+ components: {
67
+ Field: ThemeImportField,
68
+ },
69
+ },
19
70
  }),
20
- fontFamily: text({
21
- defaultValue: "Inter, sans-serif",
22
- label: "Font Family",
71
+ colors: tabs({
72
+ label: "Theme Colors",
73
+ tabs: [
74
+ {
75
+ label: "Light",
76
+ slug: "light",
77
+ fields: themeColorFields(),
78
+ },
79
+ {
80
+ label: "Dark",
81
+ slug: "dark",
82
+ fields: {
83
+ ...themeColorFields(),
84
+ // Override dark mode defaults
85
+ background: color({ label: "Background", defaultValue: "#0a0a0a" }),
86
+ foreground: color({ label: "Foreground", defaultValue: "#fafafa" }),
87
+ card: color({ label: "Card", defaultValue: "#0a0a0a" }),
88
+ cardForeground: color({ label: "Card Foreground", defaultValue: "#fafafa" }),
89
+ popover: color({ label: "Popover", defaultValue: "#0a0a0a" }),
90
+ popoverForeground: color({ label: "Popover Foreground", defaultValue: "#fafafa" }),
91
+ primary: color({ label: "Primary", defaultValue: "#fafafa" }),
92
+ primaryForeground: color({ label: "Primary Foreground", defaultValue: "#171717" }),
93
+ secondary: color({ label: "Secondary", defaultValue: "#262626" }),
94
+ secondaryForeground: color({ label: "Secondary Foreground", defaultValue: "#fafafa" }),
95
+ muted: color({ label: "Muted", defaultValue: "#262626" }),
96
+ mutedForeground: color({ label: "Muted Foreground", defaultValue: "#a3a3a3" }),
97
+ accent: color({ label: "Accent", defaultValue: "#262626" }),
98
+ accentForeground: color({ label: "Accent Foreground", defaultValue: "#fafafa" }),
99
+ border: color({ label: "Border", defaultValue: "#262626" }),
100
+ input: color({ label: "Input", defaultValue: "#262626" }),
101
+ ring: color({ label: "Ring", defaultValue: "#d4d4d4" }),
102
+ sidebar: color({ label: "Sidebar", defaultValue: "#171717" }),
103
+ sidebarForeground: color({ label: "Sidebar Foreground", defaultValue: "#fafafa" }),
104
+ sidebarPrimary: color({ label: "Sidebar Primary", defaultValue: "#fafafa" }),
105
+ sidebarPrimaryForeground: color({
106
+ label: "Sidebar Primary FG",
107
+ defaultValue: "#171717",
108
+ }),
109
+ sidebarAccent: color({ label: "Sidebar Accent", defaultValue: "#262626" }),
110
+ sidebarAccentForeground: color({ label: "Sidebar Accent FG", defaultValue: "#fafafa" }),
111
+ sidebarBorder: color({ label: "Sidebar Border", defaultValue: "#262626" }),
112
+ sidebarRing: color({ label: "Sidebar Ring", defaultValue: "#d4d4d4" }),
113
+ },
114
+ },
115
+ ],
23
116
  }),
24
- primaryColor: text({
25
- defaultValue: "#3b82f6",
26
- label: "Primary Color",
117
+ radius: text({
118
+ label: "Border Radius",
119
+ defaultValue: "0.625rem",
27
120
  }),
28
- secondaryColor: text({
29
- defaultValue: "#6b7280",
30
- label: "Secondary Color",
121
+ fontFamily: text({
122
+ label: "Font Family",
123
+ defaultValue: "Inter, sans-serif",
31
124
  }),
32
125
  },
33
126
  labels: {
@@ -0,0 +1 @@
1
+ export { siteSettings } from "./siteSettings"
@@ -0,0 +1,57 @@
1
+ import { defineGlobal, relationship, text, upload } from "@vexcms/core"
2
+
3
+ import { TABLE_SLUG_MEDIA, TABLE_SLUG_SITE_SETTINGS, TABLE_SLUG_THEMES } from "~/db/constants"
4
+
5
+ export const siteSettings = defineGlobal({
6
+ slug: TABLE_SLUG_SITE_SETTINGS,
7
+ admin: {
8
+ group: "Site Builder",
9
+ livePreview: {
10
+ url: "/preview/home",
11
+ },
12
+ useAsTitle: "name",
13
+ },
14
+ fields: {
15
+ name: text({
16
+ label: "Site Name",
17
+ required: true,
18
+ }),
19
+ activeTheme: relationship({
20
+ label: "Active Theme",
21
+ to: TABLE_SLUG_THEMES,
22
+ }),
23
+ description: text({
24
+ label: "Site Description",
25
+ }),
26
+ favicon: upload({
27
+ admin: { description: "Site favicon image" },
28
+ label: "Favicon",
29
+ to: TABLE_SLUG_MEDIA,
30
+ }),
31
+ googleAnalyticsId: text({
32
+ admin: { description: "GA4 measurement ID (G-XXXXXXXXXX)" },
33
+ label: "Google Analytics ID",
34
+ }),
35
+ metaDescription: text({
36
+ admin: { description: "Default meta description for SEO" },
37
+ label: "Meta Description",
38
+ }),
39
+ metaTitle: text({
40
+ admin: { description: "Default <title> tag for the site" },
41
+ label: "Meta Title",
42
+ }),
43
+ ogImage: upload({
44
+ admin: { description: "Default Open Graph image for social sharing" },
45
+ label: "OG Image",
46
+ to: TABLE_SLUG_MEDIA,
47
+ }),
48
+ twitterHandle: text({
49
+ admin: { description: "@handle for Twitter cards" },
50
+ label: "Twitter Handle",
51
+ }),
52
+ },
53
+ label: "Site Settings",
54
+ versions: {
55
+ drafts: true,
56
+ },
57
+ })
@@ -2,29 +2,29 @@ import { defineConfig } from "@vexcms/core"
2
2
  import { convexFileStorage } from "@vexcms/file-storage-convex"
3
3
 
4
4
  import { auth } from "~/vexcms/auth"
5
+ import { access } from "~/vexcms/access"
5
6
  import {
6
7
  footers,
7
8
  headers,
8
9
  media,
9
10
  pages,
10
- siteSettings,
11
11
  themes,
12
12
  users,
13
13
  } from "~/vexcms/collections"
14
+ import { siteSettings } from "~/vexcms/globals"
14
15
 
15
16
  export default defineConfig({
17
+ access,
16
18
  admin: {
17
19
  meta: {
18
20
  titleSuffix: " | My Site",
19
21
  },
20
- sidebar: {
21
- hideGlobals: true,
22
- },
23
22
  user: "user",
24
23
  },
25
24
  auth,
26
25
  basePath: "/admin",
27
- collections: [pages, headers, footers, themes, siteSettings, users],
26
+ collections: [pages, headers, footers, themes, users],
27
+ globals: [siteSettings],
28
28
  media: {
29
29
  collections: [media],
30
30
  storageAdapter: convexFileStorage(),
@@ -1,102 +0,0 @@
1
- import type { User } from "~/db/types"
2
-
3
- import { TABLE_SLUG_USERS, USER_ROLES, type UserRole } from "~/db/constants"
4
-
5
- export const AUTH_ACTIONS = {
6
- create: "create",
7
- delete: "delete",
8
- read: "read",
9
- update: "update",
10
- } as const
11
- export type AuthAction = (typeof AUTH_ACTIONS)[keyof typeof AUTH_ACTIONS]
12
-
13
- export const RESOURCES = {
14
- users: TABLE_SLUG_USERS,
15
- } as const
16
- export type Permissions = {
17
- [TABLE_SLUG_USERS]: {
18
- action: AuthAction
19
- dataType: User
20
- }
21
- }
22
-
23
- export type Resource = (typeof RESOURCES)[keyof typeof RESOURCES]
24
-
25
- export type RolesWithPermissions = Record<
26
- UserRole,
27
- Partial<{
28
- [Key in keyof Permissions]: Partial<Record<Permissions[Key]["action"], PermissionCheck<Key>>>
29
- }>
30
- >
31
-
32
- type PermissionCheck<Key extends keyof Permissions> =
33
- | (({ data, user }: { data: Permissions[Key]["dataType"]; user: User }) => boolean)
34
- | boolean
35
-
36
- // type PermissionDataTypeExcluding<K extends keyof Permissions> = Permissions[Exclude<keyof Permissions, K>]["dataType"];
37
- // usage: T extends PermissionDataTypeExcluding<"orgs" | "users">
38
-
39
- const ROLES = {
40
- [USER_ROLES.admin]: {
41
- [TABLE_SLUG_USERS]: {
42
- create: true,
43
- delete: true,
44
- read: true,
45
- update: true,
46
- },
47
- },
48
- [USER_ROLES.editor]: {
49
- [TABLE_SLUG_USERS]: {
50
- read: true,
51
- },
52
- },
53
- [USER_ROLES.author]: {
54
- [TABLE_SLUG_USERS]: {
55
- read: ({ data: targetUser, user }) => user._id === targetUser._id,
56
- update: ({ data: targetUser, user }) => user._id === targetUser._id,
57
- },
58
- },
59
- [USER_ROLES.member]: {
60
- [TABLE_SLUG_USERS]: {
61
- read: ({ data: targetUser, user }) => user._id === targetUser._id,
62
- },
63
- },
64
- [USER_ROLES.user]: {
65
- [TABLE_SLUG_USERS]: {
66
- create: false,
67
- delete: ({ data: targetUser, user }) => user._id === targetUser._id,
68
- read: ({ data: targetUser, user }) => user._id === targetUser._id,
69
- update: ({ data: targetUser, user }) => user._id === targetUser._id,
70
- },
71
- },
72
- } as const satisfies RolesWithPermissions
73
-
74
- export function hasPermission<Resource extends keyof Permissions>({
75
- action,
76
- data,
77
- resource,
78
- user,
79
- }: {
80
- action: Permissions[Resource]["action"]
81
- data?: Permissions[Resource]["dataType"]
82
- resource: Resource
83
- user?: null | User
84
- }): boolean {
85
- if (!user?.role) {
86
- return false
87
- }
88
-
89
- return user.role.some((role) => {
90
- const permission = (ROLES as RolesWithPermissions)[role as UserRole][resource]?.[action]
91
-
92
- if (!permission) {
93
- return false
94
- }
95
-
96
- if (typeof permission === "boolean") {
97
- return permission
98
- }
99
-
100
- return data != null && permission({ data, user })
101
- })
102
- }
@@ -1,31 +0,0 @@
1
- import { defineCollection, text, upload } from "@vexcms/core"
2
-
3
- import { TABLE_SLUG_MEDIA, TABLE_SLUG_SITE_SETTINGS } from "~/db/constants"
4
-
5
- export const siteSettings = defineCollection({
6
- slug: TABLE_SLUG_SITE_SETTINGS,
7
- admin: {
8
- group: "Site Builder",
9
- useAsTitle: "name",
10
- },
11
- fields: {
12
- name: text({
13
- label: "Site Name",
14
- required: true,
15
- }),
16
- description: text({
17
- label: "Site Description",
18
- }),
19
- favicon: upload({
20
- admin: {
21
- description: "Site favicon image",
22
- },
23
- label: "Favicon",
24
- to: TABLE_SLUG_MEDIA,
25
- }),
26
- },
27
- labels: {
28
- plural: "Site Settings",
29
- singular: "Site Settings",
30
- },
31
- })