chore(examples/live-preview): migrates to 3.0 (#6268)

This commit is contained in:
Jacob Fletcher
2024-05-09 15:32:46 -04:00
committed by GitHub
parent 5065322d31
commit 9e9111666b
86 changed files with 13338 additions and 13677 deletions

View File

@@ -7,7 +7,7 @@ keywords: live preview, frontend, react, next.js, vue, nuxt.js, svelte, hook, us
---
<Banner type="info">
If your front-end application is supports Server Components like the [Next.js App Router](https://nextjs.org/docs/app), etc., we suggest setting up [server-side Live Preview](./server).
If your front-end application supports Server Components like the [Next.js App Router](https://nextjs.org/docs/app), etc., we suggest setting up [server-side Live Preview](./server).
</Banner>
While using Live Preview, the Admin panel emits a new `window.postMessage` event every time your document has changed. Your front-end application can listen for these events and re-render accordingly.

View File

@@ -16,7 +16,7 @@ Server-side Live Preview works by making a roundtrip to the server every time yo
It is recommended that you enable [Autosave](../versions/autosave) alongside Live Preview to make the experience feel more responsive.
</Banner>
If your front-end application is built with [React](#react), you can use the `RefreshRouteOnChange` function that Payload provides and give it your own router refresh function. In the future, all other major frameworks like Vue and Svelte will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own router refresh component](#building-your-own-router-refresh-component) for more information.
If your front-end application is built with [React](#react), you can use the `RefreshRouteOnChange` function that Payload provides. In the future, all other major frameworks like Vue and Svelte will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own router refresh component](#building-your-own-router-refresh-component) for more information.
### React
@@ -39,6 +39,7 @@ import config from '../payload.config'
export default async function Page() {
const payload = await getPayloadHMR({ config })
const page = await payload.find({
collection: 'pages',
draft: true
@@ -47,7 +48,7 @@ export default async function Page() {
return (
<Fragment>
<RefreshRouteOnSave />
<h1>Hello, world!</h1>
<h1>{page.title}</h1>
</Fragment>
)
}
@@ -166,7 +167,7 @@ If you are noticing that updates feel less snappy than client-side Live Preview
versions: {
drafts: {
autosave: {
interval: 10,
interval: 375,
},
},
},

View File

@@ -4,18 +4,21 @@ This is a [Next.js](https://nextjs.org) app using the [App Router](https://nextj
> This example uses the App Router, the latest API of Next.js. If your app is using the legacy [Pages Router](https://nextjs.org/docs/pages), check out the official [Pages Router Example](https://github.com/payloadcms/payload/tree/main/examples/live-preview/next-pages).
**IMPORTANT—This application runs on a different server as Payload and establishes a connection from another domain or port over HTTP.** For an integrated setup that runs on a single server and uses the [Local API](https://payloadcms.com/docs/local-api/overview#local-api), check out [how to serve Payload alongside Next.js](https://github.com/payloadcms/payload/tree/main/examples/live-preview/payload). To learn more about this, check out [how Payload can be used in its various headless capacities](https://payloadcms.com/blog/the-ultimate-guide-to-using-nextjs-with-payload).
## Getting Started
### Payload
First you'll need a running Payload app. There is one made explicitly for this example and [can be found here](https://github.com/payloadcms/payload/tree/main/examples/live-preview/payload). If you have not done so already, clone it down and follow the setup instructions there. This will provide all the necessary APIs that your Next.js app requires for authentication.
First you'll need a running Payload app. There is one made explicitly for this example and [can be found here](https://github.com/payloadcms/payload/tree/main/examples/live-preview/payload). If you have not done so already, clone it down and follow the setup instructions there. This will provide all the necessary APIs that your Next.js app requires for live preview.
### Next.js
1. Clone this repo
2. `cd` into this directory and run `yarn` or `npm install`
2. `cd` into this directory and run `pnpm i --ignore-workspace`\*, `yarn`, or `npm install`
> \*If you are running using pnpm within the Payload Monorepo, the `--ignore-workspace` flag is needed so that pnpm generates a lockfile in this example's directory despite the fact that one exists in root.
3. `cp .env.example .env` to copy the example environment variables
4. `yarn dev` or `npm run dev` to start the server
4. `pnpm dev`, `yarn dev`, or `npm run dev` to start the server
5. `open http://localhost:3001` to see the result
Once running you will find a couple seeded pages on your local environment with some basic instructions. You can also start editing the pages by modifying the documents within Payload. See the [Live Preview Example](https://github.com/payloadcms/payload/tree/main/examples/live-preview/payload) for full details.

View File

@@ -9,7 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@payloadcms/live-preview-react": "latest",
"@payloadcms/live-preview-react": "3.0.0-beta.28",
"escape-html": "^1.0.3",
"next": "^13.5.1",
"payload-admin-bar": "^1.0.6",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -4,18 +4,21 @@ This is a [Next.js](https://nextjs.org) app using the [Pages Router](https://nex
> This example uses the Pages Router, the legacy API of Next.js. If your app is using the latest [App Router](https://nextjs.org/docs/app), check out the official [App Router Example](https://github.com/payloadcms/payload/tree/main/examples/live-preview/next-app).
**IMPORTANT—This application runs on a different server as Payload and establishes a connection from another domain or port over HTTP.** For an integrated setup that runs on a single server and uses the [Local API](https://payloadcms.com/docs/local-api/overview#local-api), check out [how to serve Payload alongside Next.js](https://github.com/payloadcms/payload/tree/main/examples/live-preview/payload). To learn more about this, check out [how Payload can be used in its various headless capacities](https://payloadcms.com/blog/the-ultimate-guide-to-using-nextjs-with-payload).
## Getting Started
### Payload
First you'll need a running Payload app. There is one made explicitly for this example and [can be found here](https://github.com/payloadcms/payload/tree/main/examples/live-preview/payload). If you have not done so already, clone it down and follow the setup instructions there. This will provide all the necessary APIs that your Next.js app requires for authentication.
First you'll need a running Payload app. There is one made explicitly for this example and [can be found here](https://github.com/payloadcms/payload/tree/main/examples/live-preview/payload). If you have not done so already, clone it down and follow the setup instructions there. This will provide all the necessary APIs that your Next.js app requires for live preview.
### Next.js
1. Clone this repo
2. `cd` into this directory and run `yarn` or `npm install`
2. `cd` into this directory and run `pnpm i --ignore-workspace`\*, `yarn`, or `npm install`
> \*If you are running using pnpm within the Payload Monorepo, the `--ignore-workspace` flag is needed so that pnpm generates a lockfile in this example's directory despite the fact that one exists in root.
3. `cp .env.example .env` to copy the example environment variables
4. `yarn dev` or `npm run dev` to start the server
4. `pnpm dev`, `yarn dev`, or `npm run dev` to start the server
5. `open http://localhost:3001` to see the result
Once running you will find a couple seeded pages on your local environment with some basic instructions. You can also start editing the pages by modifying the documents within Payload. See the [Live Preview Example](https://github.com/payloadcms/payload/tree/main/examples/live-preview/payload) for full details.

View File

@@ -9,7 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@payloadcms/live-preview-react": "latest",
"@payloadcms/live-preview-react": "3.0.0-beta.28",
"@types/escape-html": "^1.0.2",
"escape-html": "^1.0.3",
"next": "^13.5.1",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
DATABASE_URI=mongodb://127.0.0.1/payload-example-live-preview
PAYLOAD_SECRET=ENTER-STRING-HERE
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
PAYLOAD_PUBLIC_SITE_URL=http://localhost:3001
PAYLOAD_PUBLIC_SEED=true
PAYLOAD_DROP_DATABASE=true
PAYLOAD_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_SERVER_URL=http://localhost:3000

View File

@@ -0,0 +1,12 @@
.tmp
**/.git
**/.hg
**/.pnp.*
**/.svn
**/.yarn/**
**/build
**/dist/**
**/node_modules
**/temp
playwright.config.ts
jest.config.js

View File

@@ -1,4 +1,15 @@
module.exports = {
extends: ['plugin:@next/next/core-web-vitals', '@payloadcms'],
ignorePatterns: ['**/payload-types.ts'],
overrides: [
{
extends: ['plugin:@typescript-eslint/disable-type-checked'],
files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
},
],
parserOptions: {
project: ['./tsconfig.json'],
tsconfigRootDir: __dirname,
},
root: true,
extends: ['@payloadcms'],
}

View File

@@ -2,21 +2,31 @@
The [Payload Live Preview Example](https://github.com/payloadcms/payload/tree/main/examples/live-preview/payload) demonstrates how to implement [Live Preview](https://payloadcms.com/docs/live-preview) in [Payload](https://github.com/payloadcms/payload). With Live Preview you can render your front-end application directly within the Admin panel. As you type, your changes take effect in real-time. No need to save a draft or publish your changes.
There are various fully working front-ends made explicitly for this example, including:
**IMPORTANT—This example includes a fully integrated Next.js App Router front-end that runs on the same server as Payload.** If you are working on an application running on an entirely separate server, there are various fully working, separately running front-ends made explicitly for this example, including:
- [Next.js App Router](../next-app)
- [Next.js Pages Router](../next-pages)
Follow the instructions in each respective README to get started. If you are setting up Live Preview for another front-end, please consider contributing to this repo with your own example!
Those applications run directly alongside this one. Follow the instructions in each respective README to get started. If you are setting up authentication for another front-end, please consider contributing to this repo with your own example!
To learn more about this, [check out how Payload can be used in its various headless capacities](https://payloadcms.com/blog/the-ultimate-guide-to-using-nextjs-with-payload).
## Quick Start
1. Clone this repo
2. `cd` into this directory and run `yarn` or `npm install`
3. `cp .env.example .env` to copy the example environment variables
4. `yarn dev` or `npm run dev` to start the server and seed the database
5. `open http://localhost:3000/admin` to access the admin panel
6. Login with email `demo@payloadcms.com` and password `demo`
1. `cd` into this directory and run `pnpm i --ignore-workspace`\*, `yarn`, or `npm install`
> \*If you are running using pnpm within the Payload Monorepo, the `--ignore-workspace` flag is needed so that pnpm generates a lockfile in this example's directory despite the fact that one exists in root.
1. `cp .env.example .env` to copy the example environment variables
> Adjust `PAYLOAD_PUBLIC_SITE_URL` in the `.env` if your front-end is running on a separate domain or port.
1. `pnpm dev`, `yarn dev` or `npm run dev` to start the server
- Press `y` when prompted to seed the database
1. `open http://localhost:3000` to access the home page
1. `open http://localhost:3000/admin` to access the admin panel
- Login with email `demo@payloadcms.com` and password `demo`
That's it! Changes made in `./src` will be reflected in your app. See the [Development](#development) section for more details.
@@ -64,9 +74,82 @@ See the [Collections](https://payloadcms.com/docs/configuration/collections) doc
While using Live Preview, the Admin panel emits a new `window.postMessage` event every time a change is made to the document. Your front-end application can listen for these events and re-render accordingly.
### React
There are two ways to use Live Preview in your own application depending on whether your front-end framework supports server components:
If your front-end application is built with React or Next.js, use the [`useLivePreview`](#react) React hook that Payload provides.
- [Server-side Live Preview (suggested)](#server)
- [Client-side Live Preview](#client)
<Banner type="info">
We suggest using server-side Live Preview if your framework supports it, it is both simpler to setup and more performant to run than the client-side alternative.
</Banner>
### Server
> Server-side Live Preview is only for front-end frameworks that support the concept of Server Components, i.e. [React Server Components](https://react.dev/reference/rsc/server-components). If your front-end application is built with a client-side framework like the [Next.js Pages Router](https://nextjs.org/docs/pages), [React Router](https://reactrouter.com), [Vue 3](https://vuejs.org), etc., see [client-side Live Preview](#client).
Server-side Live Preview works by making a roundtrip to the server every time your document is saved, i.e. draft save, autosave, or publish. While using Live Preview, the Admin panel emits a new `window.postMessage` event which your front-end application can use to invoke this process. In Next.js, this means simply calling `router.refresh()` which will hydrate the HTML using new data straight from the [Local API](../local-api/overview).
If your server-side front-end application is built with [React](#react), you can use the `RefreshRouteOnChange` function that Payload provides. In the future, all other major frameworks like Vue and Svelte will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own router refresh component](https://payloadcms.com/docs/live-preview/server#building-your-own-router-refresh-component) for more information.
#### React
If your front-end application is built with server-side [React](https://react.dev), i.e. [Next.js App Router](https://nextjs.org/docs/app), you can use the `RefreshRouteOnSave` component that Payload provides and thread it your framework's refresh function.
First, install the `@payloadcms/live-preview-react` package:
```bash
npm install @payloadcms/live-preview-react
```
Then, render `RefreshRouteOnSave` anywhere in your `page.tsx`. Here's an example:
`page.tsx`:
```tsx
import { RefreshRouteOnSave } from './RefreshRouteOnSave.tsx'
import { getPayloadHMR } from '@payloadcms/next'
import config from '../payload.config'
export default async function Page() {
const payload = await getPayloadHMR({ config })
const page = await payload.find({
collection: 'pages',
draft: true,
})
return (
<Fragment>
<RefreshRouteOnSave />
<h1>{page.title}</h1>
</Fragment>
)
}
```
`RefreshRouteOnSave.tsx`:
```tsx
'use client'
import { RefreshRouteOnSave as PayloadLivePreview } from '@payloadcms/live-preview-react'
import { useRouter } from 'next/navigation.js'
import React from 'react'
export const RefreshRouteOnSave: React.FC = () => {
const router = useRouter()
return <PayloadLivePreview refresh={router.refresh} serverURL={process.env.PAYLOAD_SERVER_URL} />
}
```
For more details on how to setup server-side Live Preview, see the [server-side Live Preview](https://payloadcms.com/docs/live-preview/server) docs.
### Client
> If your front-end application is supports Server Components like the [Next.js App Router](https://nextjs.org/docs/app), etc., we suggest setting up [server-side Live Preview](#server).
#### React
If your front-end application is built with client-side React such as Next.js Pages Router, React Router, etc., use the [`useLivePreview`](#react) React hook that Payload provides.
First, install the `@payloadcms/live-preview-react` package:
@@ -77,8 +160,8 @@ npm install @payloadcms/live-preview-react
Then, use the `useLivePreview` hook in your React component:
```tsx
'use client';
import { useLivePreview } from '@payloadcms/live-preview-react';
'use client'
import { useLivePreview } from '@payloadcms/live-preview-react'
import { Page as PageType } from '@/payload-types'
// Fetch the page in a server component, pass it to the client component, then thread it through the hook
@@ -95,13 +178,11 @@ export const PageClient: React.FC<{
depth: 2, // Ensure that the depth matches the request for `initialPage`
})
return (
<h1>{data.title}</h1>
)
return <h1>{data.title}</h1>
}
```
### JavaScript
#### JavaScript
In the future, all other major frameworks like Vue, Svelte, etc will be officially supported. If you are using any of these framework today, you can still integrate with Live Preview yourself using the tooling that Payload provides.
@@ -114,7 +195,7 @@ npm install @payloadcms/live-preview
Then, build your own hook:
```tsx
import { subscribe, unsubscribe } from '@payloadcms/live-preview';
import { subscribe, unsubscribe } from '@payloadcms/live-preview'
// Build your own hook to subscribe to the live preview events
// This function will handle everything for you like
@@ -125,6 +206,8 @@ import { subscribe, unsubscribe } from '@payloadcms/live-preview';
See [building your own Live Preview hook](https://payloadcms.com/docs/live-preview/frontend#building-your-own-hook) for more details.
For more details on how to setup client-side Live Preview, see the [client-side Live Preview](https://payloadcms.com/docs/live-preview/client) docs.
## Development
To spin up this example locally, follow the [Quick Start](#quick-start).

View File

@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -0,0 +1,8 @@
import { withPayload } from '@payloadcms/next/withPayload'
/** @type {import('next').NextConfig} */
const nextConfig = {
// Your Next.js config here
}
export default withPayload(nextConfig)

View File

@@ -1,6 +0,0 @@
{
"$schema": "https://json.schemastore.org/nodemon.json",
"ext": "ts",
"exec": "ts-node src/server.ts -- -I",
"stdin": false
}

View File

@@ -1,49 +1,44 @@
{
"name": "payload-example-live-preview",
"description": "Payload Live Preview example.",
"version": "1.0.0",
"main": "dist/server.js",
"description": "Payload Live Preview example.",
"license": "MIT",
"main": "dist/server.js",
"scripts": {
"dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
"seed": "rm -rf media && cross-env PAYLOAD_PUBLIC_SEED=true PAYLOAD_DROP_DATABASE=true PAYLOAD_CONFIG_PATH=src/payload.config.ts ts-node src/server.ts",
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build",
"build:server": "tsc",
"build": "yarn copyfiles && yarn build:payload && yarn build:server",
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
"build": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts NODE_OPTIONS=--no-deprecation next build",
"dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts && pnpm seed && cross-env NODE_OPTIONS=--no-deprecation next dev",
"generate:graphQLSchema": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema",
"lint": "eslint src",
"lint:fix": "eslint --fix --ext .ts,.tsx src"
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
"lint": "cross-env NODE_OPTIONS=--no-deprecation next lint",
"lint:fix": "eslint --fix --ext .ts,.tsx src",
"payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload",
"seed": "npm run payload migrate:fresh",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start"
},
"dependencies": {
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"payload": "latest"
"@payloadcms/db-mongodb": "3.0.0-beta.28",
"@payloadcms/live-preview-react": "3.0.0-beta.28",
"@payloadcms/next": "3.0.0-beta.28",
"@payloadcms/richtext-slate": "3.0.0-beta.28",
"@payloadcms/ui": "3.0.0-beta.28",
"cross-env": "^7.0.3",
"next": "14.3.0-canary.7",
"payload": "3.0.0-beta.28",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.3"
},
"devDependencies": {
"@payloadcms/eslint-config": "^0.0.2",
"@types/express": "^4.17.9",
"@types/node": "18.11.3",
"@types/react": "18.0.21",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
"copyfiles": "^2.4.1",
"cross-env": "^7.0.3",
"eslint": "^8.19.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"nodemon": "^2.0.6",
"prettier": "^2.7.1",
"ts-node": "^9.1.1",
"typescript": "^4.8.4"
"@next/eslint-plugin-next": "^13.1.6",
"@payloadcms/eslint-config": "^1.1.1",
"@swc/core": "^1.4.14",
"@swc/types": "^0.1.6",
"@types/node": "^20.11.25",
"@types/react": "^18.2.64",
"@types/react-dom": "^18.2.21",
"dotenv": "^16.4.5",
"eslint": "^8.57.0",
"tsx": "^4.7.1",
"typescript": "5.4.4"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
'use client'
import { RefreshRouteOnSave as PayloadLivePreview } from '@payloadcms/live-preview-react'
import { useRouter } from 'next/navigation.js'
import React from 'react'
export const RefreshRouteOnSave: React.FC = () => {
const router = useRouter()
return (
<PayloadLivePreview
refresh={() => router.refresh()}
serverURL={process.env.NEXT_PUBLIC_SERVER_URL || ''}
/>
)
}

View File

@@ -0,0 +1,3 @@
.page {
margin-top: calc(var(--base) * 2);
}

View File

@@ -0,0 +1,70 @@
/* eslint-disable no-restricted-exports */
import { getPayloadHMR } from '@payloadcms/next/utilities'
import { notFound } from 'next/navigation'
import React from 'react'
import { Fragment } from 'react'
import type { Page as PageType } from '../../../payload-types'
import config from '../../../payload.config'
import { Gutter } from '../_components/Gutter'
import RichText from '../_components/RichText'
import { RefreshRouteOnSave } from './RefreshRouteOnSave'
import classes from './index.module.scss'
interface PageParams {
params: { slug: string }
}
export default async function Page({ params: { slug = 'home' } }: PageParams) {
const payload = await getPayloadHMR({ config })
const pageRes = await payload.find({
collection: 'pages',
draft: true,
limit: 1,
where: {
slug: {
equals: slug,
},
},
})
const data = pageRes?.docs?.[0] as PageType | null
if (data === null) {
return notFound()
}
return (
<Fragment>
<RefreshRouteOnSave />
<main className={classes.page}>
<Gutter>
<RichText content={data?.richText} />
</Gutter>
</main>
</Fragment>
)
}
export async function generateStaticParams() {
const payload = await getPayloadHMR({ config })
const pagesRes = await payload.find({
collection: 'pages',
depth: 0,
draft: true,
limit: 100,
})
const pages = pagesRes?.docs
return pages.map(({ slug }) =>
slug !== 'home'
? {
slug,
}
: {},
) // eslint-disable-line function-paren-newline
}

View File

@@ -0,0 +1,55 @@
.button {
border: none;
cursor: pointer;
display: inline-flex;
justify-content: center;
background-color: transparent;
}
.content {
display: flex;
align-items: center;
justify-content: space-around;
svg {
margin-right: calc(var(--base) / 2);
width: var(--base);
height: var(--base);
}
}
.label {
text-align: center;
display: flex;
align-items: center;
}
.button {
text-decoration: none;
display: inline-flex;
padding: 12px 24px;
}
.primary--white {
background-color: black;
color: white;
}
.primary--black {
background-color: white;
color: black;
}
.secondary--white {
background-color: white;
box-shadow: inset 0 0 0 1px black;
}
.secondary--black {
background-color: black;
box-shadow: inset 0 0 0 1px white;
}
.appearance--default {
padding: 0;
}

View File

@@ -0,0 +1,73 @@
import type { ElementType } from 'react';
import Link from 'next/link'
import React from 'react'
import classes from './index.module.scss'
export type Props = {
appearance?: 'default' | 'primary' | 'secondary'
className?: string
disabled?: boolean
el?: 'a' | 'button' | 'link'
href?: string
label?: string
newTab?: boolean
onClick?: () => void
type?: 'button' | 'submit'
}
export const Button: React.FC<Props> = ({
type = 'button',
appearance,
className: classNameFromProps,
disabled,
el: elFromProps = 'link',
href,
label,
newTab,
onClick,
}) => {
let el = elFromProps
const newTabProps = newTab ? { rel: 'noopener noreferrer', target: '_blank' } : {}
const className = [
classes.button,
classNameFromProps,
classes[`appearance--${appearance}`],
classes.button,
]
.filter(Boolean)
.join(' ')
const content = (
<div className={classes.content}>
{/* <Chevron /> */}
<span className={classes.label}>{label}</span>
</div>
)
if (onClick || type === 'submit') el = 'button'
if (el === 'link') {
return (
<Link className={className} href={href || ''} {...newTabProps} onClick={onClick}>
{content}
</Link>
)
}
const Element: ElementType = el
return (
<Element
className={className}
href={href}
type={type}
{...newTabProps}
disabled={disabled}
onClick={onClick}
>
{content}
</Element>
)
}

View File

@@ -0,0 +1,67 @@
import Link from 'next/link'
import React from 'react'
import type { Page } from '../../../payload-types'
import { Button } from '../Button'
export type CMSLinkType = {
appearance?: 'default' | 'primary' | 'secondary'
children?: React.ReactNode
className?: string
label?: string
newTab?: boolean
reference?: {
relationTo: 'pages'
value: Page | string
}
type?: 'custom' | 'reference'
url?: string
}
export const CMSLink: React.FC<CMSLinkType> = ({
type,
appearance,
children,
className,
label,
newTab,
reference,
url,
}) => {
const href =
type === 'reference' && typeof reference?.value === 'object' && reference.value.slug
? `/${reference.value.slug === 'home' ? '' : reference.value.slug}`
: url
if (!appearance) {
const newTabProps = newTab ? { rel: 'noopener noreferrer', target: '_blank' } : {}
if (type === 'custom') {
return (
<a href={url} {...newTabProps} className={className}>
{label && label}
{children && children}
</a>
)
}
if (href) {
return (
<Link href={href} {...newTabProps} className={className} prefetch={false}>
{label && label}
{children && children}
</Link>
)
}
}
const buttonProps = {
appearance,
href,
label,
newTab,
}
return <Button className={className} {...buttonProps} el="link" />
}

View File

@@ -0,0 +1,13 @@
.gutter {
max-width: var(--max-width);
width: 100%;
margin: auto;
}
.gutterLeft {
padding-left: var(--gutter-h);
}
.gutterRight {
padding-right: var(--gutter-h);
}

View File

@@ -0,0 +1,35 @@
import type { Ref } from 'react';
import React, { forwardRef } from 'react'
import classes from './index.module.scss'
type Props = {
children: React.ReactNode
className?: string
left?: boolean
ref?: Ref<HTMLDivElement>
right?: boolean
}
export const Gutter: React.FC<Props> = forwardRef<HTMLDivElement, Props>((props, ref) => {
const { children, className, left = true, right = true } = props
return (
<div
className={[
classes.gutter,
left && classes.gutterLeft,
right && classes.gutterRight,
className,
]
.filter(Boolean)
.join(' ')}
ref={ref}
>
{children}
</div>
)
})
Gutter.displayName = 'Gutter'

View File

@@ -0,0 +1,32 @@
.header {
padding: var(--base) 0;
}
.wrap {
display: flex;
justify-content: space-between;
gap: calc(var(--base) / 2);
flex-wrap: wrap;
}
.logo {
flex-shrink: 0;
}
.nav {
display: flex;
align-items: center;
gap: var(--base);
white-space: nowrap;
overflow: hidden;
flex-wrap: wrap;
a {
display: block;
text-decoration: none;
}
@media (max-width: 1000px) {
gap: 0 calc(var(--base) / 2);
}
}

View File

@@ -0,0 +1,50 @@
import { getPayloadHMR } from '@payloadcms/next/utilities'
import Image from 'next/image'
import Link from 'next/link'
import React from 'react'
import config from '../../../../payload.config'
import { CMSLink } from '../CMSLink'
import { Gutter } from '../Gutter'
import classes from './index.module.scss'
export const Header = async () => {
const payload = await getPayloadHMR({ config })
const mainMenu = await payload.findGlobal({
slug: 'main-menu',
depth: 0,
})
const { navItems } = mainMenu
const hasNavItems = navItems && Array.isArray(navItems) && navItems.length > 0
return (
<header className={classes.header}>
<Gutter className={classes.wrap}>
<Link className={classes.logo} href="/">
<picture>
<source
media="(prefers-color-scheme: dark)"
srcSet="https://raw.githubusercontent.com/payloadcms/payload/master/src/admin/assets/images/payload-logo-light.svg"
/>
<Image
alt="Payload Logo"
height={30}
src="https://raw.githubusercontent.com/payloadcms/payload/master/src/admin/assets/images/payload-logo-dark.svg"
width={150}
/>
</picture>
</Link>
{hasNavItems && (
<nav className={classes.nav}>
{navItems.map(({ link }, i) => {
return <CMSLink key={i} {...link} />
})}
</nav>
)}
</Gutter>
</header>
)
}

View File

@@ -0,0 +1,9 @@
.richText {
:first-child {
margin-top: 0;
}
a {
text-decoration: underline;
}
}

View File

@@ -0,0 +1,18 @@
import React from 'react'
import classes from './index.module.scss'
import serialize from './serialize'
const RichText: React.FC<{ className?: string; content: any }> = ({ className, content }) => {
if (!content) {
return null
}
return (
<div className={[classes.richText, className].filter(Boolean).join(' ')}>
{serialize(content)}
</div>
)
}
export default RichText

View File

@@ -0,0 +1,92 @@
import escapeHTML from 'escape-html'
import React, { Fragment } from 'react'
import { Text } from 'slate'
// eslint-disable-next-line no-use-before-define
type Children = Leaf[]
type Leaf = {
[key: string]: unknown
children: Children
type: string
url?: string
value?: {
alt: string
url: string
}
}
const serialize = (children: Children): React.ReactNode[] =>
children.map((node, i) => {
if (Text.isText(node)) {
let text = <span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />
if (node.bold) {
text = <strong key={i}>{text}</strong>
}
if (node.code) {
text = <code key={i}>{text}</code>
}
if (node.italic) {
text = <em key={i}>{text}</em>
}
if (node.underline) {
text = (
<span key={i} style={{ textDecoration: 'underline' }}>
{text}
</span>
)
}
if (node.strikethrough) {
text = (
<span key={i} style={{ textDecoration: 'line-through' }}>
{text}
</span>
)
}
return <Fragment key={i}>{text}</Fragment>
}
if (!node) {
return null
}
switch (node.type) {
case 'h1':
return <h1 key={i}>{serialize(node.children)}</h1>
case 'h2':
return <h2 key={i}>{serialize(node.children)}</h2>
case 'h3':
return <h3 key={i}>{serialize(node.children)}</h3>
case 'h4':
return <h4 key={i}>{serialize(node.children)}</h4>
case 'h5':
return <h5 key={i}>{serialize(node.children)}</h5>
case 'h6':
return <h6 key={i}>{serialize(node.children)}</h6>
case 'blockquote':
return <blockquote key={i}>{serialize(node.children)}</blockquote>
case 'ul':
return <ul key={i}>{serialize(node.children)}</ul>
case 'ol':
return <ol key={i}>{serialize(node.children)}</ol>
case 'li':
return <li key={i}>{serialize(node.children)}</li>
case 'link':
return (
<a href={escapeHTML(node.url)} key={i}>
{serialize(node.children)}
</a>
)
default:
return <p key={i}>{serialize(node.children)}</p>
}
})
export default serialize

View File

@@ -0,0 +1,107 @@
$breakpoint: 1000px;
:root {
--max-width: 1600px;
--foreground-rgb: 0, 0, 0;
--background-rgb: 255, 255, 255;
--block-spacing: 2rem;
--gutter-h: 4rem;
--base: 1rem;
@media (max-width: $breakpoint) {
--block-spacing: 1rem;
--gutter-h: 2rem;
--base: 0.75rem;
}
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-rgb: 7, 7, 7;
}
}
* {
box-sizing: border-box;
}
html {
font-size: 20px;
line-height: 1.5;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
@media (max-width: $breakpoint) {
font-size: 16px;
}
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
margin: 0;
color: rgb(var(--foreground-rgb));
background: rgb(var(--background-rgb));
}
img {
height: auto;
max-width: 100%;
display: block;
}
h1 {
font-size: 4.5rem;
line-height: 1.2;
margin: 0 0 2.5rem 0;
@media (max-width: $breakpoint) {
font-size: 3rem;
margin: 0 0 1.5rem 0;
}
}
h2 {
font-size: 3.5rem;
line-height: 1.2;
margin: 0 0 2.5rem 0;
}
h3 {
font-size: 2.5rem;
line-height: 1.2;
margin: 0 0 2rem 0;
}
h4 {
font-size: 1.5rem;
line-height: 1.2;
margin: 0 0 1rem 0;
}
h5 {
font-size: 1.25rem;
line-height: 1.2;
margin: 0 0 1rem 0;
}
h6 {
font-size: 1rem;
line-height: 1.2;
margin: 0 0 1rem 0;
}
a {
color: inherit;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}

View File

@@ -0,0 +1,23 @@
/* eslint-disable no-restricted-exports */
import React from 'react'
import { Header } from './_components/Header'
import './app.scss'
export const metadata = {
description: 'Generated by create next app',
title: 'Create Next App',
}
export default function RootLayout(props: { children: React.ReactNode }) {
const { children } = props
return (
<html lang="en">
<body>
<Header />
{children}
</body>
</html>
)
}

View File

@@ -0,0 +1,3 @@
import Page from './[slug]/page'
export default Page

View File

@@ -0,0 +1,22 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
import type { Metadata } from 'next'
import config from '@payload-config'
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'
type Args = {
params: {
segments: string[]
}
searchParams: {
[key: string]: string | string[]
}
}
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams })
const NotFound = ({ params, searchParams }: Args) => NotFoundPage({ config, params, searchParams })
export default NotFound

View File

@@ -0,0 +1,22 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
import type { Metadata } from 'next'
import config from '@payload-config'
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
type Args = {
params: {
segments: string[]
}
searchParams: {
[key: string]: string | string[]
}
}
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams })
const Page = ({ params, searchParams }: Args) => RootPage({ config, params, searchParams })
export default Page

View File

@@ -0,0 +1,10 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from '@payload-config'
import { REST_DELETE, REST_GET, REST_OPTIONS, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
export const GET = REST_GET(config)
export const POST = REST_POST(config)
export const DELETE = REST_DELETE(config)
export const PATCH = REST_PATCH(config)
export const OPTIONS = REST_OPTIONS(config)

View File

@@ -0,0 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from '@payload-config'
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
export const GET = GRAPHQL_PLAYGROUND_GET(config)

View File

@@ -0,0 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from '@payload-config'
import { GRAPHQL_POST } from '@payloadcms/next/routes'
export const POST = GRAPHQL_POST(config)

View File

@@ -0,0 +1,16 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
import configPromise from '@payload-config'
import '@payloadcms/next/css'
import { RootLayout } from '@payloadcms/next/layouts'
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import React from 'react'
import './custom.scss'
type Args = {
children: React.ReactNode
}
const Layout = ({ children }: Args) => <RootLayout config={configPromise}>{children}</RootLayout>
export default Layout

View File

@@ -8,7 +8,7 @@ const format = (val: string): string =>
const formatSlug =
(fallback: string): FieldHook =>
({ operation, value, originalDoc, data }) => {
({ data, operation, originalDoc, value }) => {
if (typeof value === 'string') {
return format(value)
}

View File

@@ -6,19 +6,21 @@ import formatSlug from './hooks/formatSlug'
export const Pages: CollectionConfig = {
slug: 'pages',
access: {
create: loggedIn,
delete: loggedIn,
read: () => true,
update: loggedIn,
},
admin: {
useAsTitle: 'title',
defaultColumns: ['title', 'slug', 'updatedAt'],
livePreview: {
url: ({ data }) =>
`${process.env.PAYLOAD_PUBLIC_SITE_URL}${data.slug !== 'home' ? `/${data.slug}` : ''}`,
url: ({ data }) => {
const isHomePage = data.slug === 'home'
return `${process.env.PAYLOAD_PUBLIC_SITE_URL}${!isHomePage ? `/${data.slug}` : ''}`
},
},
},
access: {
read: () => true,
create: loggedIn,
update: loggedIn,
delete: loggedIn,
useAsTitle: 'title',
},
fields: [
{
@@ -28,16 +30,23 @@ export const Pages: CollectionConfig = {
},
{
name: 'slug',
label: 'Slug',
type: 'text',
index: true,
admin: {
position: 'sidebar',
},
hooks: {
beforeValidate: [formatSlug('title')],
},
index: true,
label: 'Slug',
},
richText(),
],
versions: {
drafts: {
autosave: {
interval: 375,
},
},
},
}

View File

@@ -2,9 +2,9 @@ import type { CollectionConfig } from 'payload/types'
export const Users: CollectionConfig = {
slug: 'users',
auth: true,
admin: {
useAsTitle: 'email',
},
auth: true,
fields: [],
}

View File

@@ -3,6 +3,10 @@ import type { Field } from 'payload/types'
import deepMerge from '../utilities/deepMerge'
export const appearanceOptions = {
default: {
label: 'Default',
value: 'default',
},
primary: {
label: 'Primary Button',
value: 'primary',
@@ -11,13 +15,9 @@ export const appearanceOptions = {
label: 'Secondary Button',
value: 'secondary',
},
default: {
label: 'Default',
value: 'default',
},
}
export type LinkAppearances = 'primary' | 'secondary' | 'default'
export type LinkAppearances = 'default' | 'primary' | 'secondary'
type LinkType = (options?: {
appearances?: LinkAppearances[] | false
@@ -39,6 +39,11 @@ const link: LinkType = ({ appearances, disableLabel = false, overrides = {} } =
{
name: 'type',
type: 'radio',
admin: {
layout: 'horizontal',
width: '50%',
},
defaultValue: 'reference',
options: [
{
label: 'Internal link',
@@ -49,22 +54,17 @@ const link: LinkType = ({ appearances, disableLabel = false, overrides = {} } =
value: 'custom',
},
],
defaultValue: 'reference',
admin: {
layout: 'horizontal',
width: '50%',
},
},
{
name: 'newTab',
label: 'Open in new tab',
type: 'checkbox',
admin: {
width: '50%',
style: {
alignSelf: 'flex-end',
},
width: '50%',
},
label: 'Open in new tab',
},
],
},
@@ -74,23 +74,23 @@ const link: LinkType = ({ appearances, disableLabel = false, overrides = {} } =
const linkTypes: Field[] = [
{
name: 'reference',
label: 'Document to link to',
type: 'relationship',
relationTo: ['pages'],
required: true,
maxDepth: 1,
admin: {
condition: (_, siblingData) => siblingData?.type === 'reference',
},
label: 'Document to link to',
maxDepth: 1,
relationTo: ['pages'],
required: true,
},
{
name: 'url',
label: 'Custom URL',
type: 'text',
required: true,
admin: {
condition: (_, siblingData) => siblingData?.type === 'custom',
},
label: 'Custom URL',
required: true,
},
]
@@ -104,12 +104,12 @@ const link: LinkType = ({ appearances, disableLabel = false, overrides = {} } =
...linkTypes,
{
name: 'label',
label: 'Label',
type: 'text',
required: true,
admin: {
width: '50%',
},
label: 'Label',
required: true,
},
],
})
@@ -131,11 +131,11 @@ const link: LinkType = ({ appearances, disableLabel = false, overrides = {} } =
linkResult.fields.push({
name: 'appearance',
type: 'select',
defaultValue: 'default',
options: appearanceOptionsToUse,
admin: {
description: 'Choose how the link should be rendered.',
},
defaultValue: 'default',
options: appearanceOptionsToUse,
})
}

View File

@@ -1,7 +1,8 @@
import { slateEditor } from '@payloadcms/richtext-slate'
import type { RichTextElement, RichTextLeaf } from '@payloadcms/richtext-slate/dist/types'
import type { RichTextField } from 'payload/types'
import { slateEditor } from '@payloadcms/richtext-slate'
import deepMerge from '../../utilities/deepMerge'
import link from '../link'
import elements from './elements'
@@ -26,27 +27,28 @@ const richText: RichText = (
{
name: 'richText',
type: 'richText',
required: true,
editor: slateEditor({
admin: {
elements: [...elements, ...(additions.elements || [])],
leaves: [...leaves, ...(additions.leaves || [])],
upload: {
collections: {
media: {
fields: [
{
type: 'richText',
name: 'caption',
label: 'Caption',
type: 'richText',
editor: slateEditor({
admin: {
elements: [...elements],
leaves: [...leaves],
},
}),
label: 'Caption',
},
{
type: 'radio',
name: 'alignment',
type: 'radio',
label: 'Alignment',
options: [
{
@@ -81,10 +83,9 @@ const richText: RichText = (
},
},
},
elements: [...elements, ...(additions.elements || [])],
leaves: [...leaves, ...(additions.leaves || [])],
},
}),
required: true,
},
overrides,
)

View File

@@ -11,12 +11,12 @@ export const MainMenu: GlobalConfig = {
{
name: 'navItems',
type: 'array',
maxRows: 6,
fields: [
link({
appearances: false,
}),
],
maxRows: 6,
},
],
}

View File

@@ -0,0 +1,130 @@
import type { MigrateUpArgs } from '@payloadcms/db-mongodb'
import type { Page } from '../payload-types'
export const home: Partial<Page> = {
slug: 'home',
richText: [
{
type: 'h1',
children: [
{
text: 'Payload Live Preview Example',
},
],
},
{
children: [
{ text: 'This is a ' },
{
type: 'link',
children: [{ text: 'Next.js' }],
linkType: 'custom',
newTab: true,
url: 'https://nextjs.org',
},
{ text: " app made explicitly for Payload's " },
{
type: 'link',
children: [{ text: 'Live Preview Example' }],
linkType: 'custom',
newTab: true,
url: 'https://github.com/payloadcms/payload/tree/master/examples/live-preview/payload',
},
{ text: '. With ' },
{
type: 'link',
children: [{ text: 'Live Preview' }],
newTab: true,
url: 'https://payloadcms.com/docs/live-preview',
},
{
text: ' you can edit this page in the admin panel and see the changes reflected here in real time.',
},
],
},
],
title: 'Home',
}
export const examplePage: Partial<Page> = {
slug: 'example-page',
richText: [
{
type: 'h1',
children: [
{
text: 'Example Page',
},
],
},
{
children: [
{
text: 'This is an example page. You can edit this page in the Admin panel and see the changes reflected here in real time.',
},
],
},
],
title: 'Example Page',
}
export async function up({ payload }: MigrateUpArgs): Promise<void> {
await payload.create({
collection: 'users',
data: {
email: 'demo@payloadcms.com',
password: 'demo',
},
})
const { id: examplePageID } = await payload.create({
collection: 'pages',
data: examplePage as any, // eslint-disable-line
})
const homepageJSON = JSON.parse(JSON.stringify(home))
const { id: homePageID } = await payload.create({
collection: 'pages',
data: homepageJSON,
})
await payload.updateGlobal({
slug: 'main-menu',
data: {
navItems: [
{
link: {
type: 'reference',
label: 'Home',
reference: {
relationTo: 'pages',
value: homePageID,
},
url: '',
},
},
{
link: {
type: 'reference',
label: 'Example Page',
reference: {
relationTo: 'pages',
value: examplePageID,
},
url: '',
},
},
{
link: {
type: 'custom',
label: 'Dashboard',
reference: undefined,
url: 'http://localhost:3000/admin',
},
},
],
},
})
}

View File

@@ -1,6 +1,6 @@
import { webpackBundler } from '@payloadcms/bundler-webpack'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { slateEditor } from '@payloadcms/richtext-slate'
import { fileURLToPath } from 'node:url'
import path from 'path'
import { buildConfig } from 'payload/config'
@@ -9,31 +9,34 @@ import { Users } from './collections/Users'
import BeforeLogin from './components/BeforeLogin'
import { MainMenu } from './globals/MainMenu'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export default buildConfig({
collections: [Pages, Users],
admin: {
bundler: webpackBundler(), // bundler-config
components: {
beforeLogin: [BeforeLogin],
},
livePreview: {
breakpoints: [
{
label: 'Mobile',
name: 'mobile',
width: 375,
height: 667,
label: 'Mobile',
width: 375,
},
],
},
},
serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL,
globals: [MainMenu],
editor: slateEditor({}),
collections: [Pages, Users],
db: mongooseAdapter({
url: process.env.DATABASE_URI,
url: process.env.DATABASE_URI || '',
}),
editor: slateEditor({}),
globals: [MainMenu],
secret: process.env.PAYLOAD_SECRET || '',
serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL,
typescript: {
outputFile: path.resolve(__dirname, 'payload-types.ts'),
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
})

View File

@@ -1,46 +0,0 @@
import type { Page } from '../payload-types'
export const home: Partial<Page> = {
title: 'Home',
slug: 'home',
richText: [
{
type: 'h1',
children: [
{
text: 'Payload Live Preview Example',
},
],
},
{
children: [
{ text: 'This is a ' },
{
type: 'link',
linkType: 'custom',
url: 'https://nextjs.org',
newTab: true,
children: [{ text: 'Next.js' }],
},
{ text: " app made explicitly for Payload's " },
{
type: 'link',
linkType: 'custom',
newTab: true,
url: 'https://github.com/payloadcms/payload/tree/master/examples/live-preview/payload',
children: [{ text: 'Live Preview Example' }],
},
{ text: '. With ' },
{
type: 'link',
newTab: true,
url: 'https://payloadcms.com/docs/live-preview',
children: [{ text: 'Live Preview' }],
},
{
text: ' you can edit this page in the admin panel and see the changes reflected here in real time.',
},
],
},
],
}

View File

@@ -1,64 +0,0 @@
import type { Payload } from 'payload'
import { home } from './home'
import { examplePage } from './page'
export const seed = async (payload: Payload): Promise<void> => {
await payload.create({
collection: 'users',
data: {
email: 'demo@payloadcms.com',
password: 'demo',
},
})
const { id: examplePageID } = await payload.create({
collection: 'pages',
data: examplePage as any, // eslint-disable-line
})
const homepageJSON = JSON.parse(JSON.stringify(home))
const { id: homePageID } = await payload.create({
collection: 'pages',
data: homepageJSON,
})
await payload.updateGlobal({
slug: 'main-menu',
data: {
navItems: [
{
link: {
type: 'reference',
reference: {
relationTo: 'pages',
value: homePageID,
},
label: 'Home',
url: '',
},
},
{
link: {
type: 'reference',
reference: {
relationTo: 'pages',
value: examplePageID,
},
label: 'Example Page',
url: '',
},
},
{
link: {
type: 'custom',
reference: null,
label: 'Dashboard',
url: 'http://localhost:3000/admin',
},
},
],
},
})
}

View File

@@ -1,23 +0,0 @@
import type { Page } from '../payload-types'
export const examplePage: Partial<Page> = {
title: 'Example Page',
slug: 'example-page',
richText: [
{
type: 'h1',
children: [
{
text: 'Example Page',
},
],
},
{
children: [
{
text: 'This is an example page. You can edit this page in the Admin panel and see the changes reflected here in real time.',
},
],
},
],
}

View File

@@ -1,36 +0,0 @@
import dotenv from 'dotenv'
import path from 'path'
dotenv.config({
path: path.resolve(__dirname, '../.env'),
})
import express from 'express'
import payload from 'payload'
import { seed } from './seed'
const app = express()
app.get('/', (_, res) => {
res.redirect('/admin')
})
const start = async (): Promise<void> => {
await payload.init({
secret: process.env.PAYLOAD_SECRET,
express: app,
onInit: () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
},
})
if (process.env.PAYLOAD_PUBLIC_SEED === 'true') {
payload.logger.info('---- SEEDING DATABASE ----')
await seed(payload)
}
app.listen(3000)
}
start()

View File

@@ -1,33 +1,28 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"baseUrl": ".",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src",
"jsx": "react",
"sourceMap": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"node_modules/*": ["./node_modules/*"]
},
"@/*": ["./src/*"],
"@payload-config": ["src/payload.config.ts"]
}
},
"include": [
"src"
],
"exclude": [
"node_modules",
"dist",
"build",
],
"ts-node": {
"transpileOnly": true
}
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,4 +4,5 @@ export { isLivePreviewEvent } from './isLivePreviewEvent.js'
export { mergeData } from './mergeData.js'
export { ready } from './ready.js'
export { subscribe } from './subscribe.js'
export { traverseRichText } from './traverseRichText.js'
export { unsubscribe } from './unsubscribe.js'

View File

@@ -5,4 +5,7 @@ module.exports = {
project: ['./tsconfig.eslint.json'],
tsconfigRootDir: __dirname,
},
rules: {
'no-restricted-exports': ['error', { restrictDefaultExports: { direct: false } }],
},
}

View File

@@ -22,9 +22,6 @@ export const PageClient: React.FC<{
return (
<React.Fragment>
<Gutter>
<div id={renderedPageTitleID}>{data.title}</div>
</Gutter>
<Hero {...data?.hero} />
<Blocks
blocks={[
@@ -39,6 +36,9 @@ export const PageClient: React.FC<{
!data?.hero || data?.hero?.type === 'none' || data?.hero?.type === 'lowImpact'
}
/>
<Gutter>
<div id={renderedPageTitleID}>{`For Testing: ${data.title}`}</div>
</Gutter>
</React.Fragment>
)
}

View File

@@ -3,21 +3,20 @@ import React from 'react'
import type { Page } from '../../../../payload-types.js'
import { fetchDoc } from '../../_api/fetchDoc.js'
import { fetchDocs } from '../../_api/fetchDocs.js'
import { getDoc } from '../../_api/getDoc.js'
import { getDocs } from '../../_api/getDocs.js'
import { PageClient } from './page.client.js'
// eslint-disable-next-line no-restricted-exports
export default async function Page({ params: { slug = 'home' } }) {
let page: Page | null = null
try {
page = await fetchDoc<Page>({
page = await getDoc<Page>({
slug,
collection: 'pages',
})
} catch (error) {
console.error(error)
console.error(error) // eslint-disable-line no-console
}
if (!page) {
@@ -30,7 +29,7 @@ export default async function Page({ params: { slug = 'home' } }) {
export async function generateStaticParams() {
process.env.PAYLOAD_DROP_DATABASE = 'false'
try {
const pages = await fetchDocs<Page>('pages')
const pages = await getDocs<Page>('pages')
return pages?.map(({ slug }) => slug)
} catch (error) {
return []

View File

@@ -1,94 +0,0 @@
/* eslint-disable no-restricted-exports */
import { Gutter } from '@payloadcms/ui/elements/Gutter'
import { notFound } from 'next/navigation.js'
import React, { Fragment } from 'react'
import type { Post } from '../../../../../payload-types.js'
import { ssrPostsSlug } from '../../../../../shared.js'
import { renderedPageTitleID } from '../../../../../shared.js'
import { fetchDoc } from '../../../_api/fetchDoc.js'
import { fetchDocs } from '../../../_api/fetchDocs.js'
import { Blocks } from '../../../_components/Blocks/index.js'
import { PostHero } from '../../../_heros/PostHero/index.js'
import { RefreshRouteOnSave } from './RefreshRouteOnSave.js'
export default async function SSRPost({ params: { slug = '' } }) {
let data: Post | null = null
try {
data = await fetchDoc<Post>({
slug,
collection: ssrPostsSlug,
draft: true,
})
} catch (error) {
console.error(error) // eslint-disable-line no-console
}
if (!data) {
notFound()
}
return (
<Fragment>
<RefreshRouteOnSave />
<Gutter>
<div id={renderedPageTitleID}>{data.title}</div>
</Gutter>
<PostHero post={data} />
<Blocks blocks={data?.layout} />
<Blocks
blocks={[
{
blockName: 'Related Posts',
blockType: 'relatedPosts',
docs: data?.relatedPosts,
introContent: [
{
type: 'h4',
children: [
{
text: 'Related posts',
},
],
},
{
type: 'p',
children: [
{
text: 'The posts displayed here are individually selected for this page. Admins can select any number of related posts to display here and the layout will adjust accordingly. Alternatively, you could swap this out for the "Archive" block to automatically populate posts by category complete with pagination. To manage related posts, ',
},
{
type: 'link',
children: [
{
text: 'navigate to the admin dashboard',
},
],
url: `/admin/collections/ssr/${data?.id}`,
},
{
text: '.',
},
],
},
],
relationTo: 'posts',
},
]}
disableTopPadding
/>
</Fragment>
)
}
export async function generateStaticParams() {
process.env.PAYLOAD_DROP_DATABASE = 'false'
try {
const ssrPosts = await fetchDocs<Post>(ssrPostsSlug)
return ssrPosts?.map(({ slug }) => slug)
} catch (error) {
return []
}
}

View File

@@ -6,7 +6,7 @@ import React from 'react'
import type { Post as PostType } from '../../../../../payload-types.js'
import { renderedPageTitleID } from '../../../../../shared.js'
import { postsSlug, renderedPageTitleID } from '../../../../../shared.js'
import { PAYLOAD_SERVER_URL } from '../../../_api/serverURL.js'
import { Blocks } from '../../../_components/Blocks/index.js'
import { PostHero } from '../../../_heros/PostHero/index.js'
@@ -22,9 +22,6 @@ export const PostClient: React.FC<{
return (
<React.Fragment>
<Gutter>
<div id={renderedPageTitleID}>{data.title}</div>
</Gutter>
<PostHero post={data} />
<Blocks blocks={data?.layout} />
<Blocks
@@ -63,11 +60,14 @@ export const PostClient: React.FC<{
],
},
],
relationTo: 'posts',
relationTo: postsSlug,
},
]}
disableTopPadding
/>
<Gutter>
<div id={renderedPageTitleID}>{`For Testing: ${data.title}`}</div>
</Gutter>
</React.Fragment>
)
}

View File

@@ -1,19 +1,18 @@
/* eslint-disable no-restricted-exports */
import { notFound } from 'next/navigation.js'
import React from 'react'
import type { Post } from '../../../../../payload-types.js'
import { postsSlug } from '../../../../../collections/Posts.js'
import { fetchDoc } from '../../../_api/fetchDoc.js'
import { fetchDocs } from '../../../_api/fetchDocs.js'
import { postsSlug } from '../../../../../shared.js'
import { getDoc } from '../../../_api/getDoc.js'
import { getDocs } from '../../../_api/getDocs.js'
import { PostClient } from './page.client.js'
export default async function Post({ params: { slug = '' } }) {
let post: Post | null = null
try {
post = await fetchDoc<Post>({
post = await getDoc<Post>({
slug,
collection: postsSlug,
})
@@ -31,7 +30,7 @@ export default async function Post({ params: { slug = '' } }) {
export async function generateStaticParams() {
process.env.PAYLOAD_DROP_DATABASE = 'false'
try {
const ssrPosts = await fetchDocs<Post>(postsSlug)
const ssrPosts = await getDocs<Post>(postsSlug)
return ssrPosts?.map(({ slug }) => slug)
} catch (error) {
return []

View File

@@ -0,0 +1,45 @@
import { Gutter } from '@payloadcms/ui/elements/Gutter'
import { notFound } from 'next/navigation.js'
import React, { Fragment } from 'react'
import type { Page } from '../../../../../payload-types.js'
import { renderedPageTitleID, ssrPagesSlug } from '../../../../../shared.js'
import { getDoc } from '../../../_api/getDoc.js'
import { getDocs } from '../../../_api/getDocs.js'
import { Blocks } from '../../../_components/Blocks/index.js'
import { Hero } from '../../../_components/Hero/index.js'
import { RefreshRouteOnSave } from './RefreshRouteOnSave.js'
export default async function SSRPage({ params: { slug = '' } }) {
const data = await getDoc<Page>({
slug,
collection: ssrPagesSlug,
draft: true,
})
if (!data) {
notFound()
}
return (
<Fragment>
<RefreshRouteOnSave />
<Hero {...data?.hero} />
<Blocks blocks={data?.layout} />
<Gutter>
<div id={renderedPageTitleID}>{`For Testing: ${data.title}`}</div>
</Gutter>
</Fragment>
)
}
export async function generateStaticParams() {
process.env.PAYLOAD_DROP_DATABASE = 'false'
try {
const ssrPages = await getDocs<Page>(ssrPagesSlug)
return ssrPages?.map(({ slug }) => slug)
} catch (error) {
return []
}
}

View File

@@ -3,7 +3,7 @@ import type { Where } from 'payload/types'
import config from '@payload-config'
import { getPayloadHMR } from '@payloadcms/next/utilities/getPayloadHMR.js'
export const fetchDoc = async <T>(args: {
export const getDoc = async <T>(args: {
collection: string
depth?: number
draft?: boolean
@@ -30,8 +30,8 @@ export const fetchDoc = async <T>(args: {
if (docs[0]) return docs[0] as T
} catch (err) {
console.log('Error fetching doc', err)
console.log('Error getting doc', err)
}
throw new Error('Error fetching doc')
throw new Error('Error getting doc')
}

View File

@@ -1,7 +1,7 @@
import config from '@payload-config'
import { getPayloadHMR } from '@payloadcms/next/utilities/getPayloadHMR.js'
export const fetchDocs = async <T>(collection: string): Promise<T[]> => {
export const getDocs = async <T>(collection: string): Promise<T[]> => {
const payload = await getPayloadHMR({ config })
try {
@@ -16,5 +16,5 @@ export const fetchDocs = async <T>(collection: string): Promise<T[]> => {
console.error(err)
}
throw new Error('Error fetching docs')
throw new Error('Error getting docs')
}

View File

@@ -3,7 +3,7 @@ import { getPayloadHMR } from '@payloadcms/next/utilities/getPayloadHMR.js'
import type { Footer } from '../../../payload-types.js'
export async function fetchFooter(): Promise<Footer> {
export async function getFooter(): Promise<Footer> {
const payload = await getPayloadHMR({ config })
try {
@@ -16,5 +16,5 @@ export async function fetchFooter(): Promise<Footer> {
console.error(err)
}
throw new Error('Error fetching footer.')
throw new Error('Error getting footer.')
}

View File

@@ -3,7 +3,7 @@ import { getPayloadHMR } from '@payloadcms/next/utilities/getPayloadHMR.js'
import type { Header } from '../../../payload-types.js'
export async function fetchHeader(): Promise<Header> {
export async function getHeader(): Promise<Header> {
const payload = await getPayloadHMR({ config })
try {
@@ -16,5 +16,5 @@ export async function fetchHeader(): Promise<Header> {
console.error(err)
}
throw new Error('Error fetching header.')
throw new Error('Error getting header.')
}

View File

@@ -1,7 +1,7 @@
import LinkWithDefault from 'next/link.js'
import React from 'react'
import { fetchFooter } from '../../_api/fetchFooter.js'
import { getFooter } from '../../_api/getFooter.js'
import { Gutter } from '../Gutter/index.js'
import { CMSLink } from '../Link/index.js'
import classes from './index.module.scss'
@@ -9,7 +9,7 @@ import classes from './index.module.scss'
const Link = (LinkWithDefault.default || LinkWithDefault) as typeof LinkWithDefault.default
export async function Footer() {
const footer = await fetchFooter()
const footer = await getFooter()
const navItems = footer?.navItems || []

View File

@@ -1,7 +1,7 @@
import LinkWithDefault from 'next/link.js'
import React from 'react'
import { fetchHeader } from '../../_api/fetchHeader.js'
import { getHeader } from '../../_api/getHeader.js'
import { Gutter } from '../Gutter/index.js'
import { HeaderNav } from './Nav/index.js'
import classes from './index.module.scss'
@@ -9,7 +9,7 @@ import classes from './index.module.scss'
const Link = (LinkWithDefault.default || LinkWithDefault) as typeof LinkWithDefault.default
export async function Header() {
const header = await fetchHeader()
const header = await getHeader()
return (
<header className={classes.header}>

View File

@@ -9,7 +9,7 @@ import { Content } from '../blocks/Content/index.js'
import { MediaBlock } from '../blocks/MediaBlock/index.js'
import CollectionLivePreviewButton from '../components/CollectionLivePreviewButton/index.js'
import { hero } from '../fields/hero.js'
import { pagesSlug, tenantsSlug } from '../shared.js'
import { pagesSlug, postsSlug, tenantsSlug } from '../shared.js'
export const Pages: CollectionConfig = {
slug: pagesSlug,
@@ -94,23 +94,23 @@ export const Pages: CollectionConfig = {
{
name: 'relationshipMonoHasOne',
type: 'relationship',
relationTo: 'posts',
relationTo: postsSlug,
},
{
name: 'relationshipMonoHasMany',
type: 'relationship',
relationTo: 'posts',
relationTo: postsSlug,
hasMany: true,
},
{
name: 'relationshipPolyHasOne',
type: 'relationship',
relationTo: ['posts'],
relationTo: [postsSlug],
},
{
name: 'relationshipPolyHasMany',
type: 'relationship',
relationTo: ['posts'],
relationTo: [postsSlug],
hasMany: true,
},
{
@@ -129,23 +129,23 @@ export const Pages: CollectionConfig = {
{
name: 'relationshipInArrayMonoHasOne',
type: 'relationship',
relationTo: 'posts',
relationTo: postsSlug,
},
{
name: 'relationshipInArrayMonoHasMany',
type: 'relationship',
relationTo: 'posts',
relationTo: postsSlug,
hasMany: true,
},
{
name: 'relationshipInArrayPolyHasOne',
type: 'relationship',
relationTo: ['posts'],
relationTo: [postsSlug],
},
{
name: 'relationshipInArrayPolyHasMany',
type: 'relationship',
relationTo: ['posts'],
relationTo: [postsSlug],
hasMany: true,
},
],
@@ -161,7 +161,7 @@ export const Pages: CollectionConfig = {
{
name: 'relationshipInTab',
type: 'relationship',
relationTo: 'posts',
relationTo: postsSlug,
},
],
},

View File

@@ -5,9 +5,7 @@ import { CallToAction } from '../blocks/CallToAction/index.js'
import { Content } from '../blocks/Content/index.js'
import { MediaBlock } from '../blocks/MediaBlock/index.js'
import { hero } from '../fields/hero.js'
import { tenantsSlug } from '../shared.js'
export const postsSlug = 'posts'
import { postsSlug, tenantsSlug } from '../shared.js'
export const Posts: CollectionConfig = {
slug: postsSlug,
@@ -61,7 +59,7 @@ export const Posts: CollectionConfig = {
{
name: 'relatedPosts',
type: 'relationship',
relationTo: 'posts',
relationTo: postsSlug,
hasMany: true,
filterOptions: ({ id }) => {
return {

View File

@@ -5,13 +5,13 @@ import { CallToAction } from '../blocks/CallToAction/index.js'
import { Content } from '../blocks/Content/index.js'
import { MediaBlock } from '../blocks/MediaBlock/index.js'
import { hero } from '../fields/hero.js'
import { ssrPostsSlug, tenantsSlug } from '../shared.js'
import { ssrPagesSlug, tenantsSlug } from '../shared.js'
export const PostsSSR: CollectionConfig = {
slug: ssrPostsSlug,
export const SSR: CollectionConfig = {
slug: ssrPagesSlug,
labels: {
singular: 'SSR Post',
plural: 'SSR Posts',
singular: 'SSR Page',
plural: 'SSR Pages',
},
access: {
read: () => true,
@@ -22,7 +22,7 @@ export const PostsSSR: CollectionConfig = {
versions: {
drafts: {
autosave: {
interval: 10,
interval: 375,
},
},
},
@@ -67,19 +67,6 @@ export const PostsSSR: CollectionConfig = {
type: 'blocks',
blocks: [CallToAction, Content, MediaBlock, Archive],
},
{
name: 'relatedPosts',
type: 'relationship',
relationTo: 'posts',
hasMany: true,
filterOptions: ({ id }) => {
return {
id: {
not_in: [id],
},
}
},
},
],
},
],

View File

@@ -2,14 +2,14 @@ import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import Categories from './collections/Categories.js'
import { Media } from './collections/Media.js'
import { Pages } from './collections/Pages.js'
import { Posts, postsSlug } from './collections/Posts.js'
import { PostsSSR } from './collections/PostsSSR.js'
import { Posts } from './collections/Posts.js'
import { SSR } from './collections/SSR.js'
import { Tenants } from './collections/Tenants.js'
import { Users } from './collections/Users.js'
import { Footer } from './globals/Footer.js'
import { Header } from './globals/Header.js'
import { seed } from './seed/index.js'
import { mobileBreakpoint, pagesSlug, ssrPostsSlug } from './shared.js'
import { mobileBreakpoint, pagesSlug, postsSlug, ssrPagesSlug } from './shared.js'
import { formatLivePreviewURL } from './utilities/formatLivePreviewURL.js'
export default buildConfigWithDefaults({
@@ -19,13 +19,13 @@ export default buildConfigWithDefaults({
// The Live Preview config cascades from the top down, properties are inherited from here
url: formatLivePreviewURL,
breakpoints: [mobileBreakpoint],
collections: [pagesSlug, postsSlug, ssrPostsSlug],
collections: [pagesSlug, postsSlug, ssrPagesSlug],
globals: ['header', 'footer'],
},
},
cors: ['http://localhost:3000', 'http://localhost:3001'],
csrf: ['http://localhost:3000', 'http://localhost:3001'],
collections: [Users, Pages, Posts, PostsSSR, Tenants, Categories, Media],
collections: [Users, Pages, Posts, SSR, Tenants, Categories, Media],
globals: [Header, Footer],
onInit: seed,
})

View File

@@ -14,7 +14,7 @@ import {
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
import { mobileBreakpoint, pagesSlug, renderedPageTitleID, ssrPostsSlug } from './shared.js'
import { mobileBreakpoint, pagesSlug, renderedPageTitleID, ssrPagesSlug } from './shared.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
@@ -51,7 +51,7 @@ describe('Live Preview', () => {
;({ serverURL } = await initPayloadE2ENoConfig({ dirname }))
pagesURLUtil = new AdminUrlUtil(serverURL, pagesSlug)
ssrPostsURLUtil = new AdminUrlUtil(serverURL, ssrPostsSlug)
ssrPostsURLUtil = new AdminUrlUtil(serverURL, ssrPagesSlug)
const context = await browser.newContext()
page = await context.newPage()
@@ -105,14 +105,14 @@ describe('Live Preview', () => {
timeout: POLL_TOPASS_TIMEOUT,
})
await expect(frame.locator(renderedPageTitleLocator)).toHaveText('Home')
await expect(frame.locator(renderedPageTitleLocator)).toHaveText('For Testing: Home')
const newTitleValue = 'Home (Edited)'
await titleField.fill(newTitleValue)
await expect(() =>
expect(frame.locator(renderedPageTitleLocator)).toHaveText(newTitleValue),
expect(frame.locator(renderedPageTitleLocator)).toHaveText(`For Testing: ${newTitleValue}`),
).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
@@ -135,14 +135,14 @@ describe('Live Preview', () => {
timeout: POLL_TOPASS_TIMEOUT,
})
await expect(frame.locator(renderedPageTitleLocator)).toHaveText('SSR Post 1')
await expect(frame.locator(renderedPageTitleLocator)).toHaveText('For Testing: SSR Home')
const newTitleValue = 'SSR Post 1 (Edited)'
const newTitleValue = 'SSR Home (Edited)'
await titleField.fill(newTitleValue)
await expect(() =>
expect(frame.locator(renderedPageTitleLocator)).toHaveText(newTitleValue),
expect(frame.locator(renderedPageTitleLocator)).toHaveText(`For Testing: ${newTitleValue}`),
).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})

View File

@@ -1,5 +1,6 @@
import type { Field } from 'payload/types'
import { pagesSlug, postsSlug } from '../shared.js'
import deepMerge from '../utilities/deepMerge.js'
export const appearanceOptions = {
@@ -76,7 +77,7 @@ const link: LinkType = ({ appearances, disableLabel = false, overrides = {} } =
name: 'reference',
label: 'Document to link to',
type: 'relationship',
relationTo: ['posts', 'pages'],
relationTo: [postsSlug, pagesSlug],
required: true,
maxDepth: 1,
admin: {

View File

@@ -1,5 +1,6 @@
import type { Payload } from 'payload'
import { handleMessage, mergeData, traverseRichText } from '@payloadcms/live-preview'
import path from 'path'
import { getFileByPath } from 'payload/uploads'
import { fileURLToPath } from 'url'
@@ -7,13 +8,9 @@ import { fileURLToPath } from 'url'
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
import type { Media, Page, Post, Tenant } from './payload-types.js'
import { handleMessage } from '../../packages/live-preview/src/handleMessage.js'
import { mergeData } from '../../packages/live-preview/src/mergeData.js'
import { traverseRichText } from '../../packages/live-preview/src/traverseRichText.js'
import { Pages } from './collections/Pages.js'
import { postsSlug } from './collections/Posts.js'
import configPromise from './config.js'
import { tenantsSlug } from './shared.js'
import { postsSlug, tenantsSlug } from './shared.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
@@ -650,7 +647,7 @@ describe('Collections - Live Preview', () => {
text: ' ',
},
],
relationTo: 'posts',
relationTo: postsSlug,
type: 'relationship',
value: {
id: testPost.id,
@@ -705,7 +702,7 @@ describe('Collections - Live Preview', () => {
text: ' ',
},
],
relationTo: 'posts',
relationTo: postsSlug,
type: 'relationship',
value: {
id: testPost.id,
@@ -779,7 +776,7 @@ describe('Collections - Live Preview', () => {
format: '',
type: 'relationship',
version: 1,
relationTo: 'posts',
relationTo: postsSlug,
value: {
id: testPost.id,
},
@@ -838,7 +835,7 @@ describe('Collections - Live Preview', () => {
format: '',
type: 'relationship',
version: 1,
relationTo: 'posts',
relationTo: postsSlug,
value: {
id: testPost.id,
},
@@ -901,7 +898,7 @@ describe('Collections - Live Preview', () => {
{
type: 'reference',
reference: {
relationTo: 'posts',
relationTo: postsSlug,
value: testPost,
},
},
@@ -922,7 +919,7 @@ describe('Collections - Live Preview', () => {
{
type: 'reference',
reference: {
relationTo: 'posts',
relationTo: postsSlug,
value: testPost.id,
},
},
@@ -950,7 +947,7 @@ describe('Collections - Live Preview', () => {
label: 'Link 1',
type: 'reference',
reference: {
relationTo: 'posts',
relationTo: postsSlug,
value: shallow ? testPost?.id : testPost,
},
},

View File

@@ -8,452 +8,635 @@
export interface Config {
collections: {
users: User
pages: Page
posts: Post
tenants: Tenant
categories: Category
media: Media
'payload-preferences': PayloadPreference
'payload-migrations': PayloadMigration
}
users: User;
pages: Page;
posts: Post;
ssr: Ssr;
tenants: Tenant;
categories: Category;
media: Media;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
globals: {
header: Header
footer: Footer
}
header: Header;
footer: Footer;
};
locale: null;
user: User & {
collection: 'users';
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string
updatedAt: string
createdAt: string
email: string
resetPasswordToken?: string | null
resetPasswordExpiration?: string | null
salt?: string | null
hash?: string | null
loginAttempts?: number | null
lockUntil?: string | null
password: string | null
id: string;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "pages".
*/
export interface Page {
id: string
slug: string
tenant?: (string | null) | Tenant
title: string
id: string;
slug: string;
tenant?: (string | null) | Tenant;
title: string;
hero: {
type: 'none' | 'highImpact' | 'lowImpact'
type: 'none' | 'highImpact' | 'lowImpact';
richText?:
| {
[k: string]: unknown
[k: string]: unknown;
}[]
| null
media?: string | Media | null
}
| null;
media?: string | Media | null;
};
layout?:
| (
| {
invertBackground?: boolean | null
invertBackground?: boolean | null;
richText?:
| {
[k: string]: unknown
[k: string]: unknown;
}[]
| null
| null;
links?:
| {
link: {
type?: ('reference' | 'custom') | null
newTab?: boolean | null
type?: ('reference' | 'custom') | null;
newTab?: boolean | null;
reference?:
| ({
relationTo: 'posts'
value: string | Post
relationTo: 'posts';
value: string | Post;
} | null)
| ({
relationTo: 'pages'
value: string | Page
} | null)
url?: string | null
label: string
appearance?: ('primary' | 'secondary') | null
}
id?: string | null
relationTo: 'pages';
value: string | Page;
} | null);
url?: string | null;
label: string;
appearance?: ('primary' | 'secondary') | null;
};
id?: string | null;
}[]
| null
id?: string | null
blockName?: string | null
blockType: 'cta'
| null;
id?: string | null;
blockName?: string | null;
blockType: 'cta';
}
| {
invertBackground?: boolean | null
invertBackground?: boolean | null;
columns?:
| {
size?: ('oneThird' | 'half' | 'twoThirds' | 'full') | null
size?: ('oneThird' | 'half' | 'twoThirds' | 'full') | null;
richText?:
| {
[k: string]: unknown
[k: string]: unknown;
}[]
| null
enableLink?: boolean | null
| null;
enableLink?: boolean | null;
link?: {
type?: ('reference' | 'custom') | null
newTab?: boolean | null
type?: ('reference' | 'custom') | null;
newTab?: boolean | null;
reference?:
| ({
relationTo: 'posts'
value: string | Post
relationTo: 'posts';
value: string | Post;
} | null)
| ({
relationTo: 'pages'
value: string | Page
} | null)
url?: string | null
label: string
appearance?: ('default' | 'primary' | 'secondary') | null
}
id?: string | null
relationTo: 'pages';
value: string | Page;
} | null);
url?: string | null;
label: string;
appearance?: ('default' | 'primary' | 'secondary') | null;
};
id?: string | null;
}[]
| null
id?: string | null
blockName?: string | null
blockType: 'content'
| null;
id?: string | null;
blockName?: string | null;
blockType: 'content';
}
| {
invertBackground?: boolean | null
position?: ('default' | 'fullscreen') | null
media: string | Media
id?: string | null
blockName?: string | null
blockType: 'mediaBlock'
invertBackground?: boolean | null;
position?: ('default' | 'fullscreen') | null;
media: string | Media;
id?: string | null;
blockName?: string | null;
blockType: 'mediaBlock';
}
| {
introContent?:
| {
[k: string]: unknown
[k: string]: unknown;
}[]
| null
populateBy?: ('collection' | 'selection') | null
relationTo?: 'posts' | null
categories?: (string | Category)[] | null
limit?: number | null
| null;
populateBy?: ('collection' | 'selection') | null;
relationTo?: 'posts' | null;
categories?: (string | Category)[] | null;
limit?: number | null;
selectedDocs?:
| {
relationTo: 'posts'
value: string | Post
relationTo: 'posts';
value: string | Post;
}[]
| null
| null;
populatedDocs?:
| {
relationTo: 'posts'
value: string | Post
relationTo: 'posts';
value: string | Post;
}[]
| null
populatedDocsTotal?: number | null
id?: string | null
blockName?: string | null
blockType: 'archive'
| null;
populatedDocsTotal?: number | null;
id?: string | null;
blockName?: string | null;
blockType: 'archive';
}
)[]
| null
relationshipAsUpload?: string | Media | null
relationshipMonoHasOne?: (string | null) | Post
relationshipMonoHasMany?: (string | Post)[] | null
relationshipPolyHasOne?: {
relationTo: 'posts'
value: string | Post
} | null
relationshipPolyHasMany?:
| {
relationTo: 'posts'
value: string | Post
}[]
| null
arrayOfRelationships?:
| {
uploadInArray?: string | Media | null
richTextInArray?:
| {
[k: string]: unknown
}[]
| null
relationshipInArrayMonoHasOne?: (string | null) | Post
relationshipInArrayMonoHasMany?: (string | Post)[] | null
relationshipInArrayPolyHasOne?: {
relationTo: 'posts'
value: string | Post
} | null
relationshipInArrayPolyHasMany?:
| {
relationTo: 'posts'
value: string | Post
}[]
| null
id?: string | null
}[]
| null
| null;
richTextSlate?:
| {
[k: string]: unknown
[k: string]: unknown;
}[]
| null
| null;
richTextLexical?: {
root: {
type: string;
children: {
type: string
version: number
[k: string]: unknown
}[]
direction: ('ltr' | 'rtl') | null
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''
indent: number
type: string
version: number
}
[k: string]: unknown
} | null
tab: {
relationshipInTab?: (string | null) | Post
}
meta?: {
title?: string | null
description?: string | null
image?: string | Media | null
}
updatedAt: string
createdAt: string
}
export interface Tenant {
id: string
title: string
clientURL: string
updatedAt: string
createdAt: string
}
export interface Media {
id: string
alt: string
caption?:
type: string;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
relationshipAsUpload?: string | Media | null;
relationshipMonoHasOne?: (string | null) | Post;
relationshipMonoHasMany?: (string | Post)[] | null;
relationshipPolyHasOne?: {
relationTo: 'posts';
value: string | Post;
} | null;
relationshipPolyHasMany?:
| {
[k: string]: unknown
relationTo: 'posts';
value: string | Post;
}[]
| null
updatedAt: string
createdAt: string
url?: string | null
filename?: string | null
mimeType?: string | null
filesize?: number | null
width?: number | null
height?: number | null
| null;
arrayOfRelationships?:
| {
uploadInArray?: string | Media | null;
richTextInArray?: {
root: {
type: string;
children: {
type: string;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
relationshipInArrayMonoHasOne?: (string | null) | Post;
relationshipInArrayMonoHasMany?: (string | Post)[] | null;
relationshipInArrayPolyHasOne?: {
relationTo: 'posts';
value: string | Post;
} | null;
relationshipInArrayPolyHasMany?:
| {
relationTo: 'posts';
value: string | Post;
}[]
| null;
id?: string | null;
}[]
| null;
tab: {
relationshipInTab?: (string | null) | Post;
};
meta?: {
title?: string | null;
description?: string | null;
image?: string | Media | null;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "tenants".
*/
export interface Tenant {
id: string;
title: string;
clientURL: string;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "media".
*/
export interface Media {
id: string;
alt: string;
updatedAt: string;
createdAt: string;
url?: string | null;
thumbnailURL?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "posts".
*/
export interface Post {
id: string
slug: string
tenant?: (string | null) | Tenant
title: string
id: string;
slug: string;
tenant?: (string | null) | Tenant;
title: string;
hero: {
type: 'none' | 'highImpact' | 'lowImpact'
type: 'none' | 'highImpact' | 'lowImpact';
richText?:
| {
[k: string]: unknown
[k: string]: unknown;
}[]
| null
media?: string | Media | null
}
| null;
media?: string | Media | null;
};
layout?:
| (
| {
invertBackground?: boolean | null
invertBackground?: boolean | null;
richText?:
| {
[k: string]: unknown
[k: string]: unknown;
}[]
| null
| null;
links?:
| {
link: {
type?: ('reference' | 'custom') | null
newTab?: boolean | null
type?: ('reference' | 'custom') | null;
newTab?: boolean | null;
reference?:
| ({
relationTo: 'posts'
value: string | Post
relationTo: 'posts';
value: string | Post;
} | null)
| ({
relationTo: 'pages'
value: string | Page
} | null)
url?: string | null
label: string
appearance?: ('primary' | 'secondary') | null
}
id?: string | null
relationTo: 'pages';
value: string | Page;
} | null);
url?: string | null;
label: string;
appearance?: ('primary' | 'secondary') | null;
};
id?: string | null;
}[]
| null
id?: string | null
blockName?: string | null
blockType: 'cta'
| null;
id?: string | null;
blockName?: string | null;
blockType: 'cta';
}
| {
invertBackground?: boolean | null
invertBackground?: boolean | null;
columns?:
| {
size?: ('oneThird' | 'half' | 'twoThirds' | 'full') | null
size?: ('oneThird' | 'half' | 'twoThirds' | 'full') | null;
richText?:
| {
[k: string]: unknown
[k: string]: unknown;
}[]
| null
enableLink?: boolean | null
| null;
enableLink?: boolean | null;
link?: {
type?: ('reference' | 'custom') | null
newTab?: boolean | null
type?: ('reference' | 'custom') | null;
newTab?: boolean | null;
reference?:
| ({
relationTo: 'posts'
value: string | Post
relationTo: 'posts';
value: string | Post;
} | null)
| ({
relationTo: 'pages'
value: string | Page
} | null)
url?: string | null
label: string
appearance?: ('default' | 'primary' | 'secondary') | null
}
id?: string | null
relationTo: 'pages';
value: string | Page;
} | null);
url?: string | null;
label: string;
appearance?: ('default' | 'primary' | 'secondary') | null;
};
id?: string | null;
}[]
| null
id?: string | null
blockName?: string | null
blockType: 'content'
| null;
id?: string | null;
blockName?: string | null;
blockType: 'content';
}
| {
invertBackground?: boolean | null
position?: ('default' | 'fullscreen') | null
media: string | Media
id?: string | null
blockName?: string | null
blockType: 'mediaBlock'
invertBackground?: boolean | null;
position?: ('default' | 'fullscreen') | null;
media: string | Media;
id?: string | null;
blockName?: string | null;
blockType: 'mediaBlock';
}
| {
introContent?:
| {
[k: string]: unknown
[k: string]: unknown;
}[]
| null
populateBy?: ('collection' | 'selection') | null
relationTo?: 'posts' | null
categories?: (string | Category)[] | null
limit?: number | null
| null;
populateBy?: ('collection' | 'selection') | null;
relationTo?: 'posts' | null;
categories?: (string | Category)[] | null;
limit?: number | null;
selectedDocs?:
| {
relationTo: 'posts'
value: string | Post
relationTo: 'posts';
value: string | Post;
}[]
| null
| null;
populatedDocs?:
| {
relationTo: 'posts'
value: string | Post
relationTo: 'posts';
value: string | Post;
}[]
| null
populatedDocsTotal?: number | null
id?: string | null
blockName?: string | null
blockType: 'archive'
| null;
populatedDocsTotal?: number | null;
id?: string | null;
blockName?: string | null;
blockType: 'archive';
}
)[]
| null
relatedPosts?: (string | Post)[] | null
| null;
relatedPosts?: (string | Post)[] | null;
meta?: {
title?: string | null
description?: string | null
image?: string | Media | null
}
updatedAt: string
createdAt: string
title?: string | null;
description?: string | null;
image?: string | Media | null;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "categories".
*/
export interface Category {
id: string
title?: string | null
updatedAt: string
createdAt: string
id: string;
title?: string | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "ssr".
*/
export interface Ssr {
id: string;
slug: string;
tenant?: (string | null) | Tenant;
title: string;
hero: {
type: 'none' | 'highImpact' | 'lowImpact';
richText?:
| {
[k: string]: unknown;
}[]
| null;
media?: string | Media | null;
};
layout?:
| (
| {
invertBackground?: boolean | null;
richText?:
| {
[k: string]: unknown;
}[]
| null;
links?:
| {
link: {
type?: ('reference' | 'custom') | null;
newTab?: boolean | null;
reference?:
| ({
relationTo: 'posts';
value: string | Post;
} | null)
| ({
relationTo: 'pages';
value: string | Page;
} | null);
url?: string | null;
label: string;
appearance?: ('primary' | 'secondary') | null;
};
id?: string | null;
}[]
| null;
id?: string | null;
blockName?: string | null;
blockType: 'cta';
}
| {
invertBackground?: boolean | null;
columns?:
| {
size?: ('oneThird' | 'half' | 'twoThirds' | 'full') | null;
richText?:
| {
[k: string]: unknown;
}[]
| null;
enableLink?: boolean | null;
link?: {
type?: ('reference' | 'custom') | null;
newTab?: boolean | null;
reference?:
| ({
relationTo: 'posts';
value: string | Post;
} | null)
| ({
relationTo: 'pages';
value: string | Page;
} | null);
url?: string | null;
label: string;
appearance?: ('default' | 'primary' | 'secondary') | null;
};
id?: string | null;
}[]
| null;
id?: string | null;
blockName?: string | null;
blockType: 'content';
}
| {
invertBackground?: boolean | null;
position?: ('default' | 'fullscreen') | null;
media: string | Media;
id?: string | null;
blockName?: string | null;
blockType: 'mediaBlock';
}
| {
introContent?:
| {
[k: string]: unknown;
}[]
| null;
populateBy?: ('collection' | 'selection') | null;
relationTo?: 'posts' | null;
categories?: (string | Category)[] | null;
limit?: number | null;
selectedDocs?:
| {
relationTo: 'posts';
value: string | Post;
}[]
| null;
populatedDocs?:
| {
relationTo: 'posts';
value: string | Post;
}[]
| null;
populatedDocsTotal?: number | null;
id?: string | null;
blockName?: string | null;
blockType: 'archive';
}
)[]
| null;
meta?: {
title?: string | null;
description?: string | null;
image?: string | Media | null;
};
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: string
id: string;
user: {
relationTo: 'users'
value: string | User
}
key?: string | null
relationTo: 'users';
value: string | User;
};
key?: string | null;
value?:
| {
[k: string]: unknown
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null
updatedAt: string
createdAt: string
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: string
name?: string | null
batch?: number | null
updatedAt: string
createdAt: string
id: string;
name?: string | null;
batch?: number | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "header".
*/
export interface Header {
id: string
id: string;
navItems?:
| {
link: {
type?: ('reference' | 'custom') | null
newTab?: boolean | null
type?: ('reference' | 'custom') | null;
newTab?: boolean | null;
reference?:
| ({
relationTo: 'posts'
value: string | Post
relationTo: 'posts';
value: string | Post;
} | null)
| ({
relationTo: 'pages'
value: string | Page
} | null)
url?: string | null
label: string
appearance?: ('default' | 'primary' | 'secondary') | null
}
id?: string | null
relationTo: 'pages';
value: string | Page;
} | null);
url?: string | null;
label: string;
appearance?: ('default' | 'primary' | 'secondary') | null;
};
id?: string | null;
}[]
| null
updatedAt?: string | null
createdAt?: string | null
| null;
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "footer".
*/
export interface Footer {
id: string
id: string;
navItems?:
| {
link: {
type?: ('reference' | 'custom') | null
newTab?: boolean | null
type?: ('reference' | 'custom') | null;
newTab?: boolean | null;
reference?:
| ({
relationTo: 'posts'
value: string | Post
relationTo: 'posts';
value: string | Post;
} | null)
| ({
relationTo: 'pages'
value: string | Page
} | null)
url?: string | null
label: string
appearance?: ('default' | 'primary' | 'secondary') | null
}
id?: string | null
relationTo: 'pages';
value: string | Page;
} | null);
url?: string | null;
label: string;
appearance?: ('default' | 'primary' | 'secondary') | null;
};
id?: string | null;
}[]
| null
updatedAt?: string | null
createdAt?: string | null
| null;
updatedAt?: string | null;
createdAt?: string | null;
}

View File

@@ -1,5 +1,7 @@
import type { Page } from '../payload-types.js'
import { postsSlug } from '../shared.js'
export const home: Omit<Page, 'createdAt' | 'id' | 'updatedAt'> = {
slug: 'home',
title: 'Home',
@@ -31,15 +33,15 @@ export const home: Omit<Page, 'createdAt' | 'id' | 'updatedAt'> = {
populateBy: 'selection',
selectedDocs: [
{
relationTo: 'posts',
relationTo: postsSlug,
value: '{{POST_1_ID}}',
},
{
relationTo: 'posts',
relationTo: postsSlug,
value: '{{POST_2_ID}}',
},
{
relationTo: 'posts',
relationTo: postsSlug,
value: '{{POST_3_ID}}',
},
],
@@ -102,7 +104,7 @@ export const home: Omit<Page, 'createdAt' | 'id' | 'updatedAt'> = {
text: ' ',
},
],
relationTo: 'posts',
relationTo: postsSlug,
type: 'relationship',
value: {
id: '{{POST_1_ID}}',
@@ -140,7 +142,7 @@ export const home: Omit<Page, 'createdAt' | 'id' | 'updatedAt'> = {
format: '',
type: 'relationship',
version: 1,
relationTo: 'posts',
relationTo: postsSlug,
value: {
id: '{{POST_1_ID}}',
},
@@ -181,7 +183,7 @@ export const home: Omit<Page, 'createdAt' | 'id' | 'updatedAt'> = {
text: ' ',
},
],
relationTo: 'posts',
relationTo: postsSlug,
type: 'relationship',
value: {
id: '{{POST_1_ID}}',

View File

@@ -5,8 +5,7 @@ import { fileURLToPath } from 'url'
import { devUser } from '../../credentials.js'
import removeFiles from '../../helpers/removeFiles.js'
import { postsSlug } from '../collections/Posts.js'
import { pagesSlug, ssrPostsSlug, tenantsSlug } from '../shared.js'
import { pagesSlug, postsSlug, ssrPagesSlug, tenantsSlug } from '../shared.js'
import { footer } from './footer.js'
import { header } from './header.js'
import { home } from './home.js'
@@ -61,22 +60,6 @@ export const seed: Config['onInit'] = async (payload) => {
),
})
await payload.create({
collection: ssrPostsSlug,
data: {
...JSON.parse(
JSON.stringify(post1)
.replace(/"\{\{IMAGE\}\}"/g, mediaID)
.replace(/"\{\{TENANT_1_ID\}\}"/g, tenantID),
),
title: 'SSR Post 1',
meta: {
title: 'SSR Post 1',
description: 'This is the first SSR post.',
},
},
})
const post2Doc = await payload.create({
collection: postsSlug,
data: JSON.parse(
@@ -125,6 +108,23 @@ export const seed: Config['onInit'] = async (payload) => {
),
})
await payload.create({
collection: ssrPagesSlug,
data: {
...JSON.parse(
JSON.stringify(home)
.replace(/"\{\{MEDIA_ID\}\}"/g, mediaID)
.replace(/"\{\{POSTS_PAGE_ID\}\}"/g, postsPageDocID)
.replace(/"\{\{POST_1_ID\}\}"/g, post1DocID)
.replace(/"\{\{POST_2_ID\}\}"/g, post2DocID)
.replace(/"\{\{POST_3_ID\}\}"/g, post3DocID)
.replace(/"\{\{TENANT_1_ID\}\}"/g, tenantID),
),
title: 'SSR Home',
slug: 'home',
},
})
await payload.updateGlobal({
slug: 'header',
data: JSON.parse(JSON.stringify(header).replace(/"\{\{POSTS_PAGE_ID\}\}"/g, postsPageDocID)),

View File

@@ -1,5 +1,7 @@
import type { Page } from '../payload-types.js'
import { postsSlug } from '../shared.js'
export const postsPage: Partial<Page> = {
title: 'Posts',
slug: 'live-preview/posts',
@@ -53,7 +55,7 @@ export const postsPage: Partial<Page> = {
},
],
populateBy: 'collection',
relationTo: 'posts',
relationTo: postsSlug,
limit: 10,
categories: [],
},

View File

@@ -2,7 +2,9 @@ export const pagesSlug = 'pages'
export const tenantsSlug = 'tenants'
export const ssrPostsSlug = 'posts-ssr'
export const ssrPagesSlug = 'ssr'
export const postsSlug = 'posts'
export const mobileBreakpoint = {
label: 'Mobile',

View File

@@ -1,26 +1,7 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"plugins": [
{
"name": "next"
}
],
"baseUrl": ".",
"paths": {
"@payload-config": ["./config.ts"],
"@payloadcms/ui/assets": ["../../packages/ui/src/assets/index.ts"],

View File

@@ -37,7 +37,10 @@ export const formatLivePreviewURL: LivePreviewConfig['url'] = async ({
// Format the URL as needed, based on the document and data
// I.e. append '/posts' to the URL if the document is a post
// You can also do this on individual collection or global config, if preferred
const isPage = collectionConfig && collectionConfig.slug === 'pages'
const isHomePage = isPage && data?.slug === 'home'
return `${baseURL}${
collectionConfig && collectionConfig.slug !== 'pages' ? `/${collectionConfig.slug}` : ''
}${data?.slug && data.slug !== 'home' ? `/${data.slug}` : ''}`
!isPage && collectionConfig ? `/${collectionConfig.slug}` : ''
}${!isHomePage && data.slug ? `/${data.slug}` : ''}`
}

View File

@@ -37,7 +37,7 @@
],
"paths": {
"@payload-config": [
"./test/_community/config.ts"
"./test/live-preview/config.ts"
],
"@payloadcms/live-preview": [
"./packages/live-preview/src"