chore(templates): clean up templates dependencies (#7139)

- use react 19 types
- no need for dotenv - next has their own dotenv file loader
- disable deprecation warnings by default (newer node version spam you
with it)
- disable turbo by default as hmr is broken and we cannot test against
it yet
- remove ts-node mention in tsconfig as it's not used anymore
- remove unused packages
- [fix: potential seed issues due to parallel payload operations being
on the same
transaction](f899f6a408)
and
b3b565dd75
@DanRibbens can you sense-check this? I do remember that anything
running in parallel should never be on the same transaction

---------

Co-authored-by: Paul Popus <paul@nouance.io>
This commit is contained in:
Alessio Gravili
2024-07-16 22:43:58 -04:00
committed by GitHub
parent fe23ca5b1a
commit 4ff8b20ddb
27 changed files with 825 additions and 2348 deletions

View File

@@ -22,8 +22,8 @@
"graphql": "^16.8.1",
"next": "15.0.0-canary.53",
"payload": "beta",
"react": "^19.0.0-rc-6230622a1a-20240610",
"react-dom": "^19.0.0-rc-6230622a1a-20240610",
"react": "19.0.0-rc-6230622a1a-20240610",
"react-dom": "19.0.0-rc-6230622a1a-20240610",
"sharp": "0.32.6"
},
"devDependencies": {

View File

@@ -22,8 +22,8 @@
"graphql": "^16.8.1",
"next": "15.0.0-canary.53",
"payload": "beta",
"react": "^19.0.0-rc-6230622a1a-20240610",
"react-dom": "^19.0.0-rc-6230622a1a-20240610",
"react": "19.0.0-rc-6230622a1a-20240610",
"react-dom": "19.0.0-rc-6230622a1a-20240610",
"sharp": "0.32.6"
},
"devDependencies": {

View File

@@ -7,4 +7,4 @@ node_modules
.vercel
# Payload default media upload directory
./public/media
public/media/

View File

@@ -5,24 +5,19 @@
"license": "MIT",
"type": "module",
"scripts": {
"build": "next build",
"dev": "next dev --turbo",
"dev:prod": "rm -rf .next && pnpm build && pnpm serve",
"generate:types": "payload generate:types",
"ii": "pnpm --ignore-workspace install",
"lint": "next lint",
"lint:fix": "next lint --fix",
"payload": "payload",
"reinstall": "rm -rf node_modules && rm pnpm-lock.yaml && pnpm --ignore-workspace install",
"start": "next start"
"build": "cross-env NODE_OPTIONS=--no-deprecation next build",
"dev": "cross-env NODE_OPTIONS=--no-deprecation next dev",
"dev:prod": "cross-env NODE_OPTIONS=--no-deprecation rm -rf .next && pnpm build && pnpm serve",
"generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",
"ii": "cross-env NODE_OPTIONS=--no-deprecation pnpm --ignore-workspace install",
"lint": "cross-env NODE_OPTIONS=--no-deprecation next lint",
"lint:fix": "cross-env NODE_OPTIONS=--no-deprecation next lint --fix",
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
"reinstall": "cross-env NODE_OPTIONS=--no-deprecation rm -rf node_modules && rm pnpm-lock.yaml && pnpm --ignore-workspace install",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start"
},
"dependencies": {
"@lexical/list": "0.16.1",
"@lexical/react": "0.16.1",
"@lexical/rich-text": "0.16.1",
"@lexical/utils": "0.16.1",
"@payloadcms/db-mongodb": "3.0.0-beta.63",
"@payloadcms/db-postgres": "3.0.0-beta.63",
"@payloadcms/live-preview-react": "3.0.0-beta.63",
"@payloadcms/next": "3.0.0-beta.63",
"@payloadcms/plugin-cloud": "3.0.0-beta.63",
@@ -39,8 +34,6 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cross-env": "^7.0.3",
"dotenv": "^8.2.0",
"escape-html": "^1.0.3",
"geist": "^1.3.0",
"graphql": "^16.8.1",
"jsonwebtoken": "9.0.1",
@@ -50,11 +43,9 @@
"payload": "3.0.0-beta.63",
"payload-admin-bar": "^1.0.6",
"prism-react-renderer": "^2.3.1",
"qs": "6.11.2",
"react": "beta",
"react-dom": "beta",
"react": "19.0.0-rc-6230622a1a-20240610",
"react-dom": "19.0.0-rc-6230622a1a-20240610",
"react-hook-form": "7.45.4",
"react-router-dom": "5.3.4",
"sharp": "0.32.6",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7"
@@ -65,8 +56,8 @@
"@tailwindcss/typography": "^0.5.13",
"@types/escape-html": "^1.0.2",
"@types/node": "18.11.3",
"@types/qs": "^6.9.8",
"@types/react": "^18.3.0",
"@types/react": "npm:types-react@19.0.0-rc.0",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
"autoprefixer": "^10.4.19",
"copyfiles": "^2.4.1",
"eslint": "^8",
@@ -79,7 +70,14 @@
"engines": {
"node": "^18.20.2 || >=20.9.0"
},
"pnpm": {
"overrides": {
"@types/react": "npm:types-react@19.0.0-rc.0",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0"
}
},
"overrides": {
"@types/react": "18.2.74"
"@types/react": "npm:types-react@19.0.0-rc.0",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,15 +19,11 @@ export const RelatedPosts: React.FC<RelatedPostsProps> = (props) => {
<div className={clsx('container', className)}>
{introContent && <RichText content={introContent} enableGutter={false} />}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8 items-stretch">
{docs?.map((doc, index) => {
if (typeof doc === 'string') return null
return (
<div key={index}>
<Card doc={doc} relationTo="posts" showCategories />
</div>
)
return <Card key={index} doc={doc} relationTo="posts" showCategories />
})}
</div>
</div>

View File

@@ -223,8 +223,8 @@ export interface Page {
)[];
meta?: {
title?: string | null;
description?: string | null;
image?: string | Media | null;
description?: string | null;
};
publishedAt?: string | null;
slug?: string | null;
@@ -272,7 +272,7 @@ export interface Media {
*/
export interface Category {
id: string;
title?: string | null;
title: string;
parent?: (string | null) | Category;
breadcrumbs?:
| {
@@ -291,6 +291,7 @@ export interface Category {
*/
export interface Post {
id: string;
title: string;
content: {
root: {
type: string;
@@ -310,10 +311,9 @@ export interface Post {
categories?: (string | Category)[] | null;
meta?: {
title?: string | null;
description?: string | null;
image?: string | Media | null;
description?: string | null;
};
title: string;
publishedAt?: string | null;
authors?: (string | User)[] | null;
populatedAuthors?:

View File

@@ -16,7 +16,6 @@ import {
} from '@payloadcms/richtext-lexical'
import sharp from 'sharp' // editor-import
import { UnderlineFeature } from '@payloadcms/richtext-lexical'
import dotenv from 'dotenv'
import path from 'path'
import { buildConfig } from 'payload'
import { fileURLToPath } from 'url'
@@ -32,17 +31,21 @@ import { seed } from './payload/endpoints/seed'
import { Footer } from './payload/globals/Footer/Footer'
import { Header } from './payload/globals/Header/Header'
import { revalidateRedirects } from './payload/hooks/revalidateRedirects'
import { GenerateTitle, GenerateURL } from '@payloadcms/plugin-seo/types'
import { Page, Post } from 'src/payload-types'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const generateTitle = () => {
return 'My Website'
const generateTitle: GenerateTitle<Post | Page> = ({ doc }) => {
return doc?.title ? `${doc.title} | Payload Website Template` : 'Payload Website Template'
}
dotenv.config({
path: path.resolve(dirname, '../../.env'),
})
const generateURL: GenerateURL<Post | Page> = ({ doc }) => {
return doc?.slug
? `${process.env.NEXT_PUBLIC_SERVER_URL}/${doc.slug}`
: process.env.NEXT_PUBLIC_SERVER_URL
}
export default buildConfig({
admin: {
@@ -133,10 +136,8 @@ export default buildConfig({
collections: ['categories'],
}),
seoPlugin({
collections: ['pages', 'posts'],
generateTitle,
tabbedUI: true,
uploadsCollection: 'media',
generateURL,
}),
formBuilderPlugin({
fields: {

View File

@@ -18,6 +18,7 @@ const Categories: CollectionConfig = {
{
name: 'title',
type: 'text',
required: true,
},
],
}

View File

@@ -39,6 +39,7 @@ export const Media: CollectionConfig = {
},
],
upload: {
staticDir: path.resolve(dirname, '../../public/media'),
// Upload to the public/media directory in Next.js making them publicly accessible even outside of Payload
staticDir: path.resolve(dirname, '../../../public/media'),
},
}

View File

@@ -13,6 +13,13 @@ import { populatePublishedAt } from '../../hooks/populatePublishedAt'
import { generatePreviewPath } from '../../utilities/generatePreviewPath'
import { revalidatePage } from './hooks/revalidatePage'
import {
MetaDescriptionField,
MetaImageField,
MetaTitleField,
OverviewField,
PreviewField,
} from '@payloadcms/plugin-seo/fields'
export const Pages: CollectionConfig = {
slug: 'pages',
access: {
@@ -36,18 +43,16 @@ export const Pages: CollectionConfig = {
useAsTitle: 'title',
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
type: 'tabs',
tabs: [
{
fields: [
{
name: 'title',
type: 'text',
required: true,
},
hero,
],
fields: [hero],
label: 'Hero',
},
{
@@ -61,6 +66,33 @@ export const Pages: CollectionConfig = {
],
label: 'Content',
},
{
name: 'meta',
label: 'SEO',
fields: [
OverviewField({
titlePath: 'meta.title',
descriptionPath: 'meta.description',
imagePath: 'meta.image',
}),
MetaTitleField({
hasGenerateFn: true,
}),
MetaImageField({
relationTo: 'media',
}),
MetaDescriptionField({}),
PreviewField({
// if the `generateUrl` function is configured
hasGenerateFn: true,
// field paths to match the target field for data
titlePath: 'meta.title',
descriptionPath: 'meta.description',
}),
],
},
],
},
{
@@ -79,7 +111,7 @@ export const Pages: CollectionConfig = {
versions: {
drafts: {
autosave: {
interval: 350, // We set this interval for optimal live preview
interval: 100, // We set this interval for optimal live preview
},
},
maxPerDoc: 50,

View File

@@ -4,18 +4,20 @@ import type { CollectionAfterReadHook } from 'payload'
// This means that we need to populate the authors manually here to protect user privacy
// GraphQL will not return mutated user data that differs from the underlying schema
// So we use an alternative `populatedAuthors` field to populate the user data, hidden from the admin UI
export const populateAuthors: CollectionAfterReadHook = async ({ doc, req: { payload } }) => {
export const populateAuthors: CollectionAfterReadHook = async ({ doc, req, req: { payload } }) => {
if (doc?.authors) {
const authorDocs = await Promise.all(
doc.authors.map(
async (author) =>
await payload.findByID({
id: typeof author === 'object' ? author?.id : author,
collection: 'users',
depth: 0,
}),
),
)
const authorDocs = []
for (const author of doc.authors) {
const authorDoc = await payload.findByID({
id: typeof author === 'object' ? author?.id : author,
collection: 'users',
depth: 0,
req,
})
authorDocs.push(authorDoc)
}
doc.populatedAuthors = authorDocs.map((authorDoc) => ({
id: authorDoc.id,

View File

@@ -19,6 +19,14 @@ import { generatePreviewPath } from '../../utilities/generatePreviewPath'
import { populateAuthors } from './hooks/populateAuthors'
import { revalidatePost } from './hooks/revalidatePost'
import {
MetaDescriptionField,
MetaImageField,
MetaTitleField,
OverviewField,
PreviewField,
} from '@payloadcms/plugin-seo/fields'
export const Posts: CollectionConfig = {
slug: 'posts',
access: {
@@ -42,6 +50,11 @@ export const Posts: CollectionConfig = {
useAsTitle: 'title',
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
type: 'tabs',
tabs: [
@@ -98,16 +111,35 @@ export const Posts: CollectionConfig = {
],
label: 'Meta',
},
{
name: 'meta',
label: 'SEO',
fields: [
OverviewField({
titlePath: 'meta.title',
descriptionPath: 'meta.description',
imagePath: 'meta.image',
}),
MetaTitleField({
hasGenerateFn: true,
}),
MetaImageField({
relationTo: 'media',
}),
MetaDescriptionField({}),
PreviewField({
// if the `generateUrl` function is configured
hasGenerateFn: true,
// field paths to match the target field for data
titlePath: 'meta.title',
descriptionPath: 'meta.description',
}),
],
},
],
},
{
name: 'title',
type: 'text',
admin: {
position: 'sidebar',
},
required: true,
},
{
name: 'publishedAt',
type: 'date',
@@ -169,7 +201,9 @@ export const Posts: CollectionConfig = {
},
versions: {
drafts: {
autosave: true,
autosave: {
interval: 100, // We set this interval for optimal live preview
},
},
maxPerDoc: 50,
},

View File

@@ -1,6 +1,16 @@
'use client'
import React, { Fragment, useCallback, useState } from 'react'
import { toast } from '@payloadcms/ui'
const SuccessMessage: React.FC = () => (
<div>
Database seeded! You can now{' '}
<a target="_blank" href="/">
visit your website
</a>
</div>
)
export const SeedButton: React.FC = () => {
const [loading, setLoading] = useState(false)
@@ -17,6 +27,7 @@ export const SeedButton: React.FC = () => {
try {
await fetch('/api/seed')
setSeeded(true)
toast.success(<SuccessMessage />, { duration: 5000 })
} catch (err) {
setError(err)
}

View File

@@ -17,7 +17,9 @@ const BeforeDashboard: React.FC = () => {
<li>
<SeedButton />
{' with a few pages, posts, and projects to jump-start your new site, then '}
<a href="/">visit your website</a>
<a href="/" target="_blank">
visit your website
</a>
{' to see the results.'}
</li>
<li>

View File

@@ -10,9 +10,14 @@ export const seed: PayloadHandler = async (req): Promise<Response> => {
}
try {
// Create a transaction so that all seeding happens in one transaction
await initTransaction(req)
await seedScript({ payload, req })
// Finalise transactiojn
await commitTransaction(req)
return Response.json({ success: true })
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Unknown error'

View File

@@ -10,6 +10,7 @@ export const contact: Partial<Page> = {
{
blockType: 'formBlock',
enableIntro: true,
// @ts-ignore
form: '{{CONTACT_FORM_ID}}',
introContent: {
root: {

View File

@@ -80,552 +80,6 @@ export const homeStatic: Page = {
},
},
},
/* layout: [
{
blockName: 'Content Block',
blockType: 'content',
columns: [
{
richText: {
root: {
type: 'root',
children: [
{
type: 'heading',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Core features',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
tag: 'h2',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
},
size: 'full',
},
{
enableLink: false,
link: {
label: '',
reference: null,
url: '',
},
richText: {
root: {
type: 'root',
children: [
{
type: 'heading',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Admin Dashboard',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
tag: 'h3',
version: 1,
},
{
type: 'paragraph',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: "Manage this site's pages and posts from the ",
version: 1,
},
{
type: 'link',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'admin dashboard',
version: 1,
},
],
direction: 'ltr',
fields: {
linkType: 'custom',
newTab: false,
url: '/admin',
},
format: '',
indent: 0,
version: 2,
},
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '.',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
textFormat: 0,
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
},
size: 'oneThird',
},
{
enableLink: false,
link: {
label: '',
reference: null,
url: '',
},
richText: {
root: {
type: 'root',
children: [
{
type: 'heading',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Preview',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
tag: 'h3',
version: 1,
},
{
type: 'paragraph',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Using versions, drafts, and preview, editors can review and share their changes before publishing them.',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
textFormat: 0,
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
},
size: 'oneThird',
},
{
enableLink: false,
link: {
label: '',
reference: null,
url: '',
},
richText: {
root: {
type: 'root',
children: [
{
type: 'heading',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Page Builder',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
tag: 'h3',
version: 1,
},
{
type: 'paragraph',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Custom page builder allows you to create unique page, post, and project layouts for any type of content.',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
textFormat: 0,
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
},
size: 'oneThird',
},
{
enableLink: false,
link: {
label: '',
reference: null,
url: '',
},
richText: {
root: {
type: 'root',
children: [
{
type: 'heading',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'SEO',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
tag: 'h3',
version: 1,
},
{
type: 'paragraph',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Editors have complete control over SEO data and site content directly from the ',
version: 1,
},
{
type: 'link',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'admin dashboard',
version: 1,
},
],
direction: 'ltr',
fields: {
linkType: 'custom',
newTab: false,
url: '/admin',
},
format: '',
indent: 0,
version: 2,
},
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '.',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
textFormat: 0,
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
},
size: 'oneThird',
},
{
enableLink: false,
link: {
label: '',
reference: null,
url: '',
},
richText: {
root: {
type: 'root',
children: [
{
type: 'heading',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Dark Mode',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
tag: 'h3',
version: 1,
},
{
type: 'paragraph',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Users will experience this site in their preferred color scheme and each block can be inverted.',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
textFormat: 0,
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
},
size: 'oneThird',
},
],
},
{
blockName: 'Archive Block',
blockType: 'archive',
categories: [],
introContent: {
root: {
type: 'root',
children: [
{
type: 'heading',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Recent posts',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
tag: 'h3',
version: 1,
},
{
type: 'paragraph',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'The posts below are displayed in an "Archive" layout building block which is an extremely powerful way to display documents on a page. It can be auto-populated by collection or by category, or posts can be individually selected. Pagination controls will automatically appear if the number of results exceeds the number of items per page.',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
textFormat: 0,
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
},
populateBy: 'collection',
relationTo: 'posts',
},
{
blockName: 'CTA',
blockType: 'cta',
links: [
{
link: {
type: 'custom',
appearance: 'default',
label: 'All posts',
url: '/posts',
},
},
],
richText: {
root: {
type: 'root',
children: [
{
type: 'heading',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'This is a call to action',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
tag: 'h3',
version: 1,
},
{
type: 'paragraph',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'This is a custom layout building block ',
version: 1,
},
{
type: 'link',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'configured in the admin dashboard',
version: 1,
},
],
direction: 'ltr',
fields: {
linkType: 'custom',
newTab: false,
url: '/admin',
},
format: '',
indent: 0,
version: 2,
},
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '.',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
textFormat: 0,
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
},
},
], */
meta: {
description: 'An open-source website built with Payload and Next.js.',
title: 'Payload Website Template',

View File

@@ -1,6 +1,6 @@
import type { Page } from '../../payload-types'
import type { RequiredDataFromCollectionSlug } from 'payload'
export const home: Partial<Page> = {
export const home: RequiredDataFromCollectionSlug<'pages'> = {
slug: 'home',
_status: 'published',
hero: {
@@ -23,6 +23,7 @@ export const home: Partial<Page> = {
},
},
],
// @ts-ignore
media: '{{IMAGE_1}}',
richText: {
root: {
@@ -501,6 +502,7 @@ export const home: Partial<Page> = {
{
blockName: 'Media Block',
blockType: 'mediaBlock',
// @ts-ignore
media: '{{IMAGE_2}}',
position: 'default',
},
@@ -658,6 +660,7 @@ export const home: Partial<Page> = {
],
meta: {
description: 'An open-source website built with Payload and Next.js.',
// @ts-ignore
image: '{{IMAGE_1}}',
title: 'Payload Website Template',
},

View File

@@ -1,4 +1,4 @@
import type { Payload, PayloadRequest } from 'payload'
import type { CollectionSlug, GlobalSlug, Payload, PayloadRequest } from 'payload'
import fs from 'fs'
import path from 'path'
@@ -12,12 +12,20 @@ import { image2 } from './image-2'
import { post1 } from './post-1'
import { post2 } from './post-2'
import { post3 } from './post-3'
import { exists } from 'fs-extra'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const collections = ['categories', 'media', 'pages', 'posts', 'forms', 'form-submissions']
const globals = ['header', 'footer']
const collections: CollectionSlug[] = [
'categories',
'media',
'pages',
'posts',
'forms',
'form-submissions',
]
const globals: GlobalSlug[] = ['header', 'footer']
// Next.js revalidation errors are normal when seeding the database without a server running
// i.e. running `yarn seed` locally instead of using the admin UI within an active app
@@ -47,133 +55,140 @@ export const seed = async ({
payload.logger.info(`— Clearing collections and globals...`)
// clear the database
await Promise.all([
...collections.map((collection) =>
payload.delete({
collection: collection as 'media',
req,
where: {},
}),
),
...globals.map((global) =>
payload.updateGlobal({
slug: global as 'header',
data: {},
req,
}),
),
])
for (const global of globals) {
await payload.updateGlobal({
slug: global,
data: {
navItems: [],
},
req,
})
}
for (const collection of collections) {
console.log('delete', collection)
await payload.delete({
collection: collection,
where: {
id: {
exists: true,
},
},
req,
})
}
const pages = await payload.delete({
collection: 'pages',
where: {},
req,
})
console.log({ pages })
payload.logger.info(`— Seeding demo author and user...`)
await Promise.all(
['demo-author@payloadcms.com'].map(async (email) => {
await payload.delete({
collection: 'users',
req,
where: {
email: {
equals: email,
},
},
})
}),
)
const [demoAuthor] = await Promise.all([
await payload.create({
collection: 'users',
data: {
name: 'Demo Author',
email: 'demo-author@payloadcms.com',
password: 'password',
await payload.delete({
collection: 'users',
where: {
email: {
equals: 'demo-author@payloadcms.com',
},
req,
}),
])
},
req,
})
let demoAuthorID = demoAuthor.id
const demoAuthor = await payload.create({
collection: 'users',
data: {
name: 'Demo Author',
email: 'demo-author@payloadcms.com',
password: 'password',
},
req,
})
let demoAuthorID: number | string = demoAuthor.id
payload.logger.info(`— Seeding media...`)
const [image1Doc, image2Doc, image3Doc, imageHomeDoc] = await Promise.all([
await payload.create({
collection: 'media',
data: image1,
filePath: path.resolve(dirname, 'image-post1.webp'),
req,
}),
await payload.create({
collection: 'media',
data: image2,
filePath: path.resolve(dirname, 'image-post2.webp'),
req,
}),
await payload.create({
collection: 'media',
data: image2,
filePath: path.resolve(dirname, 'image-post3.webp'),
req,
}),
await payload.create({
collection: 'media',
data: image2,
filePath: path.resolve(dirname, 'image-hero1.webp'),
req,
}),
])
const image1Doc = await payload.create({
collection: 'media',
data: image1,
filePath: path.resolve(dirname, 'image-post1.webp'),
req,
})
const image2Doc = await payload.create({
collection: 'media',
data: image2,
filePath: path.resolve(dirname, 'image-post2.webp'),
req,
})
const image3Doc = await payload.create({
collection: 'media',
data: image2,
filePath: path.resolve(dirname, 'image-post3.webp'),
req,
})
const imageHomeDoc = await payload.create({
collection: 'media',
data: image2,
filePath: path.resolve(dirname, 'image-hero1.webp'),
req,
})
payload.logger.info(`— Seeding categories...`)
const technologyCategory = await payload.create({
collection: 'categories',
data: {
title: 'Technology',
},
req,
})
const [technologyCategory, newsCategory, financeCategory] = await Promise.all([
await payload.create({
collection: 'categories',
data: {
title: 'Technology',
},
req,
}),
await payload.create({
collection: 'categories',
data: {
title: 'News',
},
req,
}),
await payload.create({
collection: 'categories',
data: {
title: 'Finance',
},
req,
}),
await payload.create({
collection: 'categories',
data: {
title: 'Design',
},
req,
}),
await payload.create({
collection: 'categories',
data: {
title: 'Software',
},
req,
}),
await payload.create({
collection: 'categories',
data: {
title: 'Engineering',
},
req,
}),
])
const newsCategory = await payload.create({
collection: 'categories',
data: {
title: 'News',
},
req,
})
let image1ID = image1Doc.id
let image2ID = image2Doc.id
let image3ID = image3Doc.id
let imageHomeID = imageHomeDoc.id
const financeCategory = await payload.create({
collection: 'categories',
data: {
title: 'Finance',
},
req,
})
await payload.create({
collection: 'categories',
data: {
title: 'Design',
},
req,
})
await payload.create({
collection: 'categories',
data: {
title: 'Software',
},
req,
})
await payload.create({
collection: 'categories',
data: {
title: 'Engineering',
},
req,
})
let image1ID: number | string = image1Doc.id
let image2ID: number | string = image2Doc.id
let image3ID: number | string = image3Doc.id
let imageHomeID: number | string = imageHomeDoc.id
if (payload.db.defaultIDType === 'text') {
image1ID = `"${image1Doc.id}"`
@@ -191,9 +206,9 @@ export const seed = async ({
collection: 'posts',
data: JSON.parse(
JSON.stringify({ ...post1, categories: [technologyCategory.id] })
.replace(/"\{\{IMAGE_1\}\}"/g, image1ID)
.replace(/"\{\{IMAGE_2\}\}"/g, image2ID)
.replace(/"\{\{AUTHOR\}\}"/g, demoAuthorID),
.replace(/"\{\{IMAGE_1\}\}"/g, String(image1ID))
.replace(/"\{\{IMAGE_2\}\}"/g, String(image2ID))
.replace(/"\{\{AUTHOR\}\}"/g, String(demoAuthorID)),
),
req,
})
@@ -202,9 +217,9 @@ export const seed = async ({
collection: 'posts',
data: JSON.parse(
JSON.stringify({ ...post2, categories: [newsCategory.id] })
.replace(/"\{\{IMAGE_1\}\}"/g, image2ID)
.replace(/"\{\{IMAGE_2\}\}"/g, image3ID)
.replace(/"\{\{AUTHOR\}\}"/g, demoAuthorID),
.replace(/"\{\{IMAGE_1\}\}"/g, String(image2ID))
.replace(/"\{\{IMAGE_2\}\}"/g, String(image3ID))
.replace(/"\{\{AUTHOR\}\}"/g, String(demoAuthorID)),
),
req,
})
@@ -213,41 +228,38 @@ export const seed = async ({
collection: 'posts',
data: JSON.parse(
JSON.stringify({ ...post3, categories: [financeCategory.id] })
.replace(/"\{\{IMAGE_1\}\}"/g, image3ID)
.replace(/"\{\{IMAGE_2\}\}"/g, image1ID)
.replace(/"\{\{AUTHOR\}\}"/g, demoAuthorID),
.replace(/"\{\{IMAGE_1\}\}"/g, String(image3ID))
.replace(/"\{\{IMAGE_2\}\}"/g, String(image1ID))
.replace(/"\{\{AUTHOR\}\}"/g, String(demoAuthorID)),
),
req,
})
// update each post with related posts
await Promise.all([
await payload.update({
id: post1Doc.id,
collection: 'posts',
data: {
relatedPosts: [post2Doc.id, post3Doc.id],
},
req,
}),
await payload.update({
id: post2Doc.id,
collection: 'posts',
data: {
relatedPosts: [post1Doc.id, post3Doc.id],
},
req,
}),
await payload.update({
id: post3Doc.id,
collection: 'posts',
data: {
relatedPosts: [post1Doc.id, post2Doc.id],
},
req,
}),
])
await payload.update({
id: post1Doc.id,
collection: 'posts',
data: {
relatedPosts: [post2Doc.id, post3Doc.id],
},
req,
})
await payload.update({
id: post2Doc.id,
collection: 'posts',
data: {
relatedPosts: [post1Doc.id, post3Doc.id],
},
req,
})
await payload.update({
id: post3Doc.id,
collection: 'posts',
data: {
relatedPosts: [post1Doc.id, post2Doc.id],
},
req,
})
payload.logger.info(`— Seeding home page...`)
@@ -255,8 +267,8 @@ export const seed = async ({
collection: 'pages',
data: JSON.parse(
JSON.stringify(home)
.replace(/"\{\{IMAGE_1\}\}"/g, imageHomeID)
.replace(/"\{\{IMAGE_2\}\}"/g, image2ID),
.replace(/"\{\{IMAGE_1\}\}"/g, String(imageHomeID))
.replace(/"\{\{IMAGE_2\}\}"/g, String(image2ID)),
),
req,
})
@@ -269,7 +281,7 @@ export const seed = async ({
req,
})
let contactFormID = contactForm.id
let contactFormID: number | string = contactForm.id
if (payload.db.defaultIDType === 'text') {
contactFormID = `"${contactFormID}"`
@@ -280,7 +292,7 @@ export const seed = async ({
const contactPage = await payload.create({
collection: 'pages',
data: JSON.parse(
JSON.stringify(contactPageData).replace(/"\{\{CONTACT_FORM_ID\}\}"/g, contactFormID),
JSON.stringify(contactPageData).replace(/"\{\{CONTACT_FORM_ID\}\}"/g, String(contactFormID)),
),
req,
})

View File

@@ -3,6 +3,7 @@ import type { Post } from '../../payload-types'
export const post1: Partial<Post> = {
slug: 'digital-horizons',
_status: 'published',
// @ts-ignore
authors: ['{{AUTHOR}}'],
content: {
root: {
@@ -295,6 +296,7 @@ export const post1: Partial<Post> = {
meta: {
description:
'Dive into the marvels of modern innovation, where the only constant is change. A journey where pixels and data converge to craft the future.',
// @ts-ignore
image: '{{IMAGE_1}}',
title: 'Digital Horizons: A Glimpse into Tomorrow',
},

View File

@@ -3,6 +3,7 @@ import type { Post } from '../../payload-types'
export const post2: Partial<Post> = {
slug: 'global-gaze',
_status: 'published',
// @ts-ignore
authors: ['{{AUTHOR}}'],
content: {
root: {
@@ -217,6 +218,7 @@ export const post2: Partial<Post> = {
meta: {
description:
'Explore the untold and overlooked. A magnified view into the corners of the world, where every story deserves its spotlight.',
// @ts-ignore
image: '{{IMAGE_1}}',
title: 'Global Gaze: Beyond the Headlines',
},

View File

@@ -3,6 +3,7 @@ import type { Post } from '../../payload-types'
export const post3: Partial<Post> = {
slug: 'dollar-and-sense-the-financial-forecast',
_status: 'published',
// @ts-ignore
authors: ['{{AUTHOR}}'],
content: {
root: {
@@ -253,6 +254,7 @@ export const post3: Partial<Post> = {
},
meta: {
description: `Money isn't just currency; it's a language. Dive deep into its nuances, where strategy meets intuition in the vast sea of finance.`,
// @ts-ignore
image: '{{IMAGE_1}}',
title: 'Dollar and Sense: The Financial Forecast',
},

View File

@@ -2,8 +2,12 @@
"compilerOptions": {
"baseUrl": ".",
"esModuleInterop": true,
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"target": "es6",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
@@ -22,15 +26,25 @@
}
],
"paths": {
"@payload-config": ["./src/payload.config.ts"],
"react": ["./node_modules/@types/react"],
"@/*": ["./src/app/*"]
"@payload-config": [
"./src/payload.config.ts"
],
"react": [
"./node_modules/@types/react"
],
"@/*": [
"./src/app/*"
]
}
},
"include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "redirects.js", "next.config.js"],
"exclude": ["node_modules"],
"ts-node": {
"transpileOnly": true,
"swc": true
}
"include": [
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"redirects.js",
"next.config.js"
],
"exclude": [
"node_modules"
]
}

View File

@@ -22,8 +22,8 @@
"graphql": "^16.8.1",
"next": "15.0.0-canary.53",
"payload": "beta",
"react": "^19.0.0-rc-6230622a1a-20240610",
"react-dom": "^19.0.0-rc-6230622a1a-20240610",
"react": "19.0.0-rc-6230622a1a-20240610",
"react-dom": "19.0.0-rc-6230622a1a-20240610",
"sharp": "0.32.6"
},
"devDependencies": {

View File

@@ -23,8 +23,8 @@
"graphql": "^16.8.1",
"next": "15.0.0-canary.53",
"payload": "beta",
"react": "^19.0.0-rc-6230622a1a-20240610",
"react-dom": "^19.0.0-rc-6230622a1a-20240610"
"react": "19.0.0-rc-6230622a1a-20240610",
"react-dom": "19.0.0-rc-6230622a1a-20240610"
},
"devDependencies": {
"@types/node": "^20.14.9",

View File

@@ -23,8 +23,8 @@
"graphql": "^16.8.1",
"next": "15.0.0-canary.53",
"payload": "beta",
"react": "^19.0.0-rc-6230622a1a-20240610",
"react-dom": "^19.0.0-rc-6230622a1a-20240610"
"react": "19.0.0-rc-6230622a1a-20240610",
"react-dom": "19.0.0-rc-6230622a1a-20240610"
},
"devDependencies": {
"@types/node": "^20.14.9",