payload-next-starter 1.0.5 → 1.0.7

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 (44) hide show
  1. package/README.md +85 -20
  2. package/bin/setup.js +66 -2
  3. package/package.json +10 -1
  4. package/src/collections/Media.ts +16 -0
  5. package/src/collections/Pages.ts +358 -0
  6. package/src/collections/Users.ts +13 -0
  7. package/src/components/BlockRenderer.tsx +39 -0
  8. package/src/components/RichTextContent.tsx +81 -0
  9. package/src/components/blocks/CTABlock.tsx +81 -0
  10. package/src/components/blocks/ContactFormBlock.tsx +230 -0
  11. package/src/components/blocks/ContentBlock.tsx +23 -0
  12. package/src/components/blocks/FAQBlock.tsx +93 -0
  13. package/src/components/blocks/FeaturesBlock.tsx +82 -0
  14. package/src/components/blocks/HeroBlock.tsx +99 -0
  15. package/src/components/blocks/ImageGalleryBlock.tsx +77 -0
  16. package/src/components/blocks/PricingBlock.tsx +164 -0
  17. package/src/components/blocks/TeamBlock.tsx +134 -0
  18. package/src/components/blocks/TestimonialsBlock.tsx +93 -0
  19. package/src/components/blocks/index.ts +10 -0
  20. package/src/lib/getPageBySlug.ts +25 -0
  21. package/src/scripts/seedPages.ts +774 -0
  22. package/templates/app/(frontend)/[slug]/page.tsx +95 -0
  23. package/templates/app/(frontend)/page.tsx +3 -57
  24. package/templates/app/(frontend)/styles.css +9 -9
  25. package/templates/app/next/preview/route.ts +17 -0
  26. package/templates/collections/Pages.ts +358 -0
  27. package/templates/components/BlockRenderer.tsx +39 -0
  28. package/templates/components/RichTextContent.tsx +81 -0
  29. package/templates/components/blocks/CTABlock.tsx +81 -0
  30. package/templates/components/blocks/ContactFormBlock.tsx +230 -0
  31. package/templates/components/blocks/ContentBlock.tsx +23 -0
  32. package/templates/components/blocks/FAQBlock.tsx +93 -0
  33. package/templates/components/blocks/FeaturesBlock.tsx +82 -0
  34. package/templates/components/blocks/HeroBlock.tsx +99 -0
  35. package/templates/components/blocks/ImageGalleryBlock.tsx +77 -0
  36. package/templates/components/blocks/PricingBlock.tsx +164 -0
  37. package/templates/components/blocks/TeamBlock.tsx +134 -0
  38. package/templates/components/blocks/TestimonialsBlock.tsx +93 -0
  39. package/templates/lib/getPageBySlug.ts +25 -0
  40. package/templates/payload.config.ts +2 -1
  41. package/templates/scripts/seedPages.ts +774 -0
  42. package/templates/app/(frontend)/about-us/page.tsx +0 -22
  43. package/templates/app/(frontend)/contact-us/page.tsx +0 -421
  44. package/templates/app/(frontend)/share/page.tsx +0 -72
package/README.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # Payload Next.js Starter
2
2
 
3
- Quickly add PayloadCMS to any existing Next.js project. This package scaffolds PayloadCMS with PostgreSQL support into your Next.js application.
3
+ A block-based page builder powered by PayloadCMS for Next.js. Install this package into any Next.js project to add a full CMS with dynamic page content that can be shared across multiple applications.
4
+
5
+ ## Features
6
+
7
+ - **Block-Based Page Builder** — Hero, Content, Contact Form, Features, Pricing, Testimonials, CTA, Team, FAQ, Image Gallery
8
+ - **Multi-App Support** — Share one PostgreSQL database across multiple Next.js apps
9
+ - **Admin Panel** — Visual editor for managing pages and content
10
+ - **Dynamic Routes** — Automatic page rendering at `/[slug]`
11
+ - **SEO Fields** — Meta titles, descriptions, images per page
12
+ - **Draft Mode** — Preview changes before publishing
13
+ - **TypeScript** — Full type safety with generated types
4
14
 
5
15
  ## Installation
6
16
 
@@ -19,31 +29,36 @@ payload-next-starter
19
29
 
20
30
  ## What it does
21
31
 
22
- This CLI tool will:
32
+ The setup script will:
23
33
  1. Copy Payload admin routes to `src/app/(payload)/`
24
34
  2. Copy API routes to `src/app/api/[...payload]/`
25
- 3. Copy collections (Users, Media) to `src/collections/`
26
- 4. Install required dependencies
27
- 5. Update `next.config.ts` with Payload plugin
28
- 6. Create `.env.example` with required variables
29
-
30
- ## After installation
35
+ 3. Copy collections (Users, Media, Pages) to `src/collections/`
36
+ 4. Copy block components to `src/components/blocks/`
37
+ 5. Copy BlockRenderer and RichTextContent components
38
+ 6. Copy dynamic `[slug]` page renderer to `src/app/(frontend)/[slug]/`
39
+ 7. Copy seed script to `src/scripts/seedPages.ts`
40
+ 8. Install required dependencies
41
+ 9. Update `next.config.ts` with Payload plugin
42
+ 10. Add scripts to `package.json`
43
+ 11. Create `.env.example` with required variables
44
+
45
+ ## After Installation
31
46
 
32
47
  1. Copy `.env.example` to `.env` and fill in your values:
33
48
  ```bash
34
- DATABASE_URI=postgresql://user:password@host:5432/database
49
+ DATABASE_URL=postgresql://user:password@host:5432/database?sslmode=require
35
50
  PAYLOAD_SECRET=your-secret-key-min-16-chars
36
51
  NEXT_PUBLIC_SERVER_URL=http://localhost:3000
37
52
  ```
38
53
 
39
54
  2. Generate Payload import maps:
40
55
  ```bash
41
- npx payload generate:importmap
56
+ npm run generate:importmap
42
57
  ```
43
58
 
44
59
  3. Generate TypeScript types:
45
60
  ```bash
46
- npx payload generate:types
61
+ npm run generate:types
47
62
  ```
48
63
 
49
64
  4. Start your dev server:
@@ -53,16 +68,61 @@ This CLI tool will:
53
68
 
54
69
  5. Visit `http://localhost:3000/admin` to create your first admin user
55
70
 
71
+ 6. Seed sample pages (optional):
72
+ ```bash
73
+ npm run seed:pages
74
+ ```
75
+
76
+ ## Using in Another Next.js App
77
+
78
+ This package is designed to be installed in multiple Next.js apps that share the same database. See [CONSUMER_GUIDE.md](./CONSUMER_GUIDE.md) for detailed instructions on:
79
+
80
+ - Installing the package in a consumer app
81
+ - Configuring the shared database connection
82
+ - Rendering dynamic pages with BlockRenderer
83
+ - Using individual block components
84
+ - Managing content across multiple apps
85
+
86
+ ## Collections Included
87
+
88
+ - **Users** — Authentication-enabled collection with admin access
89
+ - **Media** — Uploads collection with image handling
90
+ - **Pages** — Block-based page builder with SEO fields
91
+
92
+ ## Available Blocks
93
+
94
+ | Block | Description |
95
+ |-------|-------------|
96
+ | `HeroBlock` | Hero section with title, subtitle, CTA |
97
+ | `ContentBlock` | Rich text content with heading |
98
+ | `ContactFormBlock` | Contact form with info display |
99
+ | `FeaturesBlock` | Feature grid with icons |
100
+ | `PricingBlock` | Pricing plans table |
101
+ | `TestimonialsBlock` | Customer testimonials |
102
+ | `CTABlock` | Call-to-action banner |
103
+ | `TeamBlock` | Team member cards |
104
+ | `FAQBlock` | Accordion FAQ section |
105
+ | `ImageGalleryBlock` | Image grid gallery |
106
+
107
+ ## Sample Pages
108
+
109
+ After running `npm run seed:pages`, the following sample pages are created:
110
+
111
+ | Route | Content |
112
+ |-------|---------|
113
+ | `/home` | Hero → Features → Testimonials → CTA |
114
+ | `/about-us` | Hero → Story → Values → Team → CTA |
115
+ | `/contact-us` | Hero → Contact Form → FAQ |
116
+ | `/services` | Hero → Services → How We Work → CTA |
117
+ | `/pricing` | Hero → Pricing → FAQ → CTA |
118
+ | `/faq` | Hero → General FAQ → Technical FAQ → CTA |
119
+ | `/share` | Hero → Share Ways → Referral → Testimonials → CTA |
120
+
56
121
  ## Requirements
57
122
 
58
123
  - Next.js 13+ with App Router
59
124
  - Node.js 18.20+ or 20.9+
60
- - PostgreSQL database
61
-
62
- ## Collections included
63
-
64
- - **Users** - Authentication-enabled collection with admin access
65
- - **Media** - Uploads collection with image handling
125
+ - PostgreSQL database (Neon, Supabase, or any managed Postgres)
66
126
 
67
127
  ## Deploying
68
128
 
@@ -73,7 +133,7 @@ npm run build
73
133
  ```
74
134
 
75
135
  Ensure these environment variables are set in production:
76
- - `DATABASE_URI`
136
+ - `DATABASE_URL`
77
137
  - `PAYLOAD_SECRET`
78
138
  - `NEXT_PUBLIC_SERVER_URL`
79
139
 
@@ -84,7 +144,9 @@ Edit `src/payload.config.ts` to add:
84
144
  - Globals
85
145
  - Hooks
86
146
  - Access control
87
- - Custom components
147
+ - Custom block types
148
+
149
+ Edit block components in `src/components/blocks/` to customize styling and behavior.
88
150
 
89
151
  ## License
90
152
 
@@ -92,4 +154,7 @@ MIT
92
154
 
93
155
  ## Support
94
156
 
95
- [GitHub Issues](https://github.com/sentayhu19/test-payload/issues)
157
+ - [GitHub Issues](https://github.com/sentayhu19/test-payload/issues)
158
+ - [Consumer Guide](./CONSUMER_GUIDE.md) — For using in other Next.js apps
159
+ - [Payload Docs](https://payloadcms.com/docs)
160
+
package/bin/setup.js CHANGED
@@ -36,6 +36,11 @@ try {
36
36
  mkdirSync(join(targetDir, 'src/app/(payload)'), { recursive: true })
37
37
  mkdirSync(join(targetDir, 'src/app/api/[...payload]'), { recursive: true })
38
38
  mkdirSync(join(targetDir, 'src/collections'), { recursive: true })
39
+ mkdirSync(join(targetDir, 'src/components'), { recursive: true })
40
+ mkdirSync(join(targetDir, 'src/components/blocks'), { recursive: true })
41
+ mkdirSync(join(targetDir, 'src/lib'), { recursive: true })
42
+ mkdirSync(join(targetDir, 'src/scripts'), { recursive: true })
43
+ mkdirSync(join(targetDir, 'src/app/next/preview'), { recursive: true })
39
44
  } catch (e) {
40
45
  // Directories might already exist
41
46
  }
@@ -71,7 +76,16 @@ if (existsSync(join(templatesDir, 'app/(frontend)'))) {
71
76
  recursive: true,
72
77
  force: true,
73
78
  })
74
- console.log(' ✓ Frontend pages (about-us, contact-us, share)/')
79
+ console.log(' ✓ Frontend pages (about-us, contact-us, share, [slug])/')
80
+ }
81
+
82
+ // Copy components
83
+ if (existsSync(join(templatesDir, 'components'))) {
84
+ cpSync(join(templatesDir, 'components'), join(targetDir, 'src/components'), {
85
+ recursive: true,
86
+ force: true,
87
+ })
88
+ console.log(' ✓ components/ (blocks, BlockRenderer, RichTextContent)/')
75
89
  }
76
90
 
77
91
  if (existsSync(join(templatesDir, 'app/api/[...payload]'))) {
@@ -82,6 +96,25 @@ if (existsSync(join(templatesDir, 'app/api/[...payload]'))) {
82
96
  console.log(' ✓ app/api/[...payload]/')
83
97
  }
84
98
 
99
+ // Copy preview route
100
+ if (existsSync(join(templatesDir, 'app/next/preview'))) {
101
+ mkdirSync(join(targetDir, 'src/app/next/preview'), { recursive: true })
102
+ cpSync(join(templatesDir, 'app/next/preview'), join(targetDir, 'src/app/next/preview'), {
103
+ recursive: true,
104
+ force: true,
105
+ })
106
+ console.log(' ✓ app/next/preview/ (draft mode)')
107
+ }
108
+
109
+ // Copy seed script
110
+ if (existsSync(join(templatesDir, 'scripts'))) {
111
+ cpSync(join(templatesDir, 'scripts'), join(targetDir, 'src/scripts'), {
112
+ recursive: true,
113
+ force: true,
114
+ })
115
+ console.log(' ✓ scripts/seedPages.ts')
116
+ }
117
+
85
118
  console.log('\n📦 Installing dependencies...')
86
119
 
87
120
  const deps = [
@@ -105,12 +138,38 @@ try {
105
138
  cwd: targetDir,
106
139
  stdio: 'inherit',
107
140
  })
141
+
142
+ // Install dev deps for seed script
143
+ execSync(`${pkgManager} add -D tsx cross-env`, {
144
+ cwd: targetDir,
145
+ stdio: 'inherit',
146
+ })
147
+
108
148
  console.log('✓ Dependencies installed\n')
109
149
  } catch (e) {
110
150
  console.error('❌ Failed to install dependencies:', e.message)
111
151
  process.exit(1)
112
152
  }
113
153
 
154
+ // Add seed script to package.json
155
+ console.log('📝 Adding scripts to package.json...')
156
+ try {
157
+ const targetPkg = JSON.parse(readFileSync(join(targetDir, 'package.json'), 'utf8'))
158
+ if (!targetPkg.scripts) targetPkg.scripts = {}
159
+ if (!targetPkg.scripts['seed:pages']) {
160
+ targetPkg.scripts['seed:pages'] =
161
+ 'cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/seedPages.ts'
162
+ targetPkg.scripts['generate:types'] =
163
+ 'cross-env NODE_OPTIONS=--no-deprecation payload generate:types'
164
+ targetPkg.scripts['generate:importmap'] =
165
+ 'cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap'
166
+ writeFileSync(join(targetDir, 'package.json'), JSON.stringify(targetPkg, null, 2) + '\n')
167
+ console.log(' ✓ Added seed:pages, generate:types, generate:importmap scripts')
168
+ }
169
+ } catch (e) {
170
+ console.log(' ⚠ Could not update scripts in package.json')
171
+ }
172
+
114
173
  // Update next.config
115
174
  console.log('⚙️ Updating next.config...')
116
175
  const nextConfigPath = existsSync(join(targetDir, 'next.config.ts'))
@@ -168,4 +227,9 @@ console.log(' 1. Copy .env.example to .env and fill in your values')
168
227
  console.log(' 2. Run: npx payload generate:importmap')
169
228
  console.log(' 3. Run: npx payload generate:types')
170
229
  console.log(' 4. Start your dev server: npm run dev')
171
- console.log('\nAdmin panel will be at: http://localhost:3000/admin')
230
+ console.log(' 5. Seed sample pages: npm run seed:pages')
231
+ console.log(" (This only creates pages that don't already exist in the DB)")
232
+ console.log('\nAdmin panel: http://localhost:3000/admin')
233
+ console.log(
234
+ 'Pages: http://localhost:3000/home, /about-us, /contact-us, /services, /pricing, /faq, /share',
235
+ )
package/package.json CHANGED
@@ -1,13 +1,22 @@
1
1
  {
2
2
  "name": "payload-next-starter",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "PayloadCMS starter for Next.js - install into any Next.js app",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": "./bin/setup.js",
8
+ "exports": {
9
+ "./blocks": "./src/components/blocks/index.ts",
10
+ "./BlockRenderer": "./src/components/BlockRenderer.tsx",
11
+ "./RichTextContent": "./src/components/RichTextContent.tsx"
12
+ },
8
13
  "files": [
9
14
  "bin/",
10
15
  "templates/",
16
+ "src/components/",
17
+ "src/collections/",
18
+ "src/scripts/",
19
+ "src/lib/",
11
20
  "README.md"
12
21
  ],
13
22
  "keywords": [
@@ -0,0 +1,16 @@
1
+ import type { CollectionConfig } from 'payload'
2
+
3
+ export const Media: CollectionConfig = {
4
+ slug: 'media',
5
+ access: {
6
+ read: () => true,
7
+ },
8
+ fields: [
9
+ {
10
+ name: 'alt',
11
+ type: 'text',
12
+ required: true,
13
+ },
14
+ ],
15
+ upload: true,
16
+ }
@@ -0,0 +1,358 @@
1
+ import type { CollectionConfig, Block } from 'payload'
2
+
3
+ const getPreviewPath = (slug?: string) => {
4
+ const baseURL = process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:3000'
5
+ const path = slug === 'home' || !slug ? '/' : `/${slug}`
6
+ const previewSecret = process.env.PAYLOAD_SECRET || ''
7
+
8
+ return `${baseURL}/next/preview?secret=${encodeURIComponent(previewSecret)}&slug=${encodeURIComponent(path)}`
9
+ }
10
+
11
+ const HeroBlock: Block = {
12
+ slug: 'hero',
13
+ labels: {
14
+ singular: 'Hero Section',
15
+ plural: 'Hero Sections',
16
+ },
17
+ fields: [
18
+ { name: 'title', type: 'text', required: true },
19
+ { name: 'subtitle', type: 'textarea' },
20
+ { name: 'backgroundImage', type: 'upload', relationTo: 'media' },
21
+ { name: 'ctaText', type: 'text', admin: { description: 'Call to action button text' } },
22
+ { name: 'ctaLink', type: 'text', admin: { description: 'Call to action button link' } },
23
+ {
24
+ name: 'alignment',
25
+ type: 'select',
26
+ defaultValue: 'center',
27
+ options: [
28
+ { label: 'Left', value: 'left' },
29
+ { label: 'Center', value: 'center' },
30
+ { label: 'Right', value: 'right' },
31
+ ],
32
+ },
33
+ ],
34
+ }
35
+
36
+ const ContentBlock: Block = {
37
+ slug: 'content',
38
+ labels: {
39
+ singular: 'Content Section',
40
+ plural: 'Content Sections',
41
+ },
42
+ fields: [
43
+ { name: 'heading', type: 'text' },
44
+ { name: 'body', type: 'richText', required: true },
45
+ ],
46
+ }
47
+
48
+ const ContactFormBlock: Block = {
49
+ slug: 'contactForm',
50
+ labels: {
51
+ singular: 'Contact Form',
52
+ plural: 'Contact Forms',
53
+ },
54
+ fields: [
55
+ { name: 'heading', type: 'text', defaultValue: 'Get In Touch' },
56
+ { name: 'description', type: 'textarea' },
57
+ { name: 'email', type: 'text', required: true },
58
+ { name: 'phone', type: 'text' },
59
+ { name: 'address', type: 'textarea' },
60
+ {
61
+ name: 'formFields',
62
+ type: 'select',
63
+ hasMany: true,
64
+ defaultValue: ['name', 'email', 'message'],
65
+ options: [
66
+ { label: 'Name', value: 'name' },
67
+ { label: 'Email', value: 'email' },
68
+ { label: 'Phone', value: 'phone' },
69
+ { label: 'Subject', value: 'subject' },
70
+ { label: 'Message', value: 'message' },
71
+ ],
72
+ },
73
+ ],
74
+ }
75
+
76
+ const FeaturesBlock: Block = {
77
+ slug: 'features',
78
+ labels: {
79
+ singular: 'Features Section',
80
+ plural: 'Features Sections',
81
+ },
82
+ fields: [
83
+ { name: 'heading', type: 'text' },
84
+ { name: 'subheading', type: 'textarea' },
85
+ {
86
+ name: 'items',
87
+ type: 'array',
88
+ minRows: 1,
89
+ fields: [
90
+ { name: 'icon', type: 'text', admin: { description: 'Emoji or icon name' } },
91
+ { name: 'title', type: 'text', required: true },
92
+ { name: 'description', type: 'textarea' },
93
+ ],
94
+ },
95
+ {
96
+ name: 'columns',
97
+ type: 'select',
98
+ defaultValue: '3',
99
+ options: [
100
+ { label: '2 Columns', value: '2' },
101
+ { label: '3 Columns', value: '3' },
102
+ { label: '4 Columns', value: '4' },
103
+ ],
104
+ },
105
+ ],
106
+ }
107
+
108
+ const ImageGalleryBlock: Block = {
109
+ slug: 'imageGallery',
110
+ labels: {
111
+ singular: 'Image Gallery',
112
+ plural: 'Image Galleries',
113
+ },
114
+ fields: [
115
+ { name: 'heading', type: 'text' },
116
+ {
117
+ name: 'images',
118
+ type: 'array',
119
+ minRows: 1,
120
+ fields: [
121
+ { name: 'image', type: 'upload', relationTo: 'media', required: true },
122
+ { name: 'caption', type: 'text' },
123
+ ],
124
+ },
125
+ {
126
+ name: 'columns',
127
+ type: 'select',
128
+ defaultValue: '3',
129
+ options: [
130
+ { label: '2 Columns', value: '2' },
131
+ { label: '3 Columns', value: '3' },
132
+ { label: '4 Columns', value: '4' },
133
+ ],
134
+ },
135
+ ],
136
+ }
137
+
138
+ const PricingBlock: Block = {
139
+ slug: 'pricing',
140
+ labels: {
141
+ singular: 'Pricing Section',
142
+ plural: 'Pricing Sections',
143
+ },
144
+ fields: [
145
+ { name: 'heading', type: 'text', defaultValue: 'Pricing Plans' },
146
+ { name: 'subheading', type: 'textarea' },
147
+ {
148
+ name: 'plans',
149
+ type: 'array',
150
+ minRows: 1,
151
+ fields: [
152
+ { name: 'name', type: 'text', required: true },
153
+ { name: 'price', type: 'text', required: true },
154
+ { name: 'period', type: 'text', defaultValue: '/month' },
155
+ { name: 'description', type: 'textarea' },
156
+ {
157
+ name: 'features',
158
+ type: 'array',
159
+ fields: [{ name: 'feature', type: 'text', required: true }],
160
+ },
161
+ { name: 'ctaText', type: 'text', defaultValue: 'Get Started' },
162
+ { name: 'ctaLink', type: 'text' },
163
+ { name: 'highlighted', type: 'checkbox', defaultValue: false },
164
+ ],
165
+ },
166
+ ],
167
+ }
168
+
169
+ const TestimonialsBlock: Block = {
170
+ slug: 'testimonials',
171
+ labels: {
172
+ singular: 'Testimonials Section',
173
+ plural: 'Testimonials Sections',
174
+ },
175
+ fields: [
176
+ { name: 'heading', type: 'text', defaultValue: 'What Our Customers Say' },
177
+ {
178
+ name: 'testimonials',
179
+ type: 'array',
180
+ minRows: 1,
181
+ fields: [
182
+ { name: 'name', type: 'text', required: true },
183
+ { name: 'role', type: 'text' },
184
+ { name: 'company', type: 'text' },
185
+ { name: 'quote', type: 'textarea', required: true },
186
+ { name: 'avatar', type: 'upload', relationTo: 'media' },
187
+ {
188
+ name: 'rating',
189
+ type: 'select',
190
+ options: [
191
+ { label: '5 Stars', value: '5' },
192
+ { label: '4 Stars', value: '4' },
193
+ { label: '3 Stars', value: '3' },
194
+ ],
195
+ },
196
+ ],
197
+ },
198
+ ],
199
+ }
200
+
201
+ const CTABlock: Block = {
202
+ slug: 'cta',
203
+ labels: {
204
+ singular: 'Call to Action',
205
+ plural: 'Call to Actions',
206
+ },
207
+ fields: [
208
+ { name: 'heading', type: 'text', required: true },
209
+ { name: 'description', type: 'textarea' },
210
+ { name: 'buttonText', type: 'text', required: true },
211
+ { name: 'buttonLink', type: 'text', required: true },
212
+ { name: 'secondaryButtonText', type: 'text' },
213
+ { name: 'secondaryButtonLink', type: 'text' },
214
+ {
215
+ name: 'style',
216
+ type: 'select',
217
+ defaultValue: 'primary',
218
+ options: [
219
+ { label: 'Primary (Blue)', value: 'primary' },
220
+ { label: 'Secondary (Gray)', value: 'secondary' },
221
+ { label: 'Accent (Gradient)', value: 'accent' },
222
+ ],
223
+ },
224
+ ],
225
+ }
226
+
227
+ const TeamBlock: Block = {
228
+ slug: 'team',
229
+ labels: {
230
+ singular: 'Team Section',
231
+ plural: 'Team Sections',
232
+ },
233
+ fields: [
234
+ { name: 'heading', type: 'text', defaultValue: 'Meet Our Team' },
235
+ { name: 'subheading', type: 'textarea' },
236
+ {
237
+ name: 'members',
238
+ type: 'array',
239
+ minRows: 1,
240
+ fields: [
241
+ { name: 'name', type: 'text', required: true },
242
+ { name: 'role', type: 'text', required: true },
243
+ { name: 'bio', type: 'textarea' },
244
+ { name: 'photo', type: 'upload', relationTo: 'media' },
245
+ { name: 'linkedin', type: 'text' },
246
+ { name: 'twitter', type: 'text' },
247
+ ],
248
+ },
249
+ ],
250
+ }
251
+
252
+ const FAQBlock: Block = {
253
+ slug: 'faq',
254
+ labels: {
255
+ singular: 'FAQ Section',
256
+ plural: 'FAQ Sections',
257
+ },
258
+ fields: [
259
+ { name: 'heading', type: 'text', defaultValue: 'Frequently Asked Questions' },
260
+ {
261
+ name: 'questions',
262
+ type: 'array',
263
+ minRows: 1,
264
+ fields: [
265
+ { name: 'question', type: 'text', required: true },
266
+ { name: 'answer', type: 'textarea', required: true },
267
+ ],
268
+ },
269
+ ],
270
+ }
271
+
272
+ export const Pages: CollectionConfig = {
273
+ slug: 'pages',
274
+ admin: {
275
+ useAsTitle: 'title',
276
+ defaultColumns: ['title', 'slug', '_status', 'updatedAt'],
277
+ livePreview: {
278
+ url: ({ data }) => {
279
+ const pageData = data as { slug?: string } | undefined
280
+ return getPreviewPath(pageData?.slug)
281
+ },
282
+ },
283
+ preview: ({ data }) => {
284
+ const pageData = data as { slug?: string } | undefined
285
+ return getPreviewPath(pageData?.slug)
286
+ },
287
+ },
288
+ versions: {
289
+ drafts: {
290
+ autosave: {
291
+ interval: 300,
292
+ },
293
+ schedulePublish: true,
294
+ },
295
+ },
296
+ fields: [
297
+ {
298
+ type: 'tabs',
299
+ tabs: [
300
+ {
301
+ label: 'Content',
302
+ fields: [
303
+ {
304
+ name: 'title',
305
+ type: 'text',
306
+ required: true,
307
+ },
308
+ {
309
+ name: 'slug',
310
+ type: 'text',
311
+ required: true,
312
+ unique: true,
313
+ index: true,
314
+ admin: {
315
+ description: 'URL path (e.g., "contact-us", "about", "pricing")',
316
+ },
317
+ },
318
+ {
319
+ name: 'layout',
320
+ type: 'blocks',
321
+ blocks: [
322
+ HeroBlock,
323
+ ContentBlock,
324
+ ContactFormBlock,
325
+ FeaturesBlock,
326
+ ImageGalleryBlock,
327
+ PricingBlock,
328
+ TestimonialsBlock,
329
+ CTABlock,
330
+ TeamBlock,
331
+ FAQBlock,
332
+ ],
333
+ },
334
+ ],
335
+ },
336
+ {
337
+ label: 'SEO',
338
+ fields: [
339
+ {
340
+ name: 'seo',
341
+ type: 'group',
342
+ fields: [
343
+ {
344
+ name: 'metaTitle',
345
+ type: 'text',
346
+ admin: { description: 'Defaults to page title if empty' },
347
+ },
348
+ { name: 'metaDescription', type: 'textarea' },
349
+ { name: 'metaImage', type: 'upload', relationTo: 'media' },
350
+ { name: 'noIndex', type: 'checkbox', defaultValue: false },
351
+ ],
352
+ },
353
+ ],
354
+ },
355
+ ],
356
+ },
357
+ ],
358
+ }
@@ -0,0 +1,13 @@
1
+ import type { CollectionConfig } from 'payload'
2
+
3
+ export const Users: CollectionConfig = {
4
+ slug: 'users',
5
+ admin: {
6
+ useAsTitle: 'email',
7
+ },
8
+ auth: true,
9
+ fields: [
10
+ // Email added by default
11
+ // Add more fields as needed
12
+ ],
13
+ }