Compare commits

..

32 Commits

Author SHA1 Message Date
James
e37897f7a5 chore: pnpm i 2024-05-10 16:05:26 -07:00
James
dbf1a1bd8b chore: adds a bit of safety to form-state endpoint 2024-05-10 16:01:29 -07:00
Jarrod Flesch
4f0ddcf632 chore: improves lexical fixed toolbar styles (#6317) 2024-05-10 17:38:24 -04:00
Jarrod Flesch
693621a6e3 chore: improves types for lexical client features (#6318) 2024-05-10 17:38:10 -04:00
Elliot DeNolf
5b201392cc ci: add storage-uploadthing 2024-05-10 17:15:22 -04:00
Elliot DeNolf
a70bcf81c0 chore(release): v3.0.0-beta.30 [skip ci] 2024-05-10 17:10:18 -04:00
Elliot DeNolf
ed880d5018 feat: storage-uploadthing package (#6316)
Co-authored-by: James <james@trbl.design>
2024-05-10 17:05:35 -04:00
Patrik
ea84e82ad5 feat(payload, ui): adds disableListColumn & disableListFilter to fields admin props (#6238) 2024-05-10 15:59:29 -04:00
Patrik
4216d69ccb fix(richtext-slate): list item values returning null (#6291) 2024-05-10 15:42:37 -04:00
Patrik
dcad5003f5 fix(ui): appends editDepth value to radio & checkbox IDs when inside drawer (#6252) 2024-05-10 15:56:24 +00:00
Paul
bd9c06a99d chore: update readme for tailwind example (#6314) 2024-05-10 12:13:10 -03:00
Elliot DeNolf
48932ef54d chore: format plugin object ids (#6310) 2024-05-10 14:20:21 +00:00
Patrik
4aeefc5a1a feat: adds plugin-relationship-object-ids package (#6045) 2024-05-10 09:31:25 -04:00
Ritsu
e96ff90029 fix(next): respect fallback locale null value (#6207) 2024-05-10 14:27:13 +01:00
Elliot DeNolf
f6e77b845b ci: add npm provenance to canary releases 2024-05-10 09:08:16 -04:00
Elliot DeNolf
a0bb02d05a chore: remove unneeded val in redirects publish config 2024-05-09 23:58:58 -04:00
Elliot DeNolf
354ad7092c chore: type gen formatting (#6309) 2024-05-09 23:55:55 -04:00
Elliot DeNolf
f9d862d854 ci(scripts): update getPackageRegistryVersions [skip ci] 2024-05-09 23:35:50 -04:00
Elliot DeNolf
f41576dd65 ci: canary releases (#6308) 2024-05-09 23:12:47 -04:00
Elliot DeNolf
ffa20aa7d0 chore(release): v3.0.0-beta.29 [skip ci] 2024-05-09 17:19:54 -04:00
Alessio Gravili
f7a2cf96b9 chore: properly working generated types within tests (#6288) 2024-05-09 17:12:51 -04:00
Alessio Gravili
cfeac79b99 feat!: fix non-functional custom RSC component handling, separate label and description props, fix non-functional label function handling (#6264)
Breaking Changes:

- Globals config: `admin.description` no longer accepts a custom component. You will have to move it to `admin.components.elements.Description`
- Collections config: `admin.description` no longer accepts a custom component. You will have to move it to `admin.components.edit.Description`
- All Fields: `field.admin.description` no longer accepts a custom component. You will have to move it to `field.admin.components.Description`
- Collapsible Field: `field.label` no longer accepts a custom component. You will have to move it to `field.admin.components.RowLabel`
- Array Field: `field.admin.components.RowLabel` no longer accepts strings or records
- If you are using our exported field components in your own app, their `labelProps` property has been stripped down and no longer contains the `label` and `required` prop. Those can now only be configured at the top-level
2024-05-09 17:12:01 -04:00
Elliot DeNolf
821bed0ea6 ci: all green (#6289) 2024-05-09 16:33:05 -04:00
Jacob Fletcher
9e9111666b chore(examples/live-preview): migrates to 3.0 (#6268) 2024-05-09 15:32:46 -04:00
David Velasco
5065322d31 fix(plugin-form-builder): resolve labelValue from LabelFunction (#5817) 2024-05-09 16:23:44 -03:00
Paul
ad4796cdb2 fix(plugin-form-builder): export types correctly (#6287) 2024-05-09 14:42:14 -03:00
Alessio Gravili
43b7ba82da chore: fix dev:generate-types not working (#6284) 2024-05-09 10:37:11 -04:00
Alessio Gravili
3785c79ac9 fix(templates): yarn install broken for new template installs (#6283) 2024-05-09 10:17:09 -04:00
Jarrod Flesch
4384e9eb0e chore: fixes cannot destructure property 'schema' issue (#6282) 2024-05-09 10:16:30 -04:00
Alessio Gravili
9364f8da2e fix(templates): blank-3.0: pin next version, as it was breaking new installs (#6281) 2024-05-09 10:05:38 -04:00
Elliot DeNolf
a4ef359660 chore: examples linting (#6269) 2024-05-08 14:58:57 -04:00
Elliot DeNolf
848c05f247 chore(deps): sync pnpm-lock.yaml 2024-05-08 14:37:48 -04:00
366 changed files with 25223 additions and 18939 deletions

48
.github/actions/setup/action.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Setup node and pnpm
description: Configure the Node.js and pnpm versions
inputs:
node-version:
description: 'The Node.js version to use'
required: true
default: 18.20.2
pnpm-version:
description: 'The pnpm version to use'
required: true
default: 8.15.7
runs:
using: composite
steps:
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
shell: bash
run: sudo ethtool -K eth0 tx off rx off
- name: Setup Node@${{ inputs.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: ${{ inputs.pnpm-version }}
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
pnpm-store-
pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- shell: bash
run: pnpm install

View File

@@ -93,10 +93,10 @@ jobs:
pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- run: pnpm install
- name: List changed files
run: git diff --name-only --diff-filter=d origin/${GITHUB_BASE_REF}...origin/${GITHUB_HEAD_REF}
- name: Lint staged
run: npx lint-staged --diff="origin/${GITHUB_BASE_REF}...origin/${GITHUB_HEAD_REF}"
run: |
git diff --name-only --diff-filter=d origin/${GITHUB_BASE_REF}...${GITHUB_SHA}
npx lint-staged --diff="origin/${GITHUB_BASE_REF}...${GITHUB_SHA}"
build:
needs: changes
@@ -490,3 +490,18 @@ jobs:
yarn install
yarn build
yarn generate:types
all-green:
name: All Green
if: always()
runs-on: ubuntu-latest
needs:
- lint
- build
- tests-unit
- tests-int
- tests-e2e
steps:
- if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}
run: exit 1

36
.github/workflows/release-canary.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: release-canary
on:
workflow_dispatch:
branches:
- beta
env:
NODE_VERSION: 18.20.2
PNPM_VERSION: 8.15.7
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
jobs:
release:
permissions:
id-token: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
- name: Load npm token
run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Canary release script
# dry run hard-coded to true for testing and no npm token provided
run: pnpm tsx ./scripts/publish-canary.ts
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: true

View File

@@ -12,3 +12,4 @@
tsconfig.json
packages/payload/*.js
packages/payload/*.d.ts
payload-types.ts

8
.vscode/launch.json vendored
View File

@@ -16,6 +16,14 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "node --no-deprecation test/dev.js storage-uploadthing",
"cwd": "${workspaceFolder}",
"name": "Uploadthing",
"request": "launch",
"type": "node-terminal",
"envFile": "${workspaceFolder}/test/storage-uploadthing/.env"
},
{
"command": "node --no-deprecation test/dev.js live-preview",
"cwd": "${workspaceFolder}",

View File

@@ -163,19 +163,21 @@ Example:
In addition to each field's base configuration, you can define specific traits and properties for fields that only have effect on how they are rendered in the Admin panel. The following properties are available for all fields within the `admin` property:
| Option | Description |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `condition` | You can programmatically show / hide fields based on what other fields are doing. [Click here](#conditional-logic) for more info. |
| `components` | All field components can be completely and easily swapped out for custom components that you define. [Click here](#custom-components) for more info. |
| `description` | Helper text to display with the field to provide more information for the editor user. [Click here](#description) for more info. |
| `position` | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
| `width` | Restrict the width of a field. you can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
| `style` | Attach raw CSS style properties to the root DOM element of a field. |
| `className` | Attach a CSS class name to the root DOM element of a field. |
| `readOnly` | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
| `disabled` | If a field is `disabled`, it is completely omitted from the Admin panel. |
| `disableBulkEdit` | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. |
| `hidden` | Setting a field's `hidden` property on its `admin` config will transform it into a `hidden` input type. Its value will still submit with the Admin panel's requests, but the field itself will not be visible to editors. |
| Option | Description |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `condition` | You can programmatically show / hide fields based on what other fields are doing. [Click here](#conditional-logic) for more info. |
| `components` | All field components can be completely and easily swapped out for custom components that you define. [Click here](#custom-components) for more info. |
| `description` | Helper text to display with the field to provide more information for the editor user. [Click here](#description) for more info. |
| `position` | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
| `width` | Restrict the width of a field. you can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
| `style` | Attach raw CSS style properties to the root DOM element of a field. |
| `className` | Attach a CSS class name to the root DOM element of a field. |
| `readOnly` | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
| `disabled` | If a field is `disabled`, it is completely omitted from the Admin panel. |
| `disableBulkEdit` | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. |
| `disableListColumn` | Set `disableListColumn` to `true` to prevent fields from appearing in the list view column selector. |
| `disableListFilter` | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. |
| `hidden` | Setting a field's `hidden` property on its `admin` config will transform it into a `hidden` input type. Its value will still submit with the Admin panel's requests, but the field itself will not be visible to editors. |
### Custom components

View File

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

View File

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

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: "typescript",
semi: false,
singleQuote: true,
trailingComma: "all",
arrowParens: "avoid",
};

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: "typescript",
semi: false,
singleQuote: true,
trailingComma: "all",
arrowParens: "avoid",
};

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: "typescript",
semi: false,
singleQuote: true,
trailingComma: "all",
arrowParens: "avoid",
};

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

@@ -1,6 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"semi": false
}

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: "typescript",
semi: false,
singleQuote: true,
trailingComma: "all",
arrowParens: "avoid",
};

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

@@ -1,6 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"semi": false
}

View File

@@ -1,42 +1,25 @@
# Payload Blank Template
# Payload with TailwindCSS and shadcn/ui
A blank template for [Payload](https://github.com/payloadcms/payload) to help you get up and running quickly. This repo may have been created by running `npx create-payload-app@latest` and selecting the "blank" template or by cloning this template on [Payload Cloud](https://payloadcms.com/new/clone/blank).
This is an example repo of Payload being setup with TailwindCSS and shadcn/ui components ready to be used in the admin panel itself.
See the official [Examples Directory](https://github.com/payloadcms/payload/tree/main/examples) for details on how to use Payload in a variety of different ways.
Checkout our [tutorial](https://payloadcms.com/blog/how-to-setup-tailwindcss-and-shadcn-ui-in-payload) on our website
## Development
## Quick Start
To spin up the project locally, follow these steps:
To spin up this example locally, follow these steps:
1. First clone the repo
1. Then `cd YOUR_PROJECT_REPO && cp .env.example .env`
1. Next `yarn && yarn dev` (or `docker-compose up`, see [Docker](#docker))
1. Now `open http://localhost:3000/admin` to access the admin panel
1. Create your first admin user using the form on the page
1. Clone this repo
1. `cd` into this directory and run `pnpm i --ignore-workspace`\*, or `npm install`
That's it! Changes made in `./src` will be reflected in your app.
> \*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.
### Docker
1. `cp .env.example .env` to copy the example environment variables
Alternatively, you can use [Docker](https://www.docker.com) to spin up this project locally. To do so, follow these steps:
> Adjust `PAYLOAD_PUBLIC_SITE_URL` in the `.env` if your front-end is running on a separate domain or port.
1. Follow [steps 1 and 2 from above](#development), the docker-compose file will automatically use the `.env` file in your project root
1. Next run `docker-compose up`
1. Follow [steps 4 and 5 from above](#development) to login and create your first admin user
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
That's it! The Docker instance will help you get up and running quickly while also standardizing the development environment across your teams.
## Production
To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps:
1. First invoke the `payload build` script by running `yarn build` or `npm run build` in your project root. This creates a `./build` directory with a production-ready admin bundle.
1. Then run `yarn serve` or `npm run serve` to run Node in production and serve Payload from the `./build` directory.
### Deployment
The easiest way to deploy your project is to use [Payload Cloud](https://payloadcms.com/new/import), a one-click hosting solution to deploy production-ready instances of your Payload apps directly from your GitHub repo. You can also deploy your app manually, check out the [deployment documentation](https://payloadcms.com/docs/production/deployment) for full details.
## Questions
If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/payload) or start a [GitHub discussion](https://github.com/payloadcms/payload/discussions).
That's it! Changes made in `./src` will be reflected in your app. See the [Development](#development) section for more details.

View File

@@ -9,7 +9,7 @@ const withBundleAnalyzer = bundleAnalyzer({
// eslint-disable-next-line no-restricted-exports
export default withBundleAnalyzer(
withPayload({
reactStrictMode: false,
// reactStrictMode: false,
eslint: {
ignoreDuringBuilds: true,
},

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.28",
"version": "3.0.0-beta.30",
"private": true,
"type": "module",
"scripts": {
@@ -25,6 +25,7 @@
"build:plugin-form-builder": "turbo build --filter plugin-form-builder",
"build:plugin-nested-docs": "turbo build --filter plugin-nested-docs",
"build:plugin-redirects": "turbo build --filter plugin-redirects",
"build:plugin-relationship-object-ids": "turbo build --filter plugin-relationship-object-ids",
"build:plugin-search": "turbo build --filter plugin-search",
"build:plugin-sentry": "turbo build --filter plugin-sentry",
"build:plugin-seo": "turbo build --filter plugin-seo",
@@ -32,6 +33,11 @@
"build:plugins": "turbo build --filter \"@payloadcms/plugin-*\"",
"build:richtext-lexical": "turbo build --filter richtext-lexical",
"build:richtext-slate": "turbo build --filter richtext-slate",
"build:storage-azure": "turbo build --filter storage-azure",
"build:storage-gcs": "turbo build --filter storage-gcs",
"build:storage-s3": "turbo build --filter storage-s3",
"build:storage-uploadthing": "turbo build --filter storage-uploadthing",
"build:storage-vercel-blob": "turbo build --filter storage-vercel-blob",
"build:tests": "pnpm --filter payload-test-suite run typecheck",
"build:translations": "turbo build --filter translations",
"build:ui": "turbo build --filter ui",
@@ -127,7 +133,7 @@
"lint-staged": "^14.0.1",
"minimist": "1.2.8",
"mongodb-memory-server": "^9.0",
"next": "^14.3.0-canary.7",
"next": "^14.3.0-canary.37",
"node-mocks-http": "^1.14.1",
"nodemon": "3.0.3",
"open": "^10.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-beta.28",
"version": "3.0.0-beta.30",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-beta.28",
"version": "3.0.0-beta.30",
"description": "The officially supported MongoDB database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-beta.28",
"version": "3.0.0-beta.30",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-nodemailer",
"version": "3.0.0-beta.28",
"version": "3.0.0-beta.30",
"description": "Payload Nodemailer Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-resend",
"version": "3.0.0-beta.28",
"version": "3.0.0-beta.30",
"description": "Payload Resend Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-beta.28",
"version": "3.0.0-beta.30",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-react",
"version": "3.0.0-beta.28",
"version": "3.0.0-beta.30",
"description": "The official live preview React SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview",
"version": "3.0.0-beta.28",
"version": "3.0.0-beta.30",
"description": "The official live preview JavaScript SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-beta.28",
"version": "3.0.0-beta.30",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -63,11 +63,10 @@ export const getGraphql = async (config: Promise<SanitizedConfig> | SanitizedCon
}
if (!cached.promise) {
// eslint-disable-next-line no-async-promise-executor
cached.promise = new Promise(async (resolve) => {
const resolvedConfig = await config
const resolvedConfig = await config
cached.promise = new Promise((resolve) => {
const schema = configToSchema(resolvedConfig)
resolve(schema)
resolve(cached.graphql || schema)
})
}

View File

@@ -36,6 +36,19 @@ export const buildFormState = async ({ req }: { req: PayloadRequestWithData }) =
try {
const reqData: BuildFormStateArgs = req.data as BuildFormStateArgs
if (!reqData) {
return Response.json(
{
message: 'No options provided',
},
{
headers,
status: httpStatus.BAD_REQUEST,
},
)
}
const { collectionSlug, formState, globalSlug, locale, operation, schemaPath } = reqData
const incomingUserSlug = req.user?.collection

View File

@@ -1,4 +1,4 @@
import type { Collection, PayloadRequestWithData, Where } from 'payload/types'
import type { Collection, PayloadRequestWithData, TypeWithID, Where } from 'payload/types'
import { executeAccess } from 'payload/auth'
import { Forbidden } from 'payload/errors'
@@ -13,7 +13,7 @@ export async function checkFileAccess({
collection: Collection
filename: string
req: PayloadRequestWithData
}) {
}): Promise<Response | TypeWithID> {
const { config } = collection
const disableEndpoints = endpointsAreDisabled({ endpoints: config.endpoints, request: req })
if (disableEndpoints) return disableEndpoints
@@ -55,5 +55,7 @@ export async function checkFileAccess({
if (!doc) {
throw new Forbidden(req.t)
}
return doc
}
}

View File

@@ -27,16 +27,19 @@ export const getFile = async ({ collection, filename, req }: Args): Promise<Resp
)
}
await checkFileAccess({
const accessResult = await checkFileAccess({
collection,
filename,
req,
})
if (accessResult instanceof Response) return accessResult
let response: Response = null
if (collection.config.upload.handlers?.length) {
for (const handler of collection.config.upload.handlers) {
response = await handler(req, {
doc: accessResult,
params: {
collection: collection.config.slug,
filename,

View File

@@ -62,7 +62,7 @@ export const sanitizeLocales = ({
locale,
localization,
}: SanitizeLocalesArgs): SanitizeLocalesReturn => {
if (fallbackLocale === 'none') {
if (['none', 'null'].includes(fallbackLocale)) {
fallbackLocale = 'null'
} else if (localization && !localization.localeCodes.includes(fallbackLocale)) {
fallbackLocale = localization.defaultLocale

View File

@@ -1,5 +1,6 @@
import type { AdminViewProps } from 'payload/types'
import { WithServerSideProps } from '@payloadcms/ui/elements/WithServerSideProps'
import { Logo } from '@payloadcms/ui/graphics/Logo'
import { redirect } from 'next/navigation.js'
import React, { Fragment } from 'react'
@@ -16,6 +17,7 @@ export const LoginView: React.FC<AdminViewProps> = ({ initPageResult, searchPara
const {
payload: { config },
payload,
user,
} = req
@@ -25,6 +27,18 @@ export const LoginView: React.FC<AdminViewProps> = ({ initPageResult, searchPara
routes: { admin },
} = config
const BeforeLogins = Array.isArray(beforeLogin)
? beforeLogin.map((Component, i) => (
<WithServerSideProps Component={Component} key={i} payload={payload} />
))
: null
const AfterLogins = Array.isArray(afterLogin)
? afterLogin.map((Component, i) => (
<WithServerSideProps Component={Component} key={i} payload={payload} />
))
: null
if (user) {
redirect(admin)
}
@@ -36,9 +50,9 @@ export const LoginView: React.FC<AdminViewProps> = ({ initPageResult, searchPara
<div className={`${loginBaseClass}__brand`}>
<Logo config={config} />
</div>
{Array.isArray(beforeLogin) && beforeLogin.map((Component, i) => <Component key={i} />)}
{Array.isArray(BeforeLogins) && BeforeLogins.map((Component) => Component)}
{!collectionConfig?.auth?.disableLocalStrategy && <LoginForm searchParams={searchParams} />}
{Array.isArray(afterLogin) && afterLogin.map((Component, i) => <Component key={i} />)}
{Array.isArray(AfterLogins) && AfterLogins.map((Component) => Component)}
</Fragment>
)
}

Some files were not shown because too many files have changed in this diff Show More