Compare commits
87 Commits
v2.0.1
...
db-postgre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d2dd5849d | ||
|
|
6d9353b53f | ||
|
|
1914be75aa | ||
|
|
b17f627e02 | ||
|
|
bef79621ee | ||
|
|
af892ecb0e | ||
|
|
a42e84bbb2 | ||
|
|
470bdb72ff | ||
|
|
5c36be949c | ||
|
|
eb97acd408 | ||
|
|
2567ac58ba | ||
|
|
9ff014bbfe | ||
|
|
e6f0d35985 | ||
|
|
b1e449e005 | ||
|
|
9ae585d23c | ||
|
|
9de3320933 | ||
|
|
5d429fa7ae | ||
|
|
dc8f1925f0 | ||
|
|
15f650afde | ||
|
|
c945384d63 | ||
|
|
dfada1b238 | ||
|
|
229bdda2c1 | ||
|
|
a1d51fb410 | ||
|
|
46430f5598 | ||
|
|
e41899cd27 | ||
|
|
890af8be05 | ||
|
|
8bfae6b932 | ||
|
|
ace3e577f6 | ||
|
|
9198245ad9 | ||
|
|
f0095937ba | ||
|
|
0bbd7137cd | ||
|
|
ffed34cf27 | ||
|
|
040f3a34d1 | ||
|
|
0985825b08 | ||
|
|
c18d3b5f0e | ||
|
|
e9e25ceac9 | ||
|
|
35aed59a1a | ||
|
|
26002173b1 | ||
|
|
41d968771e | ||
|
|
26c34541d2 | ||
|
|
263d40d169 | ||
|
|
6ced11d44d | ||
|
|
be049cea00 | ||
|
|
491e50c236 | ||
|
|
ad253db691 | ||
|
|
aff5fdff8a | ||
|
|
aa97bebbd4 | ||
|
|
a2410ea9fc | ||
|
|
de20ef1e8d | ||
|
|
08f7497040 | ||
|
|
74e99ce251 | ||
|
|
b5c56efb4b | ||
|
|
4ff6d63c94 | ||
|
|
c90d1faa7f | ||
|
|
1848b120ce | ||
|
|
62679baa91 | ||
|
|
2de36550ae | ||
|
|
61ea5becbb | ||
|
|
5d9384f530 | ||
|
|
d75ffa0ea7 | ||
|
|
26967fb924 | ||
|
|
830d9867b6 | ||
|
|
2f86c196e1 | ||
|
|
86a35ed441 | ||
|
|
70e068b182 | ||
|
|
4ac01a7fa3 | ||
|
|
8058a6d800 | ||
|
|
252b04097f | ||
|
|
23066aec71 | ||
|
|
b9a595b00c | ||
|
|
ea49d74941 | ||
|
|
f5f41f929e | ||
|
|
61151c9c5d | ||
|
|
c12c1a7472 | ||
|
|
7afa1e999d | ||
|
|
71c41dbe03 | ||
|
|
a161dc7bb6 | ||
|
|
6c17222a6a | ||
|
|
63bf7d9303 | ||
|
|
f878135e8b | ||
|
|
11a77ae489 | ||
|
|
e5d6a75449 | ||
|
|
3cb0d9c2ca | ||
|
|
229149a5a6 | ||
|
|
9006643102 | ||
|
|
25ae4a24f2 | ||
|
|
501ec4464f |
5
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
5
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
@@ -31,6 +31,11 @@ body:
|
||||
description: What version of Payload are you running?
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: adapters-plugins
|
||||
attributes:
|
||||
label: Adapters and Plugins
|
||||
description: What adapters and plugins are you using? ie. db-mongodb, db-postgres, bundler-webpack, etc.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Before submitting the issue, go through the steps you've written down to make sure the steps provided are detailed and clear.
|
||||
|
||||
186
.github/workflows/main.yml
vendored
186
.github/workflows/main.yml
vendored
@@ -108,6 +108,10 @@ jobs:
|
||||
tests-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
part: [1/4, 2/4, 3/4, 4/4]
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
@@ -128,14 +132,14 @@ jobs:
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: E2E Tests
|
||||
run: pnpm test:e2e --bail
|
||||
run: pnpm test:e2e --part ${{ matrix.part }} --bail
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results/
|
||||
retention-days: 30
|
||||
retention-days: 1
|
||||
|
||||
tests-type-generation:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -165,11 +169,22 @@ jobs:
|
||||
- name: Generate GraphQL schema file
|
||||
run: pnpm dev:generate-graphql-schema graphql-schema-gen
|
||||
|
||||
# DB Adapters
|
||||
|
||||
build-db-mongodb:
|
||||
build-packages:
|
||||
name: Build Packages
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pkg:
|
||||
- db-mongodb
|
||||
- db-postgres
|
||||
- bundler-webpack
|
||||
- bundler-vite
|
||||
- richtext-slate
|
||||
- richtext-lexical
|
||||
- live-preview
|
||||
- live-preview-react
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
@@ -189,162 +204,5 @@ jobs:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build db-mongodb
|
||||
run: pnpm turbo run build --filter=db-mongodb
|
||||
|
||||
build-db-postgres:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build db-postgres
|
||||
run: pnpm turbo run build --filter=db-postgres
|
||||
|
||||
# Bundlers
|
||||
|
||||
build-bundler-webpack:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build bundler-webpack
|
||||
run: pnpm turbo run build --filter=bundler-webpack
|
||||
|
||||
build-bundler-vite:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build bundler-vite
|
||||
run: pnpm turbo run build --filter=bundler-vite
|
||||
|
||||
# Other Plugins
|
||||
|
||||
build-plugin-richtext-slate:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build richtext-slate
|
||||
run: pnpm turbo run build --filter=richtext-slate
|
||||
|
||||
build-plugin-richtext-lexical:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build richtext-lexical
|
||||
run: pnpm turbo run build --filter=richtext-lexical
|
||||
|
||||
build-plugin-live-preview:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build live-preview
|
||||
run: pnpm turbo run build --filter=live-preview
|
||||
|
||||
- name: Build live-preview-react
|
||||
run: pnpm turbo run build --filter=live-preview-react
|
||||
- name: Build ${{ matrix.pkg }}
|
||||
run: pnpm turbo run build --filter=${{ matrix.pkg }}
|
||||
|
||||
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -17,7 +17,7 @@
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev:postgres collections-graphql",
|
||||
"command": "pnpm run dev:postgres fields",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Postgres",
|
||||
"request": "launch",
|
||||
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -51,11 +51,29 @@ export default buildConfig({
|
||||
|
||||
These new properties are all now required for Payload to function, and you will have to install each separate adapter that you use. Feel free to swap out any of the adapters with your choice (Lexical, Postgres, Vite, etc.)
|
||||
|
||||
Make sure to install the packages that you need. In the above example, you would need to install the following:
|
||||
|
||||
```bash
|
||||
npm install --save @payloadcms/db-mongodb @payloadcms/richtext-slate @payloadcms/bundler-webpack
|
||||
```
|
||||
|
||||
### ⚠️ Draft versions now require a `latest: true` property to be set on the most recent draft in your `_versions` collections(s)
|
||||
|
||||
We have a ready-to-go migration script for your versions from v1 to v2, and to use it, all you have to do is run the following commands:
|
||||
|
||||
**1. Create a migration, using the new Payload migration API**
|
||||
**1. First, make sure you have a `payload` npm script in your `package.json`**
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Adjust the `PAYLOAD_CONFIG_PATH` to point to your Payload config file if necessary.
|
||||
|
||||
**2. Create a migration, using the new Payload migration API**
|
||||
|
||||
```bash
|
||||
npm run payload migrate:create --file @payloadcms/db-mongodb/versions-v1-v2
|
||||
@@ -63,7 +81,7 @@ npm run payload migrate:create --file @payloadcms/db-mongodb/versions-v1-v2
|
||||
|
||||
The above command will output a migration file into your `./src/migrations` folder (default migrations location). It contains a migration script to automatically add a `latest: true` flag to each of your newest drafts, for all draft-enabled collections. It works out of the box!
|
||||
|
||||
**2. Run migrations**
|
||||
**3. Run migrations**
|
||||
|
||||
From there, you need to run migrations. Run the following command to execute your new migration:
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
<a target="_blank" href="https://payloadcms.com/docs/getting-started/what-is-payload" rel="dofollow"><strong>Explore the Docs</strong></a> · <a target="_blank" href="https://payloadcms.com/community-help" rel="dofollow"><strong>Community Help</strong></a> · <a target="_blank" href="https://demo.payloadcms.com/" rel="dofollow"><strong>Try Live Demo</strong></a> · <a target="_blank" href="https://github.com/payloadcms/payload/discussions/1539" rel="dofollow"><strong>Roadmap</strong></a> · <a target="_blank" href="https://www.g2.com/products/payload-cms/reviews#reviews" rel="dofollow"><strong>View G2 Reviews</strong></a>
|
||||
</h4>
|
||||
<hr/>
|
||||
|
||||
### 🎉 Payload 2.0 is now available! Read more in the [announcement post](https://payloadcms.com/blog/payload-2-0).
|
||||
|
||||
<h3>Benefits over a regular CMS</h3>
|
||||
<ul>
|
||||
<li>Don’t hit some third-party SaaS API, hit your own API</li>
|
||||
@@ -95,6 +98,8 @@ We're constantly adding more templates to our [Templates Directory](https://gith
|
||||
|
||||
Check out the [Payload website](https://payloadcms.com/docs/getting-started/what-is-payload) to find in-depth documentation for everything that Payload offers.
|
||||
|
||||
Migrating from v1 to v2? Check out the [2.0 Release Notes](https://github.com/payloadcms/payload/releases/tag/v2.0.0) on how to do it.
|
||||
|
||||
## 🙋 Contributing
|
||||
|
||||
If you want to add contributions to this repository, please follow the instructions in [contributing.md](./CONTRIBUTING.md).
|
||||
|
||||
@@ -12,13 +12,13 @@ Payload has two official bundlers, the [Webpack Bundler](/docs/admin/webpack) an
|
||||
Webpack (recommended):
|
||||
|
||||
```text
|
||||
yarn add @payloadcms/webpack-bundler
|
||||
yarn add @payloadcms/bundler-webpack
|
||||
```
|
||||
|
||||
Vite (beta):
|
||||
|
||||
```text
|
||||
yarn add @payloadcms/vite-bundler
|
||||
yarn add @payloadcms/bundler-vite
|
||||
```
|
||||
|
||||
##### Configure the bundler
|
||||
|
||||
@@ -72,7 +72,7 @@ export default buildConfig({
|
||||
|
||||
#### Views
|
||||
|
||||
You can easily swap entire views with your own by using the `admin.components.views` property. At the root level, Payload renders the following views that can be overridden:
|
||||
You can easily swap entire views with your own by using the `admin.components.views` property. At the root level, Payload renders the following views dy default, all of which can be overridden:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -84,6 +84,7 @@ To swap out any of these views, simply pass in your custom component to the `adm
|
||||
```ts
|
||||
// payload.config.ts
|
||||
{
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
@@ -114,6 +115,7 @@ To add a _new_ view to the Admin Panel, simply add another key to the `views` ob
|
||||
```ts
|
||||
// payload.config.ts
|
||||
{
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
@@ -127,7 +129,9 @@ To add a _new_ view to the Admin Panel, simply add another key to the `views` ob
|
||||
}
|
||||
```
|
||||
|
||||
_For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/master/test/admin/components)._ For help on how to build your own custom view components, see the [Building a custom view component](#building-a-custom-view-component) section.
|
||||
_For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/master/test/admin/components)._
|
||||
|
||||
For help on how to build your own custom view components, see [building a custom view component](#building-a-custom-view-component).
|
||||
|
||||
### Collections
|
||||
|
||||
@@ -207,19 +211,19 @@ export const MyCollection: SanitizedCollectionConfig = {
|
||||
|
||||
#### Collection views
|
||||
|
||||
To swap out entire views on collections, you can use the `admin.components.views` property on the collection's config. Payload renders the following views by default that can be overridden:
|
||||
To swap out entire views on collections, you can use the `admin.components.views` property on the collection's config. Payload renders the following views dy default, all of which can be overridden:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Edit`** | The Edit view is used to edit a single document for a given Collection. |
|
||||
| **`List`** | The List view is used to show a list of documents for a given Collection. |
|
||||
| **`Edit`** | The Edit view is used to edit a single document for a given collection. |
|
||||
| **`List`** | The List view is used to show a list of documents for a given collection. |
|
||||
|
||||
To swap out any of these views, simply pass in your custom component to the `admin.components.views` property of your Payload config. For example:
|
||||
To swap out any of these views, simply pass in your custom component to the `admin.components.views` property of your Payload config. This will replace the entire view, including the page breadcrumbs, title, tabs, etc, _as well as all nested routes_.
|
||||
|
||||
```ts
|
||||
// Collection.ts
|
||||
export const MyCollection: SanitizedCollectionConfig = {
|
||||
slug: 'my-collection',
|
||||
{
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
@@ -231,7 +235,27 @@ export const MyCollection: SanitizedCollectionConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
To swap or add new _tabs_ to the `Edit` view, you can use the `admin.components.views.Edit` property on the collection's config. See the [Custom Tabs](#custom-tabs) section for more information. For help on how to build your own custom view components, see the [Building a custom view component](#building-a-custom-view-component) section.
|
||||
_For help on how to build your own custom view components, see [building a custom view component](#building-a-custom-view-component)._
|
||||
|
||||
To swap specific _nested_ views within the parent `Edit` view, you can use the `admin.components.views.Edit` property on the globals's config. This will only replace the nested view, leaving the page breadcrumbs, title, tabs, etc intact.
|
||||
|
||||
```ts
|
||||
// Collection.ts
|
||||
{
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
Edit: {
|
||||
Default: MyCustomDefaultTab,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
You can also add _new_ tabs to the `Edit` view by adding another key to the `components.views.Edit[key]` object with a `path` and `Component` property. See [Custom Tabs](#custom-tabs) for more information.
|
||||
|
||||
### Globals
|
||||
|
||||
@@ -247,17 +271,18 @@ As with Collections, you can override components on a global-by-global basis via
|
||||
|
||||
#### Global views
|
||||
|
||||
To swap out views for globals, you can use the `admin.components.views` property on the global's config. Payload renders the following views by default that you can override:
|
||||
To swap out views for globals, you can use the `admin.components.views` property on the global's config. Payload renders the following views dy default, all of which can be overridden:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Edit`** | The Edit view is used to edit a single document for a given Global. |
|
||||
|
||||
To swap out any of these views, simply pass in your custom component to the `admin.components.views` property of your Payload config. For example:
|
||||
To swap out any of these views, simply pass in your custom component to the `admin.components.views` property of your Payload config. This will replace the entire view, including the page breadcrumbs, title, and tabs, _as well as all nested views_.
|
||||
|
||||
```ts
|
||||
export const MyGlobal: SanitizedGlobalConfig = {
|
||||
slug: 'my-global',
|
||||
// Global.ts
|
||||
{
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
@@ -268,17 +293,37 @@ export const MyGlobal: SanitizedGlobalConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
To swap or add new _tabs_ to the `Edit` view, you can use the `admin.components.views.Edit` property on the collection's config. See the [Custom Tabs](#custom-tabs) section for more information. For help on how to build your own custom view components, see the [Building a custom view component](#building-a-custom-view-component) section.
|
||||
_For help on how to build your own custom view components, see [building a custom view component](#building-a-custom-view-component)._
|
||||
|
||||
To swap specific _nested_ views within the parent `Edit` view, you can use the `admin.components.views.Edit` property on the globals's config. This will only replace the nested view, leaving the page breadcrumbs, title, and tabs intact.
|
||||
|
||||
```ts
|
||||
// Global.ts
|
||||
{
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
Edit: {
|
||||
Default: MyCustomDefaultTab,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
You can also add _new_ tabs to the `Edit` view by adding another key to the `components.views.Edit[key]` object with a `path` and `Component` property. See [Custom Tabs](#custom-tabs) for more information.
|
||||
|
||||
### Custom Tabs
|
||||
|
||||
To can swap collection or global tabs, pass an _object_ to the `admin.components.views.Edit` property on any collection or global config. Payload renders the following views by default which can be overridden:
|
||||
You can easily swap individual collection or global edit views. To do this, pass an _object_ to the `admin.components.views.Edit` property of the config. Payload renders the following views dy default, all of which can be overridden:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Default`** | The Default view is the primary view in which your document is edited. |
|
||||
| **`Versions`** | The Versions view is used to view the version history of a single document. [More details](../versions) |
|
||||
| **`Version`** | The Version view is used to view a single version of a single document for a given Collection. [More details](../versions). |
|
||||
| **`Version`** | The Version view is used to view a single version of a single document for a given collection. [More details](../versions). |
|
||||
| **`API`** | The API view is used to display the REST API JSON response for a given document. |
|
||||
| **`LivePreview`** | The LivePreview view is used to display the Live Preview interface. [More details](../live-preview) |
|
||||
|
||||
@@ -291,7 +336,7 @@ export const MyCollection: SanitizedCollectionConfig = {
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
Edit: {
|
||||
Edit: { // You can also define `components.views.Edit` as a component, this will override _all_ nested views
|
||||
Default: MyCustomDefaultTab,
|
||||
Versions: MyCustomVersionsTab,
|
||||
Version: MyCustomVersionTab,
|
||||
@@ -304,7 +349,7 @@ export const MyCollection: SanitizedCollectionConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
To add a _new_ tab to the `Edit` view, simply add another key to the `views.Edit` object with at least a `path` and `Component` property. For example:
|
||||
To add a _new_ tab to the `Edit` view, simply add another key to `components.views.Edit[key]` with at least a `path` and `Component` property. For example:
|
||||
|
||||
```ts
|
||||
// `Collection.ts` or `Global.ts`
|
||||
@@ -317,6 +362,17 @@ export const MyCollection: SanitizedCollectionConfig = {
|
||||
MyCustomTab: {
|
||||
Component: MyCustomTab,
|
||||
path: '/my-custom-tab',
|
||||
// You an swap the entire tab component out for your own
|
||||
Tab: MyCustomTab
|
||||
},
|
||||
AnotherCustomView: {
|
||||
Component: AnotherCustomView,
|
||||
path: '/another-custom-view',
|
||||
// Or you can use the default tab component and just pass in your own label and href
|
||||
Tab: {
|
||||
label: 'Another Custom View',
|
||||
href: '/another-custom-view',
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,8 +5,6 @@ order: 100
|
||||
desc: NEEDS TO BE WRITTEN
|
||||
---
|
||||
|
||||
TODO: expand on this and make sure it looks nice, talk about how if you use a `process.env.SERVER_URL`, it might not be usable in your Payload config in your admin UI, but it needs to be, so it should be prefixed like `process.env.PAYLOAD_PUBLIC_SERVER_URL` to work in both places
|
||||
|
||||
## Admin environment vars
|
||||
|
||||
<Banner type="warning">
|
||||
|
||||
@@ -5,18 +5,16 @@ order: 70
|
||||
desc: Learn how to exclude server-only code from the Payload Admin UI bundle
|
||||
---
|
||||
|
||||
### Aliasing server-only modules
|
||||
|
||||
Because the Admin Panel browser bundle includes your Payload Config file, files using server-only modules need to be excluded.
|
||||
It's common for your config to rely on server only modules to perform logic in access control functions, hooks, and other contexts.
|
||||
|
||||
Any file that imports a server-only module such as `fs`, `stripe`, `authorizenet`, `nodemailer`, etc. **cannot** be included in the browser bundle.
|
||||
|
||||
#### Let's Look at an example
|
||||
#### Example Scenario
|
||||
|
||||
Say we have a collection called `Subscriptions` that has a `beforeChange` hook that creates a Stripe subscription whenever a Subscription document is created in Payload.
|
||||
|
||||
##### Your collection config
|
||||
**Collection config**:
|
||||
|
||||
```ts
|
||||
// collections/Subscriptions/index.ts
|
||||
@@ -39,7 +37,7 @@ export const Subscription: CollectionConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
##### Your collection hook
|
||||
**Collection hook**:
|
||||
|
||||
```ts
|
||||
// collections/Subscriptions/hooks/createStripeSubscription.ts
|
||||
@@ -83,7 +81,7 @@ export const createStripeSubscription = async ({ data, operation }) => {
|
||||
|
||||
#### How to fix this
|
||||
|
||||
You need to make sure that you use `alias`es to tell your bundler to import "safe" files vs. attempting to import any server-side code that you need to get rid of. Depending on your bundler (Webpack, Vite, etc.) the steps involved may be slightly different.
|
||||
You need to make sure that you use `alias`es to tell your bundler to import "safe" files vs. attempting to import any server-side code that you need to get rid of. Depending on your bundler (Webpack, Vite, etc.) the steps involved may be slightly different.
|
||||
|
||||
The basic idea is to create a file that exports an empty object, and then alias import paths of any files that import server-only modules to that empty object file.
|
||||
|
||||
|
||||
@@ -347,7 +347,7 @@ The `useForm` hook returns an object with the following properties: |
|
||||
value: <strong><code>rowIndex</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "The index of the row to add",
|
||||
value: "The index of the row to add. If omitted, the row will be added to the end of the array.",
|
||||
},
|
||||
],
|
||||
[
|
||||
|
||||
@@ -29,7 +29,6 @@ It's often best practice to write your Collections in separate files and then im
|
||||
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. |
|
||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`defaultSort`** | Pass a top-level field to sort by default in the collection List view. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
|
||||
| **`pagination`** | Set pagination-specific options for this collection. [More](#pagination) |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
@@ -84,6 +83,7 @@ property on a collection's config.
|
||||
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
|
||||
| `components` | Swap in your own React components to be used within this collection. [More](/docs/admin/components#collections) |
|
||||
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More](#list-searchable-fields) |
|
||||
| **`pagination`** | Set pagination-specific options for this collection. [More](#pagination) |
|
||||
|
||||
### Preview
|
||||
|
||||
|
||||
@@ -6,10 +6,18 @@ keywords: database, migrations, ddl, sql, mongodb, postgres, documentation, Cont
|
||||
desc: Payload features first-party database migrations all done in TypeScript.
|
||||
---
|
||||
|
||||
## Migrations
|
||||
|
||||
Payload exposes a full suite of migration controls available for your use. Migration commands are accessible via the `npm run payload` command in your project directory.
|
||||
|
||||
Ensure you have an npm script called "payload" in your `package.json` file.
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Banner>
|
||||
Note that you need to run Payload migrations through the package manager that you are using, because Payload should not be globally installed on your system.
|
||||
</Banner>
|
||||
@@ -38,7 +46,7 @@ export async function down({ payload }: MigrateDownArgs): Promise<void> {
|
||||
};
|
||||
```
|
||||
|
||||
All database adapters should implement similar migration patterns, but there will be small differences based on the adapter and its specific needs. Below is a list of all migration commands that should be supported by your database adapter.
|
||||
All database adapters should implement similar migration patterns, but there will be small differences based on the adapter and its specific needs. Below is a list of all migration commands that should be supported by your database adapter.
|
||||
|
||||
### Migrate
|
||||
|
||||
@@ -92,7 +100,7 @@ npm run payload migrate:reset
|
||||
|
||||
### Fresh
|
||||
|
||||
Drops all entities from the database and re-runs all migrations from scratch.
|
||||
Drops all entities from the database and re-runs all migrations from scratch.
|
||||
|
||||
```text
|
||||
npm run payload migrate:fresh
|
||||
|
||||
@@ -11,8 +11,8 @@ keywords: documentation, getting started, guide, Content Management System, cms,
|
||||
Payload requires the following software:
|
||||
|
||||
- Yarn or NPM
|
||||
- Node.js version 14+
|
||||
- A Database (MongoDB or Postgres)
|
||||
- Node.js version 16+
|
||||
- Any [compatible database](/docs/database/overview) (MongoDB or Postgres)
|
||||
|
||||
<Banner type="warning">
|
||||
Before proceeding any further, please ensure that you have the above requirements met.
|
||||
@@ -104,43 +104,43 @@ PAYLOAD_SECRET=your-payload-secret
|
||||
|
||||
Here is a list of all properties available to pass through `payload.init`:
|
||||
|
||||
##### `secret`
|
||||
##### secret
|
||||
**Required**. This is a secure string that will be used to authenticate with Payload. It can be random but should be at least 14 characters and be very difficult to guess.
|
||||
|
||||
Payload uses this secret key to generate secure user tokens (JWT). Behind the scenes, we do not use your secret key to encrypt directly - instead, we first take the secret key and create an encrypted string using the SHA-256 hash function. Then, we reduce the encrypted string to its first 32 characters. This final value is what Payload uses for encryption.
|
||||
|
||||
##### `config`
|
||||
##### config
|
||||
|
||||
Allows you to pass your config directly to the onInit function. The config passed here should match the payload.config file.
|
||||
|
||||
##### `disableOnInit`
|
||||
##### disableOnInit
|
||||
|
||||
A boolean that disables running your `onInit` function when Payload starts up.
|
||||
|
||||
##### `disableDBConnect`
|
||||
##### disableDBConnect
|
||||
|
||||
A boolean that disables the database connection when Payload starts up.
|
||||
|
||||
##### `email`
|
||||
##### email
|
||||
|
||||
An object used to configure SMTP. [Read more](/docs/email/overview).
|
||||
|
||||
##### `express`
|
||||
##### express
|
||||
This is your Express app as shown above. Payload will tie into your existing `app` and scope all of its functionalities to sub-routers. By default, Payload will add an `/admin` router and an `/api` router, but you can customize these paths.
|
||||
|
||||
##### `local`
|
||||
##### local
|
||||
|
||||
A boolean that when set to `true` tells Payload to start in local-only mode which will bypass setting up API routes. When set to `true`, `express` is not required. This is useful when running scripts that need to use Payload's [local-api](/docs/local-api/overview).
|
||||
|
||||
##### `loggerDestination`
|
||||
##### loggerDestination
|
||||
|
||||
Specify destination stream for the built-in Pino logger that Payload uses for internal logging. See [Pino Docs](https://getpino.io/#/docs/api?id=pino-destination) for more info on what is available.
|
||||
|
||||
##### `loggerOptions`
|
||||
##### loggerOptions
|
||||
|
||||
Specify options for the built-in Pino logger that Payload uses for internal logging. See [Pino Docs](https://getpino.io/#/docs/api?id=options) for more info on what is available.
|
||||
|
||||
##### `onInit`
|
||||
##### onInit
|
||||
|
||||
A function that is called immediately following startup that receives the Payload instance as it's only argument.
|
||||
|
||||
|
||||
@@ -412,7 +412,7 @@ dotenv.config({
|
||||
path: path.resolve(__dirname, '../.env'),
|
||||
})
|
||||
|
||||
const { PAYLOAD_SECRET, MONGODB_URI } = process.env
|
||||
const { PAYLOAD_SECRET } = process.env
|
||||
|
||||
const doAction = async (): Promise<void> => {
|
||||
await payload.init({
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Admin panel compatibility
|
||||
label: Admin compatibility
|
||||
order: 20
|
||||
order: 30
|
||||
desc: NEEDS TO BE WRITTEN
|
||||
---
|
||||
|
||||
TODO: talk about how plugins need to ensure compatibility with both Vite and Webpack
|
||||
|
||||
- It's best practice to alias your plugin to an admin-only version, so if you have admin-only changes, put them in the admin plugin, and then leave the full plugin, complete with server code, to be installed on the server side
|
||||
<Banner type="success">
|
||||
COMING SOON: This page is a work in progress. Check back soon for more information.
|
||||
</Banner>
|
||||
|
||||
294
docs/plugins/build-your-own.mdx
Normal file
294
docs/plugins/build-your-own.mdx
Normal file
@@ -0,0 +1,294 @@
|
||||
---
|
||||
title: Building Your Own Plugin
|
||||
label: Build Your Own
|
||||
order: 20
|
||||
desc: Starting to build your own plugin? Find everything you need and learn best practices with the Payload plugin template.
|
||||
keywords: plugins, template, config, configuration, extensions, custom, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
Building your own plugin is easy, and if you're already familiar with Payload then you'll have everything you need to get started. You can either start from scratch or use the Payload plugin template to get up and running quickly.
|
||||
|
||||
<Banner type="success">
|
||||
To use the template, run `npx create-payload-app@latest -t plugin -n my-new-plugin` directly in your terminal or [clone the template directly from GitHub](https://github.com/payloadcms/payload-plugin-template).
|
||||
</Banner>
|
||||
|
||||
Our plugin template includes everything you need to build a full life-cycle plugin:
|
||||
|
||||
* Example files and functions for extending the payload config
|
||||
* A local dev environment to develop the plugin
|
||||
* Test suite with integrated GitHub workflow
|
||||
|
||||
By abstracting your code into a plugin, you'll be able to reuse your feature across multiple projects and make it available for other developers to use.
|
||||
|
||||
### Plugins Recap
|
||||
|
||||
Here is a brief recap of how to integrate plugins with Payload, to learn more head back to the [plugin overview page](https://payloadcms.com/docs/plugins/overview).
|
||||
|
||||
|
||||
#### How to install a plugin
|
||||
|
||||
To install any plugin, simply add it to your Payload config in the plugins array.
|
||||
|
||||
```
|
||||
import samplePlugin from 'sample-plugin';
|
||||
|
||||
const config = buildConfig({
|
||||
plugins: [
|
||||
// Add plugins here
|
||||
samplePlugin({
|
||||
enabled: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
|
||||
#### Initialization
|
||||
|
||||
The initialization process goes in the following order:
|
||||
|
||||
1. Incoming config is validated
|
||||
2. Plugins execute
|
||||
3. Default options are integrated
|
||||
4. Sanitization cleans and validates data
|
||||
5. Final config gets initialized
|
||||
|
||||
|
||||
### Plugin Template
|
||||
|
||||
In the [Payload plugin template](https://github.com/payloadcms/payload-plugin-template), you will see a common file structure that is used across plugins:
|
||||
|
||||
1. root folder - general configuration
|
||||
2. /src folder - everything related to the plugin
|
||||
3. /dev folder - sanitized test project for development
|
||||
|
||||
|
||||
#### Root
|
||||
|
||||
In the root folder, you will see various files related to the configuration of the plugin. We set up our environment in a similar manner in Payload core and across other projects. The only two files you need to modify are:
|
||||
|
||||
* **README**.md - This contains instructions on how to use the template. When you are ready, update this to contain instructions on how to use your Plugin.
|
||||
* **package**.json - Contains necessary scripts and dependencies. Overwrite the metadata in this file to describe your Plugin.
|
||||
|
||||
|
||||
#### Dev
|
||||
|
||||
The purpose of the **dev** folder is to provide a sanitized local Payload project. so you can run and test your plugin while you are actively developing it.
|
||||
|
||||
Do **not** store any of the plugin functionality in this folder - it is purely an environment to _assist_ you with developing the plugin.
|
||||
|
||||
If you're starting from scratch, you can easily setup a dev environment like this:
|
||||
|
||||
```
|
||||
mkdir dev
|
||||
cd dev
|
||||
npx create-payload-app
|
||||
```
|
||||
|
||||
If you're using the plugin template, the dev folder is built out for you and the `samplePlugin` has already been installed in `dev/payload.config()`.
|
||||
|
||||
```
|
||||
plugins: [
|
||||
// when you rename the plugin or add options, make sure to update it here
|
||||
samplePlugin({
|
||||
enabled: false,
|
||||
})
|
||||
]
|
||||
```
|
||||
|
||||
You can add to the `dev/payload.config` and build out the dev project as needed to test your plugin.
|
||||
|
||||
When you're ready to start development, navigate into this folder with `cd dev`
|
||||
|
||||
And then start the project with `yarn dev` and pull up `http://localhost:3000` in your browser.
|
||||
|
||||
|
||||
### Testing
|
||||
|
||||
Another benefit of the dev folder is that you have the perfect environment established for testing.
|
||||
|
||||
A good test suite is essential to ensure quality and stability in your plugin. Payload typically uses [Jest](https://jestjs.io/); a popular testing framework, widely used for testing JavaScript and particularly for applications built with React.
|
||||
|
||||
Jest organizes tests into test suites and cases. We recommend creating tests based on the expected behavior of your plugin from start to finish. Read more about tests in the [Jest documentation.](https://jestjs.io/)
|
||||
|
||||
The plugin template provides a stubbed out test suite at `dev/plugin.spec.ts` which is ready to go - just add in your own test conditions and you're all set!
|
||||
|
||||
```
|
||||
import payload from 'payload'
|
||||
|
||||
describe('Plugin tests', () => {
|
||||
// Example test to check for seeded data
|
||||
it('seeds data accordingly', async () => {
|
||||
const newCollectionQuery = await payload.find({
|
||||
collection: 'newCollection',
|
||||
sort: 'createdAt',
|
||||
})
|
||||
|
||||
newCollection = newCollectionQuery.docs
|
||||
|
||||
expect(newCollectionQuery.totalDocs).toEqual(1)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### Seeding data
|
||||
|
||||
For development and testing, you will likely need some data to work with. You can streamline this process by seeding and dropping your database - instead of manually entering data.
|
||||
|
||||
In the plugin template, you can navigate to `dev/src/server.ts` and see an example seed function.
|
||||
|
||||
```
|
||||
if (process.env.PAYLOAD_SEED === 'true') {
|
||||
await seed(payload)
|
||||
}
|
||||
```
|
||||
|
||||
A sample seed function has been created for you at `dev/src/seed`, update this file with additional data as needed.
|
||||
|
||||
```
|
||||
export const seed = async (payload: Payload): Promise<void> => {
|
||||
payload.logger.info('Seeding data...')
|
||||
|
||||
await payload.create({
|
||||
collection: 'new-collection',
|
||||
data: {
|
||||
title: 'Seeded title',
|
||||
},
|
||||
})
|
||||
|
||||
// Add additional seed data here
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### Src
|
||||
|
||||
Now that we have our environment setup and dev project ready to go - it's time to build the plugin!
|
||||
|
||||
|
||||
**index.ts**
|
||||
|
||||
First up, the `src/index.ts` file - this is where the plugin should be imported from. It is best practice not to build the plugin directly in this file, instead we use this to export the plugin and types from their respective files.
|
||||
|
||||
|
||||
**Plugin.ts**
|
||||
|
||||
To reiterate, the essence of a payload plugin is simply to extend the Payload config - and that is exactly what we are doing in this file.
|
||||
|
||||
```
|
||||
export const samplePlugin =
|
||||
(pluginOptions: PluginTypes) =>
|
||||
(incomingConfig: Config): Config => {
|
||||
let config = { ...incomingConfig }
|
||||
|
||||
// do something cool with the config here
|
||||
|
||||
return config
|
||||
}
|
||||
```
|
||||
|
||||
1. First, you need to receive the existing Payload config along with any plugin options.
|
||||
2. Then set the variable `config` to be equal to a copy of the existing config.
|
||||
3. From here, you can extend the config however you like!
|
||||
4. Finally, return the config and you're all set.
|
||||
|
||||
|
||||
### Spread Syntax
|
||||
|
||||
[Spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) (or the spread operator) is a feature in JavaScript that uses the dot notation **(...)** to spread elements from arrays, strings, or objects into various contexts.
|
||||
|
||||
We are going to use spread syntax to allow us to add data to existing arrays without losing the existing data. It is crucial to spread the existing data correctly, else this can cause adverse behavior and conflicts with Payload config and other plugins.
|
||||
|
||||
Let's say you want to build a plugin that adds a new collection:
|
||||
|
||||
```
|
||||
config.collections = [
|
||||
...(config.collections || []),
|
||||
newCollection,
|
||||
// Add additional collections here
|
||||
]
|
||||
```
|
||||
|
||||
First, you need to spread the `config.collections` to ensure that we don't lose the existing collections. Then you can add any additional collections, just as you would in a regular payload config.
|
||||
|
||||
This same logic is applied to other properties like admin, globals, hooks:
|
||||
|
||||
```
|
||||
config.globals = [
|
||||
...(config.globals || []),
|
||||
// Add additional globals here
|
||||
]
|
||||
|
||||
config.hooks = {
|
||||
...(config.hooks || {}),
|
||||
// Add additional hooks here
|
||||
}
|
||||
```
|
||||
|
||||
Some properties will be slightly different to extend, for instance the `onInit` property:
|
||||
|
||||
```
|
||||
config.onInit = async payload => {
|
||||
if (incomingConfig.onInit) await incomingConfig.onInit(payload)
|
||||
|
||||
// Add additional onInit code by using the onInitExtension function
|
||||
onInitExtension(pluginOptions, payload)
|
||||
}
|
||||
```
|
||||
|
||||
If you wish to add to the `onInit`, you must include the async/await. We don't use spread syntax in this case, instead you must await the existing `onInit` before running additional functionality.
|
||||
|
||||
In the template, we have stubbed out a basic `onInitExtension` file that you can use, if not needed feel free to delete it.
|
||||
|
||||
|
||||
### Webpack
|
||||
|
||||
If any of your files use server only packages such as fs, stripe, nodemailer, etc, they will need to be removed from the browser bundle. To do that, you can [alias the file imports with webpack](https://payloadcms.com/docs/admin/webpack#aliasing-server-only-modules).
|
||||
|
||||
When files are bundled for the browser, the import paths are essentially crawled to determine what files to include in the bundle. To prevent the server only files from making it into the bundle, we can alias their import paths to a file that can be included in the browser. This will short-circuit the import path crawling and ensure browser only code is bundled.
|
||||
|
||||
Webpack is another part of the Payload config that can be a little more tricky to extend. To help here, the template includes a helper function `extendWebpackConfig()` which takes care of spreading the existing webpack, so you can just add your new stuff:
|
||||
|
||||
```
|
||||
config.admin = {
|
||||
...(config.admin || {}),
|
||||
// Add your aliases to the helper function below
|
||||
webpack: extendWebpackConfig(incomingConfig)
|
||||
}
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
If your plugin has options, you should define and provide types for these options in a separate file which gets exported from the main `index.ts`.
|
||||
|
||||
```
|
||||
export interface PluginTypes {
|
||||
/**
|
||||
* Enable or disable plugin
|
||||
* @default false
|
||||
*/
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
If possible, include [JSDoc comments](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#types-1) to describe the options and their types. This allows a developer to see details about the options in their editor.
|
||||
|
||||
### Best practices
|
||||
|
||||
In addition to the setup covered above, here are other best practices to follow:
|
||||
|
||||
##### Providing an enable / disable option:
|
||||
For a better user experience, provide a way to disable the plugin without uninstalling it. This is especially important if your plugin adds additional webpack aliases, this will allow you to still let the webpack run to prevent errors.
|
||||
##### Include tests in your GitHub CI workflow:
|
||||
If you've configured tests for your package, integrate them into your workflow to run the tests each time you commit to the plugin repository. Learn more about [how to configure tests into your GitHub CI workflow.](https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs)
|
||||
##### Publish your finished plugin to NPM:
|
||||
The best way to share and allow others to use your plugin once it is complete is to publish an NPM package. This process is straightforward and well documented, find out more about [creating and publishing a NPM package here](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/).
|
||||
##### Add payload-plugin topic tag:
|
||||
Apply the tag **payload-plugin** to your GitHub repository. This will boost the visibility of your plugin and ensure it gets listed with [existing payload plugins](https://github.com/topics/payload-plugin).
|
||||
##### Use [Semantic Versioning](https://semver.org/) (SemVar):
|
||||
With the SemVar system you release version numbers that reflect the nature of changes (major, minor, patch). Ensure all major versions reference their Payload compatibility.
|
||||
@@ -160,7 +160,7 @@ DigitalOcean provides extremely helpful documentation that can walk you through
|
||||
|
||||
## Docker
|
||||
|
||||
This is an example of a multi-stage docker build of Payload for production. Ensure you are setting your environment variables on deployment, like `PAYLOAD_SECRET`, `PAYLOAD_CONFIG_PATH`, and `MONGODB_URI` if needed.
|
||||
This is an example of a multi-stage docker build of Payload for production. Ensure you are setting your environment variables on deployment, like `PAYLOAD_SECRET`, `PAYLOAD_CONFIG_PATH`, and `DATABASE_URI` if needed.
|
||||
|
||||
```dockerfile
|
||||
FROM node:18-alpine as base
|
||||
@@ -210,7 +210,7 @@ services:
|
||||
depends_on:
|
||||
- mongo
|
||||
environment:
|
||||
MONGODB_URI: mongodb://mongo:27017/payload
|
||||
DATABASE_URI: mongodb://mongo:27017/payload
|
||||
PORT: 3000
|
||||
NODE_ENV: development
|
||||
PAYLOAD_SECRET: TESTING
|
||||
|
||||
@@ -1 +1 @@
|
||||
NEXT_PUBLIC_CMS_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_PAYLOAD_URL=http://localhost:3000
|
||||
|
||||
@@ -8,7 +8,7 @@ export const USER = `
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const gql = async (query: string): Promise<any> => {
|
||||
try {
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/graphql`, {
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/graphql`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
|
||||
@@ -18,7 +18,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
const create = useCallback<Create>(
|
||||
async args => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users`, args)
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users`, args)
|
||||
setUser(user)
|
||||
return user
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
const login = useCallback<Login>(
|
||||
async args => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/login`, args)
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/login`, args)
|
||||
setUser(user)
|
||||
return user
|
||||
}
|
||||
@@ -64,7 +64,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
|
||||
const logout = useCallback<Logout>(async () => {
|
||||
if (api === 'rest') {
|
||||
await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/logout`)
|
||||
await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/logout`)
|
||||
setUser(null)
|
||||
return
|
||||
}
|
||||
@@ -83,7 +83,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
const fetchMe = async () => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(
|
||||
`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/me`,
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/me`,
|
||||
{},
|
||||
{
|
||||
method: 'GET',
|
||||
@@ -113,7 +113,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
async args => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(
|
||||
`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/forgot-password`,
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/forgot-password`,
|
||||
args,
|
||||
)
|
||||
setUser(user)
|
||||
@@ -134,7 +134,10 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
const resetPassword = useCallback<ResetPassword>(
|
||||
async args => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/reset-password`, args)
|
||||
const user = await rest(
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/reset-password`,
|
||||
args,
|
||||
)
|
||||
setUser(user)
|
||||
return user
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export const getMeUser = async (args?: {
|
||||
const cookieStore = cookies()
|
||||
const token = cookieStore.get('payload-token')?.value
|
||||
|
||||
const meUserReq = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/me`, {
|
||||
const meUserReq = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/me`, {
|
||||
headers: {
|
||||
Authorization: `JWT ${token}`,
|
||||
},
|
||||
|
||||
@@ -39,15 +39,18 @@ export const AccountForm: React.FC = () => {
|
||||
const onSubmit = useCallback(
|
||||
async (data: FormData) => {
|
||||
if (user) {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/${user.id}`, {
|
||||
// Make sure to include cookies with fetch
|
||||
credentials: 'include',
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/${user.id}`,
|
||||
{
|
||||
// Make sure to include cookies with fetch
|
||||
credentials: 'include',
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
if (response.ok) {
|
||||
const json = await response.json()
|
||||
|
||||
@@ -22,7 +22,7 @@ export default async function Account() {
|
||||
<h1>Account</h1>
|
||||
<p>
|
||||
{`This is your account dashboard. Here you can update your account information and more. To manage all users, `}
|
||||
<Link href={`${process.env.NEXT_PUBLIC_CMS_URL}/admin/collections/users`}>
|
||||
<Link href={`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/admin/collections/users`}>
|
||||
login to the admin dashboard
|
||||
</Link>
|
||||
{'.'}
|
||||
|
||||
@@ -38,7 +38,7 @@ export const CreateAccountForm: React.FC = () => {
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (data: FormData) => {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users`, {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
@@ -75,7 +75,7 @@ export const CreateAccountForm: React.FC = () => {
|
||||
<form onSubmit={handleSubmit(onSubmit)} className={classes.form}>
|
||||
<p>
|
||||
{`This is where new customers can signup and create a new account. To manage all users, `}
|
||||
<Link href={`${process.env.NEXT_PUBLIC_CMS_URL}/admin/collections/users`}>
|
||||
<Link href={`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/admin/collections/users`}>
|
||||
login to the admin dashboard
|
||||
</Link>
|
||||
{'.'}
|
||||
|
||||
@@ -57,7 +57,7 @@ export const LoginForm: React.FC = () => {
|
||||
{' with the password '}
|
||||
<b>demo</b>
|
||||
{'. To manage your users, '}
|
||||
<Link href={`${process.env.NEXT_PUBLIC_CMS_URL}/admin/collections/users`}>
|
||||
<Link href={`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/admin/collections/users`}>
|
||||
login to the admin dashboard
|
||||
</Link>
|
||||
.
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function Home() {
|
||||
{' to start the authentication flow. Once logged in, you will be redirected to the '}
|
||||
<Link href="/account">account page</Link>
|
||||
{` which is restricted to users only. To manage all users, `}
|
||||
<Link href={`${process.env.NEXT_PUBLIC_CMS_URL}/admin/collections/users`}>
|
||||
<Link href={`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/admin/collections/users`}>
|
||||
login to the admin dashboard
|
||||
</Link>
|
||||
{'.'}
|
||||
|
||||
@@ -25,13 +25,16 @@ export const RecoverPasswordForm: React.FC = () => {
|
||||
} = useForm<FormData>()
|
||||
|
||||
const onSubmit = useCallback(async (data: FormData) => {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/forgot-password`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/forgot-password`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
if (response.ok) {
|
||||
setSuccess(true)
|
||||
@@ -52,7 +55,7 @@ export const RecoverPasswordForm: React.FC = () => {
|
||||
<p>
|
||||
{`Please enter your email below. You will receive an email message with instructions on
|
||||
how to reset your password. To manage your all users, `}
|
||||
<Link href={`${process.env.NEXT_PUBLIC_CMS_URL}/admin/collections/users`}>
|
||||
<Link href={`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/admin/collections/users`}>
|
||||
login to the admin dashboard
|
||||
</Link>
|
||||
{'.'}
|
||||
|
||||
@@ -32,13 +32,16 @@ export const ResetPasswordForm: React.FC = () => {
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (data: FormData) => {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/reset-password`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/reset-password`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
if (response.ok) {
|
||||
const json = await response.json()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
NEXT_PUBLIC_CMS_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_PAYLOAD_URL=http://localhost:3000
|
||||
|
||||
@@ -40,15 +40,18 @@ const Account: React.FC = () => {
|
||||
const onSubmit = useCallback(
|
||||
async (data: FormData) => {
|
||||
if (user) {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/${user.id}`, {
|
||||
// Make sure to include cookies with fetch
|
||||
credentials: 'include',
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/${user.id}`,
|
||||
{
|
||||
// Make sure to include cookies with fetch
|
||||
credentials: 'include',
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
if (response.ok) {
|
||||
const json = await response.json()
|
||||
@@ -91,7 +94,7 @@ const Account: React.FC = () => {
|
||||
<h1>Account</h1>
|
||||
<p>
|
||||
{`This is your account dashboard. Here you can update your account information and more. To manage all users, `}
|
||||
<Link href={`${process.env.NEXT_PUBLIC_CMS_URL}/admin/collections/users`}>
|
||||
<Link href={`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/admin/collections/users`}>
|
||||
login to the admin dashboard
|
||||
</Link>
|
||||
{'.'}
|
||||
|
||||
@@ -38,7 +38,7 @@ const CreateAccount: React.FC = () => {
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (data: FormData) => {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users`, {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
@@ -78,7 +78,7 @@ const CreateAccount: React.FC = () => {
|
||||
<form onSubmit={handleSubmit(onSubmit)} className={classes.form}>
|
||||
<p>
|
||||
{`This is where new customers can signup and create a new account. To manage all users, `}
|
||||
<Link href={`${process.env.NEXT_PUBLIC_CMS_URL}/admin/collections/users`}>
|
||||
<Link href={`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/admin/collections/users`}>
|
||||
login to the admin dashboard
|
||||
</Link>
|
||||
{'.'}
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function Home() {
|
||||
{' to start the authentication flow. Once logged in, you will be redirected to the '}
|
||||
<Link href="/account">account page</Link>
|
||||
{` which is restricted to users only. To manage all users, `}
|
||||
<Link href={`${process.env.NEXT_PUBLIC_CMS_URL}/admin/collections/users`}>
|
||||
<Link href={`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/admin/collections/users`}>
|
||||
login to the admin dashboard
|
||||
</Link>
|
||||
{'.'}
|
||||
|
||||
@@ -60,7 +60,7 @@ const Login: React.FC = () => {
|
||||
{' with the password '}
|
||||
<b>demo</b>
|
||||
{'. To manage your users, '}
|
||||
<Link href={`${process.env.NEXT_PUBLIC_CMS_URL}/admin/collections/users`}>
|
||||
<Link href={`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/admin/collections/users`}>
|
||||
login to the admin dashboard
|
||||
</Link>
|
||||
.
|
||||
|
||||
@@ -24,13 +24,16 @@ const RecoverPassword: React.FC = () => {
|
||||
} = useForm<FormData>()
|
||||
|
||||
const onSubmit = useCallback(async (data: FormData) => {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/forgot-password`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/forgot-password`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
if (response.ok) {
|
||||
setSuccess(true)
|
||||
@@ -51,7 +54,7 @@ const RecoverPassword: React.FC = () => {
|
||||
<p>
|
||||
{`Please enter your email below. You will receive an email message with instructions on
|
||||
how to reset your password. To manage your all users, `}
|
||||
<Link href={`${process.env.NEXT_PUBLIC_CMS_URL}/admin/collections/users`}>
|
||||
<Link href={`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/admin/collections/users`}>
|
||||
login to the admin dashboard
|
||||
</Link>
|
||||
{'.'}
|
||||
|
||||
@@ -31,13 +31,16 @@ const ResetPassword: React.FC = () => {
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (data: FormData) => {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/reset-password`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/reset-password`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
if (response.ok) {
|
||||
const json = await response.json()
|
||||
|
||||
@@ -8,7 +8,7 @@ export const USER = `
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const gql = async (query): Promise<any> => {
|
||||
try {
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/graphql`, {
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/graphql`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
|
||||
@@ -16,7 +16,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
const create = useCallback<Create>(
|
||||
async args => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users`, args)
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users`, args)
|
||||
setUser(user)
|
||||
return user
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
const login = useCallback<Login>(
|
||||
async args => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/login`, args)
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/login`, args)
|
||||
setUser(user)
|
||||
return user
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
|
||||
const logout = useCallback<Logout>(async () => {
|
||||
if (api === 'rest') {
|
||||
await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/logout`)
|
||||
await rest(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/logout`)
|
||||
setUser(null)
|
||||
return
|
||||
}
|
||||
@@ -81,7 +81,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
const fetchMe = async () => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(
|
||||
`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/me`,
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/me`,
|
||||
{},
|
||||
{
|
||||
method: 'GET',
|
||||
@@ -111,7 +111,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
async args => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(
|
||||
`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/forgot-password`,
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/forgot-password`,
|
||||
args,
|
||||
)
|
||||
setUser(user)
|
||||
@@ -132,7 +132,10 @@ export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' |
|
||||
const resetPassword = useCallback<ResetPassword>(
|
||||
async args => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/reset-password`, args)
|
||||
const user = await rest(
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/reset-password`,
|
||||
args,
|
||||
)
|
||||
setUser(user)
|
||||
return user
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
PAYLOAD_PUBLIC_SITE_URL=http://localhost:3001
|
||||
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
|
||||
MONGODB_URI=mongodb://127.0.0.1/payload-example-auth
|
||||
DATABASE_URI=mongodb://127.0.0.1/payload-example-auth
|
||||
PAYLOAD_SECRET=PAYLOAD_AUTH_EXAMPLE_SECRET_KEY
|
||||
COOKIE_DOMAIN=localhost
|
||||
PAYLOAD_PUBLIC_SEED=true
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
"lint:fix": "eslint --fix --ext .ts,.tsx src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
|
||||
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
|
||||
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"payload": "latest"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { User } from 'payload/generated-types'
|
||||
import type { FieldHook } from 'payload/types'
|
||||
|
||||
import type { User } from '../../payload-types'
|
||||
|
||||
// ensure there is always a `user` role
|
||||
// do not let non-admins change roles
|
||||
export const protectRoles: FieldHook<User & { id: string }> = async ({ req, data }) => {
|
||||
|
||||
@@ -8,23 +8,61 @@
|
||||
|
||||
export interface Config {
|
||||
collections: {
|
||||
users: User;
|
||||
};
|
||||
globals: {};
|
||||
users: User
|
||||
'payload-preferences': PayloadPreference
|
||||
'payload-migrations': PayloadMigration
|
||||
}
|
||||
globals: {}
|
||||
}
|
||||
export interface User {
|
||||
id: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
roles?: ('admin' | 'user')[];
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpiration?: string;
|
||||
salt?: string;
|
||||
hash?: string;
|
||||
loginAttempts?: number;
|
||||
lockUntil?: string;
|
||||
password?: string;
|
||||
id: string
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
roles?: ('admin' | 'user')[]
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
email: string
|
||||
resetPasswordToken?: string
|
||||
resetPasswordExpiration?: string
|
||||
salt?: string
|
||||
hash?: string
|
||||
loginAttempts?: number
|
||||
lockUntil?: string
|
||||
password?: string
|
||||
}
|
||||
export interface PayloadPreference {
|
||||
id: string
|
||||
user: {
|
||||
relationTo: 'users'
|
||||
value: string | User
|
||||
}
|
||||
key?: string
|
||||
value?:
|
||||
| {
|
||||
[k: string]: unknown
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface PayloadMigration {
|
||||
id: string
|
||||
name?: string
|
||||
batch?: number
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
declare module 'payload' {
|
||||
export interface GeneratedTypes {
|
||||
collections: {
|
||||
users: User
|
||||
'payload-preferences': PayloadPreference
|
||||
'payload-migrations': PayloadMigration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { webpackBundler } from '@payloadcms/bundler-webpack'
|
||||
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
import { slateEditor } from '@payloadcms/richtext-slate'
|
||||
import path from 'path'
|
||||
import { buildConfig } from 'payload/config'
|
||||
|
||||
@@ -7,10 +10,15 @@ import BeforeLogin from './components/BeforeLogin'
|
||||
export default buildConfig({
|
||||
collections: [Users],
|
||||
admin: {
|
||||
bundler: webpackBundler(),
|
||||
components: {
|
||||
beforeLogin: [BeforeLogin],
|
||||
},
|
||||
},
|
||||
editor: slateEditor({}),
|
||||
db: mongooseAdapter({
|
||||
url: process.env.DATABASE_URI,
|
||||
}),
|
||||
cors: [
|
||||
process.env.PAYLOAD_PUBLIC_SERVER_URL || '',
|
||||
process.env.PAYLOAD_PUBLIC_SITE_URL || '',
|
||||
|
||||
@@ -18,7 +18,6 @@ app.get('/', (_, res) => {
|
||||
const start = async (): Promise<void> => {
|
||||
await payload.init({
|
||||
secret: process.env.PAYLOAD_SECRET,
|
||||
mongoURL: process.env.MONGODB_URI,
|
||||
express: app,
|
||||
onInit: () => {
|
||||
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"paths": {
|
||||
"payload/generated-types": [
|
||||
"./src/payload-types.ts"
|
||||
],
|
||||
"node_modules/*": [
|
||||
"./node_modules/*"
|
||||
]
|
||||
@@ -36,4 +33,4 @@
|
||||
"ts-node": {
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
MONGODB_URI=mongodb://127.0.0.1/payload-example-custom-server
|
||||
DATABASE_URI=mongodb://127.0.0.1/payload-example-custom-server
|
||||
PAYLOAD_SECRET=PAYLOAD_CUSTOM_SERVER_EXAMPLE_SECRET_KEY
|
||||
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_SERVER_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_PAYLOAD_URL=http://localhost:3000
|
||||
PAYLOAD_PUBLIC_SEED=true
|
||||
PAYLOAD_DROP_DATABASE=true
|
||||
|
||||
@@ -4,6 +4,6 @@ module.exports = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
images: {
|
||||
domains: ['localhost', process.env.NEXT_PUBLIC_SERVER_URL],
|
||||
domains: ['localhost', process.env.NEXT_PUBLIC_PAYLOAD_URL],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
"lint:fix": "eslint --fix --ext .ts,.tsx src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
|
||||
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
|
||||
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
|
||||
"dotenv": "^8.2.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"express": "^4.17.1",
|
||||
|
||||
3
examples/custom-server/src/dotenv.js
Normal file
3
examples/custom-server/src/dotenv.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
config: () => null,
|
||||
}
|
||||
@@ -22,10 +22,6 @@ interface Args {
|
||||
}
|
||||
|
||||
export const getPayloadClient = async ({ initOptions, seed }: Args = {}): Promise<Payload> => {
|
||||
if (!process.env.MONGODB_URI) {
|
||||
throw new Error('MONGODB_URI environment variable is missing')
|
||||
}
|
||||
|
||||
if (!process.env.PAYLOAD_SECRET) {
|
||||
throw new Error('PAYLOAD_SECRET environment variable is missing')
|
||||
}
|
||||
@@ -36,7 +32,6 @@ export const getPayloadClient = async ({ initOptions, seed }: Args = {}): Promis
|
||||
|
||||
if (!cached.promise) {
|
||||
cached.promise = payload.init({
|
||||
mongoURL: process.env.MONGODB_URI,
|
||||
secret: process.env.PAYLOAD_SECRET,
|
||||
local: initOptions?.express ? false : true,
|
||||
...(initOptions || {}),
|
||||
|
||||
@@ -8,31 +8,70 @@
|
||||
|
||||
export interface Config {
|
||||
collections: {
|
||||
pages: Page;
|
||||
users: User;
|
||||
};
|
||||
globals: {};
|
||||
pages: Page
|
||||
users: User
|
||||
'payload-preferences': PayloadPreference
|
||||
'payload-migrations': PayloadMigration
|
||||
}
|
||||
globals: {}
|
||||
}
|
||||
export interface Page {
|
||||
id: string;
|
||||
title: string;
|
||||
id: string
|
||||
title: string
|
||||
richText?: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
slug?: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
slug?: string
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface User {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpiration?: string;
|
||||
salt?: string;
|
||||
hash?: string;
|
||||
loginAttempts?: number;
|
||||
lockUntil?: string;
|
||||
password?: string;
|
||||
id: string
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
email: string
|
||||
resetPasswordToken?: string
|
||||
resetPasswordExpiration?: string
|
||||
salt?: string
|
||||
hash?: string
|
||||
loginAttempts?: number
|
||||
lockUntil?: string
|
||||
password?: string
|
||||
}
|
||||
export interface PayloadPreference {
|
||||
id: string
|
||||
user: {
|
||||
relationTo: 'users'
|
||||
value: string | User
|
||||
}
|
||||
key?: string
|
||||
value?:
|
||||
| {
|
||||
[k: string]: unknown
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface PayloadMigration {
|
||||
id: string
|
||||
name?: string
|
||||
batch?: number
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
declare module 'payload' {
|
||||
export interface GeneratedTypes {
|
||||
collections: {
|
||||
pages: Page
|
||||
users: User
|
||||
'payload-preferences': PayloadPreference
|
||||
'payload-migrations': PayloadMigration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { webpackBundler } from '@payloadcms/bundler-webpack'
|
||||
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
import { slateEditor } from '@payloadcms/richtext-slate'
|
||||
import dotenv from 'dotenv'
|
||||
import path from 'path'
|
||||
|
||||
@@ -14,10 +17,25 @@ export default buildConfig({
|
||||
serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL || '',
|
||||
collections: [Pages],
|
||||
admin: {
|
||||
bundler: webpackBundler(),
|
||||
components: {
|
||||
beforeLogin: [BeforeLogin],
|
||||
},
|
||||
webpack: config => ({
|
||||
...config,
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
alias: {
|
||||
...config.resolve.alias,
|
||||
dotenv: path.resolve(__dirname, './dotenv.js'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
editor: slateEditor({}),
|
||||
db: mongooseAdapter({
|
||||
url: process.env.DATABASE_URI,
|
||||
}),
|
||||
typescript: {
|
||||
outputFile: path.resolve(__dirname, 'payload-types.ts'),
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
||||
NEXT_PUBLIC_CMS_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_PAYLOAD_URL=http://localhost:3000
|
||||
NEXT_PRIVATE_DRAFT_SECRET=EXAMPLE_DRAFT_SECRET
|
||||
NEXT_PRIVATE_REVALIDATION_KEY=EXAMPLE_REVALIDATION_KEY
|
||||
|
||||
@@ -16,7 +16,7 @@ export const fetchPage = async (
|
||||
const pageRes: {
|
||||
docs: Page[]
|
||||
} = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_CMS_URL}/api/pages?where[slug][equals]=${slug}${
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/pages?where[slug][equals]=${slug}${
|
||||
draft && payloadToken ? '&draft=true' : ''
|
||||
}`,
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Page } from '../../payload-types'
|
||||
export const fetchPages = async (): Promise<Page[]> => {
|
||||
const pageRes: {
|
||||
docs: Page[]
|
||||
} = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/pages?depth=0&limit=100`).then(res =>
|
||||
} = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/pages?depth=0&limit=100`).then(res =>
|
||||
res.json(),
|
||||
) // eslint-disable-line function-paren-newline
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export const AdminBarClient: React.FC<PayloadAdminBarProps> = props => {
|
||||
<PayloadAdminBar
|
||||
{...props}
|
||||
logo={<Title />}
|
||||
cmsURL={process.env.NEXT_PUBLIC_CMS_URL}
|
||||
cmsURL={process.env.NEXT_PUBLIC_PAYLOAD_URL}
|
||||
onPreviewExit={async () => {
|
||||
await fetch(`/api/exit-preview`)
|
||||
window.location.reload()
|
||||
|
||||
@@ -10,7 +10,7 @@ import classes from './index.module.scss'
|
||||
|
||||
export async function Header() {
|
||||
const mainMenu: MainMenu = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_CMS_URL}/api/globals/main-menu`,
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/globals/main-menu`,
|
||||
).then(res => res.json())
|
||||
|
||||
const { navItems } = mainMenu
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function GET(
|
||||
}
|
||||
|
||||
// validate the Payload token
|
||||
const userReq = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/me`, {
|
||||
const userReq = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/me`, {
|
||||
headers: {
|
||||
Authorization: `JWT ${payloadToken}`,
|
||||
},
|
||||
|
||||
@@ -8,52 +8,94 @@
|
||||
|
||||
export interface Config {
|
||||
collections: {
|
||||
pages: Page;
|
||||
users: User;
|
||||
};
|
||||
pages: Page
|
||||
users: User
|
||||
'payload-preferences': PayloadPreference
|
||||
'payload-migrations': PayloadMigration
|
||||
}
|
||||
globals: {
|
||||
'main-menu': MainMenu;
|
||||
};
|
||||
'main-menu': MainMenu
|
||||
}
|
||||
}
|
||||
export interface Page {
|
||||
id: string;
|
||||
title: string;
|
||||
slug?: string;
|
||||
id: string
|
||||
title: string
|
||||
slug?: string
|
||||
richText: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: 'draft' | 'published';
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
_status?: 'draft' | 'published'
|
||||
}
|
||||
export interface User {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpiration?: string;
|
||||
salt?: string;
|
||||
hash?: string;
|
||||
loginAttempts?: number;
|
||||
lockUntil?: string;
|
||||
password?: string;
|
||||
id: string
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
email: string
|
||||
resetPasswordToken?: string
|
||||
resetPasswordExpiration?: string
|
||||
salt?: string
|
||||
hash?: string
|
||||
loginAttempts?: number
|
||||
lockUntil?: string
|
||||
password?: string
|
||||
}
|
||||
export interface PayloadPreference {
|
||||
id: string
|
||||
user: {
|
||||
relationTo: 'users'
|
||||
value: string | User
|
||||
}
|
||||
key?: string
|
||||
value?:
|
||||
| {
|
||||
[k: string]: unknown
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface PayloadMigration {
|
||||
id: string
|
||||
name?: string
|
||||
batch?: number
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface MainMenu {
|
||||
id: string;
|
||||
id: string
|
||||
navItems?: {
|
||||
link: {
|
||||
type?: 'reference' | 'custom';
|
||||
newTab?: boolean;
|
||||
type?: 'reference' | 'custom'
|
||||
newTab?: boolean
|
||||
reference: {
|
||||
value: string | Page;
|
||||
relationTo: 'pages';
|
||||
};
|
||||
url: string;
|
||||
label: string;
|
||||
};
|
||||
id?: string;
|
||||
}[];
|
||||
updatedAt?: string;
|
||||
createdAt?: string;
|
||||
relationTo: 'pages'
|
||||
value: string | Page
|
||||
}
|
||||
url: string
|
||||
label: string
|
||||
}
|
||||
id?: string
|
||||
}[]
|
||||
updatedAt?: string
|
||||
createdAt?: string
|
||||
}
|
||||
|
||||
declare module 'payload' {
|
||||
export interface GeneratedTypes {
|
||||
collections: {
|
||||
pages: Page
|
||||
users: User
|
||||
'payload-preferences': PayloadPreference
|
||||
'payload-migrations': PayloadMigration
|
||||
}
|
||||
globals: {
|
||||
'main-menu': MainMenu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
NEXT_PUBLIC_CMS_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_PAYLOAD_URL=http://localhost:3000
|
||||
NEXT_PRIVATE_REVALIDATION_KEY=EXAMPLE_REVALIDATION_KEY
|
||||
|
||||
@@ -20,7 +20,7 @@ export const AdminBar: React.FC<{
|
||||
<PayloadAdminBar
|
||||
{...adminBarProps}
|
||||
logo={<Title />}
|
||||
cmsURL={process.env.NEXT_PUBLIC_CMS_URL}
|
||||
cmsURL={process.env.NEXT_PUBLIC_PAYLOAD_URL}
|
||||
onAuthChange={setUser}
|
||||
className={classes.payloadAdminBar}
|
||||
classNames={{
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Gutter } from '../components/Gutter'
|
||||
import RichText from '../components/RichText'
|
||||
import type { MainMenu, Page as PageType } from '../payload-types'
|
||||
|
||||
import classes from './[slug].module.scss';
|
||||
import classes from './[slug].module.scss'
|
||||
|
||||
const Page: React.FC<
|
||||
PageType & {
|
||||
@@ -67,7 +67,7 @@ export const getStaticProps: GetStaticProps = async (context: GetStaticPropsCont
|
||||
)
|
||||
|
||||
// when previewing, send the payload token to bypass draft access control
|
||||
const pageReq = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/pages${searchParams}`, {
|
||||
const pageReq = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/pages${searchParams}`, {
|
||||
headers: {
|
||||
...(preview
|
||||
? {
|
||||
@@ -110,7 +110,7 @@ export const getStaticPaths: GetStaticPaths = async () => {
|
||||
let paths: Paths = []
|
||||
|
||||
const pagesReq = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_CMS_URL}/api/pages?where[_status][equals]=published&depth=0&limit=300`,
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/pages?where[_status][equals]=published&depth=0&limit=300`,
|
||||
)
|
||||
|
||||
const pagesData = await pagesReq.json()
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface IGlobals {
|
||||
}
|
||||
|
||||
export const getAllGlobals = async (): Promise<IGlobals> => {
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/globals/main-menu?depth=1`)
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/globals/main-menu?depth=1`)
|
||||
const mainMenu = await res.json()
|
||||
|
||||
return {
|
||||
|
||||
@@ -20,7 +20,7 @@ const preview = async (req: NextApiRequest, res: NextApiResponse): Promise<void>
|
||||
}
|
||||
|
||||
// validate the Payload token
|
||||
const userReq = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/me`, {
|
||||
const userReq = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/users/me`, {
|
||||
headers: {
|
||||
Authorization: `JWT ${payloadToken}`,
|
||||
},
|
||||
|
||||
@@ -8,52 +8,94 @@
|
||||
|
||||
export interface Config {
|
||||
collections: {
|
||||
pages: Page;
|
||||
users: User;
|
||||
};
|
||||
pages: Page
|
||||
users: User
|
||||
'payload-preferences': PayloadPreference
|
||||
'payload-migrations': PayloadMigration
|
||||
}
|
||||
globals: {
|
||||
'main-menu': MainMenu;
|
||||
};
|
||||
'main-menu': MainMenu
|
||||
}
|
||||
}
|
||||
export interface Page {
|
||||
id: string;
|
||||
title: string;
|
||||
slug?: string;
|
||||
id: string
|
||||
title: string
|
||||
slug?: string
|
||||
richText: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: 'draft' | 'published';
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
_status?: 'draft' | 'published'
|
||||
}
|
||||
export interface User {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpiration?: string;
|
||||
salt?: string;
|
||||
hash?: string;
|
||||
loginAttempts?: number;
|
||||
lockUntil?: string;
|
||||
password?: string;
|
||||
id: string
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
email: string
|
||||
resetPasswordToken?: string
|
||||
resetPasswordExpiration?: string
|
||||
salt?: string
|
||||
hash?: string
|
||||
loginAttempts?: number
|
||||
lockUntil?: string
|
||||
password?: string
|
||||
}
|
||||
export interface PayloadPreference {
|
||||
id: string
|
||||
user: {
|
||||
relationTo: 'users'
|
||||
value: string | User
|
||||
}
|
||||
key?: string
|
||||
value?:
|
||||
| {
|
||||
[k: string]: unknown
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface PayloadMigration {
|
||||
id: string
|
||||
name?: string
|
||||
batch?: number
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface MainMenu {
|
||||
id: string;
|
||||
id: string
|
||||
navItems?: {
|
||||
link: {
|
||||
type?: 'reference' | 'custom';
|
||||
newTab?: boolean;
|
||||
type?: 'reference' | 'custom'
|
||||
newTab?: boolean
|
||||
reference: {
|
||||
value: string | Page;
|
||||
relationTo: 'pages';
|
||||
};
|
||||
url: string;
|
||||
label: string;
|
||||
};
|
||||
id?: string;
|
||||
}[];
|
||||
updatedAt?: string;
|
||||
createdAt?: string;
|
||||
relationTo: 'pages'
|
||||
value: string | Page
|
||||
}
|
||||
url: string
|
||||
label: string
|
||||
}
|
||||
id?: string
|
||||
}[]
|
||||
updatedAt?: string
|
||||
createdAt?: string
|
||||
}
|
||||
|
||||
declare module 'payload' {
|
||||
export interface GeneratedTypes {
|
||||
collections: {
|
||||
pages: Page
|
||||
users: User
|
||||
'payload-preferences': PayloadPreference
|
||||
'payload-migrations': PayloadMigration
|
||||
}
|
||||
globals: {
|
||||
'main-menu': MainMenu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
MONGODB_URI=mongodb://127.0.0.1/payload-example-draft-preview
|
||||
DATABASE_URI=mongodb://127.0.0.1/payload-example-draft-preview
|
||||
PAYLOAD_SECRET=ENTER-STRING-HERE
|
||||
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
|
||||
PAYLOAD_PUBLIC_SITE_URL=http://localhost:3001
|
||||
|
||||
@@ -42,7 +42,7 @@ See the [Collections](https://payloadcms.com/docs/configuration/collections) doc
|
||||
const searchParams = `?where[slug][equals]=${pageSlug}&depth=1${preview ? `&draft=true` : ''}`
|
||||
|
||||
// when previewing, send the payload token to bypass draft access control
|
||||
const pageReq = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/pages${searchParams}`, {
|
||||
const pageReq = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/pages${searchParams}`, {
|
||||
headers: {
|
||||
...preview ? {
|
||||
Authorization: `JWT ${payloadToken}`,
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
"lint:fix": "eslint --fix --ext .ts,.tsx src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
|
||||
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
|
||||
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"payload": "latest"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RichTextElement } from 'payload/dist/fields/config/types'
|
||||
import type { RichTextElement } from '@payloadcms/richtext-slate'
|
||||
|
||||
const elements: RichTextElement[] = ['blockquote', 'h2', 'h3', 'h4', 'h5', 'h6', 'link']
|
||||
const elements: RichTextElement[] = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'link']
|
||||
|
||||
export default elements
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { RichTextElement, RichTextField, RichTextLeaf } from 'payload/dist/fields/config/types'
|
||||
import { slateEditor } from '@payloadcms/richtext-slate'
|
||||
import type { RichTextElement, RichTextLeaf } from '@payloadcms/richtext-slate/dist/types'
|
||||
import type { RichTextField } from 'payload/types'
|
||||
|
||||
import deepMerge from '../../utilities/deepMerge'
|
||||
import link from '../link'
|
||||
@@ -25,60 +27,64 @@ const richText: RichText = (
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
admin: {
|
||||
upload: {
|
||||
collections: {
|
||||
media: {
|
||||
fields: [
|
||||
{
|
||||
type: 'richText',
|
||||
name: 'caption',
|
||||
label: 'Caption',
|
||||
admin: {
|
||||
elements: [...elements],
|
||||
leaves: [...leaves],
|
||||
editor: slateEditor({
|
||||
admin: {
|
||||
upload: {
|
||||
collections: {
|
||||
media: {
|
||||
fields: [
|
||||
{
|
||||
type: 'richText',
|
||||
name: 'caption',
|
||||
label: 'Caption',
|
||||
editor: slateEditor({
|
||||
admin: {
|
||||
elements: [...elements],
|
||||
leaves: [...leaves],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'radio',
|
||||
name: 'alignment',
|
||||
label: 'Alignment',
|
||||
options: [
|
||||
{
|
||||
label: 'Left',
|
||||
value: 'left',
|
||||
},
|
||||
{
|
||||
label: 'Center',
|
||||
value: 'center',
|
||||
},
|
||||
{
|
||||
label: 'Right',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'enableLink',
|
||||
type: 'checkbox',
|
||||
label: 'Enable Link',
|
||||
},
|
||||
link({
|
||||
appearances: false,
|
||||
disableLabel: true,
|
||||
overrides: {
|
||||
admin: {
|
||||
condition: (_, data) => Boolean(data?.enableLink),
|
||||
},
|
||||
{
|
||||
type: 'radio',
|
||||
name: 'alignment',
|
||||
label: 'Alignment',
|
||||
options: [
|
||||
{
|
||||
label: 'Left',
|
||||
value: 'left',
|
||||
},
|
||||
{
|
||||
label: 'Center',
|
||||
value: 'center',
|
||||
},
|
||||
{
|
||||
label: 'Right',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
{
|
||||
name: 'enableLink',
|
||||
type: 'checkbox',
|
||||
label: 'Enable Link',
|
||||
},
|
||||
link({
|
||||
appearances: false,
|
||||
disableLabel: true,
|
||||
overrides: {
|
||||
admin: {
|
||||
condition: (_, data) => Boolean(data?.enableLink),
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
elements: [...elements, ...(additions.elements || [])],
|
||||
leaves: [...leaves, ...(additions.leaves || [])],
|
||||
},
|
||||
elements: [...elements, ...(additions.elements || [])],
|
||||
leaves: [...leaves, ...(additions.leaves || [])],
|
||||
},
|
||||
}),
|
||||
},
|
||||
overrides,
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RichTextLeaf } from 'payload/dist/fields/config/types'
|
||||
import { RichTextLeaf } from '@payloadcms/richtext-slate'
|
||||
|
||||
const defaultLeaves: RichTextLeaf[] = ['bold', 'italic', 'underline']
|
||||
|
||||
|
||||
@@ -8,52 +8,94 @@
|
||||
|
||||
export interface Config {
|
||||
collections: {
|
||||
pages: Page;
|
||||
users: User;
|
||||
};
|
||||
pages: Page
|
||||
users: User
|
||||
'payload-preferences': PayloadPreference
|
||||
'payload-migrations': PayloadMigration
|
||||
}
|
||||
globals: {
|
||||
'main-menu': MainMenu;
|
||||
};
|
||||
'main-menu': MainMenu
|
||||
}
|
||||
}
|
||||
export interface Page {
|
||||
id: string;
|
||||
title: string;
|
||||
slug?: string;
|
||||
id: string
|
||||
title: string
|
||||
slug?: string
|
||||
richText: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: 'draft' | 'published';
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
_status?: 'draft' | 'published'
|
||||
}
|
||||
export interface User {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpiration?: string;
|
||||
salt?: string;
|
||||
hash?: string;
|
||||
loginAttempts?: number;
|
||||
lockUntil?: string;
|
||||
password?: string;
|
||||
id: string
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
email: string
|
||||
resetPasswordToken?: string
|
||||
resetPasswordExpiration?: string
|
||||
salt?: string
|
||||
hash?: string
|
||||
loginAttempts?: number
|
||||
lockUntil?: string
|
||||
password?: string
|
||||
}
|
||||
export interface PayloadPreference {
|
||||
id: string
|
||||
user: {
|
||||
relationTo: 'users'
|
||||
value: string | User
|
||||
}
|
||||
key?: string
|
||||
value?:
|
||||
| {
|
||||
[k: string]: unknown
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface PayloadMigration {
|
||||
id: string
|
||||
name?: string
|
||||
batch?: number
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface MainMenu {
|
||||
id: string;
|
||||
id: string
|
||||
navItems?: {
|
||||
link: {
|
||||
type?: 'reference' | 'custom';
|
||||
newTab?: boolean;
|
||||
type?: 'reference' | 'custom'
|
||||
newTab?: boolean
|
||||
reference: {
|
||||
value: string | Page;
|
||||
relationTo: 'pages';
|
||||
};
|
||||
url: string;
|
||||
label: string;
|
||||
};
|
||||
id?: string;
|
||||
}[];
|
||||
updatedAt?: string;
|
||||
createdAt?: string;
|
||||
relationTo: 'pages'
|
||||
value: string | Page
|
||||
}
|
||||
url: string
|
||||
label: string
|
||||
}
|
||||
id?: string
|
||||
}[]
|
||||
updatedAt?: string
|
||||
createdAt?: string
|
||||
}
|
||||
|
||||
declare module 'payload' {
|
||||
export interface GeneratedTypes {
|
||||
collections: {
|
||||
pages: Page
|
||||
users: User
|
||||
'payload-preferences': PayloadPreference
|
||||
'payload-migrations': PayloadMigration
|
||||
}
|
||||
globals: {
|
||||
'main-menu': MainMenu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { webpackBundler } from '@payloadcms/bundler-webpack'
|
||||
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
import { slateEditor } from '@payloadcms/richtext-slate'
|
||||
import path from 'path'
|
||||
import { buildConfig } from 'payload/config'
|
||||
|
||||
@@ -9,10 +12,15 @@ import { MainMenu } from './globals/MainMenu'
|
||||
export default buildConfig({
|
||||
collections: [Pages, Users],
|
||||
admin: {
|
||||
bundler: webpackBundler(),
|
||||
components: {
|
||||
beforeLogin: [BeforeLogin],
|
||||
},
|
||||
},
|
||||
editor: slateEditor({}),
|
||||
db: mongooseAdapter({
|
||||
url: process.env.DATABASE_URI,
|
||||
}),
|
||||
serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL,
|
||||
cors: [
|
||||
process.env.PAYLOAD_PUBLIC_SERVER_URL || '',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Page } from '../payload-types'
|
||||
|
||||
export const examplePage: Partial<Page> = {
|
||||
title: 'Example Page',
|
||||
title: 'Example Page (Published)',
|
||||
slug: 'example-page',
|
||||
_status: 'published',
|
||||
richText: [
|
||||
|
||||
@@ -19,7 +19,6 @@ app.get('/', (_, res) => {
|
||||
const start = async (): Promise<void> => {
|
||||
await payload.init({
|
||||
secret: process.env.PAYLOAD_SECRET,
|
||||
mongoURL: process.env.MONGODB_URI,
|
||||
express: app,
|
||||
onInit: () => {
|
||||
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"paths": {
|
||||
"payload/generated-types": ["./src/payload-types.ts"],
|
||||
"node_modules/*": ["./node_modules/*"]
|
||||
},
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
MONGODB_URI=mongodb://127.0.0.1/payload-example-form-builder
|
||||
PAYLOAD_SECRET=ENTER-STRING-HERE
|
||||
PAYLOAD_PUBLIC_SITE_URL=http://localhost:3000
|
||||
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:8000
|
||||
@@ -1,8 +0,0 @@
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
parser: "typescript",
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
trailingComma: "all",
|
||||
arrowParens: "avoid",
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
import { RichTextElement } from 'payload/dist/fields/config/types';
|
||||
|
||||
const elements: RichTextElement[] = [
|
||||
'blockquote',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'link',
|
||||
];
|
||||
|
||||
export default elements;
|
||||
@@ -1,94 +0,0 @@
|
||||
import { RichTextElement, RichTextField, RichTextLeaf } from 'payload/dist/fields/config/types';
|
||||
import deepMerge from '../../utilities/deepMerge';
|
||||
import elements from './elements';
|
||||
import leaves from './leaves';
|
||||
import link from '../link';
|
||||
|
||||
type RichText = (
|
||||
overrides?: Partial<RichTextField>,
|
||||
additions?: {
|
||||
elements?: RichTextElement[]
|
||||
leaves?: RichTextLeaf[]
|
||||
}
|
||||
) => RichTextField
|
||||
|
||||
const richText: RichText = (
|
||||
overrides,
|
||||
additions = {
|
||||
elements: [],
|
||||
leaves: [],
|
||||
},
|
||||
) => deepMerge<RichTextField, Partial<RichTextField>>(
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
admin: {
|
||||
upload: {
|
||||
collections: {
|
||||
media: {
|
||||
fields: [
|
||||
{
|
||||
type: 'richText',
|
||||
name: 'caption',
|
||||
label: 'Caption',
|
||||
admin: {
|
||||
elements: [
|
||||
...elements,
|
||||
],
|
||||
leaves: [
|
||||
...leaves,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'radio',
|
||||
name: 'alignment',
|
||||
label: 'Alignment',
|
||||
options: [
|
||||
{
|
||||
label: 'Left',
|
||||
value: 'left',
|
||||
},
|
||||
{
|
||||
label: 'Center',
|
||||
value: 'center',
|
||||
},
|
||||
{
|
||||
label: 'Right',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'enableLink',
|
||||
type: 'checkbox',
|
||||
label: 'Enable Link',
|
||||
},
|
||||
link({
|
||||
appearances: false,
|
||||
disableLabel: true,
|
||||
overrides: {
|
||||
admin: {
|
||||
condition: (_, data) => Boolean(data?.enableLink),
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
elements: [
|
||||
...elements,
|
||||
...additions.elements || [],
|
||||
],
|
||||
leaves: [
|
||||
...leaves,
|
||||
...additions.leaves || [],
|
||||
],
|
||||
},
|
||||
},
|
||||
overrides,
|
||||
);
|
||||
|
||||
export default richText;
|
||||
@@ -1,9 +0,0 @@
|
||||
import { RichTextLeaf } from 'payload/dist/fields/config/types';
|
||||
|
||||
const defaultLeaves: RichTextLeaf[] = [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
];
|
||||
|
||||
export default defaultLeaves;
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Field } from 'payload/types';
|
||||
import formatSlug from '../utilities/formatSlug';
|
||||
import deepMerge from '../utilities/deepMerge';
|
||||
|
||||
type Slug = (fieldToUse?: string, overrides?: Partial<Field>) => Field
|
||||
|
||||
export const slugField: Slug = (fieldToUse = 'title', overrides) => deepMerge<Field, Partial<Field>>(
|
||||
{
|
||||
name: 'slug',
|
||||
label: 'Slug',
|
||||
type: 'text',
|
||||
index: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
hooks: {
|
||||
beforeValidate: [
|
||||
formatSlug(fieldToUse),
|
||||
],
|
||||
},
|
||||
},
|
||||
overrides,
|
||||
);
|
||||
@@ -1,202 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/**
|
||||
* This file was automatically generated by Payload.
|
||||
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
export interface Config {}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "main-menu".
|
||||
*/
|
||||
export interface MainMenu {
|
||||
id: string;
|
||||
navItems: {
|
||||
link: {
|
||||
type?: 'reference' | 'custom';
|
||||
newTab?: boolean;
|
||||
reference: {
|
||||
value: string | Page;
|
||||
relationTo: 'pages';
|
||||
};
|
||||
url: string;
|
||||
label: string;
|
||||
};
|
||||
id?: string;
|
||||
}[];
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "pages".
|
||||
*/
|
||||
export interface Page {
|
||||
id: string;
|
||||
title: string;
|
||||
layout: {
|
||||
form: string | Form;
|
||||
enableIntro?: boolean;
|
||||
introContent: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'formBlock';
|
||||
}[];
|
||||
slug?: string;
|
||||
_status?: 'draft' | 'published';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "forms".
|
||||
*/
|
||||
export interface Form {
|
||||
id: string;
|
||||
title: string;
|
||||
fields: (
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
defaultValue?: string;
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'text';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
defaultValue?: string;
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'textarea';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
defaultValue?: string;
|
||||
options: {
|
||||
label: string;
|
||||
value: string;
|
||||
id?: string;
|
||||
}[];
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'select';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'email';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'state';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'country';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
defaultValue?: number;
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'number';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
required?: boolean;
|
||||
defaultValue?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'checkbox';
|
||||
}
|
||||
| {
|
||||
message?: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'message';
|
||||
}
|
||||
)[];
|
||||
submitButtonLabel?: string;
|
||||
confirmationType?: 'message' | 'redirect';
|
||||
confirmationMessage: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
redirect: {
|
||||
url: string;
|
||||
};
|
||||
emails: {
|
||||
emailTo: string;
|
||||
bcc?: string;
|
||||
replyTo?: string;
|
||||
replyToName?: string;
|
||||
emailFrom?: string;
|
||||
emailFromName?: string;
|
||||
subject: string;
|
||||
message?: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
id?: string;
|
||||
}[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
email?: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpiration?: string;
|
||||
loginAttempts?: number;
|
||||
lockUntil?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "form-submissions".
|
||||
*/
|
||||
export interface FormSubmission {
|
||||
id: string;
|
||||
form: string | Form;
|
||||
submissionData: {
|
||||
field: string;
|
||||
value: string;
|
||||
id?: string;
|
||||
}[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { buildConfig } from 'payload/config';
|
||||
import path from 'path';
|
||||
import FormBuilder from '@payloadcms/plugin-form-builder';
|
||||
import { Users } from './collections/Users';
|
||||
import { Pages } from './collections/Pages';
|
||||
import { MainMenu } from './globals/MainMenu';
|
||||
|
||||
export default buildConfig({
|
||||
collections: [
|
||||
Pages,
|
||||
Users,
|
||||
],
|
||||
globals: [
|
||||
MainMenu,
|
||||
],
|
||||
cors: [
|
||||
'http://localhost:3000',
|
||||
process.env.PAYLOAD_PUBLIC_SITE_URL,
|
||||
],
|
||||
typescript: {
|
||||
outputFile: path.resolve(__dirname, 'payload-types.ts'),
|
||||
},
|
||||
plugins: [
|
||||
FormBuilder({
|
||||
fields: {
|
||||
payment: false,
|
||||
},
|
||||
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import { FieldHook } from 'payload/types';
|
||||
|
||||
const format = (val: string): string => val.replace(/ /g, '-').replace(/[^\w-]+/g, '').toLowerCase();
|
||||
|
||||
const formatSlug = (fallback: string): FieldHook => ({ operation, value, originalDoc, data }) => {
|
||||
if (typeof value === 'string') {
|
||||
return format(value);
|
||||
}
|
||||
|
||||
if (operation === 'create') {
|
||||
const fallbackData = (data && data[fallback]) || (originalDoc && originalDoc[fallback]);
|
||||
|
||||
if (fallbackData && typeof fallbackData === 'string') {
|
||||
return format(fallbackData);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export default formatSlug;
|
||||
1
examples/form-builder/next-pages/.env.example
Normal file
1
examples/form-builder/next-pages/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
NEXT_PUBLIC_PAYLOAD_URL=http://localhost:3000
|
||||
8
examples/form-builder/next-pages/.prettierrc.js
Normal file
8
examples/form-builder/next-pages/.prettierrc.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
parser: 'typescript',
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
arrowParens: 'avoid',
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
# Form Builder Example Website
|
||||
# Form Builder Example Front-End
|
||||
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) that fetches data from [Payload](https://payloadcms.com).
|
||||
This is a [Next.js](https://nextjs.org) app using the [Pages Router](https://nextjs.org/docs/pages). It was made explicitly for Payload's [Form Builder Example](https://github.com/payloadcms/payload/tree/master/examples/form-builder/payload).
|
||||
|
||||
This example repo was made explicitly to demonstrate the power and convenience of the [Form-Builder plugin](https://github.com/payloadcms/plugin-form-builder). Along with the `Form-Builder plugin`, this repo takes advantage of the popular [React Hooks Form](https://react-hook-form.com/) library for easy validation, giving users an easy way to build and manage forms.
|
||||
> 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), we will add an example for that soon.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Payload
|
||||
|
||||
First you'll need a running Payload app. If you have not done so already, open up the `cms` folder and follow the setup instructions. Take note of your server URL, you'll need this in the next step.
|
||||
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/master/examples/form-builder/payload). If you have not done so already, clone it down and follow the setup instructions there.
|
||||
|
||||
### Next.js App
|
||||
|
||||
@@ -16,13 +16,9 @@ First you'll need a running Payload app. If you have not done so already, open u
|
||||
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
|
||||
5. `open http://localhost:3000` to see the result
|
||||
5. `open http://localhost:3001` to see the result
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
Once running, you will find a few seeded example forms on your local environment. Give them a try!
|
||||
|
||||
You can also start editing the pages by modifying the documents within your CMS.
|
||||
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 [Form Builder Example](https://github.com/payloadcms/payload/tree/master/examples/form-builder/payload) for full details.
|
||||
|
||||
## Learn More
|
||||
|
||||
@@ -1,32 +1,40 @@
|
||||
import React, { useState } from 'react';
|
||||
import { CheckboxField } from 'payload-plugin-form-builder/dist/types';
|
||||
import { UseFormRegister, FieldErrorsImpl, FieldValues } from 'react-hook-form';
|
||||
import { Check } from '../../../icons/Check';
|
||||
import { Error } from '../Error';
|
||||
import { Width } from '../Width';
|
||||
import React, { useState } from 'react'
|
||||
import { CheckboxField } from 'payload-plugin-form-builder/dist/types'
|
||||
import { UseFormRegister, FieldErrorsImpl, FieldValues } from 'react-hook-form'
|
||||
import { Check } from '../../../icons/Check'
|
||||
import { Error } from '../Error'
|
||||
import { Width } from '../Width'
|
||||
|
||||
import classes from './index.module.scss';
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export const Checkbox: React.FC<CheckboxField & {
|
||||
register: UseFormRegister<FieldValues & any>,
|
||||
setValue: any,
|
||||
getValues: any,
|
||||
errors: Partial<FieldErrorsImpl<{
|
||||
[x: string]: any;
|
||||
}>>
|
||||
}> = ({ name, label, width, register, setValue, getValues, required: requiredFromProps, errors }) => {
|
||||
const [checked, setChecked] = useState(false);
|
||||
export const Checkbox: React.FC<
|
||||
CheckboxField & {
|
||||
register: UseFormRegister<FieldValues & any>
|
||||
setValue: any
|
||||
getValues: any
|
||||
errors: Partial<
|
||||
FieldErrorsImpl<{
|
||||
[x: string]: any
|
||||
}>
|
||||
>
|
||||
}
|
||||
> = ({
|
||||
name,
|
||||
label,
|
||||
width,
|
||||
register,
|
||||
setValue,
|
||||
getValues,
|
||||
required: requiredFromProps,
|
||||
errors,
|
||||
}) => {
|
||||
const [checked, setChecked] = useState(false)
|
||||
|
||||
const isCheckboxChecked = getValues(name);
|
||||
const isCheckboxChecked = getValues(name)
|
||||
|
||||
return (
|
||||
<Width width={width}>
|
||||
<div
|
||||
className={[
|
||||
classes.checkbox,
|
||||
checked && classes.checked
|
||||
].filter(Boolean).join(' ')}
|
||||
>
|
||||
<div className={[classes.checkbox, checked && classes.checked].filter(Boolean).join(' ')}>
|
||||
<div className={classes.container}>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -49,5 +57,5 @@ export const Checkbox: React.FC<CheckboxField & {
|
||||
{requiredFromProps && errors[name] && checked === false && <Error />}
|
||||
</div>
|
||||
</Width>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
@@ -1,19 +1,23 @@
|
||||
import React from 'react';
|
||||
import ReactSelect from 'react-select';
|
||||
import { CountryField } from 'payload-plugin-form-builder/dist/types';
|
||||
import { Controller, Control, FieldValues, FieldErrorsImpl } from 'react-hook-form';
|
||||
import { countryOptions } from './options';
|
||||
import { Error } from '../Error';
|
||||
import { Width } from '../Width';
|
||||
import React from 'react'
|
||||
import ReactSelect from 'react-select'
|
||||
import { CountryField } from 'payload-plugin-form-builder/dist/types'
|
||||
import { Controller, Control, FieldValues, FieldErrorsImpl } from 'react-hook-form'
|
||||
import { countryOptions } from './options'
|
||||
import { Error } from '../Error'
|
||||
import { Width } from '../Width'
|
||||
|
||||
import classes from './index.module.scss';
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export const Country: React.FC<CountryField & {
|
||||
control: Control<FieldValues, any>
|
||||
errors: Partial<FieldErrorsImpl<{
|
||||
[x: string]: any;
|
||||
}>>
|
||||
}> = ({ name, label, width, control, required, errors }) => {
|
||||
export const Country: React.FC<
|
||||
CountryField & {
|
||||
control: Control<FieldValues, any>
|
||||
errors: Partial<
|
||||
FieldErrorsImpl<{
|
||||
[x: string]: any
|
||||
}>
|
||||
>
|
||||
}
|
||||
> = ({ name, label, width, control, required, errors }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<div className={classes.select}>
|
||||
@@ -30,7 +34,7 @@ export const Country: React.FC<CountryField & {
|
||||
instanceId={name}
|
||||
options={countryOptions}
|
||||
value={countryOptions.find(c => c.value === value)}
|
||||
onChange={(val) => onChange(val.value)}
|
||||
onChange={val => onChange(val.value)}
|
||||
className={classes.reactSelect}
|
||||
classNamePrefix="rs"
|
||||
/>
|
||||
@@ -39,5 +43,5 @@ export const Country: React.FC<CountryField & {
|
||||
{required && errors[name] && <Error />}
|
||||
</div>
|
||||
</Width>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user