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.
- package/README.md +85 -20
- package/bin/setup.js +66 -2
- package/package.json +10 -1
- package/src/collections/Media.ts +16 -0
- package/src/collections/Pages.ts +358 -0
- package/src/collections/Users.ts +13 -0
- package/src/components/BlockRenderer.tsx +39 -0
- package/src/components/RichTextContent.tsx +81 -0
- package/src/components/blocks/CTABlock.tsx +81 -0
- package/src/components/blocks/ContactFormBlock.tsx +230 -0
- package/src/components/blocks/ContentBlock.tsx +23 -0
- package/src/components/blocks/FAQBlock.tsx +93 -0
- package/src/components/blocks/FeaturesBlock.tsx +82 -0
- package/src/components/blocks/HeroBlock.tsx +99 -0
- package/src/components/blocks/ImageGalleryBlock.tsx +77 -0
- package/src/components/blocks/PricingBlock.tsx +164 -0
- package/src/components/blocks/TeamBlock.tsx +134 -0
- package/src/components/blocks/TestimonialsBlock.tsx +93 -0
- package/src/components/blocks/index.ts +10 -0
- package/src/lib/getPageBySlug.ts +25 -0
- package/src/scripts/seedPages.ts +774 -0
- package/templates/app/(frontend)/[slug]/page.tsx +95 -0
- package/templates/app/(frontend)/page.tsx +3 -57
- package/templates/app/(frontend)/styles.css +9 -9
- package/templates/app/next/preview/route.ts +17 -0
- package/templates/collections/Pages.ts +358 -0
- package/templates/components/BlockRenderer.tsx +39 -0
- package/templates/components/RichTextContent.tsx +81 -0
- package/templates/components/blocks/CTABlock.tsx +81 -0
- package/templates/components/blocks/ContactFormBlock.tsx +230 -0
- package/templates/components/blocks/ContentBlock.tsx +23 -0
- package/templates/components/blocks/FAQBlock.tsx +93 -0
- package/templates/components/blocks/FeaturesBlock.tsx +82 -0
- package/templates/components/blocks/HeroBlock.tsx +99 -0
- package/templates/components/blocks/ImageGalleryBlock.tsx +77 -0
- package/templates/components/blocks/PricingBlock.tsx +164 -0
- package/templates/components/blocks/TeamBlock.tsx +134 -0
- package/templates/components/blocks/TestimonialsBlock.tsx +93 -0
- package/templates/lib/getPageBySlug.ts +25 -0
- package/templates/payload.config.ts +2 -1
- package/templates/scripts/seedPages.ts +774 -0
- package/templates/app/(frontend)/about-us/page.tsx +0 -22
- package/templates/app/(frontend)/contact-us/page.tsx +0 -421
- 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
|
-
|
|
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
|
-
|
|
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.
|
|
27
|
-
5.
|
|
28
|
-
6.
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
+
npm run generate:importmap
|
|
42
57
|
```
|
|
43
58
|
|
|
44
59
|
3. Generate TypeScript types:
|
|
45
60
|
```bash
|
|
46
|
-
|
|
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
|
-
- `
|
|
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
|
|
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('
|
|
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.
|
|
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,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
|
+
}
|