Compare commits

...

117 Commits

Author SHA1 Message Date
Elliot DeNolf
465acf0123 chore(release): v1.15.12 2025-03-11 09:23:34 -04:00
Elliot DeNolf
a6524720f5 chore: simplify query parsing 2025-03-10 14:38:52 -04:00
Jonathan Wu
59fb70077d chore(examples/form-builder): improves form input accessibility 2023-11-15 17:06:18 -05:00
Patrik
6b3f20d01b fix: ensures compare-version select field cannot be cleared (#3413) 2023-10-27 12:29:07 -04:00
Aleem Isiaka
5f6a0db869 fix: ctrl s on globals (#3342) 2023-10-10 23:32:57 -04:00
Kári Rögnvaldsson
26fcfba8fd fix: hide rich text toolbar if leaves and elements arrays are empty (#3226) 2023-10-10 13:27:07 -04:00
Jarrod Flesch
88ad2ee1e3 chore: adds example code and README for setting up testing with a payload project (#3333) 2023-10-09 14:37:00 -04:00
Elliot DeNolf
b76aea9b9a chore(release): v1.15.8 2023-10-08 17:21:16 -04:00
Elliot DeNolf
d770b40cc5 chore: update yarn.lock 2023-10-08 17:12:27 -04:00
Calvin Chong
3e6718034f chore(templates/website): adds seeded users to readme (#3412)
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2023-10-08 16:46:57 -04:00
dependabot[bot]
a7beb1bb8f chore(deps): bump graphql from 16.8.0 to 16.8.1 (#3380)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.8.0 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.8.0...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:42:59 -04:00
dependabot[bot]
6a79efc5f5 chore(deps): bump word-wrap in /examples/redirects/nextjs (#3057)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.5.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:41:46 -04:00
dependabot[bot]
9697269816 chore(deps): bump semver in /examples/redirects/nextjs (#3058)
Bumps [semver](https://github.com/npm/node-semver) from 7.3.8 to 7.5.4.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.3.8...v7.5.4)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:41:20 -04:00
dependabot[bot]
af375123a8 chore(deps): bump word-wrap in /examples/form-builder/nextjs (#3076)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.5.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:41:07 -04:00
dependabot[bot]
00b66a70c9 chore(deps): bump semver in /examples/draft-preview/next-app (#3111)
Bumps [semver](https://github.com/npm/node-semver) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v6.3.1/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v6.3.0...v6.3.1)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:40:29 -04:00
dependabot[bot]
b0135a7ee0 chore(deps): bump postcss from 8.4.27 to 8.4.31 in /templates/blank (#3463)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.27 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.27...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:39:17 -04:00
dependabot[bot]
fe2940a447 chore(deps): bump postcss from 8.4.27 to 8.4.31 in /examples/whitelabel (#3464)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.27 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.27...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:36:11 -04:00
dependabot[bot]
c745de15b9 chore(deps): bump postcss in /examples/form-builder/cms (#3465)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.27 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.27...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:35:55 -04:00
dependabot[bot]
4dcf0bcad6 chore(deps): bump postcss in /examples/multi-tenant (#3466)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.27 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.27...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:35:44 -04:00
dependabot[bot]
93f043bc79 chore(deps): bump postcss in /examples/redirects/cms (#3467)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.27 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.27...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:35:32 -04:00
dependabot[bot]
63bceedca4 chore(deps): bump postcss from 8.4.27 to 8.4.31 in /examples/email (#3468)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.27 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.27...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:35:21 -04:00
dependabot[bot]
0bbb5790d5 chore(deps): bump postcss in /examples/auth/payload (#3469)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.27 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.27...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:35:09 -04:00
dependabot[bot]
05c8bdc16a chore(deps): bump postcss in /examples/draft-preview/payload (#3470)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.27 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.27...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:34:53 -04:00
dependabot[bot]
77c14033d6 chore(deps): bump postcss from 8.4.28 to 8.4.31 (#3471)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.28 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.28...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 15:34:40 -04:00
Jacob Fletcher
0280cdd355 chore: updates README.md (#3430) 2023-10-03 17:54:09 -04:00
Petipois
891ab34a12 chore: updates link to contributing in readme (#3418) 2023-10-03 15:49:49 -04:00
Jarrod Flesch
26939a3331 fix: secures the user response from the me auth route (#3409) 2023-10-03 12:21:41 -04:00
PatrikKozak
9fa4a8fcfb chore(examples/custom-server): updates readme with correct login info (#3422) 2023-10-03 10:08:00 -04:00
Elliot DeNolf
96608a509b chore(release): v1.15.7 2023-10-02 17:38:22 -04:00
Alessio Gravili
8586c85fab fix: hotkey's pressed keys are not unset when window is not focused (#3400)
* fix: hotkey's pressed keys are not unset when window is not focused

* chore: remove unnecessary console.log
2023-10-02 10:41:59 -04:00
dependabot[bot]
1a76e9580f chore(deps): bump graphql from 16.8.0 to 16.8.1 in /templates/ecommerce (#3381)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.8.0 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.8.0...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 07:34:18 -07:00
dependabot[bot]
8127fe39e2 chore(deps): bump graphql in /examples/auth/payload (#3379)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.7.1 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.7.1...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 07:32:21 -07:00
dependabot[bot]
e42c4dec98 chore(deps): bump graphql in /examples/draft-preview/payload (#3378)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.7.1 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.7.1...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 07:31:59 -07:00
dependabot[bot]
2c82ea80de chore(deps): bump graphql from 16.7.1 to 16.8.1 in /examples/email (#3377)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.7.1 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.7.1...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 07:31:44 -07:00
dependabot[bot]
428aebd73e chore(deps): bump graphql from 16.7.1 to 16.8.1 in /templates/blank (#3376)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.7.1 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.7.1...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 07:31:37 -07:00
dependabot[bot]
712bd6e8b1 chore(deps): bump graphql in /examples/form-builder/cms (#3375)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.7.1 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.7.1...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 07:31:31 -07:00
dependabot[bot]
1ca55b41c6 chore(deps): bump graphql in /examples/custom-server (#3374)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.7.1 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.7.1...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 07:31:14 -07:00
dependabot[bot]
81352f3d41 chore(deps): bump graphql in /examples/multi-tenant (#3373)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.7.1 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.7.1...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 07:29:14 -07:00
dependabot[bot]
6fb60cdbe3 chore(deps): bump graphql from 16.7.1 to 16.8.1 in /templates/website (#3372)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.7.1 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.7.1...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 07:29:04 -07:00
dependabot[bot]
e18784dd5b chore(deps): bump graphql in /examples/redirects/cms (#3371)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.7.1 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.7.1...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 07:28:57 -07:00
dependabot[bot]
ff417368ff chore(deps): bump graphql from 16.7.1 to 16.8.1 in /examples/whitelabel (#3370)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.7.1 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.7.1...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 07:28:50 -07:00
dependabot[bot]
e28c77069e chore(deps): bump graphql in /examples/form-builder/nextjs (#3369)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.6.0 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.6.0...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 07:28:40 -07:00
Elliot DeNolf
37fe2df2de chore(release): v1.15.6 2023-09-13 10:30:03 -04:00
Jarrod Flesch
8ca67d5aaa fix(#3289): removes HMR plugin from prod webpack configs (#3319) 2023-09-12 13:09:56 -04:00
Jarrod Flesch
096d33718d fix: fields with relationTo[] correctly load returned data from form submission (#3317) 2023-09-12 11:43:45 -04:00
Jessica Chowdhury
0bd335303d fix: greater than equal admin filter not working (#3306) 2023-09-11 13:08:06 -04:00
James
e8e1ba00fe chore(release): v1.15.5 2023-09-09 09:53:57 -04:00
Jarrod Flesch
a7d47c627d fix: corrects hasMany relationships within addFieldStatePromise (#3300) 2023-09-09 00:32:29 -04:00
Jarrod Flesch
5e1bed3177 chore: fixes console errors for checkboxes (#3291) 2023-09-07 09:11:35 -04:00
Elliot DeNolf
364014a1e9 chore(release): v1.15.4 2023-09-06 17:39:02 -04:00
Jarrod Flesch
9cd5e5aefa fix: appends versions key to incoming where query (#3287) 2023-09-06 15:07:46 -04:00
Bas Dalenoord
429a88a5a1 fix: change scoping of force parameter to prevent false negation; (#3278) 2023-09-06 12:54:39 -04:00
Jarrod Flesch
cf12b5fc70 fix(#3274): sets full user data from fetchFullUser instead of partial jwt data (#3279) 2023-09-06 12:52:48 -04:00
Jarrod Flesch
5096c37874 fix: aligns depth behaviour between local api and admin panel (#3276) 2023-09-06 12:51:59 -04:00
Jacob Fletcher
ac592b6d0d chore(templates/ecommerce): misc cleanup 2023-09-06 11:59:03 -04:00
Jacob Fletcher
007f7e7d18 chore(templates/ecommerce): fixes nextjs hmr 2023-09-06 11:49:59 -04:00
Jacob Fletcher
4ba072b0c6 chore(templates/ecommerce): related products 2023-09-06 11:39:18 -04:00
Jacob Fletcher
8a4db150f0 chore(templates/ecommerce): prevents server from restarting when app files change 2023-09-06 11:11:30 -04:00
Jacob Fletcher
b28b9020ea chore(templates/ecommerce): fixes product published dates 2023-09-06 11:07:59 -04:00
Jacob Fletcher
baa73fae62 chore(templates/ecommerce): wires in redirects 2023-09-06 11:03:30 -04:00
Jacob Fletcher
55c27b6d2f chore(templates/website): builds nextjs front-end (#3282) 2023-09-06 08:38:04 -04:00
James
6d148d7309 chore(release): v1.15.3 2023-09-05 16:53:47 -04:00
James Mikrut
8e10ecae4b Merge pull request #3256 from payloadcms/feat/versions-perf
Feat/versions perf
2023-09-05 16:47:48 -04:00
Jarrod Flesch
ef27b9f641 chore: revert changes related to the status displayed by the Status component 2023-09-05 16:33:01 -04:00
Jarrod Flesch
2dcce0339c chore: simplify logic for global status rendering 2023-09-05 16:04:56 -04:00
Jarrod Flesch
b649ad7bb5 chore: ensure not to update the version doc that was just created 2023-09-05 16:04:12 -04:00
Jarrod Flesch
3cee0be314 chore: remove script file example 2023-09-05 16:01:07 -04:00
Jacob Fletcher
8fc953605a chore(templates/ecommerce): properly formats csp and handles post requests in next app (#3260) 2023-09-01 10:52:05 -04:00
Jarrod Flesch
e28dfc0c93 chore: tweak script 2023-08-31 21:34:25 -04:00
Jarrod Flesch
33561a8ea2 chore: adds feature flag to config 2023-08-31 21:16:50 -04:00
Jarrod Flesch
e500b46576 chore: removes console log 2023-08-31 16:57:49 -04:00
Jarrod Flesch
e79a84d200 chore: migration script 2023-08-31 16:56:21 -04:00
Jarrod Flesch
16e94d401b feat: improves query speed for version enabled collections 2023-08-31 16:50:35 -04:00
Jarrod Flesch
9fbabc8fd6 fix: globals not saving updatedAt and createdAt and version dates correctly 2023-08-31 16:49:33 -04:00
Jarrod Flesch
9bc072ccaf fix: draft globals always displaying unpublish button 2023-08-31 16:48:25 -04:00
Jacob Fletcher
45905c312f chore(templates/ecommerce): bypasses next cache (#3254) 2023-08-31 09:34:10 -04:00
Jacob Fletcher
2c7a15ceca chore(templates/ecommerce): uses svg favicon 2023-08-28 16:11:11 -04:00
Jacob Fletcher
28e128241e chore(examples/custom-server): uses getPayloadClient in default server and removes admin redirect 2023-08-28 16:06:45 -04:00
Jacob Fletcher
21336cd61a chore(examples/custom-server): handles front-end post requests (#3221) 2023-08-28 15:58:56 -04:00
Jarrod Flesch
ff1c10c382 chore(examples): simplify getPayloadClient logic 2023-08-28 13:49:40 -04:00
Jarrod Flesch
6e8aa5e8af Merge branch 'master' of https://github.com/payloadcms/payload 2023-08-28 11:24:28 -04:00
Jarrod Flesch
32e7c56a0d chore(example): adjusts custom server example with pattern to utilize local api 2023-08-28 11:24:24 -04:00
Jacob Fletcher
e5c783df5d chore(templates/ecommerce): migrates to @payloadcms/plugin-stripe v0.0.14 (#3231) 2023-08-25 17:49:51 -04:00
Jacob Fletcher
63698e5e88 chore(templates): correctly sets next images domain (#3230) 2023-08-25 15:40:40 -04:00
Dan Ribbens
016ad3afc9 chore(release): v1.15.2 2023-08-25 11:40:42 -04:00
Dan Ribbens
33e1e15ca9 chore: pins swc v1.3.78 (#3228) 2023-08-25 11:00:43 -04:00
Jarrod Flesch
59918aac91 chore(docs): clarifies reason for returning doc in afterChange collection hook 2023-08-25 10:23:27 -04:00
Dan Ribbens
a0c3cbd68d chore(release): v1.15.1 2023-08-25 06:55:22 -04:00
Tylan Davis
3a15e077c6 fix: correct out of order dark-mode color variables (#3197) 2023-08-24 15:09:31 -04:00
Jessica Chowdhury
90a9e14e9d docs: adds custom select example and video (#3223) 2023-08-24 15:07:06 -04:00
Paul
6d3b8636f4 fix: mutation type with tabs missing previous tabs (#3196) 2023-08-24 14:29:25 -04:00
PatrikKozak
cb8e07f852 fix: arrays in richtext uploads (#3222)
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2023-08-24 14:12:18 -04:00
Jessica Chowdhury
301be0a5d4 docs: adds minRow and maxRow to array and blocks pages (#3203) 2023-08-24 11:19:49 -04:00
Dan Ribbens
466589b483 chore(release): v1.15.0 2023-08-24 10:22:55 -04:00
Alessio Gravili
6754f55ce0 chore: improve filtering for hasMany number field (#3193)
* chore: improve fiiltering for hasMany number field

* chore: add translation for 'items' and replace rows with items

* chore: new exceededLimit key

* Revert "chore: add translation for 'items' and replace rows with items"

This reverts commit 3a91dabdfd.

* chore: undo adding items key in translation schema

* chore: new limitReached key

* chore: remove unnecessary exceededLimit key
2023-08-21 18:01:10 +02:00
Alessio Gravili
84d2bacb56 chore: improve checkboxes (#3191) 2023-08-21 10:39:40 -04:00
Alessio Gravili
739abdcd81 feat: query support for geo within and intersects + dynamic GraphQL operator types (#3183)
Co-authored-by: Lucas Blancas <lablancas@gmail.com>
2023-08-21 10:12:27 -04:00
Jacob Fletcher
c7cf2d3d2c chore(examples): updates draft-preview next-app example to use revalidateTag (#3199) 2023-08-21 08:45:44 -04:00
Jacob Fletcher
79561497f9 Merge pull request #3200 from payloadcms/chore/ecom-deploy
chore(templates): updates e-commerce template
2023-08-21 08:45:31 -04:00
Jacob Fletcher
600306274e chore(templates): renders static cart page fallback 2023-08-20 18:26:53 -04:00
Jacob Fletcher
398378a867 chore(templates): implements draft preview and on-demand revalidation 2023-08-20 13:20:06 -04:00
Jacob Fletcher
4e755dfde2 chore(templates): safely handles bad network requests 2023-08-20 02:09:51 -04:00
Elliot DeNolf
3634e2cc4d chore(templates): default port on website 2023-08-17 15:01:55 -04:00
Jarrod Flesch
294fb5e574 chore: improve ts typing in sanitization functions (#3194) 2023-08-17 10:47:54 -04:00
Dan Ribbens
f5f2332755 chore(release): v1.14.0 2023-08-16 13:58:20 -04:00
Jarrod Flesch
0acd7b8706 chore: file cleanup (#3190) 2023-08-16 13:03:43 -04:00
James Mikrut
d91b44cbb3 feat: improve field ops (#3172)
Co-authored-by: PatrikKozak <patrik@trbl.design>
2023-08-16 11:00:52 -04:00
Greg Willard
e03a8e6b03 feat: Improve admin dashboard accessibility (#3053)
Co-authored-by: Alessio Gravili <alessio@gravili.de>
2023-08-16 10:26:17 -04:00
Jacob Fletcher
846485388a docs: removes auto-formatting from rich-text.mdx (#3188) 2023-08-16 10:15:27 -04:00
Jacob Fletcher
8d83e05948 docs: fixes syntax error in rich-text.mdx that was breaking build 2023-08-16 03:28:19 -04:00
PatrikKozak
7963d04a27 fix: passes in height to resizeOptions upload option to allow height resize (#3171) 2023-08-15 14:58:16 -04:00
Jessica Chowdhury
20b6b29c79 chore(test): adds test to ensure relationship returns over 10 docs (#3181)
* chore(test): adds test to ensure relationship returns over 10 docs

* chore: remove unnecessary movieDocs variable
2023-08-15 19:48:33 +02:00
Alessio Gravili
fdfdfc83f3 fix: WhereBuilder component does not accept all valid Where queries (#3087)
* chore: add jsDocs for ListControls

* chore: add jsDocs for ListView

* chore: add jsDocs for WhereBuilder

* chore: add comment

* chore: remove unnecessary console log

* chore: improve operator type

* fix: transform where queries which aren't necessarily incorrect, and improve their validation

* chore: add type to import

* fix: do not merge existing old query params with new ones if the existing old ones got transformed and are not valid, as that would cause duplicates

* chore: sort imports and remove extra validation

* fix: transformWhereQuery logic

* chore: add back extra validation

* chore: add e2e tests
2023-08-15 19:22:57 +02:00
Jarrod Flesch
c154eb7e2b chore: remove swc version pin (#3179) 2023-08-15 09:19:18 -04:00
Stef Gootzen
33686c6db8 feat: add afterOperation hook (#2697)
* feat: add afterOperation hook for Find operation

* docs: change #afterOperation to #afteroperation

* chore: extract afterOperation in function

* chore: implement afterChange in operations

* docs: use proper CollectionAfterOperationHook

* chore: remove outdated info

* chore: types afterOperation hook

* chore: improves afterOperation tests

* docs: updates description of afterOperation hook

* chore: improve typings

* chore: improve types

* chore: rename index.tsx => index.ts

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
Co-authored-by: Alessio Gravili <alessio@gravili.de>
2023-08-15 14:37:01 +02:00
Alessio Gravili
6d6acbcfc1 Merge pull request #3176 from payloadcms/chore/docs-design-contributions
chore: add section for design contributions in contributing.md
2023-08-15 14:25:50 +02:00
Alessio Gravili
4e2f2561ff chore: add section for design contributions in contributing.md 2023-08-15 13:34:11 +02:00
571 changed files with 44556 additions and 7302 deletions

View File

@@ -2,16 +2,17 @@
"verbose": true,
"git": {
"commitMessage": "chore(release): v${version}",
"requireCleanWorkingDir": true
"requireCleanWorkingDir": false
},
"github": {
"release": true
},
"npm": {
"skipChecks": true
"skipChecks": true,
"tag": "payload-1"
},
"hooks": {
"before:init": ["yarn", "yarn clean", "yarn test"]
"before:init": ["yarn", "yarn clean"]
},
"plugins": {
"@release-it/conventional-changelog": {

View File

@@ -11,7 +11,7 @@
},
"npm": {
"skipChecks": true,
"tag": "canary"
"tag": "payload-1"
},
"hooks": {
"before:init": ["yarn", "yarn clean", "yarn test"]

9
.vscode/launch.json vendored
View File

@@ -18,5 +18,12 @@
"type": "node-terminal",
"cwd": "${workspaceFolder}"
},
{
"command": "yarn run dev versions",
"name": "Debug Versions",
"request": "launch",
"type": "node-terminal",
"cwd": "${workspaceFolder}"
},
]
}
}

View File

@@ -1,5 +1,100 @@
## [1.15.10](https://github.com/payloadcms/payload/compare/v1.15.9...v1.15.10) (2025-03-10)
## [1.15.8](https://github.com/payloadcms/payload/compare/v1.15.7...v1.15.8) (2023-10-08)
### Bug Fixes
* secures the user response from the me auth route ([#3409](https://github.com/payloadcms/payload/issues/3409)) ([26939a3](https://github.com/payloadcms/payload/commit/26939a333135810f2eebd3ebaf05885d152f6f13))
## [1.15.7](https://github.com/payloadcms/payload/compare/v1.15.6...v1.15.7) (2023-10-02)
### Bug Fixes
* hotkey's pressed keys are not unset when window is not focused ([#3400](https://github.com/payloadcms/payload/issues/3400)) ([8586c85](https://github.com/payloadcms/payload/commit/8586c85fab3009a09be9bf86c22952f85aa0ad82))
## [1.15.6](https://github.com/payloadcms/payload/compare/v1.15.5...v1.15.6) (2023-09-13)
### Bug Fixes
* **#3289:** removes HMR plugin from prod webpack configs ([#3319](https://github.com/payloadcms/payload/issues/3319)) ([8ca67d5](https://github.com/payloadcms/payload/commit/8ca67d5aaa99f0f45eac56766fe42e07ab4a41f1)), closes [#3289](https://github.com/payloadcms/payload/issues/3289)
* fields with relationTo[] correctly load returned data from form submission ([#3317](https://github.com/payloadcms/payload/issues/3317)) ([096d337](https://github.com/payloadcms/payload/commit/096d33718d28cb5207027f6737982b29a0ced90d))
* greater than equal admin filter not working ([#3306](https://github.com/payloadcms/payload/issues/3306)) ([0bd3353](https://github.com/payloadcms/payload/commit/0bd335303dff71977b46b373fbee859d11c33337))
## [1.15.5](https://github.com/payloadcms/payload/compare/v1.15.4...v1.15.5) (2023-09-09)
### Bug Fixes
* corrects hasMany relationships within addFieldStatePromise ([#3300](https://github.com/payloadcms/payload/issues/3300)) ([a7d47c6](https://github.com/payloadcms/payload/commit/a7d47c627d064e92ca541f70caf0ff3d903b2d1d))
## [1.15.4](https://github.com/payloadcms/payload/compare/v1.15.3...v1.15.4) (2023-09-06)
### Bug Fixes
* **#3274:** sets full user data from fetchFullUser instead of partial jwt data ([#3279](https://github.com/payloadcms/payload/issues/3279)) ([cf12b5f](https://github.com/payloadcms/payload/commit/cf12b5fc703be6341b2b23efebd8aa85e8602567)), closes [#3274](https://github.com/payloadcms/payload/issues/3274)
* aligns depth behaviour between local api and admin panel ([#3276](https://github.com/payloadcms/payload/issues/3276)) ([5096c37](https://github.com/payloadcms/payload/commit/5096c378743f4c5eb5f4f2f7e67e5e206cc9da40))
* appends versions key to incoming where query ([#3287](https://github.com/payloadcms/payload/issues/3287)) ([9cd5e5a](https://github.com/payloadcms/payload/commit/9cd5e5aefaf0ee2af4f577da9578bb31bf4b0acb))
* change scoping of `force` parameter to prevent false negation; ([#3278](https://github.com/payloadcms/payload/issues/3278)) ([429a88a](https://github.com/payloadcms/payload/commit/429a88a5a18e8905c63dfe00b78b3e71d56758fd))
## [1.15.3](https://github.com/payloadcms/payload/compare/v1.15.2...v1.15.3) (2023-09-05)
### Bug Fixes
* draft globals always displaying unpublish button ([9bc072c](https://github.com/payloadcms/payload/commit/9bc072ccaf318c61b2c4e2a553604a24ff6a188e))
* globals not saving updatedAt and createdAt and version dates correctly ([9fbabc8](https://github.com/payloadcms/payload/commit/9fbabc8fd6a3bea5628bea8d0acc915ddb33bb5c))
### Features
* improves query speed for version enabled collections ([16e94d4](https://github.com/payloadcms/payload/commit/16e94d401bd7cb82de53142c5f9a325abd31a81a))
## [1.15.2](https://github.com/payloadcms/payload/compare/v1.15.1...v1.15.2) (2023-08-25)
## [1.15.1](https://github.com/payloadcms/payload/compare/v1.15.0...v1.15.1) (2023-08-25)
### Bug Fixes
* arrays in richtext uploads ([#3222](https://github.com/payloadcms/payload/issues/3222)) ([cb8e07f](https://github.com/payloadcms/payload/commit/cb8e07f85232a26c265872faf408644424312af6))
* correct out of order dark-mode color variables ([#3197](https://github.com/payloadcms/payload/issues/3197)) ([3a15e07](https://github.com/payloadcms/payload/commit/3a15e077c6914aba3ef26e453fee23c89f3db829))
* mutation type with tabs missing previous tabs ([#3196](https://github.com/payloadcms/payload/issues/3196)) ([6d3b863](https://github.com/payloadcms/payload/commit/6d3b8636f4e14a4e4155279353fa06e86fe2b25c))
# [1.15.0](https://github.com/payloadcms/payload/compare/v1.14.0...v1.15.0) (2023-08-24)
### Features
* query support for geo within and intersects + dynamic GraphQL operator types ([#3183](https://github.com/payloadcms/payload/issues/3183)) ([739abdc](https://github.com/payloadcms/payload/commit/739abdcd81176b3e812470eeea97b1be0d8c4a27))
# [1.14.0](https://github.com/payloadcms/payload/compare/v1.13.4...v1.14.0) (2023-08-16)
### Bug Fixes
* DatePicker showing only selected day by default ([#3169](https://github.com/payloadcms/payload/issues/3169)) ([edcb393](https://github.com/payloadcms/payload/commit/edcb3933cfb4532180c822135ea6a8be928e0fdc))
* only allow redirects to /admin sub-routes ([c0f05a1](https://github.com/payloadcms/payload/commit/c0f05a1c38fb9c958de920fabb698b5ecfb661f0))
* passes in height to resizeOptions upload option to allow height resize ([#3171](https://github.com/payloadcms/payload/issues/3171)) ([7963d04](https://github.com/payloadcms/payload/commit/7963d04a27888eb5a12d0ab37f2082cd33638abd))
* WhereBuilder component does not accept all valid Where queries ([#3087](https://github.com/payloadcms/payload/issues/3087)) ([fdfdfc8](https://github.com/payloadcms/payload/commit/fdfdfc83f36a958971f8e4e4f9f5e51560cb26e0))
### Features
* add afterOperation hook ([#2697](https://github.com/payloadcms/payload/issues/2697)) ([33686c6](https://github.com/payloadcms/payload/commit/33686c6db8373a16d7f6b0192e0701bf15881aa4))
* add support for hotkeys ([#1821](https://github.com/payloadcms/payload/issues/1821)) ([942cfec](https://github.com/payloadcms/payload/commit/942cfec286ff050e13417b037cca64b9d757d868))
* Added Azerbaijani language file ([#3164](https://github.com/payloadcms/payload/issues/3164)) ([63e3063](https://github.com/payloadcms/payload/commit/63e3063b9ecc1afd62d7a287a798d41215008f2a))
* allow async relationship filter options ([#2951](https://github.com/payloadcms/payload/issues/2951)) ([bad3638](https://github.com/payloadcms/payload/commit/bad363882c9d00d3c73547ca3329eba988e728ff))
* Improve admin dashboard accessibility ([#3053](https://github.com/payloadcms/payload/issues/3053)) ([e03a8e6](https://github.com/payloadcms/payload/commit/e03a8e6b030e82a17e1cdae5b4032433cf9c75a4))
* improve field ops ([#3172](https://github.com/payloadcms/payload/issues/3172)) ([d91b44c](https://github.com/payloadcms/payload/commit/d91b44cbb3fd526caca2a6f4bd30fd06ede3a5da))
* make PAYLOAD_CONFIG_PATH optional ([#2839](https://github.com/payloadcms/payload/issues/2839)) ([5744de7](https://github.com/payloadcms/payload/commit/5744de7ec63e3f17df7e02a7cc827818a79dbbb8))
* text alignment for richtext editor ([#2803](https://github.com/payloadcms/payload/issues/2803)) ([a0b13a5](https://github.com/payloadcms/payload/commit/a0b13a5b01fa0d7f4c4dffd1895bfe507e5c676d))
## [1.13.4](https://github.com/payloadcms/payload/compare/v1.13.3...v1.13.4) (2023-08-11)

View File

@@ -20,6 +20,12 @@ Payload documentation can be found directly within its codebase and you can feel
If you're an incredibly awesome person and want to help us make Payload even better through new features or additions, we would be thrilled to work with you.
## Design Contributions
When it comes to design-related changes or additions, it's crucial for us to ensure a cohesive user experience and alignment with our broader design vision. Before embarking on any implementation that would affect the design or UI/UX, we ask that you **first share your design proposal** with us for review and approval.
Our design review ensures that proposed changes fit seamlessly with other components, both existing and planned. This step is meant to prevent unintentional design inconsistencies and to save you from investing time in implementing features that might need significant design alterations later.
### Before Starting
To help us work on new features, you can create a new feature request post in [GitHub Discussion](https://github.com/payloadcms/payload/discussions) or discuss it in our [Discord](https://discord.com/invite/payload). New functionality often has large implications across the entire Payload repo, so it is best to discuss the architecture and approach before starting work on a pull request.

View File

@@ -52,12 +52,21 @@ npx create-payload-app
Alternatively, it only takes about five minutes to [create an app from scratch](https://payloadcms.com/docs/getting-started/installation#from-scratch).
## 🖱️ One-click templates
### 🛒 [E-Commerce](https://github.com/payloadcms/payload/tree/master/templates/ecommerce)
Eliminate the need to combine Shopify and a CMS, and instead do it all with Payload + Stripe. Best of all, you can extend it as much as you need.
[All Official Templates](https://github.com/orgs/payloadcms/repositories?q=topic%3Apayload-template)&nbsp;·&nbsp;[Community Templates](https://github.com/topics/payload-template)
Jumpstart your next project by starting with a pre-made template. These are production-ready, end-to-end solutions designed to get you to market as fast as possible.
**If you maintain your own template, consider adding the `payload-template` topic to your GitHub repository for others to find.**
### [🛒 E-Commerce](https://github.com/payloadcms/payload/tree/master/templates/ecommerce)
Eliminate the need to combine Shopify and a CMS, and instead do it all with Payload + Stripe. Comes with a beautiful, fully functional front-end complete with shopping cart, checkout, orders, and much more.
### [🌐 Website](https://github.com/payloadcms/payload/tree/master/templates/website)
Build any kind of website, blog, or portfolio from small to enterprise. Comes with a beautiful, fully functional front-end complete with posts, projects, comments, and much more.
We're constantly adding more templates to our [Templates Directory](https://github.com/payloadcms/payload/tree/master/templates). If you maintain your own template, consider adding the `payload-template` topic to your GitHub repository for others to find.
- [Official Templates](https://github.com/payloadcms/payload/tree/master/templates)
- [Community Templates](https://github.com/topics/payload-template)
## ✨ Features
@@ -87,13 +96,15 @@ Check out the [Payload website](https://payloadcms.com/docs/getting-started/what
## 🙋 Contributing
If you want to add contributions to this repository, please follow the instructions in [contributing.md](./contributing.md).
If you want to add contributions to this repository, please follow the instructions in [contributing.md](./CONTRIBUTING.md).
## 📚 Examples
The examples directory is a great resource for learning how to setup Payload in a variety of different ways.
The [Examples Directory](./examples) is a great resource for learning how to setup Payload in a variety of different ways, but you can also find great examples in our blog and throughout our social media.
[Examples Directory](./examples)
- [Examples Directory](./examples)
- [Payload Blog](https://payloadcms.com/blog)
- [Payload YouTube](https://www.youtube.com/@payloadcms)
## 🔌 Plugins

View File

@@ -31,8 +31,10 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as the heading in the Admin panel or an object with keys for each language. Auto-generated from name if not defined. |
| **`fields`** \* | Array of field types to correspond to each row of the Array. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`fields`** \* | Array of field types to correspond to each row of the Array. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation)
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |

View File

@@ -33,7 +33,9 @@ keywords: blocks, fields, config, configuration, documentation, Content Manageme
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as the heading in the Admin panel or an object with keys for each language. Auto-generated from name if not defined. |
| **`blocks`** * | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation)
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-level hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-level access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |

View File

@@ -7,9 +7,7 @@ keywords: rich text, fields, config, configuration, documentation, Content Manag
---
<Banner>
The Rich Text field is a powerful way to allow editors to write dynamic
content. The content is saved as JSON in the database and can be converted
into any format, including HTML, that you need.
The Rich Text field is a powerful way to allow editors to write dynamic content. The content is saved as JSON in the database and can be converted into any format, including HTML, that you need.
</Banner>
<LightDarkImage
@@ -22,14 +20,7 @@ keywords: rich text, fields, config, configuration, documentation, Content Manag
The Admin component is built on the powerful [`slatejs`](https://docs.slatejs.org/) editor and is meant to be as extensible and customizable as possible.
<Banner type="success">
<strong>
Consistent with Payload's goal of making you learn as little of Payload as
possible, customizing and using the Rich Text Editor does not involve
learning how to develop for a <em>Payload</em> rich text editor.
</strong>{" "}
Instead, you can invest your time and effort into learning Slate, an
open-source tool that will allow you to apply your learnings elsewhere as
well.
<strong>Consistent with Payload's goal of making you learn as little of Payload as possible, customizing and using the Rich Text Editor does not involve learning how to develop for a <em>Payload</em> rich text editor.</strong> Instead, you can invest your time and effort into learning Slate, an open-source tool that will allow you to apply your learnings elsewhere as well.
</Banner>
### Config
@@ -125,13 +116,7 @@ The built-in `relationship` element is a powerful way to reference other Documen
Similar to the `relationship` element, the `upload` element is a user-friendly way to reference [Upload-enabled collections](/docs/upload/overview) with a UI specifically designed for media / image-based uploads.
<Banner type="success">
<strong>Tip:</strong>
<br />
Collections are automatically allowed to be selected within the Rich Text
relationship and upload elements by default. If you want to disable a
collection from being able to be referenced in Rich Text fields, set the
collection admin options of <strong>enableRichTextLink</strong> and{" "}
<strong>enableRichTextRelationship</strong> to false.
<strong>Tip:</strong><br />Collections are automatically allowed to be selected within the Rich Text relationship and upload elements by default. If you want to disable a collection from being able to be referenced in Rich Text fields, set the collection admin options of <strong>enableRichTextLink</strong> and <strong>enableRichTextRelationship</strong> to false.
</Banner>
Relationship and Upload elements are populated dynamically into your Rich Text field' content. Within the REST and Local APIs, any present RichText `relationship` or `upload` elements will respect the `depth` option that you pass, and will be populated accordingly. In GraphQL, each `richText` field accepts an argument of `depth` for you to utilize.
@@ -307,10 +292,7 @@ const serialize = (children) =>
```
<Banner>
<strong>Note:</strong>
<br />
The above example is for how to render to JSX, although for plain HTML the
pattern is similar. Just remove the JSX and return HTML strings instead!
<strong>Note:</strong><br />The above example is for how to render to JSX, although for plain HTML the pattern is similar. Just remove the JSX and return HTML strings instead!
</Banner>
### Built-in SlateJS Plugins

View File

@@ -10,8 +10,7 @@ keywords: select, multi-select, fields, config, configuration, documentation, Co
The Select field provides a dropdown-style interface for choosing options from
a predefined list as an enumeration.
</Banner>
<LightDarkImage
<LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/select.png'
srcDark='https://payloadcms.com/images/docs/fields/select-dark.png'
alt='Shows a Select field in the Payload admin panel'
@@ -99,3 +98,85 @@ export const ExampleCollection: CollectionConfig = {
}
```
### Customization
The Select field UI component can be customized by providing a custom React component to the `components` object in the Base config.
```ts
export const CustomSelectField: Field = {
name: 'customSelectField',
type: 'select', // or 'text' if you have dynamic options
admin: {
components: {
Field: CustomSelectComponent({
options: [
{
label: 'Option 1',
value: '1',
},
{
label: 'Option 2',
value: '2',
},
],
}),
},
}
}
```
You can import the existing Select component directly from Payload, then extend and customize it as needed.
```ts
import * as React from 'react';
import { SelectInput, useField } from 'payload/components/forms';
import { useAuth } from 'payload/components/utilities';
type customSelectProps = {
path: string;
options: {
label: string;
value: string;
}[];
}
export const CustomSelectComponent: React.FC<CustomSelectProps> = ({ path, options }) => {
const { value, setValue } = useField<string>({ path });
const { user } = useAuth();
const adjustedOptions = options.filter((option) => {
/*
A common use case for a custom select
is to show different options based on
the current user's role.
*/
return option;
});
return (
<div>
<label className="field-label">
Custom Select
</label>
<SelectInput
path={path}
name={path}
options={adjustedOptions}
value={value}
onChange={() => setValue(e.value)}
/>
</div>
);
};
```
If you are looking to create a dynamic select field, the following tutorial will walk you through the process of creating a custom select field that fetches its options from an external API.
<VideoDrawer
id='Efn9OxSjA6Y'
label='How to Create a Custom Select Field'
drawerTitle='How to Create a Custom Select Field: A Step-by-Step Guide'
/>
If you want to learn more about custom components check out the [Admin > Custom Component](/docs/admin/components#field-component) docs.

View File

@@ -16,6 +16,7 @@ Collections feature the ability to define the following hooks:
- [afterRead](#afterread)
- [beforeDelete](#beforedelete)
- [afterDelete](#afterdelete)
- [afterOperation](#afteroperation)
Additionally, `auth`-enabled collections feature the following hooks:
@@ -31,6 +32,7 @@ Additionally, `auth`-enabled collections feature the following hooks:
All collection Hook properties accept arrays of synchronous or asynchronous functions. Each Hook type receives specific arguments and has the ability to modify specific outputs.
`collections/exampleHooks.js`
```ts
import { CollectionConfig } from 'payload/types';
@@ -48,6 +50,7 @@ export const ExampleHooks: CollectionConfig = {
afterChange: [(args) => {...}],
afterRead: [(args) => {...}],
afterDelete: [(args) => {...}],
afterOperation: [(args) => {...}],
// Auth-enabled hooks
beforeLogin: [(args) => {...}],
@@ -62,19 +65,19 @@ export const ExampleHooks: CollectionConfig = {
### beforeOperation
The `beforeOperation` Hook type can be used to modify the arguments that operations accept or execute side-effects that run before an operation begins.
The `beforeOperation` hook can be used to modify the arguments that operations accept or execute side-effects that run before an operation begins.
Available Collection operations include `create`, `read`, `update`, `delete`, `login`, `refresh` and `forgotPassword`.
Available Collection operations include `create`, `read`, `update`, `delete`, `login`, `refresh`, and `forgotPassword`.
```ts
import { CollectionBeforeOperationHook } from 'payload/types';
import { CollectionBeforeOperationHook } from "payload/types";
const beforeOperationHook: CollectionBeforeOperationHook = async ({
args, // Original arguments passed into the operation
args, // original arguments passed into the operation
operation, // name of the operation
}) => {
return args; // Return operation arguments as necessary
}
return args; // return modified operation arguments as necessary
};
```
### beforeValidate
@@ -88,7 +91,7 @@ Please do note that this does not run before the client-side validation. If you
3. `validate` runs on the server
```ts
import { CollectionBeforeOperationHook } from 'payload/types';
import { CollectionBeforeOperationHook } from "payload/types";
const beforeValidateHook: CollectionBeforeValidateHook = async ({
data, // incoming data to update or create with
@@ -97,7 +100,7 @@ const beforeValidateHook: CollectionBeforeValidateHook = async ({
originalDoc, // original document
}) => {
return data; // Return data to either create or update a document with
}
};
```
### beforeChange
@@ -105,7 +108,7 @@ const beforeValidateHook: CollectionBeforeValidateHook = async ({
Immediately following validation, `beforeChange` hooks will run within `create` and `update` operations. At this stage, you can be confident that the data that will be saved to the document is valid in accordance to your field validations. You can optionally modify the shape of data to be saved.
```ts
import { CollectionBeforeChangeHook } from 'payload/types';
import { CollectionBeforeChangeHook } from "payload/types";
const beforeChangeHook: CollectionBeforeChangeHook = async ({
data, // incoming data to update or create with
@@ -114,7 +117,7 @@ const beforeChangeHook: CollectionBeforeChangeHook = async ({
originalDoc, // original document
}) => {
return data; // Return data to either create or update a document with
}
};
```
### afterChange
@@ -122,7 +125,7 @@ const beforeChangeHook: CollectionBeforeChangeHook = async ({
After a document is created or updated, the `afterChange` hook runs. This hook is helpful to recalculate statistics such as total sales within a global, syncing user profile changes to a CRM, and more.
```ts
import { CollectionAfterChangeHook } from 'payload/types';
import { CollectionAfterChangeHook } from "payload/types";
const afterChangeHook: CollectionAfterChangeHook = async ({
doc, // full document data
@@ -130,8 +133,8 @@ const afterChangeHook: CollectionAfterChangeHook = async ({
previousDoc, // document data before updating the collection
operation, // name of the operation ie. 'create', 'update'
}) => {
return doc;
}
return doc; // value to be used in subsequent afterChange hooks
};
```
### beforeRead
@@ -139,7 +142,7 @@ const afterChangeHook: CollectionAfterChangeHook = async ({
Runs before `find` and `findByID` operations are transformed for output by `afterRead`. This hook fires before hidden fields are removed and before localized fields are flattened into the requested locale. Using this Hook will provide you with all locales and all hidden fields via the `doc` argument.
```ts
import { CollectionBeforeReadHook } from 'payload/types';
import { CollectionBeforeReadHook } from "payload/types";
const beforeReadHook: CollectionBeforeReadHook = async ({
doc, // full document data
@@ -147,7 +150,7 @@ const beforeReadHook: CollectionBeforeReadHook = async ({
query, // JSON formatted query
}) => {
return doc;
}
};
```
### afterRead
@@ -155,7 +158,7 @@ const beforeReadHook: CollectionBeforeReadHook = async ({
Runs as the last step before documents are returned. Flattens locales, hides protected fields, and removes fields that users do not have access to.
```ts
import { CollectionAfterReadHook } from 'payload/types';
import { CollectionAfterReadHook } from "payload/types";
const afterReadHook: CollectionAfterReadHook = async ({
doc, // full document data
@@ -164,7 +167,7 @@ const afterReadHook: CollectionAfterReadHook = async ({
findMany, // boolean to denote if this hook is running against finding one, or finding many
}) => {
return doc;
}
};
```
### beforeDelete
@@ -194,19 +197,37 @@ const afterDeleteHook: CollectionAfterDeleteHook = async ({
}) => {...}
```
### afterOperation
The `afterOperation` hook can be used to modify the result of operations or execute side-effects that run after an operation has completed.
Available Collection operations include `create`, `find`, `findByID`, `update`, `updateByID`, `delete`, `deleteByID`, `login`, `refresh`, and `forgotPassword`.
```ts
import { CollectionAfterOperationHook } from "payload/types";
const afterOperationHook: CollectionAfterOperationHook = async ({
args, // arguments passed into the operation
operation, // name of the operation
result, // the result of the operation, before modifications
}) => {
return result; // return modified result as necessary
};
```
### beforeLogin
For auth-enabled Collections, this hook runs during `login` operations where a user with the provided credentials exist, but before a token is generated and added to the response. You can optionally modify the user that is returned, or throw an error in order to deny the login operation.
```ts
import { CollectionBeforeLoginHook } from 'payload/types';
import { CollectionBeforeLoginHook } from "payload/types";
const beforeLoginHook: CollectionBeforeLoginHook = async ({
req, // full express request
user, // user being logged in
}) => {
return user;
}
};
```
### afterLogin
@@ -267,7 +288,7 @@ const afterMeHook: CollectionAfterMeHook = async ({
For auth-enabled Collections, this hook runs after successful `forgotPassword` operations. Returned values are discarded.
```ts
import { CollectionAfterForgotPasswordHook } from 'payload/types';
import { CollectionAfterForgotPasswordHook } from "payload/types";
const afterLoginHook: CollectionAfterForgotPasswordHook = async ({
req, // full express request
@@ -275,7 +296,7 @@ const afterLoginHook: CollectionAfterForgotPasswordHook = async ({
token, // user token
}) => {
return user;
}
};
```
## TypeScript
@@ -298,5 +319,5 @@ import type {
CollectionAfterRefreshHook,
CollectionAfterMeHook,
CollectionAfterForgotPasswordHook,
} from 'payload/types';
} from "payload/types";
```

View File

@@ -4082,9 +4082,9 @@ graphql-type-json@^0.3.2:
integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==
graphql@^16.6.0:
version "16.7.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.7.1.tgz#11475b74a7bff2aefd4691df52a0eca0abd9b642"
integrity sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==
version "16.8.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07"
integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==
gzip-size@^6.0.0:
version "6.0.0"
@@ -6256,9 +6256,9 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.2.15, postcss@^8.4.21, postcss@^8.4.24:
version "8.4.27"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057"
integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==
version "8.4.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"

View File

@@ -2,5 +2,5 @@ MONGODB_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
PAYLOAD_SEED=true
PAYLOAD_PUBLIC_SEED=true
PAYLOAD_DROP_DATABASE=true

View File

@@ -10,7 +10,7 @@ To spin up this example locally, follow these steps:
1. Then `cd YOUR_PROJECT_REPO && cp .env.example .env`
1. Next `yarn && yarn dev`
1. Now `open http://localhost:3000/admin` to access the admin panel
1. Login with email `dev@payloadcms.com` and password `test`
1. Login with email `demo@payloadcms.com` and password `demo`
That's it! Changes made in `./src` will be reflected in your app. See the [Development](#development) section for more details.
@@ -94,7 +94,7 @@ To spin up this example locally, follow the [Quick Start](#quick-start).
### Seed
On boot, a seed script is included to scaffold a basic database for you to use as an example. This is done by setting the `PAYLOAD_DROP_DATABASE` and `PAYLOAD_SEED` environment variables which are included in the `.env.example` by default. You can remove these from your `.env` to prevent this behavior. You can also freshly seed your project at any time by running `yarn seed`. This seed creates an admin user with email `dev@payloadcms.com`, password `test`, and a `home` page.
On boot, a seed script is included to scaffold a basic database for you to use as an example. This is done by setting the `PAYLOAD_DROP_DATABASE` and `PAYLOAD_PUBLIC_SEED` environment variables which are included in the `.env.example` by default. You can remove these from your `.env` to prevent this behavior. You can also freshly seed your project at any time by running `yarn seed`. This seed creates an admin user with email `demo@payloadcms.com`, password `demo`, and a `home` page.
> NOTICE: seeding the database is destructive because it drops your current database to populate a fresh one from the seed template. Only run this command if you are starting a new project or can afford to lose your current data.

View File

@@ -6,7 +6,7 @@
"license": "MIT",
"scripts": {
"dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
"seed": "rm -rf media && cross-env PAYLOAD_SEED=true PAYLOAD_DROP_DATABASE=true PAYLOAD_CONFIG_PATH=src/payload.config.ts ts-node src/server.ts",
"seed": "rm -rf media && cross-env PAYLOAD_PUBLIC_SEED=true PAYLOAD_DROP_DATABASE=true PAYLOAD_CONFIG_PATH=src/payload.config.ts ts-node src/server.ts",
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build",
"build:server": "tsc --project tsconfig.server.json",
"build:next": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NEXT_BUILD=true node dist/server.js",
@@ -52,4 +52,4 @@
"ts-node": "^10.9.1",
"typescript": "^4.8.4"
}
}
}

View File

@@ -0,0 +1,5 @@
import { NextResponse } from 'next/server'
export async function GET(): Promise<NextResponse> {
return NextResponse.json({ success: true })
}

View File

@@ -0,0 +1,5 @@
import { NextResponse } from 'next/server'
export async function POST(): Promise<NextResponse> {
return NextResponse.json({ success: true })
}

View File

@@ -1,6 +1,7 @@
import React, { Fragment } from 'react'
import { notFound } from 'next/navigation'
import { getPayloadClient } from '../getPayload'
import { Page } from './../payload-types'
import { Gutter } from './_components/Gutter'
import { RichText } from './_components/RichText'
@@ -8,11 +9,17 @@ import { RichText } from './_components/RichText'
import classes from './page.module.scss'
export default async function Home() {
const home: Page = await fetch(
`${process.env.NEXT_PUBLIC_SERVER_URL}/api/pages?where[slug][equals]=home`,
)
.then(res => res.json())
.then(res => res?.docs?.[0])
const payload = await getPayloadClient()
const { docs } = await payload.find({
collection: 'pages',
where: {
slug: {
equals: 'home',
},
},
})
const home = docs?.[0] as Page
if (!home) {
return notFound()

View File

@@ -0,0 +1,17 @@
import React from 'react'
const BeforeLogin: React.FC = () => {
if (process.env.PAYLOAD_PUBLIC_SEED === 'true') {
return (
<p>
{'Log in with the email '}
<strong>demo@payloadcms.com</strong>
{' and the password '}
<strong>demo</strong>.
</p>
)
}
return null
}
export default BeforeLogin

View File

@@ -0,0 +1,59 @@
import dotenv from 'dotenv'
import path from 'path'
import type { Payload } from 'payload'
import payload from 'payload'
import type { InitOptions } from 'payload/config'
import { seed as seedData } from './seed'
dotenv.config({
path: path.resolve(__dirname, '../.env'),
})
let cached = (global as any).payload
if (!cached) {
cached = (global as any).payload = { client: null, promise: null }
}
interface Args {
initOptions?: Partial<InitOptions>
seed?: boolean
}
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')
}
if (cached.client) {
return cached.client
}
if (!cached.promise) {
cached.promise = payload.init({
mongoURL: process.env.MONGODB_URI,
secret: process.env.PAYLOAD_SECRET,
local: initOptions?.express ? false : true,
...(initOptions || {}),
})
}
try {
process.env.PAYLOAD_DROP_DATABASE = seed ? 'true' : 'false'
cached.client = await cached.promise
if (seed) {
await seedData(payload)
}
} catch (e: unknown) {
cached.promise = null
throw e
}
return cached.client
}

View File

@@ -8,10 +8,16 @@ dotenv.config({
import { buildConfig } from 'payload/config'
import { Pages } from './collections/Pages'
import BeforeLogin from './components/BeforeLogin'
export default buildConfig({
serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL || '',
collections: [Pages],
admin: {
components: {
beforeLogin: [BeforeLogin],
},
},
typescript: {
outputFile: path.resolve(__dirname, 'payload-types.ts'),
},

View File

@@ -5,8 +5,8 @@ export const seed = async (payload: Payload): Promise<void> => {
await payload.create({
collection: 'users',
data: {
email: 'dev@payloadcms.com',
password: 'test',
email: 'demo@payloadcms.com',
password: 'demo',
},
})

View File

@@ -10,33 +10,23 @@ dotenv.config({
})
import express from 'express'
import payload from 'payload'
import { seed } from './seed'
import { getPayloadClient } from './getPayload'
const app = express()
const PORT = process.env.PORT || 3000
// Redirect root to the admin panel
app.get('/', (_, res) => {
res.redirect('/admin')
})
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()}`)
const payload = await getPayloadClient({
initOptions: {
express: app,
onInit: async newPayload => {
newPayload.logger.info(`Payload Admin URL: ${newPayload.getAdminURL()}`)
},
},
seed: process.env.PAYLOAD_PUBLIC_SEED === 'true',
})
if (process.env.PAYLOAD_SEED === 'true') {
payload.logger.info('---- SEEDING DATABASE ----')
await seed(payload)
}
app.listen(PORT, async () => {
payload.logger.info(`App URL: ${process.env.PAYLOAD_PUBLIC_SERVER_URL}`)
})

View File

@@ -8,28 +8,23 @@ dotenv.config({
})
import express from 'express'
import payload from 'payload'
import { seed } from './seed'
import { getPayloadClient } from './getPayload'
const app = express()
const PORT = process.env.PORT || 3000
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()}`)
const payload = await getPayloadClient({
initOptions: {
express: app,
onInit: async newPayload => {
newPayload.logger.info(`Payload Admin URL: ${newPayload.getAdminURL()}`)
},
},
seed: process.env.PAYLOAD_PUBLIC_SEED === 'true',
})
if (process.env.PAYLOAD_SEED === 'true') {
payload.logger.info('---- SEEDING DATABASE ----')
await seed(payload)
}
if (process.env.NEXT_BUILD) {
app.listen(PORT, async () => {
payload.logger.info(`Next.js is now building...`)
@@ -47,7 +42,7 @@ const start = async (): Promise<void> => {
const nextHandler = nextApp.getRequestHandler()
app.get('*', (req, res) => nextHandler(req, res))
app.use((req, res) => nextHandler(req, res))
nextApp.prepare().then(() => {
payload.logger.info('Next.js started')

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# Payload Draft Preview Example Front-End
This is a [Next.js](https://nextjs.org) app using the [App Router](https://nextjs.org/docs/app). It was made explicitly for Payload's [Draft Preview Example](https://github.com/payloadcms/payload/tree/master/examples/draft-preview).
This is a [Next.js](https://nextjs.org) app using the [App Router](https://nextjs.org/docs/app). It was made explicitly for Payload's [Draft Preview Example](https://github.com/payloadcms/payload/tree/master/examples/draft-preview/payload).
> This example uses the App Router, the latest API of Next.js. If your app is using the legacy [Pages Router](https://nextjs.org/docs/pages), check out the official [Pages Router Example](https://github.com/payloadcms/payload/tree/master/examples/draft-preview/next-pages).

View File

@@ -20,6 +20,11 @@ export const fetchPage = async (
draft && payloadToken ? '&draft=true' : ''
}`,
{
method: 'GET',
// this is the key we'll use to on-demand revalidate pages that use this data
// we do this by calling `revalidateTag()` using the same key
// see `app/api/revalidate.ts` for more info
next: { tags: [`pages_${slug}`] },
...(draft && payloadToken
? {
headers: {

View File

@@ -1,23 +1,32 @@
import { revalidatePath } from 'next/cache'
import { revalidatePath, revalidateTag } from 'next/cache'
import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'
// this endpoint will revalidate a page by tag or path
// this is to achieve on-demand revalidation of pages that use this data
// send either `collection` and `slug` or `revalidatePath` as query params
export async function GET(request: NextRequest): Promise<unknown> {
const path = request.nextUrl.searchParams.get('revalidatePath')
const collection = request.nextUrl.searchParams.get('collection')
const slug = request.nextUrl.searchParams.get('slug')
const path = request.nextUrl.searchParams.get('path')
const secret = request.nextUrl.searchParams.get('secret')
if (secret !== process.env.NEXT_PRIVATE_REVALIDATION_KEY) {
return NextResponse.json({ revalidated: false, now: Date.now() })
}
if (typeof collection === 'string' && typeof slug === 'string') {
revalidateTag(`${collection}_${slug}`)
return NextResponse.json({ revalidated: true, now: Date.now() })
}
// there is a known limitation with `revalidatePath` where it will not revalidate exact paths of dynamic routes
// instead, Next.js expects us to revalidate entire directories, i.e. `revalidatePath('/[slug]')` instead of `/example-page`
// for this reason, it is preferred to use `revalidateTag` instead of `revalidatePath`
// - https://github.com/vercel/next.js/issues/49387
// - https://github.com/vercel/next.js/issues/49778#issuecomment-1547028830
if (typeof path === 'string') {
// there is a known bug with `revalidatePath` where it will not revalidate exact paths of dynamic routes
// instead, Next.js expects us to revalidate entire directories, i.e. `/[slug]` instead of `/example-page`
// for now we'll make this change but with expectation that it will be fixed so we can use `revalidatePath('/example-page')`
// - https://github.com/vercel/next.js/issues/49387
// - https://github.com/vercel/next.js/issues/49778#issuecomment-1547028830
// revalidatePath(path)
revalidatePath('/[slug]')
revalidatePath(path)
return NextResponse.json({ revalidated: true, now: Date.now() })
}

View File

@@ -2107,14 +2107,14 @@ scheduler@^0.23.0:
loose-envify "^1.1.0"
semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.3.7:
version "7.5.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e"
integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
dependencies:
lru-cache "^6.0.0"

View File

@@ -1,6 +1,6 @@
# Payload Draft Preview Example Front-End
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 [Draft Preview Example](https://github.com/payloadcms/payload/tree/master/examples/draft-preview).
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 [Draft Preview Example](https://github.com/payloadcms/payload/tree/master/examples/draft-preview/payload).
> This example uses the Pages Router, the legacy API of Next.js. If your app is using the latest [App Router](https://nextjs.org/docs/app), check out the official [App Router Example](https://github.com/payloadcms/payload/tree/master/examples/draft-preview/next-app).

View File

@@ -6,9 +6,9 @@ const revalidate = async (req: NextApiRequest, res: NextApiResponse): Promise<vo
return res.status(401).json({ message: 'Invalid token' })
}
if (typeof req.query.revalidatePath === 'string') {
if (typeof req.query.path === 'string') {
try {
await res.revalidate(req.query.revalidatePath)
await res.revalidate(req.query.path)
return res.json({ revalidated: true })
} catch (err: unknown) {
// If there was an error, Next.js will continue

View File

@@ -1,6 +1,6 @@
# Payload Draft Preview Example
The [Payload Draft Preview Example](https://github.com/payloadcms/payload/tree/master/examples/draft-preview) demonstrates how to implement draft preview in [Payload](https://github.com/payloadcms/payload) using [Versions](https://payloadcms.com/docs/versions/overview) and [Drafts](https://payloadcms.com/docs/versions/drafts). Draft preview allows you to see content on your front-end before it is published. There are various fully working front-ends made explicitly for this example, including:
The [Payload Draft Preview Example](https://github.com/payloadcms/payload/tree/master/examples/draft-preview/payload) demonstrates how to implement draft preview in [Payload](https://github.com/payloadcms/payload) using [Versions](https://payloadcms.com/docs/versions/overview) and [Drafts](https://payloadcms.com/docs/versions/drafts). Draft preview allows you to see content on your front-end before it is published. There are various fully working front-ends made explicitly for this example, including:
- [Next.js App Router](../next-app)
- [Next.js Pages Router](../next-pages)

View File

@@ -7,9 +7,11 @@ export const formatAppURL = ({ doc }): string => {
return pathname
}
// Revalidate the page in the background, so the user doesn't have to wait
// Notice that the hook itself is not async and we are not awaiting `revalidate`
// Only revalidate existing docs that are published
// revalidate the page in the background, so the user doesn't have to wait
// notice that the hook itself is not async and we are not awaiting `revalidate`
// only revalidate existing docs that are published (not drafts)
// send `revalidatePath`, `collection`, and `slug` to the frontend to use in its revalidate route
// frameworks may have different ways of doing this, but the idea is the same
export const revalidatePage: AfterChangeHook = ({ doc, req, operation }) => {
if (operation === 'update' && doc._status === 'published') {
const url = formatAppURL({ doc })
@@ -17,7 +19,7 @@ export const revalidatePage: AfterChangeHook = ({ doc, req, operation }) => {
const revalidate = async (): Promise<void> => {
try {
const res = await fetch(
`${process.env.PAYLOAD_PUBLIC_SITE_URL}/api/revalidate?secret=${process.env.REVALIDATION_KEY}&revalidatePath=${url}`,
`${process.env.PAYLOAD_PUBLIC_SITE_URL}/api/revalidate?secret=${process.env.REVALIDATION_KEY}&collection=pages&slug=${doc?.slug}&path=${url}`,
)
if (res.ok) {

View File

@@ -16,7 +16,7 @@ export const Pages: CollectionConfig = {
formatAppURL({
doc,
}),
)}&secret=${process.env.PAYLOAD_PUBLIC_DRAFT_SECRET}`
)}&collection=pages&slug=${doc.slug}&secret=${process.env.PAYLOAD_PUBLIC_DRAFT_SECRET}`
},
},
versions: {

View File

@@ -1,6 +1,7 @@
import type { Page } from '../payload-types'
export const examplePageDraft: Partial<Page> = {
title: 'Example Page (Draft)',
richText: [
{
children: [

View File

@@ -4082,9 +4082,9 @@ graphql-type-json@^0.3.2:
integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==
graphql@^16.6.0:
version "16.7.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.7.1.tgz#11475b74a7bff2aefd4691df52a0eca0abd9b642"
integrity sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==
version "16.8.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07"
integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==
gzip-size@^6.0.0:
version "6.0.0"
@@ -6256,9 +6256,9 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.2.15, postcss@^8.4.21, postcss@^8.4.24:
version "8.4.27"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057"
integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==
version "8.4.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"

View File

@@ -3933,9 +3933,9 @@ graphql-type-json@^0.3.2:
integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==
graphql@^16.6.0:
version "16.7.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.7.1.tgz#11475b74a7bff2aefd4691df52a0eca0abd9b642"
integrity sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==
version "16.8.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07"
integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==
gzip-size@^6.0.0:
version "6.0.0"
@@ -6215,9 +6215,9 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.2.15, postcss@^8.4.21, postcss@^8.4.24:
version "8.4.27"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057"
integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==
version "8.4.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"

View File

@@ -3510,9 +3510,9 @@ graphql-type-json@^0.3.2:
integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==
graphql@^16.6.0:
version "16.7.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.7.1.tgz#11475b74a7bff2aefd4691df52a0eca0abd9b642"
integrity sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==
version "16.8.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07"
integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==
gzip-size@^6.0.0:
version "6.0.0"
@@ -5547,9 +5547,9 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.2.15, postcss@^8.4.21, postcss@^8.4.24:
version "8.4.27"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057"
integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==
version "8.4.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"

View File

@@ -17,7 +17,7 @@ export const Country: React.FC<CountryField & {
return (
<Width width={width}>
<div className={classes.select}>
<label htmlFor="name" className={classes.label}>
<label htmlFor={name} className={classes.label}>
{label}
</label>
<Controller
@@ -33,6 +33,7 @@ export const Country: React.FC<CountryField & {
onChange={(val) => onChange(val.value)}
className={classes.reactSelect}
classNamePrefix="rs"
inputId={name}
/>
)}
/>

View File

@@ -15,13 +15,14 @@ export const Email: React.FC<EmailField & {
return (
<Width width={width}>
<div className={classes.wrap}>
<label htmlFor="name" className={classes.label}>
<label htmlFor={name} className={classes.label}>
{label}
</label>
<input
type="text"
placeholder="Email"
className={classes.input}
id={name}
{...register(name, { required: requiredFromProps, pattern: /^\S+@\S+$/i })}
/>
{requiredFromProps && errors[name] && <Error />}

View File

@@ -15,12 +15,13 @@ export const Number: React.FC<TextField & {
return (
<Width width={width}>
<div className={classes.wrap}>
<label htmlFor="name" className={classes.label}>
<label htmlFor={name} className={classes.label}>
{label}
</label>
<input
type="number"
className={classes.input}
id={name}
{...register(name, { required: requiredFromProps })}
/>
{requiredFromProps && errors[name] && <Error />}

View File

@@ -16,7 +16,7 @@ export const Select: React.FC<SelectField & {
return (
<Width width={width}>
<div className={classes.select}>
<label htmlFor="name" className={classes.label}>
<label htmlFor={name} className={classes.label}>
{label}
</label>
<Controller
@@ -32,6 +32,7 @@ export const Select: React.FC<SelectField & {
onChange={(val) => onChange(val.value)}
className={classes.reactSelect}
classNamePrefix="rs"
inputId={name}
/>
)}
/>

View File

@@ -18,7 +18,7 @@ export const State: React.FC<StateField & {
return (
<Width width={width}>
<div className={classes.select}>
<label htmlFor="name" className={classes.label}>
<label htmlFor={name} className={classes.label}>
{label}
</label>
<Controller
@@ -34,6 +34,7 @@ export const State: React.FC<StateField & {
onChange={(val) => onChange(val.value)}
className={classes.reactSelect}
classNamePrefix="rs"
inputId={name}
/>
)}
/>

View File

@@ -16,12 +16,13 @@ export const Text: React.FC<TextField & {
return (
<Width width={width}>
<div className={classes.wrap}>
<label htmlFor="name" className={classes.label}>
<label htmlFor={name} className={classes.label}>
{label}
</label>
<input
type="text"
className={classes.input}
id={name}
{...register(name, { required: requiredFromProps })}
/>
{requiredFromProps && errors[name] && <Error />}

View File

@@ -16,12 +16,13 @@ export const Textarea: React.FC<TextField & {
return (
<Width width={width}>
<div className={classes.wrap}>
<label htmlFor="name" className={classes.label}>
<label htmlFor={name} className={classes.label}>
{label}
</label>
<textarea
rows={rows}
className={classes.textarea}
id={name}
{...register(name, { required: requiredFromProps })}
/>
{requiredFromProps && errors[name] && <Error />}

View File

@@ -13,7 +13,7 @@
"@faceless-ui/css-grid": "^1.2.0",
"@faceless-ui/modal": "^2.0.1",
"escape-html": "^1.0.3",
"graphql": "^16.6.0",
"graphql": "^16.8.1",
"next": "12.3.1",
"react": "18.2.0",
"react-dom": "18.2.0",

View File

@@ -1345,10 +1345,10 @@ graphql-tag@^2.12.6:
dependencies:
tslib "^2.1.0"
graphql@^16.6.0:
version "16.6.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb"
integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==
graphql@^16.8.1:
version "16.8.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07"
integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==
has-bigints@^1.0.1, has-bigints@^1.0.2:
version "1.0.2"
@@ -2467,9 +2467,9 @@ which@^2.0.1:
isexe "^2.0.0"
word-wrap@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
version "1.2.5"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
wrappy@1:
version "1.0.2"

View File

@@ -4082,9 +4082,9 @@ graphql-type-json@^0.3.2:
integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==
graphql@^16.6.0:
version "16.7.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.7.1.tgz#11475b74a7bff2aefd4691df52a0eca0abd9b642"
integrity sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==
version "16.8.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07"
integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==
gzip-size@^6.0.0:
version "6.0.0"
@@ -6256,9 +6256,9 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.2.15, postcss@^8.4.21, postcss@^8.4.24:
version "8.4.27"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057"
integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==
version "8.4.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"

View File

@@ -4087,9 +4087,9 @@ graphql-type-json@^0.3.2:
integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==
graphql@^16.6.0:
version "16.7.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.7.1.tgz#11475b74a7bff2aefd4691df52a0eca0abd9b642"
integrity sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==
version "16.8.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07"
integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==
gzip-size@^6.0.0:
version "6.0.0"
@@ -6261,9 +6261,9 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.2.15, postcss@^8.4.21, postcss@^8.4.24:
version "8.4.27"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057"
integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==
version "8.4.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"

View File

@@ -1596,9 +1596,9 @@ scheduler@^0.23.0:
loose-envify "^1.1.0"
semver@^7.3.7:
version "7.3.8"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
dependencies:
lru-cache "^6.0.0"
@@ -1814,9 +1814,9 @@ which@^2.0.1:
isexe "^2.0.0"
word-wrap@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
version "1.2.5"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
wrappy@1:
version "1.0.2"

View File

@@ -0,0 +1,5 @@
PORT=3000
MONGO_URL=mongodb://127.0.0.1/your-project-name
PAYLOAD_SECRET_KEY=alwifhjoq284jgo5w34jgo43f3
PAYLOAD_CONFIG_PATH=src/payload.config.ts
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000

142
examples/testing/README.md Normal file
View File

@@ -0,0 +1,142 @@
# Payload Testing Example
This example demonstrates how to get started with testing Payload using [Jest](https://jestjs.io/). You can clone this down and use it as a starting point for your own Payload projects, or you can follow the steps below to add testing to your existing Payload project.
## Add testing to your existing Payload project
1. Initial setup:
```bash
# install dependencies
yarn add --dev jest mongodb-memory-server @swc/jest @swc/core isomorphic-fetch @types/jest
```
```bash
# create a .env file
cp .env.example .env
```
2. This example uses the following folder structure:
```
root
└─ /src
└─ payload.config.ts
└─ /tests
```
3. Add test credentials to your project. Create a file at `src/tests/credentials.ts` with the following contents:
```ts
export default {
email: 'test@test.com',
password: 'test',
};
```
4. Add the global setup file to your project. This file will be run before any tests are run. It will start a MongoDB server and create a Payload instance for you to use in your tests. Create a file at `src/tests/globalSetup.ts` with the following contents:
```ts
import { resolve } from 'path';
import payload from 'payload';
import express from 'express';
import testCredentials from './credentials';
require('dotenv').config({
path: resolve(__dirname, '../../.env'),
});
const app = express();
const globalSetup = async () => {
await payload.init({
secret: process.env.PAYLOAD_SECRET_KEY,
mongoURL: process.env.MONGO_URL,
express: app,
});
app.listen(process.env.PORT, async () => {
console.log(`Express is now listening for incoming connections on port ${process.env.PORT}.`);
});
const response = await fetch(`${process.env.PAYLOAD_PUBLIC_SERVER_URL}/api/users/first-register`, {
body: JSON.stringify({
email: testCredentials.email,
password: testCredentials.password,
}),
headers: {
'Content-Type': 'application/json',
},
method: 'post',
});
const data = await response.json();
if (!data.user || !data.user.token) {
throw new Error('Failed to register first user');
}
};
export default globalSetup;
```
5. Add a `jest.config.ts` file to the root of your project:
```ts
module.exports = {
verbose: true,
globalSetup: '<rootDir>/src/tests/globalSetup.ts',
roots: ['<rootDir>/src/'],
extensionsToTreatAsEsm: ['.ts', '.tsx'],
transform: {
'^.+\\.(t|j)sx?$': [
'@swc/jest',
{
jsc: {
target: 'es2021',
},
},
],
},
};
```
6. Write your first test. Create a file at `src/tests/login.spec.ts` with the following contents:
```ts
import { User } from '../payload-types';
import testCredentials from './credentials';
describe('Users', () => {
it('should allow a user to log in', async () => {
const result: {
token: string
user: User
} = await fetch(`${process.env.PAYLOAD_PUBLIC_SERVER_URL}/api/users/login`, {
method: 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: testCredentials.email,
password: testCredentials.password,
}),
}).then((res) => res.json());
expect(result.token).toBeDefined();
});
});
```
7. Add a script to run tests via the command line. Add the following to your `package.json` scripts:
```json
"scripts": {
"test": "NODE_OPTIONS=--experimental-vm-modules jest --forceExit --detectOpenHandles"
}
```
8. Run your tests:
```bash
yarn test
```

View File

@@ -0,0 +1,16 @@
module.exports = {
verbose: true,
globalSetup: '<rootDir>/src/tests/globalSetup.ts',
roots: ['<rootDir>/src/'],
extensionsToTreatAsEsm: ['.ts', '.tsx'],
transform: {
'^.+\\.(t|j)sx?$': [
'@swc/jest',
{
jsc: {
target: 'es2021',
},
},
],
},
};

View File

@@ -0,0 +1,6 @@
{
"watch": [
"./src/**/*.ts"
],
"exec": "ts-node ./src/server.ts"
}

View File

@@ -0,0 +1,32 @@
{
"name": "jest-payload",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"dotenv": "^16.3.1",
"express": "^4.18.2",
"get-tsconfig": "^4.7.0",
"payload": "^1.15.6"
},
"devDependencies": {
"@swc/core": "^1.3.84",
"@swc/jest": "^0.2.29",
"@types/jest": "^29.5.4",
"isomorphic-fetch": "^3.0.0",
"jest": "^29.7.0",
"mongodb-memory-server": "^8.15.1",
"nodemon": "^3.0.1",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
},
"scripts": {
"generate:types": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
"dev": "nodemon",
"build:payload": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build",
"build:server": "tsc",
"build": "yarn build:server && yarn build:payload",
"serve": "PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
"test": "NODE_OPTIONS=--experimental-vm-modules jest --forceExit --detectOpenHandles"
}
}

View File

@@ -0,0 +1,35 @@
/* tslint:disable */
/* eslint-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 {
collections: {
posts: Post;
users: User;
};
globals: {};
}
export interface Post {
id: string;
title?: string;
author?: string | User;
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;
}

View File

@@ -0,0 +1,33 @@
import path from 'path';
import dotenv from 'dotenv';
import { buildConfig } from 'payload/config';
dotenv.config({
path: path.resolve(__dirname, '../.env'),
});
export default buildConfig({
serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL,
typescript: {
outputFile: path.resolve(__dirname, 'payload-types.ts'),
},
collections: [
{
slug: 'posts',
admin: {
useAsTitle: 'title',
},
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'author',
type: 'relationship',
relationTo: 'users',
},
],
},
],
});

View File

@@ -0,0 +1,24 @@
import path from 'path';
import express from 'express';
import payload from 'payload';
// Use `dotenv` to import your `.env` file automatically
require('dotenv').config({
path: path.resolve(__dirname, '../.env'),
});
const app = express();
async function start() {
await payload.init({
secret: process.env.PAYLOAD_SECRET_KEY,
mongoURL: process.env.MONGO_URL,
express: app,
});
app.listen(process.env.PORT, async () => {
console.log(`Express is now listening for incoming connections on port ${process.env.PORT}.`);
});
}
start();

View File

@@ -0,0 +1,4 @@
export default {
email: 'test@test.com',
password: 'test',
};

View File

@@ -0,0 +1,41 @@
import { resolve } from 'path';
import payload from 'payload';
import express from 'express';
import testCredentials from './credentials';
require('dotenv').config({
path: resolve(__dirname, '../../.env'),
});
const app = express();
const globalSetup = async () => {
await payload.init({
secret: process.env.PAYLOAD_SECRET_KEY,
mongoURL: process.env.MONGO_URL,
express: app,
});
app.listen(process.env.PORT, async () => {
console.log(`Express is now listening for incoming connections on port ${process.env.PORT}.`);
});
const response = await fetch(`${process.env.PAYLOAD_PUBLIC_SERVER_URL}/api/users/first-register`, {
body: JSON.stringify({
email: testCredentials.email,
password: testCredentials.password,
}),
headers: {
'Content-Type': 'application/json',
},
method: 'post',
});
const data = await response.json();
if (!data.user || !data.user.token) {
throw new Error('Failed to register first user');
}
};
export default globalSetup;

View File

@@ -0,0 +1,22 @@
import { User } from '../payload-types';
import testCredentials from './credentials';
describe('Users', () => {
it('should allow a user to log in', async () => {
const result: {
token: string
user: User
} = await fetch(`${process.env.PAYLOAD_PUBLIC_SERVER_URL}/api/users/login`, {
method: 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: testCredentials.email,
password: testCredentials.password,
}),
}).then((res) => res.json());
expect(result.token).toBeDefined();
});
});

View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"outDir": "./dist",
"skipLibCheck": true,
"strict": false,
"esModuleInterop": true,
"module": "commonjs",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"sourceMap": true
},
"include": [
"src"
],
"ts-node": {
"transpileOnly": true
}
}

8667
examples/testing/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3497,9 +3497,9 @@ graphql-type-json@^0.3.2:
integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==
graphql@^16.6.0:
version "16.7.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.7.1.tgz#11475b74a7bff2aefd4691df52a0eca0abd9b642"
integrity sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==
version "16.8.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07"
integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==
gzip-size@^6.0.0:
version "6.0.0"
@@ -5534,9 +5534,9 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.2.15, postcss@^8.4.21, postcss@^8.4.24:
version "8.4.27"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057"
integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==
version "8.4.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"

View File

@@ -2,8 +2,8 @@ module.exports = {
verbose: true,
testEnvironment: 'node',
testMatch: [
'**/src/**/*.spec.ts',
'**/test/**/*int.spec.ts',
'<rootDir>/src/**/*.spec.ts',
'<rootDir>/test/**/*int.spec.ts',
],
transform: {
'^.+\\.(t|j)sx?$': ['@swc/jest'],

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "1.13.4",
"version": "1.15.12",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"engines": {
@@ -40,15 +40,18 @@
"dev:generate-types": "ts-node -T ./test/generateTypes.ts",
"dev:generate-graphql-schema": "ts-node -T ./test/generateGraphQLSchema.ts",
"pretest": "yarn build",
"prepack": "yarn clean && yarn build",
"prepublishOnly": "yarn clean && yarn build",
"test": "yarn test:int && yarn test:components && yarn test:e2e",
"test:int": "cross-env DISABLE_LOGGING=true jest --forceExit --detectOpenHandles",
"test:e2e": "ts-node -T ./test/runE2E.ts",
"test:e2e:headed": "cross-env DISABLE_LOGGING=true playwright test --headed",
"test:e2e:debug": "cross-env PWDEBUG=1 DISABLE_LOGGING=true playwright test",
"test:components": "cross-env jest --config=jest.components.config.js",
"translateNewKeys": "ts-node -T ./scripts/translateNewKeys.ts",
"clean:cache": "rimraf node_modules/.cache",
"clean": "rimraf dist",
"release:patch": "release-it patch",
"release:patch": "release-it patch --npm.tag=payload-1",
"release:minor": "release-it minor",
"release:major": "release-it major",
"release:beta": "release-it pre --preReleaseId=beta --npm.tag=beta --config .release-it.pre.json",
@@ -88,8 +91,8 @@
"@faceless-ui/scroll-info": "^1.3.0",
"@faceless-ui/window-info": "^2.1.1",
"@monaco-editor/react": "^4.5.1",
"@swc/core": "^1.3.76",
"@swc/register": "^0.1.10",
"@swc/core": "1.3.78",
"@swc/register": "0.1.10",
"@types/sharp": "^0.31.1",
"body-parser": "^1.20.1",
"bson-objectid": "^2.0.4",
@@ -158,7 +161,6 @@
"probe-image-size": "^6.0.0",
"process": "^0.11.10",
"qs": "^6.11.0",
"qs-middleware": "^1.0.3",
"react": "^18.2.0",
"react-animate-height": "^2.1.2",
"react-datepicker": "^4.10.0",

128
scripts/translateNewKeys.ts Normal file
View File

@@ -0,0 +1,128 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable no-continue */
/* eslint-disable no-restricted-syntax */
import * as fs from 'fs';
import * as path from 'path';
const TRANSLATIONS_DIR = './src/translations';
const SOURCE_LANG_FILE = 'en.json';
const OPENAI_ENDPOINT = 'https://api.openai.com/v1/chat/completions'; // Adjust if needed
const OPENAI_API_KEY = 'sk-YOURKEYHERE'; // Remember to replace with your actual key
async function main() {
const sourceLangContent = JSON.parse(fs.readFileSync(path.join(TRANSLATIONS_DIR, SOURCE_LANG_FILE), 'utf8'));
const files = fs.readdirSync(TRANSLATIONS_DIR);
for (const file of files) {
if (file === SOURCE_LANG_FILE) {
continue;
}
// check if file ends with .json
if (!file.endsWith('.json')) {
continue;
}
// skip the translation-schema.json file
if (file === 'translation-schema.json') {
continue;
}
console.log('Processing file:', file);
const targetLangContent = JSON.parse(fs.readFileSync(path.join(TRANSLATIONS_DIR, file), 'utf8'));
const missingKeys = findMissingKeys(sourceLangContent, targetLangContent);
let hasChanged = false;
for (const missingKey of missingKeys) {
const keys = missingKey.split('.');
const sourceText = keys.reduce((acc, key) => acc[key], sourceLangContent);
const targetLang = file.split('.')[0];
const translatedText = await translateText(sourceText, targetLang);
let targetObj = targetLangContent;
for (let i = 0; i < keys.length - 1; i += 1) {
if (!targetObj[keys[i]]) {
targetObj[keys[i]] = {};
}
targetObj = targetObj[keys[i]];
}
targetObj[keys[keys.length - 1]] = translatedText;
hasChanged = true;
}
if (hasChanged) {
const sortedContent = sortKeys(targetLangContent);
fs.writeFileSync(path.join(TRANSLATIONS_DIR, file), JSON.stringify(sortedContent, null, 2));
}
}
}
main().then(() => {
console.log('Translation update completed.');
}).catch((error) => {
console.error('Error occurred:', error);
});
async function translateText(text: string, targetLang: string): Promise<string> {
const response = await fetch(OPENAI_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${OPENAI_API_KEY}`,
},
body: JSON.stringify({
max_tokens: 150,
model: 'gpt-4',
messages: [
{
role: 'system',
content: `Only respond with the translation of the text you receive. The original language is English and the translation language is ${targetLang}. Only respond with the translation - do not say anything else. If you cannot translate the text, respond with "[SKIPPED]"`,
},
{
role: 'user',
content: text,
},
],
}),
});
const data = await response.json();
console.log(' Old text:', text, 'New text:', data.choices[0].message.content.trim());
return data.choices[0].message.content.trim();
}
function findMissingKeys(baseObj: any, targetObj: any, prefix = ''): string[] {
let missingKeys = [];
for (const key in baseObj) {
if (typeof baseObj[key] === 'object') {
missingKeys = missingKeys.concat(findMissingKeys(baseObj[key], targetObj[key] || {}, `${prefix}${key}.`));
} else if (!(key in targetObj)) {
missingKeys.push(`${prefix}${key}`);
}
}
return missingKeys;
}
function sortKeys(obj: any): any {
if (typeof obj !== 'object' || obj === null) return obj;
if (Array.isArray(obj)) {
return obj.map(sortKeys);
}
const sortedKeys = Object.keys(obj).sort();
const sortedObj: { [key: string]: any } = {};
for (const key of sortedKeys) {
sortedObj[key] = sortKeys(obj[key]);
}
return sortedObj;
}

View File

@@ -72,6 +72,7 @@ const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, Props>((props,
iconPosition = 'right',
newTab,
tooltip,
'aria-label': ariaLabel,
} = props;
const [showTooltip, setShowTooltip] = React.useState(false);
@@ -101,6 +102,8 @@ const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, Props>((props,
type,
className: classes,
disabled,
'aria-disabled': disabled,
'aria-label': ariaLabel,
onMouseEnter: tooltip ? () => setShowTooltip(true) : undefined,
onMouseLeave: tooltip ? () => setShowTooltip(false) : undefined,
onClick: !disabled ? handleClick : undefined,

View File

@@ -19,4 +19,5 @@ export type Props = {
iconPosition?: 'left' | 'right',
newTab?: boolean
tooltip?: string
'aria-label'?: string
}

View File

@@ -5,7 +5,8 @@
padding: base(1.25) $baseline;
position: relative;
h5 {
&__title {
@extend %h5;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

View File

@@ -7,7 +7,7 @@ import './index.scss';
const baseClass = 'card';
const Card: React.FC<Props> = (props) => {
const { id, title, actions, onClick } = props;
const { id, title, titleAs, buttonAriaLabel, actions, onClick } = props;
const classes = [
baseClass,
@@ -15,14 +15,16 @@ const Card: React.FC<Props> = (props) => {
onClick && `${baseClass}--has-onclick`,
].filter(Boolean).join(' ');
const Tag = titleAs ?? 'div';
return (
<div
className={classes}
id={id}
>
<h5>
<Tag className={`${baseClass}__title`}>
{title}
</h5>
</Tag>
{actions && (
<div className={`${baseClass}__actions`}>
{actions}
@@ -30,6 +32,7 @@ const Card: React.FC<Props> = (props) => {
)}
{onClick && (
<Button
aria-label={buttonAriaLabel}
className={`${baseClass}__click`}
buttonStyle="none"
onClick={onClick}

View File

@@ -1,6 +1,10 @@
import { ElementType } from 'react';
export type Props = {
id?: string,
title: string,
titleAs?: ElementType,
buttonAriaLabel?: string,
actions?: React.ReactNode,
onClick?: () => void,
}

View File

@@ -2,9 +2,9 @@ import React from 'react';
import Editor from '@monaco-editor/react';
import type { Props } from './types';
import { useTheme } from '../../utilities/Theme';
import { ShimmerEffect } from '../ShimmerEffect';
import './index.scss';
import { ShimmerEffect } from '../ShimmerEffect';
const baseClass = 'code-editor';

View File

@@ -1,4 +1,4 @@
import React, { useId, useState } from 'react';
import React, { useId } from 'react';
import { useTranslation } from 'react-i18next';
import Pill from '../Pill';
import Plus from '../../icons/Plus';
@@ -61,6 +61,7 @@ const ColumnSelector: React.FC<Props> = (props) => {
alignIcon="left"
key={`${collection.slug}-${col.name || i}${editDepth ? `-${editDepth}-` : ''}${uuid}`}
icon={active ? <X /> : <Plus />}
aria-checked={active}
className={[
`${baseClass}__column`,
active && `${baseClass}__column--active`,

View File

@@ -90,7 +90,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
const isEditing = Boolean(id);
const apiURL = id ? `${serverURL}${api}/${collectionSlug}/${id}?locale=${locale}` : null;
const action = `${serverURL}${api}/${collectionSlug}${id ? `/${id}` : ''}?locale=${locale}&depth=0&fallback-locale=null`;
const action = `${serverURL}${api}/${collectionSlug}${id ? `/${id}` : ''}?locale=${locale}&fallback-locale=null`;
const hasSavePermission = (isEditing && docPermissions?.update?.permission) || (!isEditing && (docPermissions as CollectionPermission)?.create?.permission);
const isLoading = !internalState || !docPermissions || isLoadingDocument;

View File

@@ -5,11 +5,11 @@ import { useTranslation } from 'react-i18next';
import { Props, TogglerProps } from './types';
import { EditDepthContext, useEditDepth } from '../../utilities/EditDepth';
import { Gutter } from '../Gutter';
import './index.scss';
import X from '../../icons/X';
const baseClass = 'drawer';
import './index.scss';
const baseClass = 'drawer';
const zBase = 100;
export const formatDrawerSlug = ({

View File

@@ -145,6 +145,7 @@ const EditMany: React.FC<Props> = (props) => {
<Form
className={`${baseClass}__form`}
onSuccess={onSuccess}
configFieldsSchema={selected}
>
<div className={`${baseClass}__main`}>
<div className={`${baseClass}__header`}>

View File

@@ -38,6 +38,11 @@ const getUseAsTitle = (collection: SanitizedCollectionConfig) => {
return topLevelFields.find((field) => fieldAffectsData(field) && field.name === useAsTitle);
};
/**
* The ListControls component is used to render the controls (search, filter, where)
* for a collection's list view. You can find those directly above the table which lists
* the collection's documents.
*/
const ListControls: React.FC<Props> = (props) => {
const {
collection,
@@ -105,6 +110,8 @@ const ListControls: React.FC<Props> = (props) => {
pillStyle="light"
className={`${baseClass}__toggle-columns ${visibleDrawer === 'columns' ? `${baseClass}__buttons-active` : ''}`}
onClick={() => setVisibleDrawer(visibleDrawer !== 'columns' ? 'columns' : undefined)}
aria-expanded={visibleDrawer === 'columns'}
aria-controls={`${baseClass}-columns`}
icon={<Chevron />}
>
{t('columns')}
@@ -114,6 +121,8 @@ const ListControls: React.FC<Props> = (props) => {
pillStyle="light"
className={`${baseClass}__toggle-where ${visibleDrawer === 'where' ? `${baseClass}__buttons-active` : ''}`}
onClick={() => setVisibleDrawer(visibleDrawer !== 'where' ? 'where' : undefined)}
aria-expanded={visibleDrawer === 'where'}
aria-controls={`${baseClass}-where`}
icon={<Chevron />}
>
{t('filters')}
@@ -123,6 +132,8 @@ const ListControls: React.FC<Props> = (props) => {
className={`${baseClass}__toggle-sort`}
buttonStyle={visibleDrawer === 'sort' ? undefined : 'secondary'}
onClick={() => setVisibleDrawer(visibleDrawer !== 'sort' ? 'sort' : undefined)}
aria-expanded={visibleDrawer === 'sort'}
aria-controls={`${baseClass}-sort`}
icon="chevron"
iconStyle="none"
>
@@ -136,6 +147,7 @@ const ListControls: React.FC<Props> = (props) => {
<AnimateHeight
className={`${baseClass}__columns`}
height={visibleDrawer === 'columns' ? 'auto' : 0}
id={`${baseClass}-columns`}
>
<ColumnSelector collection={collection} />
</AnimateHeight>
@@ -143,6 +155,7 @@ const ListControls: React.FC<Props> = (props) => {
<AnimateHeight
className={`${baseClass}__where`}
height={visibleDrawer === 'where' ? 'auto' : 0}
id={`${baseClass}-where`}
>
<WhereBuilder
collection={collection}
@@ -154,6 +167,7 @@ const ListControls: React.FC<Props> = (props) => {
<AnimateHeight
className={`${baseClass}__sort`}
height={visibleDrawer === 'sort' ? 'auto' : 0}
id={`${baseClass}-sort`}
>
<SortComplex
modifySearchQuery={modifySearchQuery}

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useConfig } from '../../utilities/Config';
import RenderCustomComponent from '../../utilities/RenderCustomComponent';
import LogOut from '../../icons/LogOut';
@@ -7,16 +8,21 @@ import LogOut from '../../icons/LogOut';
const baseClass = 'nav';
const DefaultLogout = () => {
const { t } = useTranslation('authentication');
const config = useConfig();
const {
routes: { admin },
admin: {
logoutRoute,
components: { logout }
}
components: { logout },
},
} = config;
return (
<Link to={`${admin}${logoutRoute}`} className={`${baseClass}__log-out`}>
<Link
to={`${admin}${logoutRoute}`}
className={`${baseClass}__log-out`}
aria-label={t('logOut')}
>
<LogOut />
</Link>
);

View File

@@ -24,7 +24,7 @@ const DefaultNav = () => {
const [menuActive, setMenuActive] = useState(false);
const [groups, setGroups] = useState<Group[]>([]);
const history = useHistory();
const { i18n } = useTranslation('general');
const { t, i18n } = useTranslation('general');
const {
collections,
globals,
@@ -81,6 +81,7 @@ const DefaultNav = () => {
<Link
to={admin}
className={`${baseClass}__brand`}
aria-label={t('dashboard')}
>
<Icon />
</Link>
@@ -141,6 +142,7 @@ const DefaultNav = () => {
<Link
to={`${admin}/account`}
className={`${baseClass}__account`}
aria-label={t('authentication:account')}
>
<Account />
</Link>

View File

@@ -45,6 +45,10 @@ const StaticPill: React.FC<Props> = (props) => {
children,
elementProps,
rounded,
'aria-label': ariaLabel,
'aria-expanded': ariaExpanded,
'aria-controls': ariaControls,
'aria-checked': ariaChecked,
} = props;
const classes = [
@@ -67,6 +71,10 @@ const StaticPill: React.FC<Props> = (props) => {
return (
<Element
{...elementProps}
aria-label={ariaLabel}
aria-expanded={ariaExpanded}
aria-controls={ariaControls}
aria-checked={ariaChecked}
className={classes}
type={Element === 'button' ? 'button' : undefined}
to={to || undefined}

View File

@@ -11,6 +11,10 @@ export type Props = {
draggable?: boolean,
rounded?: boolean
id?: string
'aria-label'?: string,
'aria-expanded'?: boolean,
'aria-controls'?: string,
'aria-checked'?: boolean,
elementProps?: HTMLAttributes<HTMLElement> & {
ref: React.RefCallback<HTMLElement>
}

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { components as SelectComponents, MultiValueProps } from 'react-select';
import type { Option } from '../types';
import './index.scss';
const baseClass = 'multi-value-label';

View File

@@ -4,6 +4,7 @@ import { MultiValueRemoveProps } from 'react-select';
import X from '../../../icons/X';
import Tooltip from '../../Tooltip';
import { Option as OptionType } from '../types';
import './index.scss';
const baseClass = 'multi-value-remove';

View File

@@ -45,6 +45,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
components,
isCreatable,
selectProps,
noOptionsMessage,
} = props;
const classes = [
@@ -72,6 +73,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
filterOption={filterOption}
onMenuOpen={onMenuOpen}
menuPlacement="auto"
noOptionsMessage={noOptionsMessage}
components={{
ValueContainer,
SingleValue,
@@ -134,6 +136,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
inputValue={inputValue}
onInputChange={(newValue) => setInputValue(newValue)}
onKeyDown={handleKeyDown}
noOptionsMessage={noOptionsMessage}
components={{
ValueContainer,
SingleValue,

View File

@@ -43,6 +43,7 @@ export type OptionGroup = {
}
export type Props = {
inputId?: string
className?: string
value?: Option | Option[],
onChange?: (value: any) => void, // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -76,4 +77,5 @@ export type Props = {
*/
selectProps?: CustomSelectProps
backspaceRemovesValue?: boolean
noOptionsMessage?: (obj: { inputValue: string }) => string
}

View File

@@ -18,7 +18,7 @@ const SortColumn: React.FC<Props> = (props) => {
} = props;
const params = useSearchParams();
const history = useHistory();
const { i18n } = useTranslation();
const { t, i18n } = useTranslation('general');
const { sort } = params;
@@ -50,6 +50,7 @@ const SortColumn: React.FC<Props> = (props) => {
buttonStyle="none"
className={ascClasses.join(' ')}
onClick={() => setSort(asc)}
aria-label={t('sortByLabelDirection', { label: getTranslation(label, i18n), direction: t('ascending') })}
>
<Chevron />
</Button>
@@ -58,6 +59,7 @@ const SortColumn: React.FC<Props> = (props) => {
buttonStyle="none"
className={descClasses.join(' ')}
onClick={() => setSort(desc)}
aria-label={t('sortByLabelDirection', { label: getTranslation(label, i18n), direction: t('descending') })}
>
<Chevron />
</Button>

View File

@@ -66,11 +66,11 @@ const Status: React.FC = () => {
}
if (collection) {
url = `${serverURL}${api}/${collection.slug}/${id}?depth=0&locale=${locale}&fallback-locale=null`;
url = `${serverURL}${api}/${collection.slug}/${id}?locale=${locale}&fallback-locale=null`;
method = 'patch';
}
if (global) {
url = `${serverURL}${api}/globals/${global.slug}?depth=0&locale=${locale}&fallback-locale=null`;
url = `${serverURL}${api}/globals/${global.slug}?locale=${locale}&fallback-locale=null`;
method = 'post';
}

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { Props, isComponent } from './types';
import { getTranslation } from '../../../../utilities/getTranslation';
import './index.scss';
const ViewDescription: React.FC<Props> = (props) => {

View File

@@ -41,7 +41,7 @@ const numeric = [
},
{
label: 'isGreaterThanOrEqualTo',
value: 'greater_than_equals',
value: 'greater_than_equal',
},
];
@@ -57,6 +57,16 @@ const geo = [
},
];
const within = {
label: 'within',
value: 'within',
};
const intersects = {
label: 'intersects',
value: 'intersects',
};
const like = {
label: 'isLike',
value: 'like',
@@ -86,7 +96,7 @@ const fieldTypeConditions = {
},
json: {
component: 'Text',
operators: [...base, like, contains],
operators: [...base, like, contains, within, intersects],
},
richText: {
component: 'Text',
@@ -102,7 +112,7 @@ const fieldTypeConditions = {
},
point: {
component: 'Point',
operators: [...geo],
operators: [...geo, within, intersects],
},
upload: {
component: 'Text',

View File

@@ -13,6 +13,7 @@ import { useSearchParams } from '../../utilities/SearchParams';
import validateWhereQuery from './validateWhereQuery';
import { Where } from '../../../../types';
import { getTranslation } from '../../../../utilities/getTranslation';
import { transformWhereQuery } from './transformWhereQuery';
import './index.scss';
@@ -43,6 +44,10 @@ const reduceFields = (fields, i18n) => flattenTopLevelFields(fields).reduce((red
return reduced;
}, []);
/**
* The WhereBuilder component is used to render the filter controls for a collection's list view.
* It is part of the {@link ListControls} component which is used to render the controls (search, filter, where).
*/
const WhereBuilder: React.FC<Props> = (props) => {
const {
collection,
@@ -59,16 +64,30 @@ const WhereBuilder: React.FC<Props> = (props) => {
const params = useSearchParams();
const { t, i18n } = useTranslation('general');
// This handles initializing the where conditions from the search query (URL). That way, if you pass in
// query params to the URL, the where conditions will be initialized from those and displayed in the UI.
// Example: /admin/collections/posts?where[or][0][and][0][text][equals]=example%20post
const [conditions, dispatchConditions] = useReducer(reducer, params.where, (whereFromSearch) => {
if (modifySearchQuery && validateWhereQuery(whereFromSearch)) {
return whereFromSearch.or;
}
if (modifySearchQuery && whereFromSearch) {
if (validateWhereQuery(whereFromSearch)) {
return whereFromSearch.or;
}
// Transform the where query to be in the right format. This will transform something simple like [text][equals]=example%20post to the right format
const transformedWhere = transformWhereQuery(whereFromSearch);
if (validateWhereQuery(transformedWhere)) {
return transformedWhere.or;
}
console.warn('Invalid where query in URL. Ignoring.');
}
return [];
});
const [reducedFields] = useState(() => reduceFields(collection.fields, i18n));
// This handles updating the search query (URL) when the where conditions change
useThrottledEffect(() => {
const currentParams = queryString.parse(history.location.search, { ignoreQueryPrefix: true, depth: 10 }) as { where: Where };
@@ -83,8 +102,11 @@ const WhereBuilder: React.FC<Props> = (props) => {
];
}, []) : [];
const hasNewWhereConditions = conditions.length > 0;
const newWhereQuery = {
...typeof currentParams?.where === 'object' ? currentParams.where : {},
...typeof currentParams?.where === 'object' && (validateWhereQuery(currentParams?.where) || !hasNewWhereConditions) ? currentParams.where : {},
or: [
...conditions,
...paramsToKeep,
@@ -94,7 +116,6 @@ const WhereBuilder: React.FC<Props> = (props) => {
if (handleChange) handleChange(newWhereQuery as Where);
const hasExistingConditions = typeof currentParams?.where === 'object' && 'or' in currentParams.where;
const hasNewWhereConditions = conditions.length > 0;
if (modifySearchQuery && ((hasExistingConditions && !hasNewWhereConditions) || hasNewWhereConditions)) {
history.replace({

View File

@@ -0,0 +1,51 @@
import type { Where } from '../../../../types';
/**
* Something like [or][0][and][0][text][equals]=example%20post will work and pass through the validateWhereQuery check.
* However, something like [text][equals]=example%20post will not work and will fail the validateWhereQuery check,
* even though it is a valid Where query. This needs to be transformed here.
*/
export const transformWhereQuery = (whereQuery): Where => {
if (!whereQuery) {
return {};
}
// Check if 'whereQuery' has 'or' field but no 'and'. This is the case for "correct" queries
if (whereQuery.or && !whereQuery.and) {
return {
or: whereQuery.or.map((query) => {
// ...but if the or query does not have an and, we need to add it
if(!query.and) {
return {
and: [query]
}
}
return query;
}),
};
}
// Check if 'whereQuery' has 'and' field but no 'or'.
if (whereQuery.and && !whereQuery.or) {
return {
or: [
{
and: whereQuery.and,
},
],
};
}
// Check if 'whereQuery' has neither 'or' nor 'and'.
if (!whereQuery.or && !whereQuery.and) {
return {
or: [
{
and: [whereQuery], // top-level siblings are considered 'and'
},
],
};
}
// If 'whereQuery' has 'or' and 'and', just return it as it is.
return whereQuery;
};

View File

@@ -1,8 +1,37 @@
import { Where } from '../../../../types';
import type { Operator, Where } from '../../../../types';
import { validOperators } from '../../../../types/constants';
const validateWhereQuery = (whereQuery): whereQuery is Where => {
if (whereQuery?.or?.length > 0 && whereQuery?.or?.[0]?.and && whereQuery?.or?.[0]?.and?.length > 0) {
return true;
// At this point we know that the whereQuery has 'or' and 'and' fields,
// now let's check the structure and content of these fields.
const isValid = whereQuery.or.every((orQuery) => {
if (orQuery.and && Array.isArray(orQuery.and)) {
return orQuery.and.every((andQuery) => {
if (typeof andQuery !== 'object') {
return false;
}
const andKeys = Object.keys(andQuery);
// If there are no keys, it's not a valid WhereField.
if (andKeys.length === 0) {
return false;
}
// eslint-disable-next-line no-restricted-syntax
for (const key of andKeys) {
const operator = Object.keys(andQuery[key])[0];
// Check if the key is a valid Operator.
if (!operator || !validOperators.includes(operator as Operator)) {
return false;
}
}
return true;
});
}
return false;
});
return isValid;
}
return false;

View File

@@ -111,7 +111,7 @@ export const addFieldStatePromise = async ({
acc.rowMetadata.push({
id: row.id,
collapsed: collapsedRowIDs === undefined ? field.admin.initCollapsed : collapsedRowIDs.includes(row.id),
collapsed: collapsedRowIDs === undefined ? Boolean(field?.admin?.initCollapsed) : collapsedRowIDs.includes(row.id),
childErrorPaths: new Set(),
});
@@ -191,7 +191,7 @@ export const addFieldStatePromise = async ({
acc.rowMetadata.push({
id: row.id,
collapsed: collapsedRowIDs === undefined ? field.admin.initCollapsed : collapsedRowIDs.includes(row.id),
collapsed: collapsedRowIDs === undefined ? Boolean(field?.admin?.initCollapsed) : collapsedRowIDs.includes(row.id),
blockType: row.blockType,
childErrorPaths: new Set(),
});
@@ -245,6 +245,54 @@ export const addFieldStatePromise = async ({
break;
}
case 'relationship': {
if (field.hasMany) {
const relationshipValue = Array.isArray(valueWithDefault) ? valueWithDefault.map((relationship) => {
if (Array.isArray(field.relationTo)) {
return {
relationTo: relationship.relationTo,
value: typeof relationship.value === 'string' ? relationship.value : relationship.value?.id,
};
}
if (typeof relationship === 'object' && relationship !== null) {
return relationship.id;
}
return relationship;
}) : undefined;
fieldState.value = relationshipValue;
fieldState.initialValue = relationshipValue;
} else if (Array.isArray(field.relationTo)) {
if (valueWithDefault && typeof valueWithDefault === 'object' && 'relationTo' in valueWithDefault && 'value' in valueWithDefault) {
const value = typeof valueWithDefault?.value === 'object' && 'id' in valueWithDefault.value ? valueWithDefault.value.id : valueWithDefault.value;
const relationshipValue = {
relationTo: valueWithDefault?.relationTo,
value,
};
fieldState.value = relationshipValue;
fieldState.initialValue = relationshipValue;
}
} else {
const relationshipValue = valueWithDefault && typeof valueWithDefault === 'object' && 'id' in valueWithDefault ? valueWithDefault.id : valueWithDefault;
fieldState.value = relationshipValue;
fieldState.initialValue = relationshipValue;
}
state[`${path}${field.name}`] = fieldState;
break;
}
case 'upload': {
const relationshipValue = valueWithDefault && typeof valueWithDefault === 'object' && 'id' in valueWithDefault ? valueWithDefault.id : valueWithDefault;
fieldState.value = relationshipValue;
fieldState.initialValue = relationshipValue;
state[`${path}${field.name}`] = fieldState;
break;
}
default: {
fieldState.value = valueWithDefault;
fieldState.initialValue = valueWithDefault;

View File

@@ -48,6 +48,7 @@ const Form: React.FC<Props> = (props) => {
initialState, // fully formed initial field state
initialData, // values only, paths are required as key - form should build initial state as convenience
waitForAutocomplete,
configFieldsSchema,
} = props;
const history = useHistory();
@@ -409,12 +410,14 @@ const Form: React.FC<Props> = (props) => {
path: string,
blockType?: string
}) => {
const rowConfig = traverseRowConfigs({ path, fieldConfig: collection?.fields || global?.fields });
if (!configFieldsSchema) return null;
const rowConfig = traverseRowConfigs({ path, fieldConfig: configFieldsSchema });
const rowFieldConfigs = buildFieldSchemaMap(rowConfig);
const pathSegments = splitPathByArrayFields(path);
const fieldKey = pathSegments.at(-1);
return rowFieldConfigs.get(blockType ? `${fieldKey}.${blockType}` : fieldKey);
}, [traverseRowConfigs, collection?.fields, global?.fields]);
}, [traverseRowConfigs, configFieldsSchema]);
// Array/Block row manipulation
const addFieldRow: Context['addFieldRow'] = useCallback(async ({ path, rowIndex, data }) => {

View File

@@ -49,6 +49,7 @@ export type Props = {
validationOperation?: 'create' | 'update'
children?: React.ReactNode
action?: string
configFieldsSchema?: FieldConfig[]
}
export type SubmitOptions = {

View File

@@ -1,62 +1,74 @@
import React from 'react';
import Check from '../../../icons/Check';
import Label from '../../Label';
import Line from '../../../icons/Line';
import './index.scss';
const baseClass = 'custom-checkbox';
type CheckboxInputProps = {
onToggle: React.MouseEventHandler<HTMLButtonElement>
onToggle: React.FormEventHandler<HTMLInputElement>
inputRef?: React.MutableRefObject<HTMLInputElement>
readOnly?: boolean
checked?: boolean
partialChecked?: boolean
name?: string
id?: string
label?: string
'aria-label'?: string
required?: boolean
}
export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
const {
onToggle,
checked,
partialChecked,
inputRef,
name,
id,
label,
'aria-label': ariaLabel,
readOnly,
required,
} = props;
return (
<span
<div
className={[
baseClass,
checked && `${baseClass}--checked`,
(checked || partialChecked) && `${baseClass}--checked`,
readOnly && `${baseClass}--read-only`,
].filter(Boolean).join(' ')}
>
<input
ref={inputRef}
id={id}
type="checkbox"
name={name}
checked={checked}
readOnly
/>
<button
type="button"
onClick={onToggle}
>
<span className={`${baseClass}__input`}>
<Check />
<div className={`${baseClass}__input`}>
<input
ref={inputRef}
id={id}
type="checkbox"
name={name}
aria-label={ariaLabel}
defaultChecked={Boolean(checked)}
disabled={readOnly}
onInput={onToggle}
/>
<span className={`${baseClass}__icon ${!partialChecked ? 'check' : 'partial'}`}>
{!partialChecked && (
<Check />
)}
{partialChecked && (
<Line />
)}
</span>
</div>
{label && (
<Label
htmlFor={id}
label={label}
required={required}
/>
</button>
</span>
)}
</div>
);
};

View File

@@ -4,10 +4,6 @@
position: relative;
margin-bottom: $baseline;
input[type=checkbox] {
display: none;
}
.tooltip:not([aria-hidden="true"]) {
right: auto;
position: relative;
@@ -22,32 +18,84 @@
.custom-checkbox {
display: inline-flex;
label {
padding-bottom: 0;
}
input {
// hidden HTML checkbox
position: absolute;
top: 0;
left: 0;
opacity: 0;
padding-left: base(.5);
}
&__input {
// visible checkbox
@include formInput;
display: flex;
padding: 0;
line-height: 0;
position: relative;
width: $baseline;
height: $baseline;
margin-right: base(.5);
& input[type="checkbox"] {
position: absolute;
// Without the extra 4px, there is an uncheckable area due to the border of the parent element
width: calc(100% + 4px);
height: calc(100% + 4px);
padding: 0;
margin: 0;
margin-left: -2px;
margin-top: -2px;
opacity: 0;
border-radius: 0;
z-index: 1;
cursor: pointer;
}
}
&__icon {
position: absolute;
svg {
opacity: 0;
}
}
&:not(&--read-only) {
&:active,
&:focus-within,
&:focus {
.custom-checkbox__input, & input[type="checkbox"] {
@include inputShadowActive;
outline: 0;
box-shadow: 0 0 3px 3px var(--theme-success-400)!important;
border: 1px solid var(--theme-elevation-150);
}
}
&:hover {
.custom-checkbox__input, & input[type="checkbox"] {
border-color: var(--theme-elevation-250);
}
}
}
&:not(&--read-only):not(&--checked) {
&:hover {
cursor: pointer;
svg {
opacity: 0.2;
}
}
}
&--checked {
.custom-checkbox__icon {
svg {
opacity: 1;
}
}
}
&--read-only {
.custom-checkbox__input {
@@ -58,40 +106,6 @@
color: var(--theme-elevation-400);
}
}
button {
@extend %btn-reset;
display: flex;
align-items: center;
cursor: pointer;
&:focus,
&:active {
outline: none;
}
&:focus {
.custom-checkbox__input {
box-shadow: 0 0 3px 3px var(--theme-success-400);
}
}
&:hover {
svg {
opacity: .2;
}
}
}
&--checked {
button {
.custom-checkbox__input {
svg {
opacity: 1;
}
}
}
}
}
html[data-theme=light] {

View File

@@ -88,6 +88,7 @@ const Checkbox: React.FC<Props> = (props) => {
label={getTranslation(label || name, i18n)}
name={path}
checked={Boolean(value)}
readOnly={readOnly}
/>
<FieldDescription
value={value}

View File

@@ -10,9 +10,9 @@ import { Props } from './types';
import { getTranslation } from '../../../../../utilities/getTranslation';
import { Option } from '../../../elements/ReactSelect/types';
import ReactSelect from '../../../elements/ReactSelect';
import { isNumber } from '../../../../../utilities/isNumber';
import './index.scss';
import { isNumber } from '../../../../../utilities/isNumber';
const NumberField: React.FC<Props> = (props) => {
const {
@@ -143,9 +143,17 @@ const NumberField: React.FC<Props> = (props) => {
isMulti
isSortable
isClearable
noOptionsMessage={({ inputValue }) => {
const isOverHasMany = Array.isArray(value) && value.length >= maxRows;
if (isOverHasMany) {
return t('validation:limitReached', { value: value.length + 1, max: maxRows });
}
return t('general:noOptions');
}}
filterOption={(option, rawInput) => {
// eslint-disable-next-line no-restricted-globals
return isNumber(rawInput)
const isOverHasMany = Array.isArray(value) && value.length >= maxRows;
return isNumber(rawInput) && !isOverHasMany;
}}
numberOnly
/>

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