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
@@ -0,0 +1,74 @@
1
+ "use client"
2
+
3
+ import type { BlockComponentProps } from "@vexcms/ui"
4
+
5
+ import { icons } from "lucide-react"
6
+
7
+ import { cn } from "~/lib/utils"
8
+
9
+ export { howItWorksBlock } from "./config"
10
+
11
+ function LucideIcon({ name }: { name: string }) {
12
+ const Icon = icons[name as keyof typeof icons]
13
+ if (!Icon) return null
14
+ return <Icon className="size-5" />
15
+ }
16
+
17
+ export default function HowItWorksBlock({
18
+ block,
19
+ blockStyles,
20
+ }: BlockComponentProps) {
21
+ const { heading, subheading, steps } = block as unknown as {
22
+ heading: string
23
+ subheading?: string
24
+ steps?: Array<{ icon?: string; title: string; description: string }>
25
+ }
26
+
27
+ return (
28
+ <section className={cn("py-16 md:py-32", blockStyles)}>
29
+ <div className="mx-auto max-w-3xl px-6">
30
+ <div className="text-center">
31
+ <h2 className="text-4xl font-semibold text-balance lg:text-5xl">
32
+ {heading}
33
+ </h2>
34
+ {subheading && (
35
+ <p className="text-muted-foreground mt-4 text-balance">
36
+ {subheading}
37
+ </p>
38
+ )}
39
+ </div>
40
+
41
+ <div className="mt-16">
42
+ {(steps ?? []).map((step, index) => {
43
+ const isLast = index === (steps?.length ?? 1) - 1
44
+
45
+ return (
46
+ <div key={index} className="flex flex-row gap-6">
47
+ <div className="flex flex-col items-center">
48
+ <div className="bg-primary text-primary-foreground flex size-10 shrink-0 items-center justify-center rounded-full">
49
+ {step.icon ? (
50
+ <LucideIcon name={step.icon} />
51
+ ) : (
52
+ <span className="text-sm font-semibold">
53
+ {index + 1}
54
+ </span>
55
+ )}
56
+ </div>
57
+ {!isLast && (
58
+ <div className="bg-border w-px flex-1" />
59
+ )}
60
+ </div>
61
+ <div className={cn("flex-1", !isLast && "pb-10")}>
62
+ <h3 className="text-lg font-semibold">{step.title}</h3>
63
+ <p className="text-muted-foreground mt-1">
64
+ {step.description}
65
+ </p>
66
+ </div>
67
+ </div>
68
+ )
69
+ })}
70
+ </div>
71
+ </div>
72
+ </section>
73
+ )
74
+ }
@@ -0,0 +1,154 @@
1
+ import { array, defineBlock, object, select, text } from "@vexcms/core"
2
+
3
+ import { BLOCK_SLUG_ROADMAP } from "../constants"
4
+
5
+ export const roadmapBlock = defineBlock({
6
+ slug: BLOCK_SLUG_ROADMAP,
7
+ label: "Roadmap",
8
+ fields: {
9
+ heading: text({
10
+ label: "Heading",
11
+ required: true,
12
+ defaultValue: "Roadmap",
13
+ }),
14
+ subheading: text({
15
+ label: "Subheading",
16
+ defaultValue:
17
+ "What we've shipped and what's coming next. Vex CMS is actively developed — here's where we're headed.",
18
+ }),
19
+ items: array({
20
+ label: "Roadmap Items",
21
+ required: true,
22
+ items: object({
23
+ fields: {
24
+ feature: text({ label: "Feature Name", required: true }),
25
+ description: text({ label: "Description" }),
26
+ status: select({
27
+ label: "Status",
28
+ required: true,
29
+ options: [
30
+ { label: "Shipped", value: "shipped" },
31
+ { label: "Coming Soon", value: "coming-soon" },
32
+ { label: "Planned", value: "planned" },
33
+ ],
34
+ defaultValue: "shipped",
35
+ }),
36
+ },
37
+ }),
38
+ defaultValue: [
39
+ {
40
+ feature: "16 Field Types",
41
+ description:
42
+ "Text, number, select, date, relationship, upload, richtext, blocks, color, tabs, and more.",
43
+ status: "shipped",
44
+ },
45
+ {
46
+ feature: "Admin Panel",
47
+ description:
48
+ "Full-featured admin UI with list views, edit forms, media library, and draft/publish workflow.",
49
+ status: "shipped",
50
+ },
51
+ {
52
+ feature: "Real-Time Queries",
53
+ description:
54
+ "Every query is live via Convex. Content updates appear instantly across all connected clients.",
55
+ status: "shipped",
56
+ },
57
+ {
58
+ feature: "Live Preview",
59
+ description:
60
+ "Side-by-side iframe preview with responsive breakpoints and real-time updates as you edit.",
61
+ status: "shipped",
62
+ },
63
+ {
64
+ feature: "Block System",
65
+ description:
66
+ "Compose pages from reusable content blocks with drag-and-drop reordering and inline editing.",
67
+ status: "shipped",
68
+ },
69
+ {
70
+ feature: "Authentication & RBAC",
71
+ description:
72
+ "Better Auth integration with role-based access control at the document and field level.",
73
+ status: "shipped",
74
+ },
75
+ {
76
+ feature: "Rich Text Editor",
77
+ description:
78
+ "Plate.js-powered editor with media uploads, links, tables, and custom elements.",
79
+ status: "shipped",
80
+ },
81
+ {
82
+ feature: "CLI & Scaffolding",
83
+ description:
84
+ "vex dev with watch/generate, and create-vexcms for instant project setup.",
85
+ status: "shipped",
86
+ },
87
+ {
88
+ feature: "Theme System",
89
+ description:
90
+ "Database-driven themes with light/dark mode, CSS variables, and OKLCH color support.",
91
+ status: "shipped",
92
+ },
93
+ {
94
+ feature: "Block Styles",
95
+ description:
96
+ "Per-block responsive styling with Tailwind presets — margin, padding, typography, layout, and more.",
97
+ status: "shipped",
98
+ },
99
+ {
100
+ feature: "Content Scheduling",
101
+ description:
102
+ "Set a publishAt timestamp and content goes live automatically via Convex scheduled functions.",
103
+ status: "coming-soon",
104
+ },
105
+ {
106
+ feature: "Team Management",
107
+ description:
108
+ "Invite users, assign roles, and manage pending invitations from the admin panel.",
109
+ status: "coming-soon",
110
+ },
111
+ {
112
+ feature: "API Keys",
113
+ description:
114
+ "Read-only API tokens for external integrations with configurable rate limiting.",
115
+ status: "coming-soon",
116
+ },
117
+ {
118
+ feature: "Audit Log",
119
+ description:
120
+ "Track who changed what and when across all collections and documents.",
121
+ status: "coming-soon",
122
+ },
123
+ {
124
+ feature: "Environments",
125
+ description:
126
+ "Project-level content branching with staging and production environments and atomic promotion.",
127
+ status: "planned",
128
+ },
129
+ {
130
+ feature: "Localization",
131
+ description:
132
+ "i18n field variants with per-locale versioning and content translation workflows.",
133
+ status: "planned",
134
+ },
135
+ {
136
+ feature: "Approval Workflows",
137
+ description:
138
+ "Review and sign-off steps before content goes live. Configurable multi-step approval chains.",
139
+ status: "planned",
140
+ },
141
+ {
142
+ feature: "Plugin System",
143
+ description:
144
+ "Extend VEX with community plugins for custom fields, integrations, and admin panel features.",
145
+ status: "planned",
146
+ },
147
+ ],
148
+ }),
149
+ },
150
+ admin: {
151
+ icon: "map",
152
+ blockStyles: ["container"],
153
+ },
154
+ })
@@ -0,0 +1,122 @@
1
+ "use client"
2
+
3
+ import type { BlockComponentProps } from "@vexcms/ui"
4
+
5
+ import { Check, Clock, Compass } from "lucide-react"
6
+
7
+ import { cn } from "~/lib/utils"
8
+
9
+ export { roadmapBlock } from "./config"
10
+
11
+ const statusConfig = {
12
+ shipped: {
13
+ label: "Shipped",
14
+ icon: Check,
15
+ badgeClass:
16
+ "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20",
17
+ },
18
+ "coming-soon": {
19
+ label: "Coming Soon",
20
+ icon: Clock,
21
+ badgeClass:
22
+ "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20",
23
+ },
24
+ planned: {
25
+ label: "Planned",
26
+ icon: Compass,
27
+ badgeClass:
28
+ "bg-zinc-500/10 text-zinc-600 dark:text-zinc-400 border-zinc-500/20",
29
+ },
30
+ } as const
31
+
32
+ type RoadmapItem = {
33
+ feature: string
34
+ description?: string
35
+ status: "shipped" | "coming-soon" | "planned"
36
+ }
37
+
38
+ type StatusKey = keyof typeof statusConfig
39
+
40
+ const statusOrder: StatusKey[] = ["shipped", "coming-soon", "planned"]
41
+
42
+ export default function RoadmapBlock({
43
+ block,
44
+ blockStyles,
45
+ }: BlockComponentProps) {
46
+ const { heading, subheading, items } = block as unknown as {
47
+ heading: string
48
+ subheading?: string
49
+ items?: RoadmapItem[]
50
+ }
51
+
52
+ const grouped = statusOrder
53
+ .map((status) => ({
54
+ status,
55
+ config: statusConfig[status],
56
+ items: (items ?? []).filter(
57
+ (item) => item.status === status && item.status in statusConfig
58
+ ),
59
+ }))
60
+ .filter((group) => group.items.length > 0)
61
+
62
+ return (
63
+ <section className={cn("py-16 md:py-32", blockStyles)}>
64
+ <div className="mx-auto max-w-5xl px-6">
65
+ <div className="text-center">
66
+ <h2 className="text-4xl font-semibold text-balance lg:text-5xl">
67
+ {heading}
68
+ </h2>
69
+ {subheading && (
70
+ <p className="text-muted-foreground mt-4 text-balance">
71
+ {subheading}
72
+ </p>
73
+ )}
74
+ </div>
75
+
76
+ {grouped.map((group, groupIndex) => {
77
+ const StatusIcon = group.config.icon
78
+
79
+ return (
80
+ <div key={group.status} className={groupIndex === 0 ? "mt-16" : "mt-12"}>
81
+ <div className="flex items-center gap-2">
82
+ <StatusIcon className="size-5" />
83
+ <h3 className="text-lg font-semibold">{group.config.label}</h3>
84
+ </div>
85
+
86
+ <div className="mt-6 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
87
+ {group.items.map((item, itemIndex) => {
88
+ const BadgeIcon = group.config.icon
89
+
90
+ return (
91
+ <div
92
+ key={itemIndex}
93
+ className="bg-card rounded-xl border p-5"
94
+ >
95
+ <div className="flex items-center justify-between gap-4">
96
+ <span className="font-medium">{item.feature}</span>
97
+ <span
98
+ className={cn(
99
+ "inline-flex shrink-0 items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium",
100
+ group.config.badgeClass
101
+ )}
102
+ >
103
+ <BadgeIcon className="size-3" />
104
+ {group.config.label}
105
+ </span>
106
+ </div>
107
+ {item.description && (
108
+ <p className="text-muted-foreground mt-2 text-sm">
109
+ {item.description}
110
+ </p>
111
+ )}
112
+ </div>
113
+ )
114
+ })}
115
+ </div>
116
+ </div>
117
+ )
118
+ })}
119
+ </div>
120
+ </section>
121
+ )
122
+ }
@@ -0,0 +1,40 @@
1
+ // Block configs only — no React/motion dependencies
2
+ // Safe to import from collections and server-side code
3
+ import { heroBlock } from "./Hero/config"
4
+ import { featuresBlock } from "./Features/config"
5
+ import { ctaBlock } from "./CTA/config"
6
+ import { faqBlock } from "./FAQ/config"
7
+ import { headerBlock } from "./Header/config"
8
+ import { footerBlock } from "./Footer/config"
9
+ import { howItWorksBlock } from "./HowItWorks/config"
10
+ import { roadmapBlock } from "./Roadmap/config"
11
+
12
+ /** Page content block definitions */
13
+ export const pageBlocks = [
14
+ heroBlock,
15
+ featuresBlock,
16
+ howItWorksBlock,
17
+ roadmapBlock,
18
+ ctaBlock,
19
+ faqBlock,
20
+ ]
21
+
22
+ /** Header block definitions */
23
+ export const headerBlocks = [headerBlock]
24
+
25
+ /** Footer block definitions */
26
+ export const footerBlocks = [footerBlock]
27
+
28
+ /** All block definitions combined */
29
+ export const allBlocks = [...pageBlocks, ...headerBlocks, ...footerBlocks]
30
+
31
+ export {
32
+ heroBlock,
33
+ featuresBlock,
34
+ ctaBlock,
35
+ faqBlock,
36
+ headerBlock,
37
+ footerBlock,
38
+ howItWorksBlock,
39
+ roadmapBlock,
40
+ }
@@ -0,0 +1,9 @@
1
+ // Block slugs
2
+ export const BLOCK_SLUG_HERO = "hero" as const
3
+ export const BLOCK_SLUG_FEATURES = "features" as const
4
+ export const BLOCK_SLUG_CTA = "cta" as const
5
+ export const BLOCK_SLUG_FAQ = "faq" as const
6
+ export const BLOCK_SLUG_HEADER = "header" as const
7
+ export const BLOCK_SLUG_FOOTER = "footer" as const
8
+ export const BLOCK_SLUG_HOW_IT_WORKS = "how_it_works" as const
9
+ export const BLOCK_SLUG_ROADMAP = "roadmap" as const
@@ -0,0 +1,39 @@
1
+ import type { BlockComponentProps } from "@vexcms/ui"
2
+ import type { ComponentType } from "react"
3
+
4
+ import {
5
+ BLOCK_SLUG_CTA,
6
+ BLOCK_SLUG_FAQ,
7
+ BLOCK_SLUG_FEATURES,
8
+ BLOCK_SLUG_FOOTER,
9
+ BLOCK_SLUG_HEADER,
10
+ BLOCK_SLUG_HERO,
11
+ BLOCK_SLUG_HOW_IT_WORKS,
12
+ BLOCK_SLUG_ROADMAP,
13
+ } from "./constants"
14
+ import CTABlock from "./CTA"
15
+ import FAQBlock from "./FAQ"
16
+ import FeaturesBlock from "./Features"
17
+ import FooterBlock from "./Footer"
18
+ import HeaderBlock from "./Header"
19
+ import HeroBlock from "./Hero"
20
+ import HowItWorksBlock from "./HowItWorks"
21
+ import RoadmapBlock from "./Roadmap"
22
+
23
+ /** Block component map for use with RenderBlocks */
24
+ export const blockComponents: Record<
25
+ string,
26
+ ComponentType<BlockComponentProps>
27
+ > = {
28
+ [BLOCK_SLUG_CTA]: CTABlock,
29
+ [BLOCK_SLUG_FAQ]: FAQBlock,
30
+ [BLOCK_SLUG_FEATURES]: FeaturesBlock,
31
+ [BLOCK_SLUG_FOOTER]: FooterBlock,
32
+ [BLOCK_SLUG_HEADER]: HeaderBlock,
33
+ [BLOCK_SLUG_HERO]: HeroBlock,
34
+ [BLOCK_SLUG_HOW_IT_WORKS]: HowItWorksBlock,
35
+ [BLOCK_SLUG_ROADMAP]: RoadmapBlock,
36
+ }
37
+
38
+ // Re-export configs for convenience in client code
39
+ export { allBlocks, footerBlocks, headerBlocks, pageBlocks } from "./config"
@@ -1,6 +1,7 @@
1
- import { defineCollection, richtext, text } from "@vexcms/core"
1
+ import { blocks, defineCollection, text } from "@vexcms/core"
2
2
 
3
- import { TABLE_SLUG_FOOTERS, TABLE_SLUG_MEDIA } from "~/db/constants"
3
+ import { TABLE_SLUG_FOOTERS } from "~/db/constants"
4
+ import { footerBlocks } from "~/vexcms/blocks/config"
4
5
 
5
6
  export const footers = defineCollection({
6
7
  slug: TABLE_SLUG_FOOTERS,
@@ -13,12 +14,14 @@ export const footers = defineCollection({
13
14
  label: "Name",
14
15
  required: true,
15
16
  }),
16
- content: richtext({
17
- label: "Content",
18
- mediaCollection: TABLE_SLUG_MEDIA,
19
- }),
20
- copyright: text({
21
- label: "Copyright Text",
17
+ content: blocks({
18
+ blocks: footerBlocks,
19
+ label: "Footer",
20
+ max: 1,
21
+ labels: {
22
+ singular: "Footer",
23
+ plural: "Footers",
24
+ },
22
25
  }),
23
26
  },
24
27
  labels: {
@@ -1,6 +1,7 @@
1
- import { checkbox, defineCollection, text, upload } from "@vexcms/core"
1
+ import { blocks, defineCollection, text } from "@vexcms/core"
2
2
 
3
- import { TABLE_SLUG_HEADERS, TABLE_SLUG_MEDIA } from "~/db/constants"
3
+ import { TABLE_SLUG_HEADERS } from "~/db/constants"
4
+ import { headerBlocks } from "~/vexcms/blocks/config"
4
5
 
5
6
  export const headers = defineCollection({
6
7
  slug: TABLE_SLUG_HEADERS,
@@ -13,19 +14,14 @@ export const headers = defineCollection({
13
14
  label: "Name",
14
15
  required: true,
15
16
  }),
16
- logoText: text({
17
- label: "Logo Text",
18
- }),
19
- logoUrl: upload({
20
- admin: {
21
- description: "URL for the header logo image",
17
+ content: blocks({
18
+ blocks: headerBlocks,
19
+ label: "Header",
20
+ max: 1,
21
+ labels: {
22
+ singular: "Header",
23
+ plural: "Headers",
22
24
  },
23
- label: "Logo URL",
24
- to: TABLE_SLUG_MEDIA,
25
- }),
26
- sticky: checkbox({
27
- defaultValue: false,
28
- label: "Sticky Header",
29
25
  }),
30
26
  },
31
27
  labels: {
@@ -2,6 +2,5 @@ export * from "./footers"
2
2
  export * from "./headers"
3
3
  export * from "./media"
4
4
  export * from "./pages"
5
- export * from "./site_settings"
6
5
  export * from "./themes"
7
6
  export * from "./users"
@@ -1,6 +1,7 @@
1
- import { defineCollection, richtext, text } from "@vexcms/core"
1
+ import { blocks, defineCollection, imageUrl, text } from "@vexcms/core"
2
2
 
3
- import { TABLE_SLUG_MEDIA, TABLE_SLUG_PAGES } from "~/db/constants"
3
+ import { TABLE_SLUG_PAGES } from "~/db/constants"
4
+ import { pageBlocks } from "~/vexcms/blocks/config"
4
5
 
5
6
  export const pages = defineCollection({
6
7
  slug: TABLE_SLUG_PAGES,
@@ -25,9 +26,34 @@ export const pages = defineCollection({
25
26
  label: "Slug",
26
27
  required: true,
27
28
  }),
28
- content: richtext({
29
+ content: blocks({
30
+ blocks: pageBlocks,
29
31
  label: "Content",
30
- mediaCollection: TABLE_SLUG_MEDIA,
32
+ labels: {
33
+ singular: "Block",
34
+ plural: "Blocks",
35
+ },
36
+ }),
37
+ metaTitle: text({
38
+ label: "Meta Title",
39
+ admin: {
40
+ description: "Custom <title> tag. Falls back to page title if empty.",
41
+ position: "sidebar",
42
+ },
43
+ }),
44
+ metaDescription: text({
45
+ label: "Meta Description",
46
+ admin: {
47
+ description: "Custom meta description for search results.",
48
+ position: "sidebar",
49
+ },
50
+ }),
51
+ ogImage: imageUrl({
52
+ label: "OG Image",
53
+ admin: {
54
+ description: "Custom Open Graph image URL for social sharing.",
55
+ position: "sidebar",
56
+ },
31
57
  }),
32
58
  },
33
59
  labels: {