chore(examples/live-preview): migrates to 3.0 (#6268)
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
2769
examples/live-preview/next-app/pnpm-lock.yaml
generated
Normal file
2769
examples/live-preview/next-app/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
2325
examples/live-preview/next-pages/pnpm-lock.yaml
generated
Normal file
2325
examples/live-preview/next-pages/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
12
examples/live-preview/payload/.eslintignore
Normal file
12
examples/live-preview/payload/.eslintignore
Normal file
@@ -0,0 +1,12 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
playwright.config.ts
|
||||
jest.config.js
|
||||
@@ -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'],
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
5
examples/live-preview/payload/next-env.d.ts
vendored
Normal file
5
examples/live-preview/payload/next-env.d.ts
vendored
Normal 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.
|
||||
8
examples/live-preview/payload/next.config.mjs
Normal file
8
examples/live-preview/payload/next.config.mjs
Normal 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)
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nodemon.json",
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
6441
examples/live-preview/payload/pnpm-lock.yaml
generated
Normal file
6441
examples/live-preview/payload/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 || ''}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.page {
|
||||
margin-top: calc(var(--base) * 2);
|
||||
}
|
||||
70
examples/live-preview/payload/src/app/(app)/[slug]/page.tsx
Normal file
70
examples/live-preview/payload/src/app/(app)/[slug]/page.tsx
Normal 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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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" />
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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'
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
.richText {
|
||||
:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
107
examples/live-preview/payload/src/app/(app)/app.scss
Normal file
107
examples/live-preview/payload/src/app/(app)/app.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
23
examples/live-preview/payload/src/app/(app)/layout.tsx
Normal file
23
examples/live-preview/payload/src/app/(app)/layout.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
3
examples/live-preview/payload/src/app/(app)/page.tsx
Normal file
3
examples/live-preview/payload/src/app/(app)/page.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import Page from './[slug]/page'
|
||||
|
||||
export default Page
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
16
examples/live-preview/payload/src/app/(payload)/layout.tsx
Normal file
16
examples/live-preview/payload/src/app/(payload)/layout.tsx
Normal 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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import type { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const Users: CollectionConfig = {
|
||||
slug: 'users',
|
||||
auth: true,
|
||||
admin: {
|
||||
useAsTitle: 'email',
|
||||
},
|
||||
auth: true,
|
||||
fields: [],
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -11,12 +11,12 @@ export const MainMenu: GlobalConfig = {
|
||||
{
|
||||
name: 'navItems',
|
||||
type: 'array',
|
||||
maxRows: 6,
|
||||
fields: [
|
||||
link({
|
||||
appearances: false,
|
||||
}),
|
||||
],
|
||||
maxRows: 6,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
130
examples/live-preview/payload/src/migrations/seed.ts
Normal file
130
examples/live-preview/payload/src/migrations/seed.ts
Normal 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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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'),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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
@@ -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'
|
||||
|
||||
@@ -5,4 +5,7 @@ module.exports = {
|
||||
project: ['./tsconfig.eslint.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
rules: {
|
||||
'no-restricted-exports': ['error', { restrictDefaultExports: { direct: false } }],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 []
|
||||
|
||||
@@ -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 []
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 []
|
||||
|
||||
@@ -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 []
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
@@ -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.')
|
||||
}
|
||||
@@ -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.')
|
||||
}
|
||||
@@ -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 || []
|
||||
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}}',
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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: [],
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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}` : ''}`
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
],
|
||||
"paths": {
|
||||
"@payload-config": [
|
||||
"./test/_community/config.ts"
|
||||
"./test/live-preview/config.ts"
|
||||
],
|
||||
"@payloadcms/live-preview": [
|
||||
"./packages/live-preview/src"
|
||||
|
||||
Reference in New Issue
Block a user