Compare commits

..

524 Commits

Author SHA1 Message Date
Alessio Gravili
aa8d422421 fix tsconfig rscs paths 2024-11-11 10:37:21 -07:00
James
707775d1ae Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-11 12:34:36 -05:00
James
35ca534149 perf: speeds up admin ui by not fetching for initial user if logged in 2024-11-11 12:34:08 -05:00
Jacob Fletcher
12332ab5df removes custom server fn test 2024-11-11 12:33:33 -05:00
Jarrod Flesch
174631340d chore: duplicate id key error 2024-11-11 12:15:10 -05:00
Jacob Fletcher
dc8b1fe023 removes old custom server function reduce 2024-11-11 12:12:51 -05:00
Alessio Gravili
a24b72c530 exclude "TypeError: Failed to fetch" error from console error catch that is caused by server actions being aborted 2024-11-11 10:09:28 -07:00
James
3f74896167 chore: fixes form state for disabled fields, createFirstUser 2024-11-11 11:55:54 -05:00
Alessio Gravili
b240291913 make lexical tests wait even longer 2024-11-11 09:31:27 -07:00
Alessio Gravili
a2499352a5 rscs => rsc 2024-11-11 09:27:43 -07:00
Alessio Gravili
774838c421 ignore monaco load errors due to slow CI network 2024-11-11 09:14:50 -07:00
Jacob Fletcher
6d8fd3adeb removes custom server fn feature and related docs 2024-11-11 11:07:24 -05:00
James
7d627db8aa chore: fixes templates build 2024-11-11 11:06:11 -05:00
Jarrod Flesch
bda91d875f chore: adds autosave helper to live preview e2e 2024-11-11 10:56:10 -05:00
James
9eae3d685c chore: live preview e2e fix 2024-11-11 10:48:15 -05:00
Alessio Gravili
eb82ee457c Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-11 08:45:41 -07:00
Alessio Gravili
cc0b88a078 lexical tests: ensure blocks are rendered 2024-11-11 08:43:08 -07:00
Nate
26691377d2 chore: update README.md asset URL (#9104)
Updated README asset URL

<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?
Update README asset URL for hero

### Why?
Reflect latest Payload branding

### How?
URL change for correct asset

-->
2024-11-11 10:41:48 -05:00
Paul
8201a6cacd chore(templates): remove old ecommerce template (#8916)
removes the previous v2 ecommerce template from the repo ahead of v3
launch until ecomm v3 is ready
2024-11-11 10:19:24 -05:00
Nate
d7fc944792 fix: update README with new asset, image URL (#9099) 2024-11-11 10:18:11 -05:00
Jarrod Flesch
faeef5bc93 chore: improve reliablity of autosave expectation 2024-11-11 09:56:49 -05:00
Jarrod Flesch
b841f01da5 chore: set defaults for version max docs on globals and collections 2024-11-11 09:15:43 -05:00
Alessio Gravili
2e816614ac fix broken upload file restoration in fields and admin test suites. No more console errors about payload.jpg not being found 2024-11-10 21:39:32 -07:00
Jarrod Flesch
0c4ec43460 fix: upload test order 2024-11-10 23:27:56 -05:00
Alessio Gravili
50bab5797c fix admin-root test suite 2024-11-10 20:42:38 -07:00
Alessio Gravili
de9acedaf4 maybe fix admin-root test suite 2024-11-10 20:33:50 -07:00
Alessio Gravili
6d6370ca5e fix auth and access-control test suites 2024-11-10 20:27:05 -07:00
Alessio Gravili
a3360b5d45 fix script for running all e2e tests 2024-11-10 20:08:03 -07:00
Alessio Gravili
e6f73e0c0d make extra sure that the memorydb is started, even if runInit retries 2024-11-10 20:03:11 -07:00
Alessio Gravili
f336b2a41e fix last int test 2024-11-10 19:58:52 -07:00
Alessio Gravili
7ed2859657 add ability to disable lint-staged via DISABLE_HUSKY environment variaböe 2024-11-10 19:56:05 -07:00
Alessio Gravili
d3c5cf7aff for reliability, always ensure indexes between every test case run 2024-11-10 19:12:54 -07:00
Alessio Gravili
bc3c949d95 fix incorrect importMap import for admin-root test suite 2024-11-10 18:51:56 -07:00
Alessio Gravili
ec7028ad5b fix jest not logging anything until entire test suite has finished 2024-11-10 18:49:27 -07:00
Alessio Gravili
6d2a5ddafe remove lie in console.log 2024-11-10 18:30:42 -07:00
Alessio Gravili
b83a7f2f37 make running e2e tests more reliable 2024-11-10 18:25:22 -07:00
Alessio Gravili
8deab489df fix incorrect importMap import for admin-root test suite 2024-11-10 14:03:04 -07:00
Alessio Gravili
eab9acb35d ensure mongo memory server is started when running e2e tests in CI 2024-11-10 13:51:54 -07:00
Alessio Gravili
150a31b339 passing int tests 2024-11-10 00:38:43 -07:00
Alessio Gravili
6c3e27288e log env variables 2024-11-10 00:35:09 -07:00
Alessio Gravili
3f2566a191 ensure matching react versions 2024-11-10 00:32:58 -07:00
Alessio Gravili
8bb994fc6c fix: ensure runE2e command passed down env variables and prod flag to run dev command 2024-11-10 00:22:13 -07:00
Alessio Gravili
3a48251219 fix running dev for nested test suite paths 2024-11-10 00:19:13 -07:00
Alessio Gravili
694040c8bc debug why failing tests show as passing 2024-11-10 00:08:52 -07:00
Jacob Fletcher
d5c9628958 Merge branch 'beta' into feat/on-demand-rsc 2024-11-08 17:31:26 -05:00
Jacob Fletcher
df6578183a properly initializes new doc drawer state for join field and renames renderDocument callback 2024-11-08 17:27:03 -05:00
Jarrod Flesch
a93da8625b chore: passing uploads 2024-11-08 17:21:11 -05:00
Jarrod Flesch
859726688e chore: fixes tests using previous selectors 2024-11-08 17:20:44 -05:00
Jacob Fletcher
7fb5b60932 fix join field query when creating new doc 2024-11-08 16:56:18 -05:00
Jacob Fletcher
46ebecfde4 updates underlying table after creating new from join field 2024-11-08 16:16:43 -05:00
Jacob Fletcher
6ea473aae8 gets initial columns within renderTable 2024-11-08 16:16:43 -05:00
Alessio Gravili
e0f85b5f05 Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-08 13:30:20 -07:00
Alessio Gravili
6b2c939afd update packages 2024-11-08 13:20:34 -07:00
Alessio Gravili
20b09a8213 update lockfile 2024-11-08 13:18:24 -07:00
Alessio Gravili
7c44609af4 revert dumb code I wrote 2024-11-08 13:13:12 -07:00
Alessio Gravili
b0f243632d fix: ensure richext rscs import from client bundle 2024-11-08 13:07:41 -07:00
Jacob Fletcher
4df00f4b36 fix join field filter options 2024-11-08 14:59:02 -05:00
Alessio Gravili
7aa4a5650c fix(ui): ensure rscs not exported from client bundle imported from client bundle, in order to not duplicate components (& thus contexts) in the final bundle 2024-11-08 12:58:31 -07:00
Jacob Fletcher
1cae6cf997 returns resolved promise from getClientConfig 2024-11-08 14:33:26 -05:00
Jacob Fletcher
8a8055a0a8 proper fix for field paths 2024-11-08 14:26:20 -05:00
Jacob Fletcher
d0da6bc4e1 properly sets paths on nested unnamed fields 2024-11-08 13:56:32 -05:00
Alessio Gravili
d9711aaa89 fix e2e configs 2024-11-08 11:22:51 -07:00
Alessio Gravili
e26c95d430 fix wrong import 2024-11-08 11:09:03 -07:00
Alessio Gravili
fbb8229c54 Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-08 10:38:34 -07:00
Jacob Fletcher
36925f3956 fix selectors in list column e2e tests 2024-11-08 12:37:40 -05:00
Alessio Gravili
50cc91ead3 chore: udpate all generated types 2024-11-08 10:37:12 -07:00
Alessio Gravili
58c901dfdd fix another autosave test 2024-11-08 10:36:25 -07:00
Alessio Gravili
f599735d9d fix another autosave test 2024-11-08 10:35:05 -07:00
Alessio Gravili
c9752918c8 Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-08 09:34:31 -07:00
Alessio Gravili
0b5a294f63 fix incorrectly set up monorepo app folders 2024-11-08 09:33:38 -07:00
Alessio Gravili
7422fab2f2 fix plugin-stripe type error 2024-11-08 09:19:53 -07:00
Alessio Gravili
3fd74dd77f export UI field component types 2024-11-08 09:19:37 -07:00
Alessio Gravili
0392b3d614 fix lexical errors 2024-11-08 09:07:54 -07:00
Jacob Fletcher
f1c9b2b1d0 fix next build errors 2024-11-08 10:58:04 -05:00
Alessio Gravili
997b9eb63d fix type issues, allow '' paths to take precedence over field names in field components 2024-11-08 08:47:02 -07:00
Jacob Fletcher
f8ab503153 deflakes list filtering e2e tests 2024-11-08 10:19:58 -05:00
Alessio Gravili
3e105a20e4 get slate and lexical to build 2024-11-08 08:02:15 -07:00
Jacob Fletcher
906551bba4 fix where builder 2024-11-08 09:56:19 -05:00
Jacob Fletcher
6167bf3cca bulk edit 2024-11-08 09:39:35 -05:00
Jarrod Flesch
5ec26e8d54 chore: fixes ui, conditional, indexed field test suites 2024-11-08 09:14:09 -05:00
Alessio Gravili
d2bf12ac56 fix(ui): ensure autosave is not triggered endlessly 2024-11-07 23:42:34 -07:00
Alessio Gravili
a33131033f fix(ui): frontend version count going above maximum version count 2024-11-07 23:41:59 -07:00
Alessio Gravili
b13eac201a switch to isRedirectError 2024-11-07 17:42:31 -07:00
Jacob Fletcher
65553af461 adjusts initialData logic 2024-11-07 19:37:26 -05:00
Alessio Gravili
b212e2d989 fix(next): new doc creation for autosave-enabled collections. We need to allow NEXT_REDIRECT errors to throw instead of catching them 2024-11-07 17:31:52 -07:00
Alessio Gravili
3ee58f2211 fix(next): /create edit view not rendering any fields 2024-11-07 17:14:32 -07:00
Alessio Gravili
b2dabb78bb Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-07 15:57:10 -07:00
Jacob Fletcher
b78b1af0af properly increments edit depth for rich text drawers and more 2024-11-07 17:26:31 -05:00
Jacob Fletcher
42e20bd982 resolves type in buildTableState error handler 2024-11-07 16:46:21 -05:00
Jacob Fletcher
b8d18a73da resolves more types 2024-11-07 16:39:22 -05:00
Alessio Gravili
76ae85566e fix: lexical blocks kicking focus out of editor when writing 2024-11-07 14:38:32 -07:00
Jacob Fletcher
0f081a4999 fixes default file cell field type 2024-11-07 16:19:24 -05:00
Jacob Fletcher
36ce0438eb prevents infinitely referencing props 2024-11-07 16:19:24 -05:00
Jarrod Flesch
60fbc3e251 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-07 16:01:13 -05:00
Jarrod Flesch
6d11f790c0 chore: fix collapsible field test 2024-11-07 16:01:00 -05:00
James
2add224ee5 chore: makes alessio happy 2024-11-07 15:57:27 -05:00
James
8f3b0fd71b Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-07 15:55:10 -05:00
James
6a78be6b17 chore: fixes types 2024-11-07 15:54:56 -05:00
Alessio Gravili
f8bc243144 make fieldSchemaMap of fieldSchemasToFormState optional and add JSDocs 2024-11-07 13:30:54 -07:00
Alessio Gravili
6a6aa9a85f fix(richtext-lexical): fix missing case where empty lexical editors with required: true were not detected as empty 2024-11-07 13:30:24 -07:00
Alessio Gravili
fac30ad95e fix(richtext-lexical): correct usages of fieldSchemasToFormState for validation 2024-11-07 13:29:36 -07:00
James
58c792cfc9 Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-07 14:56:32 -05:00
James
2536107abb chore: fixes types 2024-11-07 14:55:52 -05:00
Alessio Gravili
057a37b50e fix docPermissions and getFormState type errors 2024-11-07 12:12:25 -07:00
Jacob Fletcher
aa402bca12 fix create new relationship drawer 2024-11-07 13:28:07 -05:00
Jacob Fletcher
64154e0818 fix edit depth, differentiates from drawer depth 2024-11-07 13:28:07 -05:00
Alessio Gravili
81a904af96 Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-07 10:42:39 -07:00
Alessio Gravili
0d468ffc6c fix(richtext-lexical): cell: replace headless editor functions to get text content with good old recursive loop, and improve display of non-text node types.
Using the headless editor was very glitchy. If the server encounters any error anywhere, then any consecutive visit of the list view, where the headless editor will run, will throw a lexical error about no active editor being found when "parseEditorState" is run, despite parseEditorState initializing a new active editor. This is cached in module scope, and server-side errors in Next.js seem to break that behavior for some reason
2024-11-07 10:41:23 -07:00
Jarrod Flesch
c35b3164a4 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-07 12:22:53 -05:00
Jarrod Flesch
f4517685df adjust list header rendering 2024-11-07 12:22:22 -05:00
Alessio Gravili
51ec6b2fa4 Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-07 10:17:40 -07:00
Alessio Gravili
fdbf86d394 fix document drawer abort controller madness 2024-11-07 09:52:38 -07:00
Jacob Fletcher
b9c41ee095 fix existing doc drawer initialization 2024-11-07 11:37:14 -05:00
Alessio Gravili
652e6910c9 fix some lexical tests again 2024-11-07 09:11:52 -07:00
James
a749617a04 Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-07 11:07:38 -05:00
James
f3cf304b6a chore: type fixes 2024-11-07 11:07:19 -05:00
Alessio Gravili
9d5dd5a61a fix flakes 2024-11-07 08:40:23 -07:00
Alessio Gravili
aa5abeea7e fix: re-render entire field when removing a row. Otherwise, the array index in paths of RSC sibling fields will not change 2024-11-07 08:40:09 -07:00
James
e11ad3371b Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-07 10:05:17 -05:00
James
d352c35c22 chore: type fixes 2024-11-07 10:04:35 -05:00
Jarrod Flesch
ac85bc4d04 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-07 09:56:28 -05:00
Jarrod Flesch
4b2e86328a fix: upload allowCreate 2024-11-07 09:56:16 -05:00
Jarrod Flesch
5e3eab7db4 chore: revert list header classname to current beta css class name 2024-11-07 09:55:51 -05:00
Jacob Fletcher
cde1135106 fix relationship drawers 2024-11-07 09:38:31 -05:00
Jarrod Flesch
fcecad6b2f Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-07 09:16:53 -05:00
Jarrod Flesch
1e696bdbde chore: passing fields->relationships 2024-11-07 09:16:40 -05:00
Alessio Gravili
0c8debe71d fix some slate tests due to new class names 2024-11-06 22:19:07 -07:00
Alessio Gravili
2bba6c80c5 fix: ensure drawers increase editdepth 2024-11-06 22:18:13 -07:00
Alessio Gravili
301666006c reduce lexical flakes 2024-11-06 21:14:32 -07:00
Alessio Gravili
205650d26d reduce lexical flakes 2024-11-06 21:12:09 -07:00
Jarrod Flesch
05b83f71de Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-06 21:10:34 -05:00
Jarrod Flesch
f097287c8c chore: passing fields->blocks 2024-11-06 21:08:47 -05:00
Jarrod Flesch
1caf5d9327 chore: passing fields->email 2024-11-06 21:07:43 -05:00
James
23afac9b01 chore: passing form-builder tests 2024-11-06 17:52:36 -05:00
James
4548d30f03 Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-06 17:47:17 -05:00
James
b7a1f4c773 chore: passing plugin-seo 2024-11-06 17:47:03 -05:00
Jacob Fletcher
6cc18e9a21 renames renderFieldMethod to renderFieldFn 2024-11-06 16:05:01 -05:00
Jacob Fletcher
0a61160665 deflakes custom logout route e2e 2024-11-06 15:35:10 -05:00
Jarrod Flesch
3c288facda Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-06 15:12:49 -05:00
Jarrod Flesch
f9ea396955 chore: passing fields->array 2024-11-06 15:12:32 -05:00
Alessio Gravili
9cccbba2e1 increase expect timeout. 2.5s is sometimes not enough for saving document 2024-11-06 13:09:28 -07:00
Jacob Fletcher
29c9e45ce6 deflakes routing e2e 2024-11-06 15:05:19 -05:00
Jarrod Flesch
f70ef8dac3 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-06 14:59:29 -05:00
Jarrod Flesch
d6d214b745 implement RowLabels 2024-11-06 14:59:14 -05:00
Alessio Gravili
90d9d6e17d speed up reinitDB for mongodb by not waiting for index creation to be completed 2024-11-06 12:13:25 -07:00
Alessio Gravili
f2d2630336 log time for reInitializeDB 2024-11-06 11:57:53 -07:00
James
62724b54df Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-06 13:32:56 -05:00
Alessio Gravili
096eefd005 do not let playwright handle starting up the dev server, as playwrights babel black magic was messing up our code. A dev server can now be re-used for multiple, separate test runs 2024-11-06 11:31:47 -07:00
James
4d75c56ef1 perf: loads document dependencies in parallel to reduce load time 2024-11-06 13:25:05 -05:00
Alessio Gravili
bd56af14d0 simplify lexical cell editor state handling 2024-11-06 11:12:23 -07:00
Jarrod Flesch
df007d42ea chore: fixes field labels, brings back array test config 2024-11-06 13:04:34 -05:00
Jacob Fletcher
dbaff20b1b fix flaky document meta tests 2024-11-06 13:03:29 -05:00
Jacob Fletcher
d400a7e77d prevents id field from rendering and dedupes duplicative custom id 2024-11-06 12:05:42 -05:00
Jarrod Flesch
791699146e chore: remove select from getVersions findVersions call 2024-11-06 12:01:21 -05:00
Jarrod Flesch
760200c1be chore: passing auth test suite 2024-11-06 10:47:28 -05:00
Jacob Fletcher
94f8787270 creates renderListViewSlots fn and avoids sending unnecessary slots, properly handles list view descriptions 2024-11-06 10:23:05 -05:00
Alessio Gravili
29a6ec1ee2 minor fixes 2024-11-06 01:51:06 -07:00
Alessio Gravili
9030aa81c3 fix(richtext-lexical): fix cannot find active editor state errors when setting editor state of headless Editor.
This happened through Slate => Upload Node Component => Swap (opens list drawer) => Select lexical error => Error here. This error is caused by a new initialization of an UploadNode
2024-11-06 01:39:46 -07:00
Alessio Gravili
6dce541b33 slate: ensure initial or required form state requests are not aborted 2024-11-06 01:30:38 -07:00
Alessio Gravili
55a9e946cc slate: fully-functional rsc-only cell component 2024-11-06 01:26:14 -07:00
Alessio Gravili
33dbcb5115 ensure lexical cell component displays "noLabel" if it's empty, instead of just an empty cell 2024-11-06 01:14:36 -07:00
Alessio Gravili
2dff78ae61 pass i18n to cell component serverProps 2024-11-06 01:13:46 -07:00
Alessio Gravili
189e514ecf fully-functional, lexical cell component. This is now an rsc, not a client component 2024-11-06 01:04:43 -07:00
Alessio Gravili
2e18638baf pass payload to server cell components, render richtext cells 2024-11-06 01:03:36 -07:00
Alessio Gravili
73c9a34918 passing lexical tests, and less flakes than on current beta 2024-11-05 23:14:09 -07:00
Jacob Fletcher
223cf6348e removes _isPreviewEnabled property from client config 2024-11-05 22:50:14 -05:00
Jacob Fletcher
69bcfbf2c6 properly retrieves in document controls and cleans up document slots 2024-11-05 22:43:41 -05:00
Jacob Fletcher
4e002310e3 fix props in custom field description within admin tests 2024-11-05 22:12:18 -05:00
Jacob Fletcher
81948094c3 fix document tabs key prop 2024-11-05 21:59:17 -05:00
Jacob Fletcher
1d7dc6926f fix document tabs key prop 2024-11-05 21:59:17 -05:00
Alessio Gravili
6c8de6665e reduce flakes, reduce test timeout when running locally 2024-11-05 15:57:41 -07:00
Alessio Gravili
3014f96113 fix(richtext-lexical): add missing dependency array 2024-11-05 15:50:41 -07:00
Alessio Gravili
b97c143ef8 fix(richtext-lexical): drawer fields sometimes not appearing. This ensures that all initial state requests can never be aborted 2024-11-05 15:50:19 -07:00
Jacob Fletcher
0642127094 reintroduces getDocPermissions into document info provider 2024-11-05 17:15:45 -05:00
Jarrod Flesch
4d85b91afe clear step nav on dashboard view 2024-11-05 16:56:32 -05:00
Jarrod Flesch
498d849905 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 16:49:18 -05:00
Jarrod Flesch
796c15dffc chore: renders versions list view 2024-11-05 16:49:04 -05:00
Jarrod Flesch
20723e2d79 adjust custom save button styles 2024-11-05 16:21:14 -05:00
Jacob Fletcher
fbc82d488a moves doc drawer header definition out from view 2024-11-05 16:14:10 -05:00
Jarrod Flesch
20e11e3fc3 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 16:12:42 -05:00
Jarrod Flesch
4c29ab0c44 chore: working global dependency components 2024-11-05 16:12:25 -05:00
Jacob Fletcher
dc572a1433 properly throws not-found for unauthorized docs 2024-11-05 15:46:48 -05:00
Jacob Fletcher
da1dd11949 properly throws not found based on doc permissions 2024-11-05 15:03:16 -05:00
Jarrod Flesch
8a9cf9bbdf Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 14:35:33 -05:00
Jarrod Flesch
d0f31e7dc4 chore: renders custom list view Description with props 2024-11-05 14:35:13 -05:00
Alessio Gravili
87ccd740ab lexical: more reliable slash menu closing, avoid nested editor.update's 2024-11-05 12:32:05 -07:00
Jarrod Flesch
dc6515a26e Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 14:12:24 -05:00
Jarrod Flesch
ea0fc62f5e attach custom list components to list view 2024-11-05 14:12:11 -05:00
Alessio Gravili
d8a4a6f92d perf(richtext-lexical): optimize slash menu item selection by minimizing editor.update calls and setTimeouts. This gets rid of occasional errors when selecting a slash menu entry 2024-11-05 12:12:07 -07:00
Jacob Fletcher
8fa047d3b6 account view 2024-11-05 13:41:59 -05:00
Jarrod Flesch
27c51c5114 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 13:31:31 -05:00
Jarrod Flesch
6dff40dc69 improve custom list Description props 2024-11-05 13:31:18 -05:00
Alessio Gravili
31c2a2a42a fix: ensure server form state is not merged if it's null 2024-11-05 11:30:56 -07:00
Jarrod Flesch
18d9a21bac realign list subheader to match current beta dom 2024-11-05 13:24:49 -05:00
Jarrod Flesch
c918991172 chore: use types in custom cell component 2024-11-05 13:16:33 -05:00
Jarrod Flesch
9bb2d97e75 chore: fix types remove _isPresentational prop 2024-11-05 13:12:43 -05:00
Jacob Fletcher
aad8e9385c removes logs 2024-11-05 13:08:16 -05:00
Jacob Fletcher
1807aac5c2 safely checks admin.disabled in form state 2024-11-05 13:07:09 -05:00
Jacob Fletcher
e50a899d92 fix custom server field in _community 2024-11-05 13:00:58 -05:00
Jacob Fletcher
d6d3101632 fixes step nav 2024-11-05 12:55:27 -05:00
Jarrod Flesch
516975b5cc chore: exports missing imports for ui package 2024-11-05 12:34:17 -05:00
Jarrod Flesch
164020ea24 fix handleServerFunctions export/import 2024-11-05 11:59:52 -05:00
Jacob Fletcher
0e328372ec Merge branch 'beta' into feat/on-demand-rsc 2024-11-05 11:59:47 -05:00
Jarrod Flesch
da6154afb4 fix missing handleServerFunctions export 2024-11-05 11:51:33 -05:00
Jarrod Flesch
181d8a686b Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 11:27:58 -05:00
Jarrod Flesch
5d91a2e273 chore: adjusts field not rendering logic 2024-11-05 11:27:33 -05:00
Alessio Gravili
fbe4295497 add back commented out fields test suite stuff 2024-11-05 09:27:07 -07:00
Alessio Gravili
ca485bdc0e fix fields not rendering 2024-11-05 09:23:52 -07:00
Jarrod Flesch
aa5d1f52d8 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 11:22:39 -05:00
Jarrod Flesch
4534b74e05 adds providers back in 2024-11-05 11:22:25 -05:00
Jacob Fletcher
d3c78deb08 safely accesses admin components 2024-11-05 11:12:45 -05:00
Jacob Fletcher
a96982597b enables relationship table sort 2024-11-05 11:08:51 -05:00
Jacob Fletcher
6952b751e9 poc linked cell overrides for relationship table 2024-11-05 11:08:25 -05:00
Jarrod Flesch
52ca879722 reuse Logo component 2024-11-05 11:03:23 -05:00
Jarrod Flesch
10570936bd feat: implements custom logout, avatar, Icon and Logo 2024-11-05 10:19:49 -05:00
Jarrod Flesch
f5e749f62f completes readOnly prop to custom components 2024-11-05 08:25:08 -05:00
Jarrod Flesch
15f485e10b Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 00:23:12 -05:00
Alessio Gravili
83712998c5 restore whole fields test suite 2024-11-04 20:13:43 -07:00
Alessio Gravili
0f1397815b working richtext-slate 2024-11-04 19:47:25 -07:00
Alessio Gravili
7e215f570e prevent error if array field does not receive permissions 2024-11-04 17:56:11 -07:00
Alessio Gravili
48523e1703 chore: fix jarrod 2024-11-04 17:49:55 -07:00
Alessio Gravili
560d5adf97 fix type errors 2024-11-04 17:46:11 -07:00
Alessio Gravili
a19a7235f1 misc fixes 2024-11-04 17:39:04 -07:00
Jarrod Flesch
8fef77075c chore: swap useField out for useFormFields 2024-11-04 17:08:43 -05:00
Jacob Fletcher
4d5f06de86 fixes search 2024-11-04 17:06:24 -05:00
Jarrod Flesch
6e39d25604 chore: remove useForm from renderFields 2024-11-04 16:58:43 -05:00
Jarrod Flesch
9c11be879e Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-04 15:01:56 -05:00
Jarrod Flesch
503b94c7dc type cleanup inside RenderFields 2024-11-04 15:01:15 -05:00
Jacob Fletcher
af0d3564b2 wip join field 2024-11-04 14:12:47 -05:00
Alessio Gravili
3607e4bd94 fix: ensure tabs save data by making sure the path & schemaPath of children are correct 2024-11-04 12:11:51 -07:00
Alessio Gravili
a504b08965 add missing prop to hook array 2024-11-04 11:38:30 -07:00
Alessio Gravili
65d895d7f6 fix: ensure sidebar fields do not cause error in RenderFields 2024-11-04 11:38:03 -07:00
Alessio Gravili
54509033c6 fix: tab index paths being incorrectly prepended to named field's paths 2024-11-04 11:26:17 -07:00
Alessio Gravili
eba967df78 almost restore fields test suite to its full glory 2024-11-04 11:02:14 -07:00
Alessio Gravili
968269aea9 fix: non-serializable formstate was being sent to onSubmit and beforeSubmit form hooks, leading to server action errors as client components were passed to the server action 2024-11-04 10:51:44 -07:00
Jarrod Flesch
6dd09679a5 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-04 12:49:28 -05:00
Jarrod Flesch
67acbfde18 chore: move permissions to serverProps and only require on client for fields with subfields 2024-11-04 12:49:12 -05:00
Alessio Gravili
f70db1b818 fix(richtext-lexical): fix node.getPoint errors when working with drawers 2024-11-04 10:40:58 -07:00
Alessio Gravili
da0093610c lexical: get remaining features to work 2024-11-04 10:32:01 -07:00
Jarrod Flesch
f50f5d24f5 chore: delete blacklisted client-to-server keys instead of setting undefined 2024-11-04 11:34:31 -05:00
Jarrod Flesch
10ba80264e Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-04 11:29:20 -05:00
Jarrod Flesch
d3990d1108 fix: schema paths should not include _index in schema paths unless they are leaf fields 2024-11-04 11:29:09 -05:00
Alessio Gravili
4c146776ab remove console.log 2024-11-04 08:52:16 -07:00
Alessio Gravili
cbafa720bd lexical: working blocks, working fields drawer 2024-11-04 08:51:44 -07:00
Jacob Fletcher
e81b3a8b35 fix list view filter options 2024-11-04 09:55:08 -05:00
Jacob Fletcher
3df7f6996e relocates document and list view server fn handlers 2024-11-04 09:36:05 -05:00
Jacob Fletcher
349686fce8 renames renderListFn 2024-11-04 09:26:13 -05:00
Jarrod Flesch
2066184187 fix: prevent drawer from closing upon create 2024-11-04 08:43:22 -05:00
Alessio Gravili
0647d63dd9 get lexical blocks working again, fix incorrect usages of getFormState 2024-11-04 06:38:05 -07:00
Jarrod Flesch
de1591fcb5 fix: incorrect status after publishing 2024-11-04 08:21:14 -05:00
Alessio Gravili
4a11a8e723 get lexical to load up again, type improvements 2024-11-04 00:01:59 -07:00
Alessio Gravili
e24ac29e80 no need to add duplicative fieldState to serverProps 2024-11-04 00:01:22 -07:00
Alessio Gravili
665dba9fea get field hooks to work again after getFieldPaths changes 2024-11-04 00:01:01 -07:00
Jacob Fletcher
2ea70f324a renames ListInfoProvider to ListDrawerContextProvider for parity 2024-11-02 12:44:09 -04:00
James
32df51c78e chore: unsets requiresRender from server 2024-11-02 11:59:16 -04:00
James
b05f888625 Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-02 11:33:46 -04:00
James
0b55bb4456 chore: threads through requiresRender for arrays / blocks 2024-11-02 11:33:26 -04:00
Jarrod Flesch
58321e396b fix: bad auto import 2024-11-01 22:22:38 -04:00
Jarrod Flesch
c07ce89f40 fix reduceToSerializableFields usage 2024-11-01 22:19:55 -04:00
Jarrod Flesch
1d3453c1a4 chore: implement reduceToSerializableFields function for form fields 2024-11-01 22:18:29 -04:00
Jarrod Flesch
7e3e4dbb0a removes prepareFields from Form 2024-11-01 22:09:04 -04:00
Jarrod Flesch
829a2d3c3c chore: fix versions fetching 2024-11-01 22:00:25 -04:00
Jarrod Flesch
8a9aead49e fix old path array 2024-11-01 21:54:31 -04:00
Jacob Fletcher
b52fa96b4b partially reenables fields test seed 2024-11-01 16:53:44 -04:00
Alessio Gravili
116a03b162 custom array comp 2024-11-01 14:44:29 -06:00
James
f3698db5b5 chore: cleanup 2024-11-01 16:37:17 -04:00
James
ceb49e70a3 chore: fixes typez 2024-11-01 16:08:02 -04:00
James
ebfcd4a53f chore: functional pattern 2024-11-01 16:05:25 -04:00
James
6c2925b3b4 chore: progress to field paths 2024-11-01 13:09:12 -04:00
James
1ab9f2cc75 Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-10-31 14:44:08 -04:00
James
ac0dc57ac4 feat: adds countVersions, heavily refactors DocumentInfo, improves ssr workload 2024-10-31 14:43:47 -04:00
Alessio Gravili
3f0c810569 migrate more stuff off of componentMap 2024-10-31 11:08:54 -06:00
Alessio Gravili
19b44e34b9 make more robust 2024-10-31 10:58:00 -06:00
Alessio Gravili
41ceaf5ac0 add back lexical collections 2024-10-31 10:30:39 -06:00
Alessio Gravili
ad9d18408c fix blocks 2024-10-31 10:27:45 -06:00
Alessio Gravili
1da2118d6d get blocks to render 2024-10-31 09:46:55 -06:00
Jacob Fletcher
6ad6085894 passes enableRowSelections through buildTableState 2024-10-30 18:02:17 -04:00
Jacob Fletcher
2f295f3df1 poc list drawer filter/search/sort 2024-10-30 17:16:08 -04:00
Jarrod Flesch
5baa2c4964 fix relationship drawer multiple fetches on open 2024-10-30 16:57:14 -04:00
Jarrod Flesch
e5506a39aa Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-30 16:04:59 -04:00
Jarrod Flesch
6857cc08e8 fix mismatch client and server form states on blocks 2024-10-30 16:04:44 -04:00
Jacob Fletcher
a53763ca2c fix: safely accesses view actions 2024-10-30 14:03:21 -04:00
Alessio Gravili
f883c69e11 fix: jacob's negligence 2024-10-30 11:46:20 -06:00
Jarrod Flesch
6a680773ca Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-30 13:00:55 -04:00
Jarrod Flesch
02503dbd45 corrects permission passing to renderFields 2024-10-30 13:00:42 -04:00
Jacob Fletcher
b7943b9f8e Merge branch 'beta' into feat/on-demand-rsc 2024-10-30 12:22:23 -04:00
Jarrod Flesch
184325b2cf fix hidden field props and id 2024-10-30 11:36:04 -04:00
Jacob Fletcher
72370a1581 create drawer create and duplicate 2024-10-30 11:09:39 -04:00
Jacob Fletcher
f80cc2089a cleanup 2024-10-30 11:09:39 -04:00
Jacob Fletcher
4c1fd28ce5 consolidates listinfocontext 2024-10-30 11:09:39 -04:00
Jacob Fletcher
b7b3ad16b7 poc polymorphic list drawers 2024-10-30 11:09:39 -04:00
Alessio Gravili
74bc281f3d lexical: get rid of generateClientProps in favor of field rsc 2024-10-30 09:07:27 -06:00
Jarrod Flesch
c21b555368 account for Files in deepCopyObjectComplex 2024-10-30 11:03:24 -04:00
Jarrod Flesch
e53877a868 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-30 11:00:34 -04:00
Jarrod Flesch
8ef1180fc1 fix view actions for root global 2024-10-30 11:00:23 -04:00
Alessio Gravili
52a006abd9 fix: RenderServerComponent does not respect PayloadComponent.clientProps/serverProps 2024-10-30 08:54:12 -06:00
Jarrod Flesch
6206b46b86 cant keep looking at ugly header 2024-10-30 10:52:45 -04:00
Jarrod Flesch
e13c7057e4 fixes formState files inside attachComponentsToFormState 2024-10-30 10:44:53 -04:00
Jarrod Flesch
3d9dd0b647 fix viewActions array build up 2024-10-30 09:28:48 -04:00
Jarrod Flesch
4b4ef7b4c1 sanitize serverFunctions for client 2024-10-30 08:54:20 -04:00
Jarrod Flesch
34dd3ac479 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-30 08:45:10 -04:00
Jarrod Flesch
f6f0aee553 chore: adjusts how actions are stored in the provider and set on views 2024-10-30 08:44:27 -04:00
Alessio Gravili
5d07d40960 fix: ensure newly added arrays have their children's custom components rendered. This is done by ensuring that requireRender is correctly set in the form state constructed by buildFormState, if there is an incoming previous formState 2024-10-29 22:44:29 -06:00
Alessio Gravili
98f5418ac0 fix: rendered custom components disappearing when submitting form, as prepareFields was messing up local form state 2024-10-29 22:22:24 -06:00
Alessio Gravili
b63b0cf3ad chore: fix typo 2024-10-29 22:16:35 -06:00
Alessio Gravili
2611c77211 feat: start getting lexical to work. Gets rid of generateComponentMap in favor of generateClientProps, simplifies a lot 2024-10-29 22:15:48 -06:00
Alessio Gravili
0b948673fa perf: only re-render necessary fields in form state requests, only trigger one form state request instead of two when adding new array row 2024-10-29 22:14:41 -06:00
Jarrod Flesch
f6c0dcacee chore: remove SetViewActions 2024-10-29 16:59:16 -04:00
Jarrod Flesch
4a639ccc19 fix action button creation 2024-10-29 16:55:53 -04:00
Jacob Fletcher
2c03de6dc7 conditionally renders create new btn in list view based on drawer 2024-10-29 16:48:21 -04:00
Jarrod Flesch
d7c57aeeb9 chore: gets viewActions on the server 2024-10-29 16:16:55 -04:00
Jacob Fletcher
e8d12bc3c0 fix custom cells and column labels 2024-10-29 16:02:12 -04:00
Jacob Fletcher
564591ed93 removes fields without labels from col selector 2024-10-29 13:33:16 -04:00
Jacob Fletcher
185764c477 fix filename cells 2024-10-29 13:22:13 -04:00
Jacob Fletcher
b11f072ad8 fix enable row selections logic 2024-10-29 11:40:44 -04:00
Jarrod Flesch
cb2771d3a9 default fieldState to empty object 2024-10-29 11:11:20 -04:00
Jarrod Flesch
3176e87a95 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-29 10:41:51 -04:00
Jarrod Flesch
76f3102d07 rm console log 2024-10-29 10:41:40 -04:00
Jacob Fletcher
53f05ad303 defers rendering fallback column labels to client 2024-10-29 10:32:30 -04:00
Jarrod Flesch
8e1ef2222d Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-29 10:32:13 -04:00
Jarrod Flesch
40d5ae3165 implements RenderCustomComponent 2024-10-29 10:31:45 -04:00
Jacob Fletcher
fe69f9ec35 fix doc drawer callbacks 2024-10-29 09:56:39 -04:00
Jacob Fletcher
a56c6a5757 poc doc drawer context 2024-10-28 18:01:56 -04:00
Jacob Fletcher
186f886c1d poc list drawer context 2024-10-28 18:01:43 -04:00
Jarrod Flesch
2e9af8d258 uncomment test suite text-fields config 2024-10-28 16:44:54 -04:00
Jarrod Flesch
7162ce9ef5 fix readonly not being threaded to array row render fields 2024-10-28 16:21:34 -04:00
Jarrod Flesch
b6b58550e1 correctly fallback on client for field labels, errors, descriptions 2024-10-28 16:11:49 -04:00
Jarrod Flesch
bfd08bbeaf Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-28 11:45:01 -04:00
Jarrod Flesch
290594a232 chore: migrate fields to extract custom components from fieldState 2024-10-28 11:44:44 -04:00
Alessio Gravili
c6325c13cf fix field component props: Array - Hidden 2024-10-28 08:59:28 -06:00
Jacob Fletcher
c7cd6e3fbb fix upload field allowCreate logic 2024-10-28 10:48:31 -04:00
Alessio Gravili
414e68e463 fix custom component rendering other than Field 2024-10-28 08:47:50 -06:00
Jarrod Flesch
a6df86d596 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-28 10:27:21 -04:00
Jarrod Flesch
78ffe523f5 fix form array mutations, fixes custom components disappearing again 2024-10-28 10:27:07 -04:00
Alessio Gravili
03645d172d fix: use consistent field component types 2024-10-28 08:18:49 -06:00
Alessio Gravili
9f7924b930 Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-10-27 23:56:02 -06:00
Jarrod Flesch
9f5f909094 fix ui fields disappearing, fix replaceFieldRow fn 2024-10-28 01:05:56 -04:00
Jarrod Flesch
997ddb4654 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-28 00:49:15 -04:00
Jarrod Flesch
73ee8b5549 fix: allow customComponents to be merged into state 2024-10-28 00:48:54 -04:00
Jacob Fletcher
8195bd804b poc list drawers 2024-10-28 00:10:41 -04:00
Jacob Fletcher
4395dc8901 auto increments edit depth 2024-10-26 17:57:36 -04:00
Jacob Fletcher
7668e0907e poc server renders document drawers 2024-10-26 17:56:38 -04:00
Jacob Fletcher
c46946a77c client-side renders default document buttons 2024-10-25 17:24:44 -04:00
Jacob Fletcher
4194b8bc61 rendering fields in drawers 2024-10-25 17:24:17 -04:00
Jarrod Flesch
807500d55f fix block and array fields 2024-10-25 16:53:01 -04:00
Jarrod Flesch
456eea1344 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-25 15:50:17 -04:00
Jarrod Flesch
af8fed9ab3 working blocks 2024-10-25 15:49:52 -04:00
Jacob Fletcher
38dc313051 sets column prefs server side 2024-10-25 14:18:48 -04:00
Jarrod Flesch
5a53c6b130 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-25 12:32:15 -04:00
Jarrod Flesch
d3dd8aef53 more working things 2024-10-25 12:32:03 -04:00
Jacob Fletcher
7b39acc54d fix custom filter lookup 2024-10-25 12:31:10 -04:00
Jarrod Flesch
e7b69ce70f thread readOnly into default fields 2024-10-25 10:50:22 -04:00
Jarrod Flesch
91b65b4cc6 Merge branch 'rsc-schemaPaths' into feat/on-demand-rsc 2024-10-25 10:08:25 -04:00
Jarrod Flesch
42f78480ba threads localized prop into input components 2024-10-25 10:07:17 -04:00
Jarrod Flesch
bbe0fa38ae Merge branch 'beta' into rsc-schemaPaths 2024-10-25 10:06:38 -04:00
Jarrod Flesch
d32798a2a0 removes unused props, threads readOnly through to missing fields 2024-10-25 09:20:56 -04:00
Jarrod Flesch
11c505930d undo test in array config 2024-10-25 00:52:58 -04:00
Jarrod Flesch
0dcb109101 Merge remote-tracking branch 'refs/remotes/origin/rsc-schemaPaths' into rsc-schemaPaths 2024-10-25 00:26:38 -04:00
Jarrod Flesch
95a2bc4d1e wires up field permissions 2024-10-25 00:18:25 -04:00
Alessio Gravili
3f9c7e2acd remove console log 2024-10-24 15:33:18 -06:00
Alessio Gravili
50a1770d7e fix buildFormState for partial schema paths (fixes adding array rows) 2024-10-24 15:32:37 -06:00
Alessio Gravili
9bf24c0379 fix schema path handling in createClientConfig 2024-10-24 15:13:54 -06:00
Alessio Gravili
2429f64f3a fix faulty logic in buildPathSegments 2024-10-24 15:09:24 -06:00
Alessio Gravili
5adcff3e76 fix usage of createClientField 2024-10-24 14:52:37 -06:00
Alessio Gravili
bc155c4a87 use array paths / schema paths where possible, add _index- path segments to server-side operation paths / schema paths, merge generatePath and getFieldPaths 2024-10-24 14:45:40 -06:00
Jacob Fletcher
cb03d5d197 renders filter component slots 2024-10-24 15:34:07 -04:00
Jarrod Flesch
b3021b559a fix row styling 2024-10-24 14:35:09 -04:00
Jarrod Flesch
1bc7d91c4f remove path path debug paragraph text 2024-10-24 14:27:57 -04:00
Jarrod Flesch
42dd173986 Merge remote-tracking branch 'refs/remotes/origin/rsc-schemaPaths' into rsc-schemaPaths 2024-10-24 14:26:02 -04:00
Jarrod Flesch
d9b188061d feat: working arrays 2024-10-24 14:25:02 -04:00
Alessio Gravili
8e5ec02037 ensure form state always includes _index, ensure _index is always stripped away from DATA 2024-10-24 10:19:19 -06:00
Jacob Fletcher
1d370e0d69 col ordering 2024-10-24 11:00:12 -04:00
Alessio Gravili
e51067ccaf fix: remove _index- from data before sending it to the server, as it's not processed by the server 2024-10-23 22:55:26 -06:00
Alessio Gravili
d48cb1b8eb fix schemaPath handling for collapsibles and rows 2024-10-23 22:22:51 -06:00
Alessio Gravili
acc4432a99 fix partial schema path handling 2024-10-23 22:06:58 -06:00
Alessio Gravili
be5772b71c fix buildStateFromSchema path input 2024-10-23 21:59:46 -06:00
Alessio Gravili
c1222b5e06 fixes 2024-10-23 21:49:10 -06:00
Jarrod Flesch
24d5bc88f6 dont render if hidden 2024-10-23 23:24:12 -04:00
Alessio Gravili
06750e1f4a remove schemaAccessor 2024-10-23 21:20:20 -06:00
Alessio Gravili
0aaa4fe643 throw proper error if block data contains unknown block slug 2024-10-23 20:54:34 -06:00
Alessio Gravili
cb6a0249fb fix array error 2024-10-23 20:54:16 -06:00
Jacob Fletcher
3383bf3479 poc table sorting 2024-10-23 17:56:16 -04:00
Jarrod Flesch
06536eb275 chore: almost working 2024-10-23 17:07:34 -04:00
Jacob Fletcher
e881dcd4b4 poc toggle columns 2024-10-23 16:10:20 -04:00
Jarrod Flesch
67acde45e6 not working 2024-10-23 14:08:55 -04:00
Jacob Fletcher
d6498e442f explores fields in form state 2024-10-22 17:56:27 -04:00
Jarrod Flesch
7e50fc51f1 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-22 16:48:04 -04:00
Jarrod Flesch
b49f9f92be chore: rendering rows 2024-10-22 16:47:34 -04:00
Jacob Fletcher
9ffbb3f7f9 poc list view 2024-10-22 16:25:05 -04:00
Jacob Fletcher
54301c9088 renders cells in column state 2024-10-22 16:25:05 -04:00
Jacob Fletcher
809df54cf0 progress to ssr tables 2024-10-22 16:25:04 -04:00
Jacob Fletcher
d254a6e622 begins server rendering list cells 2024-10-22 16:25:04 -04:00
Jarrod Flesch
f92dcf68c6 chore: refactor, merge generatePath and generateFieldKey 2024-10-22 15:08:44 -04:00
Jarrod Flesch
c8cba855a2 chore: thread docPrefs through to getFormState 2024-10-22 12:39:22 -04:00
Jarrod Flesch
4e5121f6d3 chore: refactors function params 2024-10-22 12:08:12 -04:00
Jarrod Flesch
c3eefec31f remove renderedFieldMap prop 2024-10-21 17:08:10 -04:00
Jarrod Flesch
7dbd1d861f fixes inifite rendering of collapsibles, remove early return for un-named fields 2024-10-21 17:07:13 -04:00
Jacob Fletcher
3ff9e34199 fixes array rows 2024-10-21 15:38:26 -04:00
Jacob Fletcher
e7c1f98b50 fix nested index paths 2024-10-21 15:27:35 -04:00
Jacob Fletcher
7aa604c0d7 reworks field key 2024-10-21 13:52:13 -04:00
Jacob Fletcher
fbfa0fd5d6 wip 2024-10-21 12:36:36 -04:00
Jacob Fletcher
be149b362a working addFieldRow and begins migrating more fields 2024-10-21 11:19:07 -04:00
Jacob Fletcher
0e05c5d60d working field keys 2024-10-21 10:46:49 -04:00
Jacob Fletcher
91cd7672e3 progress to unique field keys 2024-10-19 14:11:29 -04:00
Jacob Fletcher
ca8e8becd2 begins migrating remaining fields 2024-10-18 10:55:10 -04:00
Jacob Fletcher
b09bd65020 group fields 2024-10-18 10:42:23 -04:00
Jacob Fletcher
7882c83f03 threads index path through renderFields 2024-10-18 00:37:51 -04:00
Jacob Fletcher
7498099ede fixes index path lookup 2024-10-17 23:46:48 -04:00
Jacob Fletcher
f800cb8dc5 back to traditional field schema map 2024-10-17 17:59:32 -04:00
Jacob Fletcher
f6360d055f begins setting fields test suite back to original state 2024-10-17 11:54:39 -04:00
Jacob Fletcher
a597579354 blocks field 2024-10-17 11:31:55 -04:00
Jacob Fletcher
7d2fc41f19 a little hacky but it works 2024-10-17 09:50:10 -04:00
Jacob Fletcher
01ba2c0114 working nested arrays 2024-10-16 14:22:45 -04:00
Jacob Fletcher
f0b431e799 ensures values are not lost when rendering new array items 2024-10-16 11:36:32 -04:00
Jacob Fletcher
b68b625899 array items are dynamically rendering again 2024-10-16 11:06:47 -04:00
Jacob Fletcher
57f8475780 reworks fieldSchemaMap to accomodate new pattern 2024-10-16 09:07:49 -04:00
Jacob Fletcher
e14a8876ab puts rows into state instead of fields 2024-10-14 13:46:43 -04:00
Jacob Fletcher
e9cd82bc81 poc array rows 2024-10-14 12:00:03 -04:00
Jacob Fletcher
054d183a96 progress 2024-10-13 22:20:41 -04:00
Jacob Fletcher
e03a330fd3 please be the last poc 2024-10-11 15:59:13 -04:00
Jacob Fletcher
9038020dd9 nested fields are rendering 2024-10-10 15:17:02 -04:00
Jacob Fletcher
1f0e551578 omg it works 2024-10-10 13:12:03 -04:00
Jacob Fletcher
f2c5750e78 poc rendering components in field state 2024-10-09 14:56:46 -04:00
Jacob Fletcher
23f136ab82 path is now a direct prop 2024-10-08 15:49:22 -04:00
Jacob Fletcher
aa38ac9bd8 cleanup 2024-10-08 14:14:11 -04:00
Jacob Fletcher
5d3294b341 fix nested arrays 2024-10-08 14:05:29 -04:00
Jacob Fletcher
7fddc5fbd9 poc arrays 2024-10-08 13:51:41 -04:00
Jacob Fletcher
d056b0b964 properly isolates handleServerFunctions export to avoid client-side contamination 2024-10-07 13:19:58 -04:00
Jacob Fletcher
57e9109f93 Merge branch 'beta' into feat/on-demand-rsc 2024-10-07 10:27:21 -04:00
Jacob Fletcher
515629b51d cleanup 2024-10-07 10:13:57 -04:00
Jacob Fletcher
6ec779fb6c Squashed commit of the following:
commit 7a0609ab57
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Mon Oct 7 10:00:34 2024 -0400

    wires abort controller into server fn provider

commit 633aaa721d
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Mon Oct 7 09:50:12 2024 -0400

    adjust err handling

commit 860b6cf2bf
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Mon Oct 7 09:26:01 2024 -0400

    safely accesses signal

commit 463e05d7b9
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Mon Oct 7 09:13:39 2024 -0400

    adds additional ac check prior to firing action

commit 231c4b3d1f
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Mon Oct 7 09:00:45 2024 -0400

    cleanup

commit 494c970b0a
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Mon Oct 7 08:46:28 2024 -0400

    more acs

commit 8dcb2eae17
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Sun Oct 6 23:33:53 2024 -0400

    adds abort controllers to docinfo and form actions

commit 0da4326e43
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Sun Oct 6 23:05:14 2024 -0400

    adds back remaining actions

commit 292d8b53e4
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Sun Oct 6 22:53:44 2024 -0400

    thrads abort controller to onchange

commit ca8629e5a6
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Sun Oct 6 22:02:20 2024 -0400

    reintroduces some actions

commit 5f001c0f52
Merge: 75dd030e2 2ba40f333
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Sat Oct 5 09:15:25 2024 -0400

    Merge branch 'beta' into feat/server-actions

commit 2ba40f3335
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Sat Oct 5 09:13:43 2024 -0400

    chore: removes duplicative join field test (#8558)

    There are two of the exact same e2e tests for the join field, which
    throws an error when running these tests locally because they have
    identical names.

commit 463490f670
Author: Elliot DeNolf <denolfe@users.noreply.github.com>
Date:   Fri Oct 4 18:38:27 2024 -0700

    fix(templates): await params/cookies properly (#8560)

commit d564cd44e9
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 17:29:38 2024 -0400

    chore: deflakes lexical e2e test (#8559)

    This has caused me great pain. The problem with this test is that the
    page was waiting for a URL which includes a search query that never
    arrives. This moves the check into a regex pattern for a more accurate
    catch.

commit 75dd030e24
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 16:26:19 2024 -0400

    reverts reset back to fetch to test

commit 7c62e2a327
Author: Paul <paul@payloadcms.com>
Date:   Fri Oct 4 13:02:56 2024 -0600

    feat(ui)!: scope all payload css to payload-default layer (#8545)

    All payload css is now encapsulated inside CSS layers under `@layer
    payload-default`

    Any custom css will now have the highest possible specificity.
    We have also provided a new layer `@layer payload` if you want to use
    layers and ensure that your styles are applied after payload.

    To override existing styles in a way that the existing rules of
    specificity would be respected you can use the default layer like so
    ```css
    @layer payload-default {
      // my styles within the payload specificity
    }
    ```

commit 400293b8ee
Author: Sasha <64744993+r1tsuu@users.noreply.github.com>
Date:   Fri Oct 4 21:46:41 2024 +0300

    fix: duplicate with upload collections (#8552)

    Fixes the duplicate operation with uploads
    Enables duplicate for upload collections by default

commit e4a413eb9a
Author: Elliot DeNolf <denolfe@gmail.com>
Date:   Fri Oct 4 11:31:06 2024 -0700

    chore(release): v3.0.0-beta.111 [skip ci]

commit b99590f477
Author: Sasha <64744993+r1tsuu@users.noreply.github.com>
Date:   Fri Oct 4 21:28:43 2024 +0300

    chore(templates): update templates with next.js promises (#8547)

    Updates templates according to this PR
    https://github.com/payloadcms/payload/pull/8489

commit a4704c1453
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 13:53:52 2024 -0400

    moves docinfo back to fetch to test

commit 0d3416c96d
Author: Alessio Gravili <alessio@gravili.de>
Date:   Fri Oct 4 13:39:03 2024 -0400

    fix(db-postgres): missing types for db.pool by moving @types/pg from devDependencies to dependencies (#8556)

    Fixes lack of types in installed project:

    ![CleanShot 2024-10-04 at 19 18
    58@2x](https://github.com/user-attachments/assets/e7c519ee-72fd-424b-8f6c-41032322fa5e)

    Since we expose stuff from @types/pg to the end user, we need it to be
    installed in the end users project => move to dependencies.

commit 2f6ee80a6a
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 12:54:45 2024 -0400

    changes addFieldRow back to fetch

commit 0128eedf70
Author: Sasha <64744993+r1tsuu@users.noreply.github.com>
Date:   Fri Oct 4 19:25:05 2024 +0300

    fix(drizzle)!: make radio and select column names to snake_case (#8439)

    Fixes https://github.com/payloadcms/payload/issues/8402 and
    https://github.com/payloadcms/payload/issues/8027

    Before DB column names were camelCase:

    ![image](https://github.com/user-attachments/assets/d2965bcf-290a-4f86-9bf4-dfe7e8613934)

    After this change, they are snake_case:
    ![Screenshot 2024-10-04
    114226](https://github.com/user-attachments/assets/bbc8c20b-6745-4dd3-b0c8-56263a4e37b1)

    #### Breaking SQLite / Postgres ⚠️
    If you had any select (not `hasMany: true`) or radio fields with the
    name in camelCase, for example:
    ```ts
    {
      name: 'selectReadOnly',
      type: 'select',
      admin: {
        readOnly: true,
      },
      options: [
        {
          label: 'Value One',
          value: 'one',
        },
        {
          label: 'Value Two',
          value: 'two',
        },
      ],
    },
    ```
    This previously was mapped to the db column name `"selectReadOnly"`. Now
    it's `select_read_only`.
    Generate a new migration to rename your columns.
    ```sh
    pnpm payload migrate:create
    ```
    Then select "rename column" for targeted columns and Drizzle will handle
    the migration.

    ---------

    Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>

commit 414030e1f1
Author: Sasha <64744993+r1tsuu@users.noreply.github.com>
Date:   Fri Oct 4 18:48:54 2024 +0300

    fix(drizzle): row / collapsible inside of localized fields (#8539)

    Fixes https://github.com/payloadcms/payload/issues/8405

commit 41efcbe4ac
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 10:02:48 2024 -0400

    build err

commit 2203d6046c
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 08:32:13 2024 -0400

    reverts default edit view onChange

commit 2cd9594f55
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 07:47:49 2024 -0400

    brings back actions for everything except lexical

commit cc2cad3140
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 00:58:35 2024 -0400

    more cleanup

commit 87e11bd6ad
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 00:20:02 2024 -0400

    remove website lockfile

commit caf4c4a0df
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 00:12:25 2024 -0400

    properly scopes effect cleanup return

commit 3ddd972de0
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 00:07:09 2024 -0400

    adjusts abort controllers

commit 6c95f0a658
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 00:00:00 2024 -0400

    fix api route

commit 9206adedee
Merge: 726f8b003 f6eb027f2
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 23:45:17 2024 -0400

    Merge branch 'beta' into feat/server-actions

commit 726f8b003d
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 23:11:53 2024 -0400

    again

commit 40e5571c0e
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 22:56:55 2024 -0400

    more

commit bdbe7dd41f
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 22:10:48 2024 -0400

    temprarily brings back fetch to debug

commit 08bd461d4b
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 19:43:59 2024 -0400

    properly aborts on unmount

commit cf617767e5
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 19:21:42 2024 -0400

    prevents signal sent from client to server

commit ee853234fd
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 18:35:23 2024 -0400

    temp debug

commit 23cd537a33
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 14:59:35 2024 -0400

    removes auto gen upload dirs from test

commit 0a950c0f0b
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 14:47:56 2024 -0400

    plz

commit f84822e7d9
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 13:48:10 2024 -0400

    returns errors instead of throws

commit ee577f53f2
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 13:34:36 2024 -0400

    increases test bodysizelimit

commit 6641bff085
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 13:25:44 2024 -0400

    removes default server action body size limit

commit 53a6dcf213
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 12:49:04 2024 -0400

    plz be it

commit 9e8e7c7c3d
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 11:48:11 2024 -0400

    creates shared callback for common server fns

commit b4d081db3f
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 09:39:40 2024 -0400

    some docs

commit bc36bf7984
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Wed Oct 2 16:26:25 2024 -0400

    increases default serverActions.bodySizeLimit
2024-10-07 10:11:44 -04:00
Jacob Fletcher
84fd8d06f0 Merge feat/server-actions into feat/on-demand-rsc 2024-10-07 09:44:15 -04:00
Jacob Fletcher
01b580cb24 cleanup 2024-10-04 12:37:04 -04:00
Jacob Fletcher
4d60f9c7da Merge branch 'beta' into feat/on-demand-rsc 2024-10-04 12:16:00 -04:00
Jacob Fletcher
fde05840fe begins wiring array action 2024-10-04 12:05:59 -04:00
Jacob Fletcher
8ed1766d5c Merge branch 'feat/server-actions' into feat/on-demand-rsc 2024-10-02 15:44:40 -04:00
Jacob Fletcher
6fa47bf854 fix missing id 2024-10-02 15:16:50 -04:00
Jacob Fletcher
362cd1712d fix error handling 2024-10-02 14:55:51 -04:00
Jacob Fletcher
e669368149 proper error handling and cleanup 2024-10-02 13:45:55 -04:00
Jacob Fletcher
068d7eec52 adds next-env.d.ts to tsconfig.include 2024-10-02 12:51:02 -04:00
Jacob Fletcher
fb861a53ec small docs 2024-10-02 12:50:48 -04:00
Jacob Fletcher
f16e55fff2 fixes docinfo server fn 2024-10-02 12:08:46 -04:00
Jacob Fletcher
47c19224c2 Merge branch 'beta' into feat/server-actions 2024-10-02 11:17:17 -04:00
Jacob Fletcher
fd2444e614 properly formats errors and fixes imports in templates 2024-10-02 11:16:12 -04:00
Jacob Fletcher
31ffe3bc43 adds back missing css imports 2024-10-02 10:11:54 -04:00
Jacob Fletcher
4cd89cd5d7 fixes type imports 2024-10-02 10:06:24 -04:00
Jacob Fletcher
1d7bcb365d removes duplicate test 2024-10-02 09:39:03 -04:00
Jacob Fletcher
8ffc090cac Merge branch 'beta' into feat/server-actions 2024-10-02 09:35:02 -04:00
Jacob Fletcher
039bd0f76d adds proper error handling 2024-10-02 09:33:54 -04:00
Jacob Fletcher
5235ce819d temp 2024-10-02 00:03:08 -04:00
Jacob Fletcher
c1d2736e8b reverts auto-changes to test modules 2024-10-01 23:41:50 -04:00
Jacob Fletcher
eca0a25063 regenerates lockfile 2024-10-01 23:33:12 -04:00
Jacob Fletcher
e737c8db32 fix test component deps 2024-10-01 23:27:10 -04:00
Jacob Fletcher
bc84def8d8 temp: filter args before calling server fn 2024-10-01 23:22:38 -04:00
Jacob Fletcher
2cf4a58e89 renames ClientServerFunction to ServerFunctionClient 2024-10-01 22:26:59 -04:00
Jacob Fletcher
9c8f623068 renames props to proper plural/singular for semantics 2024-10-01 22:05:16 -04:00
Jacob Fletcher
60edd35671 more docs 2024-10-01 17:52:34 -04:00
Jacob Fletcher
bca9aece06 cleanup 2024-10-01 17:16:51 -04:00
Jacob Fletcher
e43a03d0ff scaffolds docs 2024-10-01 17:13:07 -04:00
Jacob Fletcher
4424904f58 excludes examples until released 2024-10-01 16:48:50 -04:00
Jacob Fletcher
5150a5a30f lints 2024-10-01 16:44:32 -04:00
Jacob Fletcher
b6d829acd8 updates all app dirs 2024-10-01 16:33:29 -04:00
Jacob Fletcher
41d622f613 adds temp log to test ci 2024-10-01 16:11:18 -04:00
Jacob Fletcher
c32c7d50ef builds 2024-10-01 14:42:47 -04:00
Jacob Fletcher
d59c1c01c9 cleanup 2024-10-01 14:32:15 -04:00
Jacob Fletcher
e5ce24eafb Merge branch 'beta' into feat/server-actions 2024-10-01 14:07:04 -04:00
Jacob Fletcher
0de81afa92 aborts server function results in docinfoprovider 2024-10-01 14:06:31 -04:00
Jacob Fletcher
0f5fe98a1b renames BaseServerFunctionArgs to DefaultServerFunctionArgs 2024-10-01 12:32:00 -04:00
Jacob Fletcher
e330f1756f renames RootServerFunction type to ServerFunctionHandler 2024-10-01 12:30:33 -04:00
Jacob Fletcher
c353a0f296 renames serverFunction to plural 2024-10-01 12:27:39 -04:00
Jacob Fletcher
a7c1dd057d properly inits req 2024-10-01 12:02:30 -04:00
Jacob Fletcher
3cbf7b2603 gets language from action itself and properly drills user into local req 2024-10-01 11:27:30 -04:00
Jacob Fletcher
a4135e5975 renames function property to fn 2024-10-01 10:49:04 -04:00
Jacob Fletcher
7fb860f15b removes unnecessary useAuth hooks 2024-10-01 10:43:52 -04:00
Jacob Fletcher
e684f3ac2e adds test for custom server functions 2024-10-01 10:23:06 -04:00
Jacob Fletcher
ac25118945 sanitizes server functions from client config 2024-10-01 00:47:42 -04:00
Jacob Fletcher
9a859a453e cleanup 2024-09-30 23:25:38 -04:00
Jacob Fletcher
dc46f18af9 epxoses in config and renames server actions to server function in preparation for react v19-beta 2024-09-30 22:20:53 -04:00
Jacob Fletcher
271a8c7191 cleanup 2024-09-30 17:51:15 -04:00
Jacob Fletcher
0dbc3bad57 passing tests 2024-09-30 17:46:46 -04:00
Jacob Fletcher
4f0cb93204 creates local req 2024-09-30 15:54:57 -04:00
Jacob Fletcher
b035afe4e3 adds auth 2024-09-30 15:36:24 -04:00
Jacob Fletcher
d33f9f5a1c feat!: server actions 2024-09-30 10:06:33 -04:00
Jacob Fletcher
ccba668dc1 reworks array rows and renders tabs 2024-09-28 14:29:28 -04:00
Jacob Fletcher
9188fbe396 fields with subfields 2024-09-27 16:58:35 -04:00
Jacob Fletcher
4d66c65958 cleanup old references 2024-09-27 12:18:12 -04:00
Jacob Fletcher
66c767f201 deprecates old RenderFields 2024-09-27 12:05:46 -04:00
Jacob Fletcher
4daf22c03c deprecates field props context 2024-09-27 11:36:56 -04:00
Jacob Fletcher
30cc5a018c field labels 2024-09-27 10:54:04 -04:00
Jacob Fletcher
71eb66b393 poc array fields 2024-09-27 10:35:20 -04:00
Jacob Fletcher
b88fabf148 Merge branch 'beta' into feat/on-demand-rsc 2024-09-26 23:02:16 -04:00
Jacob Fletcher
25385a4923 begins removing MappedComponent 2024-09-26 18:15:40 -04:00
Jacob Fletcher
911d93c207 migrating remaining fields 2024-09-26 18:06:53 -04:00
Jacob Fletcher
afe19b3c53 handles hidden and disabled fields 2024-09-26 17:37:20 -04:00
Jacob Fletcher
95c3eb3313 more field slots 2024-09-26 16:15:33 -04:00
Jacob Fletcher
b5ea0a787d establishes pattern for field slots 2024-09-26 15:40:54 -04:00
Jacob Fletcher
8849655afc handles sidebar fields 2024-09-26 13:36:00 -04:00
Jacob Fletcher
868698ed47 establishes render entity pattern 2024-09-26 13:18:07 -04:00
Jacob Fletcher
7291adc3c2 threads field state through props 2024-09-26 12:24:12 -04:00
Jacob Fletcher
3c71e2880e renders fields server side 2024-09-26 10:53:46 -04:00
Jacob Fletcher
b840bea4cf caches client config 2024-09-26 10:48:34 -04:00
Jacob Fletcher
a5f82d8a16 bootable edit view 2024-09-25 16:31:25 -04:00
Jacob Fletcher
0d109be224 rendering list view 2024-09-25 13:40:55 -04:00
Jacob Fletcher
c36b6a43a4 rendering admin 2024-09-25 12:50:12 -04:00
Jacob Fletcher
a154a86350 properly exports render component and achives bootable state 2024-09-25 12:49:39 -04:00
Jacob Fletcher
e9815e6ec7 Merge branch 'beta' into feat/on-demand-rsc 2024-09-25 09:48:26 -04:00
Jacob Fletcher
cdde8d729d cleanup 2024-09-25 09:40:37 -04:00
Jacob Fletcher
487599e2ee rewrites RenderComponent, removing all getCreateMappedComponent instances 2024-09-24 23:38:45 -04:00
Jacob Fletcher
c7f3278d93 rewrites client config 2024-09-24 19:44:51 -04:00
Jacob Fletcher
4d8159e9aa Merge branch 'beta' into feat/on-demand-rsc 2024-09-24 13:58:21 -04:00
Jacob Fletcher
e5956051f2 renders lite configs at root and successfully builds 2024-09-24 11:47:24 -04:00
Jacob Fletcher
1155c0aa22 reworks list info provider 2024-09-23 18:07:36 -04:00
Jacob Fletcher
6ca2f1d28b fixes nav 2024-09-23 14:30:15 -04:00
Jacob Fletcher
5d3193a164 fixes add new 2024-09-23 13:45:48 -04:00
Jacob Fletcher
d0af4f2271 collection labels 2024-09-23 12:37:18 -04:00
Jacob Fletcher
69c74ecbbc fixes duplicative render 2024-09-23 12:07:19 -04:00
Jacob Fletcher
76cc178d36 threads field state to server field components 2024-09-23 10:40:44 -04:00
Jacob Fletcher
b63e18573e adjusts config loading hierarchy 2024-09-23 10:02:25 -04:00
Jacob Fletcher
b61d271bd5 successfully threads data through server components 2024-09-23 00:35:10 -04:00
Jacob Fletcher
ddc57dd5cf works 2024-09-22 23:39:24 -04:00
Jacob Fletcher
f53ef13f4b moves views to ui 2024-09-22 23:14:26 -04:00
Jacob Fletcher
168a8c5317 executes server actions client-side 2024-09-22 13:23:55 -04:00
Jacob Fletcher
5d496c60fa achieves rendering state 2024-09-21 18:24:35 -04:00
Jacob Fletcher
72c206551b poc server actions pattern 2024-09-20 23:26:23 -04:00
948 changed files with 34207 additions and 56179 deletions

View File

@@ -1,4 +1,4 @@
<a href="https://payloadcms.com"><img width="100%" src="https://github.com/payloadcms/payload/blob/main/packages/payload/src/admin/assets/images/github-banner-alt.jpg?raw=true" alt="Payload headless CMS Admin panel built with React" /></a>
<a href="https://payloadcms.com"><img width="100%" src="https://github.com/payloadcms/payload/blob/beta/packages/payload/src/assets/images/github-banner-nextjs-native.jpg" alt="Payload headless CMS Admin panel built with React" /></a>
<br />
<br />

View File

@@ -1,8 +1,10 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import configPromise from '@payload-config'
import { RootLayout } from '@payloadcms/next/layouts'
// import '@payloadcms/ui/styles.css' // Uncomment this line if `@payloadcms/ui` in `tsconfig.json` points to `/ui/dist` instead of `/ui/src`
import type { ServerFunctionClient } from 'payload'
import config from '@payload-config'
import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'
import React from 'react'
import { importMap } from './admin/importMap.js'
@@ -12,8 +14,17 @@ type Args = {
children: React.ReactNode
}
const serverFunction: ServerFunctionClient = async function (args) {
'use server'
return handleServerFunctions({
...args,
config,
importMap,
})
}
const Layout = ({ children }: Args) => (
<RootLayout config={configPromise} importMap={importMap}>
<RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
{children}
</RootLayout>
)

View File

@@ -228,7 +228,6 @@ The following additional properties are also provided to the `field` prop:
| Property | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`_isPresentational`** | A boolean indicating that the field is purely visual and does not directly affect data or change data shape, i.e. the [UI Field](../fields/ui). |
| **`_path`** | A string representing the direct, dynamic path to the field at runtime, i.e. `myGroup.myArray[0].myField`. |
| **`_schemaPath`** | A string representing the direct, static path to the [Field Config](../fields/overview), i.e. `myGroup.myArray.myField` |

View File

@@ -370,7 +370,12 @@ const yourEditorState: SerializedEditorState // <= your current editor state her
// Import editor state into your headless editor
try {
headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState)) // This should commit the editor state immediately
headlessEditor.update(
() => {
headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState))
},
{ discrete: true }, // This should commit the editor state immediately
)
} catch (e) {
logger.error({ err: e }, 'ERROR parsing editor state')
}
@@ -382,8 +387,6 @@ headlessEditor.getEditorState().read(() => {
})
```
The `.setEditorState()` function immediately updates your editor state. Thus, there's no need for the `discrete: true` flag when reading the state afterward.
## Lexical => Plain Text
Export content from the Lexical editor into plain text using these steps:
@@ -401,8 +404,13 @@ const yourEditorState: SerializedEditorState // <= your current editor state her
// Import editor state into your headless editor
try {
headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState)) // This should commit the editor state immediately
} catch (e) {
headlessEditor.update(
() => {
headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState))
},
{ discrete: true }, // This should commit the editor state immediately
)
} catch (e) {
logger.error({ err: e }, 'ERROR parsing editor state')
}

View File

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

View File

@@ -1,8 +1,8 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import configPromise from '@payload-config'
import '@payloadcms/next/css'
import { RootLayout } from '@payloadcms/next/layouts'
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import React from 'react'
import './custom.scss'

View File

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

View File

@@ -19,6 +19,11 @@ const config = withBundleAnalyzer(
typescript: {
ignoreBuildErrors: true,
},
experimental: {
serverActions: {
bodySizeLimit: '5mb',
},
},
env: {
PAYLOAD_CORE_DEV: 'true',
ROOT_DIR: path.resolve(dirname),

View File

@@ -58,6 +58,7 @@
"dev:generate-importmap": "pnpm runts ./test/generateImportMap.ts",
"dev:generate-types": "pnpm runts ./test/generateTypes.ts",
"dev:postgres": "cross-env PAYLOAD_DATABASE=postgres pnpm runts ./test/dev.ts",
"dev:prod": "cross-env NODE_OPTIONS=--no-deprecation tsx ./test/dev.ts --prod",
"dev:vercel-postgres": "cross-env PAYLOAD_DATABASE=vercel-postgres pnpm runts ./test/dev.ts",
"devsafe": "node ./scripts/delete-recursively.js '**/.next' && pnpm dev",
"docker:restart": "pnpm docker:stop --remove-orphans && pnpm docker:start",
@@ -65,12 +66,12 @@
"docker:stop": "docker compose -f packages/plugin-cloud-storage/docker-compose.yml down",
"force:build": "pnpm run build:core:force",
"lint": "turbo run lint --concurrency 1 --continue",
"lint-staged": "lint-staged",
"lint-staged": "node ./scripts/run-lint-staged.js",
"lint:fix": "turbo run lint:fix --concurrency 1 --continue",
"obliterate-playwright-cache-macos": "rm -rf ~/Library/Caches/ms-playwright && find /System/Volumes/Data/private/var/folders -type d -name 'playwright*' -exec rm -rf {} +",
"prepare": "husky",
"prepare-run-test-against-prod": "pnpm bf && rm -rf test/packed && rm -rf test/node_modules && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd ..",
"prepare-run-test-against-prod:ci": "rm -rf test/node_modules && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd ..",
"prepare-run-test-against-prod": "pnpm bf && rm -rf test/packed && rm -rf test/node_modules && rm -rf app && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd ..",
"prepare-run-test-against-prod:ci": "rm -rf test/node_modules && rm -rf app && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd ..",
"reinstall": "pnpm clean:all && pnpm install",
"release:alpha": "pnpm runts ./scripts/release.ts --bump prerelease --tag alpha",
"release:beta": "pnpm runts ./scripts/release.ts --bump prerelease --tag beta",

View File

@@ -0,0 +1,48 @@
import type { QueryOptions } from 'mongoose'
import type { CountGlobalVersions, PayloadRequest } from 'payload'
import { flattenWhereToOperators } from 'payload'
import type { MongooseAdapter } from './index.js'
import { withSession } from './withSession.js'
export const countGlobalVersions: CountGlobalVersions = async function countGlobalVersions(
this: MongooseAdapter,
{ global, locale, req = {} as PayloadRequest, where },
) {
const Model = this.versions[global]
const options: QueryOptions = await withSession(this, req)
let hasNearConstraint = false
if (where) {
const constraints = flattenWhereToOperators(where)
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
}
const query = await Model.buildQuery({
locale,
payload: this.payload,
where,
})
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
// the correct indexed field
options.hint = {
_id: 1,
}
}
const result = await Model.countDocuments(query, options)
return {
totalDocs: result,
}
}

View File

@@ -0,0 +1,48 @@
import type { QueryOptions } from 'mongoose'
import type { CountVersions, PayloadRequest } from 'payload'
import { flattenWhereToOperators } from 'payload'
import type { MongooseAdapter } from './index.js'
import { withSession } from './withSession.js'
export const countVersions: CountVersions = async function countVersions(
this: MongooseAdapter,
{ collection, locale, req = {} as PayloadRequest, where },
) {
const Model = this.versions[collection]
const options: QueryOptions = await withSession(this, req)
let hasNearConstraint = false
if (where) {
const constraints = flattenWhereToOperators(where)
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
}
const query = await Model.buildQuery({
locale,
payload: this.payload,
where,
})
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
// the correct indexed field
options.hint = {
_id: 1,
}
}
const result = await Model.countDocuments(query, options)
return {
totalDocs: result,
}
}

View File

@@ -12,6 +12,8 @@ import type { CollectionModel, GlobalModel, MigrateDownArgs, MigrateUpArgs } fro
import { connect } from './connect.js'
import { count } from './count.js'
import { countGlobalVersions } from './countGlobalVersions.js'
import { countVersions } from './countVersions.js'
import { create } from './create.js'
import { createGlobal } from './createGlobal.js'
import { createGlobalVersion } from './createGlobalVersion.js'
@@ -154,7 +156,6 @@ export function mongooseAdapter({
collections: {},
connection: undefined,
connectOptions: connectOptions || {},
count,
disableIndexHints,
globals: undefined,
mongoMemoryServer,
@@ -166,6 +167,9 @@ export function mongooseAdapter({
beginTransaction: transactionOptions === false ? defaultBeginTransaction() : beginTransaction,
commitTransaction,
connect,
count,
countGlobalVersions,
countVersions,
create,
createGlobal,
createGlobalVersion,

View File

@@ -1,8 +1,6 @@
import type { PipelineStage } from 'mongoose'
import type { CollectionSlug, JoinQuery, SanitizedCollectionConfig, Where } from 'payload'
import { combineQueries } from 'payload'
import type { MongooseAdapter } from '../index.js'
import { buildSortParam } from '../queries/buildSortParam.js'
@@ -60,11 +58,11 @@ export const buildJoinAggregation = async ({
for (const join of joinConfig[slug]) {
const joinModel = adapter.collections[join.field.collection]
if (projection && !projection[join.schemaPath]) {
if (projection && !projection[join.joinPath]) {
continue
}
if (joins?.[join.schemaPath] === false) {
if (joins?.[join.joinPath] === false) {
continue
}
@@ -72,7 +70,7 @@ export const buildJoinAggregation = async ({
limit: limitJoin = join.field.defaultLimit ?? 10,
sort: sortJoin = join.field.defaultSort || collectionConfig.defaultSort,
where: whereJoin,
} = joins?.[join.schemaPath] || {}
} = joins?.[join.joinPath] || {}
const sort = buildSortParam({
config: adapter.payload.config,
@@ -105,7 +103,7 @@ export const buildJoinAggregation = async ({
if (adapter.payload.config.localization && locale === 'all') {
adapter.payload.config.localization.localeCodes.forEach((code) => {
const as = `${versions ? `version.${join.schemaPath}` : join.schemaPath}${code}`
const as = `${versions ? `version.${join.joinPath}` : join.joinPath}${code}`
aggregate.push(
{
@@ -146,7 +144,7 @@ export const buildJoinAggregation = async ({
} else {
const localeSuffix =
join.field.localized && adapter.payload.config.localization && locale ? `.${locale}` : ''
const as = `${versions ? `version.${join.schemaPath}` : join.schemaPath}${localeSuffix}`
const as = `${versions ? `version.${join.joinPath}` : join.joinPath}${localeSuffix}`
aggregate.push(
{

View File

@@ -19,8 +19,8 @@ export const handleError = ({
collection,
errors: [
{
field: Object.keys(error.keyValue)[0],
message: req.t('error:valueMustBeUnique'),
path: Object.keys(error.keyValue)[0],
},
],
global,

View File

@@ -4,6 +4,8 @@ import {
beginTransaction,
commitTransaction,
count,
countGlobalVersions,
countVersions,
create,
createGlobal,
createGlobalVersion,
@@ -126,6 +128,8 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
convertPathToJSONTraversal,
count,
countDistinct,
countGlobalVersions,
countVersions,
create,
createGlobal,
createGlobalVersion,

View File

@@ -5,6 +5,8 @@ import {
beginTransaction,
commitTransaction,
count,
countGlobalVersions,
countVersions,
create,
createGlobal,
createGlobalVersion,
@@ -114,6 +116,8 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
convertPathToJSONTraversal,
count,
countDistinct,
countGlobalVersions,
countVersions,
create,
createGlobal,
createGlobalVersion,

View File

@@ -4,6 +4,8 @@ import {
beginTransaction,
commitTransaction,
count,
countGlobalVersions,
countVersions,
create,
createGlobal,
createGlobalVersion,
@@ -127,6 +129,8 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
convertPathToJSONTraversal,
count,
countDistinct,
countGlobalVersions,
countVersions,
create,
createGlobal,
createGlobalVersion,

View File

@@ -0,0 +1,42 @@
import type { CountGlobalVersions, SanitizedGlobalConfig } from 'payload'
import { buildVersionGlobalFields } from 'payload'
import toSnakeCase from 'to-snake-case'
import type { DrizzleAdapter } from './types.js'
import buildQuery from './queries/buildQuery.js'
export const countGlobalVersions: CountGlobalVersions = async function countGlobalVersions(
this: DrizzleAdapter,
{ global, locale, req, where: whereArg },
) {
const globalConfig: SanitizedGlobalConfig = this.payload.globals.config.find(
({ slug }) => slug === global,
)
const tableName = this.tableNameMap.get(
`_${toSnakeCase(globalConfig.slug)}${this.versionsSuffix}`,
)
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const fields = buildVersionGlobalFields(this.payload.config, globalConfig)
const { joins, where } = buildQuery({
adapter: this,
fields,
locale,
tableName,
where: whereArg,
})
const countResult = await this.countDistinct({
db,
joins,
tableName,
where,
})
return { totalDocs: countResult }
}

View File

@@ -0,0 +1,40 @@
import type { CountVersions, SanitizedCollectionConfig } from 'payload'
import { buildVersionCollectionFields } from 'payload'
import toSnakeCase from 'to-snake-case'
import type { DrizzleAdapter } from './types.js'
import buildQuery from './queries/buildQuery.js'
export const countVersions: CountVersions = async function countVersions(
this: DrizzleAdapter,
{ collection, locale, req, where: whereArg },
) {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const tableName = this.tableNameMap.get(
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
)
const db = this.sessions[await req?.transactionID]?.db || this.drizzle
const fields = buildVersionCollectionFields(this.payload.config, collectionConfig)
const { joins, where } = buildQuery({
adapter: this,
fields,
locale,
tableName,
where: whereArg,
})
const countResult = await this.countDistinct({
db,
joins,
tableName,
where,
})
return { totalDocs: countResult }
}

View File

@@ -1,4 +1,6 @@
export { count } from './count.js'
export { countGlobalVersions } from './countGlobalVersions.js'
export { countVersions } from './countVersions.js'
export { create } from './create.js'
export { createGlobal } from './createGlobal.js'
export { createGlobalVersion } from './createGlobalVersion.js'

View File

@@ -391,8 +391,8 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
id,
errors: [
{
field: fieldName,
message: req.t('error:valueMustBeUnique'),
path: fieldName,
},
],
},

View File

@@ -1,7 +1,8 @@
import type { DocumentTabConfig, DocumentTabProps } from 'payload'
import type React from 'react'
import { getCreateMappedComponent, RenderComponent } from '@payloadcms/ui/shared'
import React, { Fragment } from 'react'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { Fragment } from 'react'
import './index.scss'
import { DocumentTabLink } from './TabLink.js'
@@ -59,17 +60,6 @@ export const DocumentTab: React.FC<
})
: label
const createMappedComponent = getCreateMappedComponent({
importMap: payload.importMap,
serverProps: {
i18n,
payload,
permissions,
},
})
const mappedPin = createMappedComponent(Pill, undefined, Pill_Component, 'Pill')
return (
<DocumentTabLink
adminRoute={routes.admin}
@@ -82,12 +72,21 @@ export const DocumentTab: React.FC<
>
<span className={`${baseClass}__label`}>
{labelToRender}
{mappedPin && (
{Pill || Pill_Component ? (
<Fragment>
&nbsp;
<RenderComponent mappedComponent={mappedPin} />
<RenderServerComponent
Component={Pill}
Fallback={Pill_Component}
importMap={payload.importMap}
serverProps={{
i18n,
payload,
permissions,
}}
/>
</Fragment>
)}
) : null}
</span>
</DocumentTabLink>
)

View File

@@ -6,7 +6,7 @@ import type {
SanitizedGlobalConfig,
} from 'payload'
import { getCreateMappedComponent, RenderComponent } from '@payloadcms/ui/shared'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import React from 'react'
import { getCustomViews } from './getCustomViews.js'
@@ -80,33 +80,21 @@ export const DocumentTabs: React.FC<{
const { path, tab } = CustomView
if (tab.Component) {
const createMappedComponent = getCreateMappedComponent({
importMap: payload.importMap,
serverProps: {
i18n,
payload,
permissions,
...props,
key: `tab-custom-${index}`,
path,
},
})
const mappedTab = createMappedComponent(
tab.Component,
undefined,
undefined,
'tab.Component',
)
return (
<RenderComponent
<RenderServerComponent
clientProps={{
key: `tab-custom-${index}`,
path,
}}
Component={tab.Component}
importMap={payload.importMap}
key={`tab-custom-${index}`}
mappedComponent={mappedTab}
serverProps={{
collectionConfig,
globalConfig,
i18n,
payload,
permissions,
}}
/>
)
}
@@ -121,6 +109,7 @@ export const DocumentTabs: React.FC<{
/>
)
}
return null
})}
</ul>

View File

@@ -5,14 +5,11 @@ import React from 'react'
import { baseClass } from '../../Tab/index.js'
export const VersionsPill: React.FC = () => {
const { versions } = useDocumentInfo()
const { versionCount } = useDocumentInfo()
// don't count snapshots
const totalVersions = versions?.docs.filter((version) => !version.snapshot).length || 0
if (!versions?.totalDocs) {
if (!versionCount) {
return null
}
return <span className={`${baseClass}__count`}>{totalVersions}</span>
return <span className={`${baseClass}__count`}>{versionCount}</span>
}

View File

@@ -7,7 +7,7 @@ import type {
} from 'payload'
import { Gutter, RenderTitle } from '@payloadcms/ui'
import React, { Fragment } from 'react'
import React from 'react'
import './index.scss'
import { DocumentTabs } from './Tabs/index.js'
@@ -16,32 +16,25 @@ const baseClass = `doc-header`
export const DocumentHeader: React.FC<{
collectionConfig?: SanitizedCollectionConfig
customHeader?: React.ReactNode
globalConfig?: SanitizedGlobalConfig
hideTabs?: boolean
i18n: I18n
payload: Payload
permissions: Permissions
}> = (props) => {
const { collectionConfig, customHeader, globalConfig, hideTabs, i18n, payload, permissions } =
props
const { collectionConfig, globalConfig, hideTabs, i18n, payload, permissions } = props
return (
<Gutter className={baseClass}>
{customHeader && customHeader}
{!customHeader && (
<Fragment>
<RenderTitle className={`${baseClass}__title`} />
{!hideTabs && (
<DocumentTabs
collectionConfig={collectionConfig}
globalConfig={globalConfig}
i18n={i18n}
payload={payload}
permissions={permissions}
/>
)}
</Fragment>
<RenderTitle className={`${baseClass}__title`} />
{!hideTabs && (
<DocumentTabs
collectionConfig={collectionConfig}
globalConfig={globalConfig}
i18n={i18n}
payload={payload}
permissions={permissions}
/>
)}
</Gutter>
)

View File

@@ -1,114 +0,0 @@
'use client'
import type { FieldPermissions, LoginWithUsernameOptions } from 'payload'
import { EmailField, RenderFields, TextField, useTranslation } from '@payloadcms/ui'
import { email, username } from 'payload/shared'
import React from 'react'
type Props = {
readonly loginWithUsername?: false | LoginWithUsernameOptions
}
function EmailFieldComponent(props: Props) {
const { loginWithUsername } = props
const { t } = useTranslation()
const requireEmail = !loginWithUsername || (loginWithUsername && loginWithUsername.requireEmail)
const showEmailField =
!loginWithUsername || loginWithUsername?.requireEmail || loginWithUsername?.allowEmailLogin
if (showEmailField) {
return (
<EmailField
autoComplete="off"
field={{
name: 'email',
label: t('general:email'),
required: requireEmail,
}}
validate={email}
/>
)
}
return null
}
function UsernameFieldComponent(props: Props) {
const { loginWithUsername } = props
const { t } = useTranslation()
const requireUsername = loginWithUsername && loginWithUsername.requireUsername
const showUsernameField = Boolean(loginWithUsername)
if (showUsernameField) {
return (
<TextField
field={{
name: 'username',
label: t('authentication:username'),
required: requireUsername,
}}
validate={username}
/>
)
}
return null
}
type RenderEmailAndUsernameFieldsProps = {
className?: string
loginWithUsername?: false | LoginWithUsernameOptions
operation?: 'create' | 'update'
permissions?: {
[fieldName: string]: FieldPermissions
}
readOnly: boolean
}
export function RenderEmailAndUsernameFields(props: RenderEmailAndUsernameFieldsProps) {
const { className, loginWithUsername, operation, permissions, readOnly } = props
return (
<RenderFields
className={className}
fields={[
{
name: 'email',
type: 'text',
admin: {
autoComplete: 'off',
components: {
Field: {
type: 'client',
Component: null,
RenderedComponent: <EmailFieldComponent loginWithUsername={loginWithUsername} />,
},
},
},
localized: false,
},
{
name: 'username',
type: 'text',
admin: {
components: {
Field: {
type: 'client',
Component: null,
RenderedComponent: <UsernameFieldComponent loginWithUsername={loginWithUsername} />,
},
},
},
localized: false,
},
]}
forceRender
operation={operation}
path=""
permissions={permissions}
readOnly={readOnly}
schemaPath=""
/>
)
}

View File

@@ -1,6 +1,7 @@
import type { ServerProps } from 'payload'
import { getCreateMappedComponent, PayloadLogo, RenderComponent } from '@payloadcms/ui/shared'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { PayloadLogo } from '@payloadcms/ui/shared'
import React from 'react'
export const Logo: React.FC<ServerProps> = (props) => {
@@ -16,20 +17,20 @@ export const Logo: React.FC<ServerProps> = (props) => {
} = {},
} = payload.config
const createMappedComponent = getCreateMappedComponent({
importMap: payload.importMap,
serverProps: {
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
},
})
const mappedCustomLogo = createMappedComponent(CustomLogo, undefined, PayloadLogo, 'CustomLogo')
return <RenderComponent mappedComponent={mappedCustomLogo} />
return (
<RenderServerComponent
Component={CustomLogo}
Fallback={PayloadLogo}
importMap={payload.importMap}
serverProps={{
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
}}
/>
)
}

View File

@@ -1,32 +1,23 @@
'use client'
import type { EntityToGroup } from '@payloadcms/ui/shared'
import type { groupNavItems } from '@payloadcms/ui/shared'
import { getTranslation } from '@payloadcms/translations'
import {
NavGroup,
useAuth,
useConfig,
useEntityVisibility,
useNav,
useTranslation,
} from '@payloadcms/ui'
import { EntityType, formatAdminURL, groupNavItems } from '@payloadcms/ui/shared'
import { NavGroup, useConfig, useNav, useTranslation } from '@payloadcms/ui'
import { EntityType, formatAdminURL } from '@payloadcms/ui/shared'
import LinkWithDefault from 'next/link.js'
import { usePathname } from 'next/navigation.js'
import React, { Fragment } from 'react'
const baseClass = 'nav'
export const DefaultNavClient: React.FC = () => {
const { permissions } = useAuth()
const { isEntityVisible } = useEntityVisibility()
export const DefaultNavClient: React.FC<{
groups: ReturnType<typeof groupNavItems>
}> = ({ groups }) => {
const pathname = usePathname()
const {
config: {
collections,
globals,
routes: { admin: adminRoute },
},
} = useConfig()
@@ -34,53 +25,23 @@ export const DefaultNavClient: React.FC = () => {
const { i18n } = useTranslation()
const { navOpen } = useNav()
const groups = groupNavItems(
[
...collections
.filter(({ slug }) => isEntityVisible({ collectionSlug: slug }))
.map((collection) => {
const entityToGroup: EntityToGroup = {
type: EntityType.collection,
entity: collection,
}
return entityToGroup
}),
...globals
.filter(({ slug }) => isEntityVisible({ globalSlug: slug }))
.map((global) => {
const entityToGroup: EntityToGroup = {
type: EntityType.global,
entity: global,
}
return entityToGroup
}),
],
permissions,
i18n,
)
return (
<Fragment>
{groups.map(({ entities, label }, key) => {
return (
<NavGroup key={key} label={label}>
{entities.map(({ type, entity }, i) => {
let entityLabel: string
{entities.map(({ slug, type, label }, i) => {
let href: string
let id: string
if (type === EntityType.collection) {
href = formatAdminURL({ adminRoute, path: `/collections/${entity.slug}` })
entityLabel = getTranslation(entity.labels.plural, i18n)
id = `nav-${entity.slug}`
href = formatAdminURL({ adminRoute, path: `/collections/${slug}` })
id = `nav-${slug}`
}
if (type === EntityType.global) {
href = formatAdminURL({ adminRoute, path: `/globals/${entity.slug}` })
entityLabel = getTranslation(entity.label, i18n)
id = `nav-global-${entity.slug}`
href = formatAdminURL({ adminRoute, path: `/globals/${slug}` })
id = `nav-global-${slug}`
}
const Link = (LinkWithDefault.default ||
@@ -102,7 +63,7 @@ export const DefaultNavClient: React.FC = () => {
tabIndex={!navOpen ? -1 : undefined}
>
{activeCollection && <div className={`${baseClass}__link-indicator`} />}
<span className={`${baseClass}__link-label`}>{entityLabel}</span>
<span className={`${baseClass}__link-label`}>{getTranslation(label, i18n)}</span>
</LinkElement>
)
})}

View File

@@ -1,7 +1,9 @@
import type { EntityToGroup } from '@payloadcms/ui/shared'
import type { ServerProps } from 'payload'
import { Logout } from '@payloadcms/ui'
import { getCreateMappedComponent, RenderComponent } from '@payloadcms/ui/shared'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { EntityType, groupNavItems } from '@payloadcms/ui/shared'
import React from 'react'
import './index.scss'
@@ -15,7 +17,7 @@ import { DefaultNavClient } from './index.client.js'
export type NavProps = ServerProps
export const DefaultNav: React.FC<NavProps> = (props) => {
const { i18n, locale, params, payload, permissions, searchParams, user } = props
const { i18n, locale, params, payload, permissions, searchParams, user, visibleEntities } = props
if (!payload?.config) {
return null
@@ -23,44 +25,82 @@ export const DefaultNav: React.FC<NavProps> = (props) => {
const {
admin: {
components: { afterNavLinks, beforeNavLinks },
components: { afterNavLinks, beforeNavLinks, logout },
},
collections,
globals,
} = payload.config
const createMappedComponent = getCreateMappedComponent({
importMap: payload.importMap,
serverProps: {
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
},
})
const mappedBeforeNavLinks = createMappedComponent(
beforeNavLinks,
undefined,
undefined,
'beforeNavLinks',
)
const mappedAfterNavLinks = createMappedComponent(
afterNavLinks,
undefined,
undefined,
'afterNavLinks',
const groups = groupNavItems(
[
...collections
.filter(({ slug }) => visibleEntities.collections.includes(slug))
.map(
(collection) =>
({
type: EntityType.collection,
entity: collection,
}) satisfies EntityToGroup,
),
...globals
.filter(({ slug }) => visibleEntities.globals.includes(slug))
.map(
(global) =>
({
type: EntityType.global,
entity: global,
}) satisfies EntityToGroup,
),
],
permissions,
i18n,
)
return (
<NavWrapper baseClass={baseClass}>
<nav className={`${baseClass}__wrap`}>
<RenderComponent mappedComponent={mappedBeforeNavLinks} />
<DefaultNavClient />
<RenderComponent mappedComponent={mappedAfterNavLinks} />
<RenderServerComponent
Component={beforeNavLinks}
importMap={payload.importMap}
serverProps={{
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
}}
/>
<DefaultNavClient groups={groups} />
<RenderServerComponent
Component={afterNavLinks}
importMap={payload.importMap}
serverProps={{
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
}}
/>
<div className={`${baseClass}__controls`}>
<Logout />
<RenderServerComponent
Component={logout?.Button}
Fallback={Logout}
importMap={payload.importMap}
serverProps={{
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
}}
/>
</div>
</nav>
<div className={`${baseClass}__header`}>

View File

@@ -1 +1,2 @@
export { metadata, RootLayout } from '../layouts/Root/index.js'
export { handleServerFunctions } from '../utilities/handleServerFunctions.js'

View File

@@ -1,3 +1,4 @@
// NOTICE: Server-only utilities, do not import anything client-side here.
export { addDataAndFileToRequest } from '../utilities/addDataAndFileToRequest.js'
export { addLocalesToRequestFromData, sanitizeLocales } from '../utilities/addLocalesToRequest.js'
export { createPayloadRequest } from '../utilities/createPayloadRequest.js'

View File

@@ -1,4 +1,2 @@
export { DefaultEditView as EditView } from '../views/Edit/Default/index.js'
export { DefaultListView as ListView } from '../views/List/Default/index.js'
export { NotFoundPage } from '../views/NotFound/index.js'
export { generatePageMetadata, type GenerateViewMetadata, RootPage } from '../views/Root/index.js'

View File

@@ -22,6 +22,7 @@ type ProcessMultipart = (args: {
export const processMultipart: ProcessMultipart = async ({ options, request }) => {
let parsingRequest = true
let shouldAbortProccessing = false
let fileCount = 0
let filesCompleted = 0
let allFilesHaveResolved: (value?: unknown) => void
@@ -42,14 +43,16 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) =
headersObject[name] = value
})
function abortAndDestroyFile(file: Readable, err: APIError) {
file.destroy()
parsingRequest = false
failedResolvingFiles(err)
}
const reader = request.body.getReader()
const busboy = Busboy({ ...options, headers: headersObject })
function abortAndDestroyFile(file: Readable, err: APIError) {
file.destroy()
shouldAbortProccessing = true
failedResolvingFiles(err)
}
// Build multipart req.body fields
busboy.on('field', (field, val) => {
result.fields = buildFields(result.fields, field, val)
@@ -136,7 +139,7 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) =
mimetype: mime,
size,
tempFilePath: getFilePath(),
truncated: Boolean('truncated' in file && file.truncated),
truncated: Boolean('truncated' in file && file.truncated) || false,
},
options,
),
@@ -164,8 +167,6 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) =
uploadTimer.set()
})
// TODO: Valid eslint error - this will likely be a floating promise. Evaluate if we need to handle this differently.
busboy.on('finish', async () => {
debugLog(options, `Busboy finished parsing request.`)
if (options.parseNested) {
@@ -190,14 +191,10 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) =
'error',
(err = new APIError('Busboy error parsing multipart request', httpStatus.BAD_REQUEST)) => {
debugLog(options, `Busboy error`)
parsingRequest = false
throw err
},
)
const reader = request.body.getReader()
// Start parsing request
while (parsingRequest) {
const { done, value } = await reader.read()
@@ -205,7 +202,7 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) =
parsingRequest = false
}
if (value) {
if (value && !shouldAbortProccessing) {
busboy.write(value)
}
}

View File

@@ -0,0 +1,30 @@
import type { Config, ImportMap } from 'payload'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import '@payloadcms/ui/scss/app.scss'
import React from 'react'
type Args = {
readonly children: React.ReactNode
readonly importMap: ImportMap
readonly providers: Config['admin']['components']['providers']
}
export function NestProviders({ children, importMap, providers }: Args): React.ReactNode {
return (
<RenderServerComponent
clientProps={{
children:
providers.length > 1 ? (
<NestProviders importMap={importMap} providers={providers.slice(1)}>
{children}
</NestProviders>
) : (
children
),
}}
Component={providers[0]}
importMap={importMap}
/>
)
}

View File

@@ -1,20 +1,19 @@
import type { AcceptedLanguages } from '@payloadcms/translations'
import type { CustomVersionParser, ImportMap, SanitizedConfig } from 'payload'
import type { CustomVersionParser, ImportMap, SanitizedConfig, ServerFunctionClient } from 'payload'
import { rtlLanguages } from '@payloadcms/translations'
import { RootProvider } from '@payloadcms/ui'
import '@payloadcms/ui/scss/app.scss'
import { createClientConfig } from '@payloadcms/ui/utilities/createClientConfig'
import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js'
import { checkDependencies, parseCookies } from 'payload'
import React from 'react'
import { getClientConfig } from '../../utilities/getClientConfig.js'
import { getPayloadHMR } from '../../utilities/getPayloadHMR.js'
import { getRequestLanguage } from '../../utilities/getRequestLanguage.js'
import { getRequestTheme } from '../../utilities/getRequestTheme.js'
import { initReq } from '../../utilities/initReq.js'
import { DefaultEditView } from '../../views/Edit/Default/index.js'
import { DefaultListView } from '../../views/List/Default/index.js'
import { NestProviders } from './NestProviders.js'
export const metadata = {
description: 'Generated by Next.js',
@@ -41,11 +40,12 @@ let checkedDependencies = false
export const RootLayout = async ({
children,
config: configPromise,
importMap,
serverFunction,
}: {
readonly children: React.ReactNode
readonly config: Promise<SanitizedConfig>
readonly importMap: ImportMap
readonly serverFunction: ServerFunctionClient
}) => {
if (
process.env.NODE_ENV !== 'production' &&
@@ -103,16 +103,6 @@ export const RootLayout = async ({
const { i18n, permissions, req, user } = await initReq(config)
const { clientConfig, render } = await createClientConfig({
children,
config,
DefaultEditView,
DefaultListView,
i18n,
importMap,
payload,
})
const dir = (rtlLanguages as unknown as AcceptedLanguages[]).includes(languageCode)
? 'RTL'
: 'LTR'
@@ -174,23 +164,39 @@ export const RootLayout = async ({
const isNavOpen = navPreferences?.value?.open ?? true
const clientConfig = await getClientConfig({
config,
i18n,
})
return (
<html data-theme={theme} dir={dir} lang={languageCode}>
<body>
<RootProvider
config={clientConfig}
dateFNSKey={i18n.dateFNSKey}
fallbackLang={clientConfig.i18n.fallbackLanguage}
fallbackLang={config.i18n.fallbackLanguage}
isNavOpen={isNavOpen}
languageCode={languageCode}
languageOptions={languageOptions}
permissions={permissions}
serverFunction={serverFunction}
switchLanguageServerAction={switchLanguageServerAction}
theme={theme}
translations={i18n.translations}
user={user}
>
{render}
{Array.isArray(config.admin?.components?.providers) &&
config.admin?.components?.providers.length > 0 ? (
<NestProviders
importMap={payload.importMap}
providers={config.admin?.components?.providers}
>
{children}
</NestProviders>
) : (
children
)}
</RootProvider>
<div id="portal" />
</body>

View File

@@ -1,50 +0,0 @@
import type { PayloadRequest } from 'payload'
import { buildFormState as buildFormStateFn } from '@payloadcms/ui/utilities/buildFormState'
import httpStatus from 'http-status'
import { headersWithCors } from '../../utilities/headersWithCors.js'
import { routeError } from './routeError.js'
export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
const headers = headersWithCors({
headers: new Headers(),
req,
})
try {
const result = await buildFormStateFn({ req })
return Response.json(result, {
headers,
status: httpStatus.OK,
})
} catch (err) {
req.payload.logger.error({ err, msg: `There was an error building form state` })
if (err.message === 'Could not find field schema for given path') {
return Response.json(
{
message: err.message,
},
{
headers,
status: httpStatus.BAD_REQUEST,
},
)
}
if (err.message === 'Unauthorized') {
return Response.json(null, {
headers,
status: httpStatus.UNAUTHORIZED,
})
}
return routeError({
config: req.payload.config,
err,
req,
})
}
}

View File

@@ -26,7 +26,6 @@ import { registerFirstUser } from './auth/registerFirstUser.js'
import { resetPassword } from './auth/resetPassword.js'
import { unlock } from './auth/unlock.js'
import { verifyEmail } from './auth/verifyEmail.js'
import { buildFormState } from './buildFormState.js'
import { endpointsAreDisabled } from './checkEndpoints.js'
import { count } from './collections/count.js'
import { create } from './collections/create.js'
@@ -110,9 +109,6 @@ const endpoints = {
access,
og: generateOGImage,
},
POST: {
'form-state': buildFormState,
},
},
}
@@ -575,10 +571,6 @@ export const POST =
res = new Response('Route Not Found', { status: 404 })
}
}
} else if (slug.length === 1 && slug1 in endpoints.root.POST) {
await addDataAndFileToRequest(req)
addLocalesToRequestFromData(req)
res = await endpoints.root.POST[slug1]({ req })
}
if (res instanceof Response) {

View File

@@ -1,15 +1,25 @@
import type { MappedComponent } from 'payload'
import type { ImportMap, PayloadComponent } from 'payload'
import { RenderComponent } from '@payloadcms/ui/shared'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import React from 'react'
export const OGImage: React.FC<{
description?: string
Fallback: React.ComponentType
fontFamily?: string
Icon: MappedComponent
Icon: PayloadComponent
importMap: ImportMap
leader?: string
title?: string
}> = ({ description, fontFamily = 'Arial, sans-serif', Icon, leader, title }) => {
}> = ({
description,
Fallback,
fontFamily = 'Arial, sans-serif',
Icon,
importMap,
leader,
title,
}) => {
return (
<div
style={{
@@ -85,11 +95,13 @@ export const OGImage: React.FC<{
width: '38px',
}}
>
<RenderComponent
<RenderServerComponent
clientProps={{
fill: 'white',
}}
mappedComponent={Icon}
Component={Icon}
Fallback={Fallback}
importMap={importMap}
/>
</div>
</div>

View File

@@ -1,6 +1,6 @@
import type { PayloadRequest } from 'payload'
import { getCreateMappedComponent, PayloadIcon } from '@payloadcms/ui/shared'
import { PayloadIcon } from '@payloadcms/ui/shared'
import fs from 'fs/promises'
import { ImageResponse } from 'next/og.js'
import { NextResponse } from 'next/server.js'
@@ -33,18 +33,6 @@ export const generateOGImage = async ({ req }: { req: PayloadRequest }) => {
const leader = hasLeader ? searchParams.get('leader')?.slice(0, 100).replace('-', ' ') : ''
const description = searchParams.has('description') ? searchParams.get('description') : ''
const createMappedComponent = getCreateMappedComponent({
importMap: req.payload.importMap,
serverProps: {},
})
const mappedIcon = createMappedComponent(
config.admin?.components?.graphics?.Icon,
undefined,
PayloadIcon,
'config.admin.components.graphics.Icon',
)
let fontData
try {
@@ -62,8 +50,10 @@ export const generateOGImage = async ({ req }: { req: PayloadRequest }) => {
(
<OGImage
description={description}
Fallback={PayloadIcon}
fontFamily={fontFamily}
Icon={mappedIcon}
Icon={config.admin?.components?.graphics?.Icon}
importMap={req.payload.importMap}
leader={leader}
title={title}
/>

View File

@@ -1,73 +1,12 @@
import type { Collection, ErrorResult, PayloadRequest, SanitizedConfig } from 'payload'
import httpStatus from 'http-status'
import { APIError, APIErrorName, ValidationErrorName } from 'payload'
import { APIError, formatErrors } from 'payload'
import { getPayloadHMR } from '../../utilities/getPayloadHMR.js'
import { headersWithCors } from '../../utilities/headersWithCors.js'
import { mergeHeaders } from '../../utilities/mergeHeaders.js'
const formatErrors = (incoming: { [key: string]: unknown } | APIError): ErrorResult => {
if (incoming) {
// Cannot use `instanceof` to check error type: https://github.com/microsoft/TypeScript/issues/13965
// Instead, get the prototype of the incoming error and check its constructor name
const proto = Object.getPrototypeOf(incoming)
// Payload 'ValidationError' and 'APIError'
if (
(proto.constructor.name === ValidationErrorName || proto.constructor.name === APIErrorName) &&
incoming.data
) {
return {
errors: [
{
name: incoming.name,
data: incoming.data,
message: incoming.message,
},
],
}
}
// Mongoose 'ValidationError': https://mongoosejs.com/docs/api/error.html#Error.ValidationError
if (proto.constructor.name === ValidationErrorName && 'errors' in incoming && incoming.errors) {
return {
errors: Object.keys(incoming.errors).reduce((acc, key) => {
acc.push({
field: incoming.errors[key].path,
message: incoming.errors[key].message,
})
return acc
}, []),
}
}
if (Array.isArray(incoming.message)) {
return {
errors: incoming.message,
}
}
if (incoming.name) {
return {
errors: [
{
message: incoming.message,
},
],
}
}
}
return {
errors: [
{
message: 'An unknown error occurred.',
},
],
}
}
export const routeError = async ({
collection,
config: configArg,

View File

@@ -1,7 +1,13 @@
import type { MappedComponent, ServerProps, VisibleEntities } from 'payload'
import type { CustomComponent, ServerProps, VisibleEntities } from 'payload'
import { AppHeader, BulkUploadProvider, EntityVisibilityProvider, NavToggler } from '@payloadcms/ui'
import { getCreateMappedComponent, RenderComponent } from '@payloadcms/ui/shared'
import {
ActionsProvider,
AppHeader,
BulkUploadProvider,
EntityVisibilityProvider,
NavToggler,
} from '@payloadcms/ui'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import React from 'react'
import { DefaultNav } from '../../elements/Nav/index.js'
@@ -14,6 +20,7 @@ const baseClass = 'template-default'
export type DefaultTemplateProps = {
children?: React.ReactNode
className?: string
viewActions?: CustomComponent[]
visibleEntities: VisibleEntities
} & ServerProps
@@ -27,10 +34,13 @@ export const DefaultTemplate: React.FC<DefaultTemplateProps> = ({
permissions,
searchParams,
user,
viewActions,
visibleEntities,
}) => {
const {
admin: {
avatar,
components,
components: { header: CustomHeader, Nav: CustomNav } = {
header: undefined,
Nav: undefined,
@@ -38,54 +48,98 @@ export const DefaultTemplate: React.FC<DefaultTemplateProps> = ({
} = {},
} = payload.config || {}
const createMappedComponent = getCreateMappedComponent({
importMap: payload.importMap,
serverProps: {
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
},
})
const { Actions } = React.useMemo<{
Actions: Record<string, React.ReactNode>
}>(() => {
return {
Actions: viewActions
? viewActions.reduce((acc, action) => {
if (action) {
if (typeof action === 'object') {
acc[action.path] = (
<RenderServerComponent Component={action} importMap={payload.importMap} />
)
} else {
acc[action] = (
<RenderServerComponent Component={action} importMap={payload.importMap} />
)
}
}
const MappedDefaultNav: MappedComponent = createMappedComponent(
CustomNav,
undefined,
DefaultNav,
'CustomNav',
)
const MappedCustomHeader = createMappedComponent(
CustomHeader,
undefined,
undefined,
'CustomHeader',
)
return acc
}, {})
: undefined,
}
}, [viewActions, payload])
return (
<EntityVisibilityProvider visibleEntities={visibleEntities}>
<BulkUploadProvider>
<RenderComponent mappedComponent={MappedCustomHeader} />
<div style={{ position: 'relative' }}>
<div className={`${baseClass}__nav-toggler-wrapper`} id="nav-toggler">
<div className={`${baseClass}__nav-toggler-container`} id="nav-toggler">
<NavToggler className={`${baseClass}__nav-toggler`}>
<NavHamburger />
</NavToggler>
<ActionsProvider Actions={Actions}>
<RenderServerComponent
clientProps={{ clientProps: { visibleEntities } }}
Component={CustomHeader}
importMap={payload.importMap}
serverProps={{
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
visibleEntities,
}}
/>
<div style={{ position: 'relative' }}>
<div className={`${baseClass}__nav-toggler-wrapper`} id="nav-toggler">
<div className={`${baseClass}__nav-toggler-container`} id="nav-toggler">
<NavToggler className={`${baseClass}__nav-toggler`}>
<NavHamburger />
</NavToggler>
</div>
</div>
<Wrapper baseClass={baseClass} className={className}>
<RenderServerComponent
clientProps={{ clientProps: { visibleEntities } }}
Component={CustomNav}
Fallback={DefaultNav}
importMap={payload.importMap}
serverProps={{
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
visibleEntities,
}}
/>
<div className={`${baseClass}__wrap`}>
<AppHeader
CustomAvatar={
avatar !== 'gravatar' && avatar !== 'default' ? (
<RenderServerComponent
Component={avatar.Component}
importMap={payload.importMap}
/>
) : undefined
}
CustomIcon={
components?.graphics?.Icon ? (
<RenderServerComponent
Component={components.graphics.Icon}
importMap={payload.importMap}
/>
) : undefined
}
/>
{children}
</div>
</Wrapper>
</div>
<Wrapper baseClass={baseClass} className={className}>
<RenderComponent mappedComponent={MappedDefaultNav} />
<div className={`${baseClass}__wrap`}>
<AppHeader />
{children}
</div>
</Wrapper>
</div>
</ActionsProvider>
</BulkUploadProvider>
</EntityVisibilityProvider>
)

View File

@@ -0,0 +1,18 @@
import type { I18nClient } from '@payloadcms/translations'
import type { ClientConfig, SanitizedConfig } from 'payload'
import { createClientConfig } from 'payload'
import { cache } from 'react'
export const getClientConfig = cache(
async (args: { config: SanitizedConfig; i18n: I18nClient }): Promise<ClientConfig> => {
const { config, i18n } = args
const clientConfig = createClientConfig({
config,
i18n,
})
return Promise.resolve(clientConfig)
},
)

View File

@@ -0,0 +1,37 @@
import type { ServerFunction, ServerFunctionHandler } from 'payload'
import { buildFormStateHandler } from '@payloadcms/ui/utilities/buildFormState'
import { buildTableStateHandler } from '@payloadcms/ui/utilities/buildTableState'
import { renderDocumentHandler } from '../views/Document/handleServerFunction.js'
import { renderDocumentSlotsHandler } from '../views/Document/renderDocumentSlots.js'
import { renderListHandler } from '../views/List/handleServerFunction.js'
import { initReq } from './initReq.js'
export const handleServerFunctions: ServerFunctionHandler = async (args) => {
const { name: fnKey, args: fnArgs, config: configPromise, importMap } = args
const { req } = await initReq(configPromise)
const augmentedArgs: Parameters<ServerFunction>[0] = {
...fnArgs,
importMap,
req,
}
const serverFunctions = {
'form-state': buildFormStateHandler as any as ServerFunction,
'render-document': renderDocumentHandler as any as ServerFunction,
'render-document-slots': renderDocumentSlotsHandler as any as ServerFunction,
'render-list': renderListHandler as any as ServerFunction,
'table-state': buildTableStateHandler as any as ServerFunction,
}
const fn = serverFunctions[fnKey]
if (!fn) {
throw new Error(`Unknown Server Function: ${fnKey}`)
}
return fn(augmentedArgs)
}

View File

@@ -1,4 +1,5 @@
import type { InitPageResult, Locale, PayloadRequest, VisibleEntities } from 'payload'
import type { I18n } from '@payloadcms/translations'
import type { InitPageResult, Locale, VisibleEntities } from 'payload'
import { findLocaleFromCode } from '@payloadcms/ui/shared'
import { headers as getHeaders } from 'next/headers.js'
@@ -47,13 +48,13 @@ export const initPage = async ({
req: {
headers,
host: headers.get('host'),
i18n,
i18n: i18n as I18n,
query: qs.parse(queryString, {
depth: 10,
ignoreQueryPrefix: true,
}),
url: `${payload.config.serverURL}${route}${searchParams ? queryString : ''}`,
} as PayloadRequest,
},
},
payload,
)

View File

@@ -1,4 +1,4 @@
import type { I18nClient } from '@payloadcms/translations'
import type { I18n, I18nClient } from '@payloadcms/translations'
import type { PayloadRequest, Permissions, SanitizedConfig, User } from 'payload'
import { initI18n } from '@payloadcms/translations'
@@ -16,7 +16,10 @@ type Result = {
user: User
}
export const initReq = cache(async function (config: SanitizedConfig): Promise<Result> {
export const initReq = cache(async function (
configPromise: Promise<SanitizedConfig> | SanitizedConfig,
): Promise<Result> {
const config = await configPromise
const payload = await getPayloadHMR({ config })
const headers = await getHeaders()
@@ -40,9 +43,9 @@ export const initReq = cache(async function (config: SanitizedConfig): Promise<R
req: {
headers,
host: headers.get('host'),
i18n,
i18n: i18n as I18n,
url: `${payload.config.serverURL}`,
} as PayloadRequest,
},
},
payload,
)

View File

@@ -15,7 +15,6 @@ export const LocaleSelector: React.FC<{
<SelectField
field={{
name: 'locale',
_path: 'locale',
label: t('general:locale'),
options: localeOptions,
}}

View File

@@ -9,7 +9,7 @@ import {
Gutter,
MinimizeMaximizeIcon,
NumberField,
SetViewActions,
SetDocumentStepNav,
useConfig,
useDocumentInfo,
useLocale,
@@ -19,7 +19,6 @@ import { useSearchParams } from 'next/navigation.js'
import * as React from 'react'
import { toast } from 'sonner'
import { SetDocumentStepNav } from '../Edit/Default/SetDocumentStepNav/index.js'
import './index.scss'
import { LocaleSelector } from './LocaleSelector/index.js'
import { RenderJSON } from './RenderJSON/index.js'
@@ -42,8 +41,8 @@ export const APIViewClient: React.FC = () => {
getEntityConfig,
} = useConfig()
const collectionClientConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig
const globalClientConfig = getEntityConfig({ globalSlug }) as ClientGlobalConfig
const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig
const globalConfig = getEntityConfig({ globalSlug }) as ClientGlobalConfig
const localeOptions =
localization &&
@@ -52,13 +51,13 @@ export const APIViewClient: React.FC = () => {
let draftsEnabled: boolean = false
let docEndpoint: string = ''
if (collectionClientConfig) {
draftsEnabled = Boolean(collectionClientConfig.versions?.drafts)
if (collectionConfig) {
draftsEnabled = Boolean(collectionConfig.versions?.drafts)
docEndpoint = `/${collectionSlug}/${id}`
}
if (globalClientConfig) {
draftsEnabled = Boolean(globalClientConfig.versions?.drafts)
if (globalConfig) {
draftsEnabled = Boolean(globalConfig.versions?.drafts)
docEndpoint = `/globals/${globalSlug}`
}
@@ -111,19 +110,13 @@ export const APIViewClient: React.FC = () => {
>
<SetDocumentStepNav
collectionSlug={collectionSlug}
globalLabel={globalClientConfig?.label}
globalLabel={globalConfig?.label}
globalSlug={globalSlug}
id={id}
pluralLabel={collectionClientConfig ? collectionClientConfig?.labels?.plural : undefined}
useAsTitle={collectionClientConfig ? collectionClientConfig?.admin?.useAsTitle : undefined}
pluralLabel={collectionConfig ? collectionConfig?.labels?.plural : undefined}
useAsTitle={collectionConfig ? collectionConfig?.admin?.useAsTitle : undefined}
view="API"
/>
<SetViewActions
actions={
(collectionClientConfig || globalClientConfig)?.admin?.components?.views?.edit?.api
?.actions
}
/>
<div className={`${baseClass}__configuration`}>
<div className={`${baseClass}__api-url`}>
<span className={`${baseClass}__label`}>

View File

@@ -22,7 +22,7 @@ export const Settings: React.FC<{
<div className={[baseClass, className].filter(Boolean).join(' ')}>
<h3>{i18n.t('general:payloadSettings')}</h3>
<div className={`${baseClass}__language`}>
<FieldLabel field={null} htmlFor="language-select" label={i18n.t('general:language')} />
<FieldLabel htmlFor="language-select" label={i18n.t('general:language')} />
<LanguageSelector languageOptions={languageOptions} />
</div>
{theme === 'all' && <ToggleTheme />}

View File

@@ -1,18 +1,17 @@
import type { AdminViewProps } from 'payload'
import {
DocumentInfoProvider,
EditDepthProvider,
HydrateAuthProvider,
RenderComponent,
} from '@payloadcms/ui'
import { getCreateMappedComponent } from '@payloadcms/ui/shared'
import { DocumentInfoProvider, EditDepthProvider, HydrateAuthProvider } from '@payloadcms/ui'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { buildFormState } from '@payloadcms/ui/utilities/buildFormState'
import { notFound } from 'next/navigation.js'
import React from 'react'
import { DocumentHeader } from '../../elements/DocumentHeader/index.js'
import { getDocPreferences } from '../Document/getDocPreferences.js'
import { getDocumentData } from '../Document/getDocumentData.js'
import { getDocumentPermissions } from '../Document/getDocumentPermissions.js'
import { getIsLocked } from '../Document/getIsLocked.js'
import { getVersions } from '../Document/getVersions.js'
import { EditView } from '../Edit/index.js'
import { AccountClient } from './index.client.js'
import { Settings } from './Settings/index.js'
@@ -50,57 +49,91 @@ export const Account: React.FC<AdminViewProps> = async ({
const collectionConfig = config.collections.find((collection) => collection.slug === userSlug)
if (collectionConfig && user?.id) {
// Fetch the data required for the view
const data = await getDocumentData({
id: user.id,
collectionSlug: collectionConfig.slug,
locale,
payload,
user,
})
if (!data) {
throw new Error('not-found')
}
// Get document preferences
const docPreferences = await getDocPreferences({
id: user.id,
collectionSlug: collectionConfig.slug,
payload,
user,
})
// Get permissions
const { docPermissions, hasPublishPermission, hasSavePermission } =
await getDocumentPermissions({
id: user.id,
collectionConfig,
data: user,
data,
req,
})
const { data, formState } = await getDocumentData({
// Build initial form state from data
const { state: formState } = await buildFormState({
id: user.id,
collectionSlug: collectionConfig.slug,
data,
docPermissions,
docPreferences,
locale: locale?.code,
operation: 'update',
renderAllFields: true,
req,
schemaPath: collectionConfig.slug,
})
// Fetch document lock state
const { currentEditor, isLocked, lastUpdateTime } = await getIsLocked({
id: user.id,
collectionConfig,
locale,
req,
isEditing: true,
payload: req.payload,
user,
})
const createMappedComponent = getCreateMappedComponent({
importMap: payload.importMap,
serverProps: {
i18n,
initPageResult,
locale,
params,
// Get all versions required for UI
const { hasPublishedDoc, mostRecentVersionIsAutosaved, unpublishedVersionCount, versionCount } =
await getVersions({
id: user.id,
collectionConfig,
docPermissions,
locale: locale?.code,
payload,
permissions,
routeSegments: [],
searchParams,
user,
},
})
const mappedAccountComponent = createMappedComponent(
CustomAccountComponent?.Component,
undefined,
EditView,
'CustomAccountComponent.Component',
)
})
return (
<DocumentInfoProvider
AfterFields={<Settings i18n={i18n} languageOptions={languageOptions} theme={theme} />}
apiURL={`${serverURL}${api}/${userSlug}${user?.id ? `/${user.id}` : ''}`}
collectionSlug={userSlug}
currentEditor={currentEditor}
docPermissions={docPermissions}
hasPublishedDoc={hasPublishedDoc}
hasPublishPermission={hasPublishPermission}
hasSavePermission={hasSavePermission}
id={user?.id}
initialData={data}
initialState={formState}
isEditing
isLocked={isLocked}
lastUpdateTime={lastUpdateTime}
mostRecentVersionIsAutosaved={mostRecentVersionIsAutosaved}
unpublishedVersionCount={unpublishedVersionCount}
versionCount={versionCount}
>
<EditDepthProvider depth={1}>
<EditDepthProvider>
<DocumentHeader
collectionConfig={collectionConfig}
hideTabs
@@ -109,7 +142,22 @@ export const Account: React.FC<AdminViewProps> = async ({
permissions={permissions}
/>
<HydrateAuthProvider permissions={permissions} />
<RenderComponent mappedComponent={mappedAccountComponent} />
<RenderServerComponent
Component={CustomAccountComponent}
importMap={payload.importMap}
serverProps={{
i18n,
initPageResult,
locale,
params,
payload,
permissions,
routeSegments: [],
searchParams,
user,
}}
/>
<EditView />
<AccountClient />
</EditDepthProvider>
</DocumentInfoProvider>

View File

@@ -1,27 +1,34 @@
'use client'
import type { FormProps, UserWithToken } from '@payloadcms/ui'
import type { ClientCollectionConfig, FormState, LoginWithUsernameOptions } from 'payload'
import type {
ClientCollectionConfig,
DocumentPermissions,
DocumentPreferences,
FormState,
LoginWithUsernameOptions,
} from 'payload'
import {
ConfirmPasswordField,
EmailAndUsernameFields,
Form,
FormSubmit,
PasswordField,
RenderFields,
useAuth,
useConfig,
useServerFunctions,
useTranslation,
} from '@payloadcms/ui'
import { getFormState } from '@payloadcms/ui/shared'
import React from 'react'
import { RenderEmailAndUsernameFields } from '../../elements/EmailAndUsername/index.js'
export const CreateFirstUserClient: React.FC<{
docPermissions: DocumentPermissions
docPreferences: DocumentPreferences
initialState: FormState
loginWithUsername?: false | LoginWithUsernameOptions
userSlug: string
}> = ({ initialState, loginWithUsername, userSlug }) => {
}> = ({ docPermissions, docPreferences, initialState, loginWithUsername, userSlug }) => {
const {
config: {
routes: { admin, api: apiRoute },
@@ -30,6 +37,8 @@ export const CreateFirstUserClient: React.FC<{
getEntityConfig,
} = useConfig()
const { getFormState } = useServerFunctions()
const { t } = useTranslation()
const { setUser } = useAuth()
@@ -38,18 +47,17 @@ export const CreateFirstUserClient: React.FC<{
const onChange: FormProps['onChange'][0] = React.useCallback(
async ({ formState: prevFormState }) => {
const { state } = await getFormState({
apiRoute,
body: {
collectionSlug: userSlug,
formState: prevFormState,
operation: 'create',
schemaPath: `_${userSlug}.auth`,
},
serverURL,
collectionSlug: userSlug,
docPermissions,
docPreferences,
formState: prevFormState,
operation: 'create',
schemaPath: `_${userSlug}.auth`,
})
return state
},
[apiRoute, userSlug, serverURL],
[userSlug, getFormState, docPermissions, docPreferences],
)
const handleFirstRegister = (data: UserWithToken) => {
@@ -66,14 +74,15 @@ export const CreateFirstUserClient: React.FC<{
redirect={admin}
validationOperation="create"
>
<RenderEmailAndUsernameFields
<EmailAndUsernameFields
className="emailAndUsername"
loginWithUsername={loginWithUsername}
operation="create"
readOnly={false}
t={t}
/>
<PasswordField
autoComplete={'off'}
autoComplete="off"
field={{
name: 'password',
label: t('authentication:newPassword'),
@@ -84,10 +93,11 @@ export const CreateFirstUserClient: React.FC<{
<RenderFields
fields={collectionConfig.fields}
forceRender
operation="create"
path=""
parentIndexPath=""
parentPath=""
parentSchemaPath={userSlug}
permissions={null}
readOnly={false}
schemaPath={userSlug}
/>
<FormSubmit size="large">{t('general:create')}</FormSubmit>
</Form>

View File

@@ -1,8 +1,11 @@
import type { AdminViewProps } from 'payload'
import { buildFormState } from '@payloadcms/ui/utilities/buildFormState'
import React from 'react'
import { getDocPreferences } from '../Document/getDocPreferences.js'
import { getDocumentData } from '../Document/getDocumentData.js'
import { getDocumentPermissions } from '../Document/getDocumentPermissions.js'
import { CreateFirstUserClient } from './index.client.js'
import './index.scss'
@@ -26,11 +29,39 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
const { auth: authOptions } = collectionConfig
const loginWithUsername = authOptions.loginWithUsername
const { formState } = await getDocumentData({
collectionConfig,
// Fetch the data required for the view
const data = await getDocumentData({
collectionSlug: collectionConfig.slug,
locale,
payload: req.payload,
user: req.user,
})
// Get document preferences
const docPreferences = await getDocPreferences({
collectionSlug: collectionConfig.slug,
payload: req.payload,
user: req.user,
})
// Get permissions
const { docPermissions } = await getDocumentPermissions({
collectionConfig,
data,
req,
schemaPath: `_${collectionConfig.slug}.auth`,
})
// Build initial form state from data
const { state: formState } = await buildFormState({
collectionSlug: collectionConfig.slug,
data,
docPermissions,
docPreferences,
locale: locale?.code,
operation: 'create',
renderAllFields: true,
req,
schemaPath: collectionConfig.slug,
})
return (
@@ -38,6 +69,8 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
<h1>{req.t('general:welcome')}</h1>
<p>{req.t('authentication:beginCreateFirstUser')}</p>
<CreateFirstUserClient
docPermissions={docPermissions}
docPreferences={docPreferences}
initialState={formState}
loginWithUsername={loginWithUsername}
userSlug={userSlug}

View File

@@ -2,13 +2,9 @@ import type { groupNavItems } from '@payloadcms/ui/shared'
import type { ClientUser, Permissions, ServerProps, VisibleEntities } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import { Button, Card, Gutter, Locked, SetStepNav, SetViewActions } from '@payloadcms/ui'
import {
EntityType,
formatAdminURL,
getCreateMappedComponent,
RenderComponent,
} from '@payloadcms/ui/shared'
import { Button, Card, Gutter, Locked } from '@payloadcms/ui'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { EntityType, formatAdminURL } from '@payloadcms/ui/shared'
import React, { Fragment } from 'react'
import './index.scss'
@@ -50,41 +46,25 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
user,
} = props
const createMappedComponent = getCreateMappedComponent({
importMap: payload.importMap,
serverProps: {
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
},
})
const mappedBeforeDashboards = createMappedComponent(
beforeDashboard,
undefined,
undefined,
'beforeDashboard',
)
const mappedAfterDashboards = createMappedComponent(
afterDashboard,
undefined,
undefined,
'afterDashboard',
)
return (
<div className={baseClass}>
<SetStepNav nav={[]} />
<SetViewActions actions={[]} />
<Gutter className={`${baseClass}__wrap`}>
<RenderComponent mappedComponent={mappedBeforeDashboards} />
{beforeDashboard && (
<RenderServerComponent
Component={beforeDashboard}
importMap={payload.importMap}
serverProps={{
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
}}
/>
)}
<Fragment>
<SetViewActions actions={[]} />
{!navGroups || navGroups?.length === 0 ? (
<p>no nav groups....</p>
) : (
@@ -93,7 +73,7 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
<div className={`${baseClass}__group`} key={groupIndex}>
<h2 className={`${baseClass}__label`}>{label}</h2>
<ul className={`${baseClass}__card-list`}>
{entities.map(({ type, entity }, entityIndex) => {
{entities.map(({ slug, type, label }, entityIndex) => {
let title: string
let buttonAriaLabel: string
let createHREF: string
@@ -103,38 +83,34 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
let userEditing = null
if (type === EntityType.collection) {
title = getTranslation(entity.labels.plural, i18n)
title = getTranslation(label, i18n)
buttonAriaLabel = t('general:showAllLabel', { label: title })
href = formatAdminURL({ adminRoute, path: `/collections/${entity.slug}` })
href = formatAdminURL({ adminRoute, path: `/collections/${slug}` })
createHREF = formatAdminURL({
adminRoute,
path: `/collections/${entity.slug}/create`,
path: `/collections/${slug}/create`,
})
hasCreatePermission =
permissions?.collections?.[entity.slug]?.create?.permission
hasCreatePermission = permissions?.collections?.[slug]?.create?.permission
}
if (type === EntityType.global) {
title = getTranslation(entity.label, i18n)
title = getTranslation(label, i18n)
buttonAriaLabel = t('general:editLabel', {
label: getTranslation(entity.label, i18n),
label: getTranslation(label, i18n),
})
href = formatAdminURL({
adminRoute,
path: `/globals/${entity.slug}`,
path: `/globals/${slug}`,
})
// Find the lock status for the global
const globalLockData = globalData.find(
(global) => global.slug === entity.slug,
)
const globalLockData = globalData.find((global) => global.slug === slug)
if (globalLockData) {
isLocked = globalLockData.data._isLocked
userEditing = globalLockData.data._userEditing
@@ -164,7 +140,7 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
) : hasCreatePermission && type === EntityType.collection ? (
<Button
aria-label={t('general:createNewLabel', {
label: getTranslation(entity.labels.singular, i18n),
label,
})}
buttonStyle="icon-label"
el="link"
@@ -178,9 +154,9 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
}
buttonAriaLabel={buttonAriaLabel}
href={href}
id={`card-${entity.slug}`}
id={`card-${slug}`}
Link={Link}
title={title}
title={getTranslation(label, i18n)}
titleAs="h3"
/>
</li>
@@ -192,7 +168,21 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
})
)}
</Fragment>
<RenderComponent mappedComponent={mappedAfterDashboards} />
{afterDashboard && (
<RenderServerComponent
Component={afterDashboard}
importMap={payload.importMap}
serverProps={{
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
}}
/>
)}
</Gutter>
</div>
)

View File

@@ -1,13 +1,9 @@
import type { EntityToGroup } from '@payloadcms/ui/shared'
import type { AdminViewProps } from 'payload'
import { HydrateAuthProvider } from '@payloadcms/ui'
import {
EntityType,
getCreateMappedComponent,
groupNavItems,
RenderComponent,
} from '@payloadcms/ui/shared'
import { HydrateAuthProvider, SetStepNav } from '@payloadcms/ui'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { EntityType, groupNavItems } from '@payloadcms/ui/shared'
import LinkImport from 'next/link.js'
import React, { Fragment } from 'react'
@@ -111,39 +107,31 @@ export const Dashboard: React.FC<AdminViewProps> = async ({
i18n,
)
const createMappedComponent = getCreateMappedComponent({
importMap: payload.importMap,
serverProps: {
globalData,
i18n,
Link,
locale,
navGroups,
params,
payload,
permissions,
searchParams,
user,
visibleEntities,
},
})
const mappedDashboardComponent = createMappedComponent(
CustomDashboardComponent?.Component,
undefined,
DefaultDashboard,
'CustomDashboardComponent.Component',
)
return (
<Fragment>
<HydrateAuthProvider permissions={permissions} />
<RenderComponent
<SetStepNav nav={[]} />
<RenderServerComponent
clientProps={{
Link,
locale,
}}
mappedComponent={mappedDashboardComponent}
Component={CustomDashboardComponent}
Fallback={DefaultDashboard}
importMap={payload.importMap}
serverProps={{
globalData,
i18n,
Link,
locale,
navGroups,
params,
payload,
permissions,
searchParams,
user,
visibleEntities,
}}
/>
</Fragment>
)

View File

@@ -0,0 +1,60 @@
import type { DocumentPreferences, Payload, TypedUser } from 'payload'
type Args = {
collectionSlug?: string
globalSlug?: string
id?: number | string
payload: Payload
user: TypedUser
}
export const getDocPreferences = async ({
id,
collectionSlug,
globalSlug,
payload,
user,
}: Args): Promise<DocumentPreferences> => {
let preferencesKey
if (collectionSlug && id) {
preferencesKey = `collection-${collectionSlug}-${id}`
}
if (globalSlug) {
preferencesKey = `global-${globalSlug}`
}
if (preferencesKey) {
const preferencesResult = (await payload.find({
collection: 'payload-preferences',
depth: 0,
limit: 1,
where: {
and: [
{
key: {
equals: preferencesKey,
},
},
{
'user.relationTo': {
equals: user.collection,
},
},
{
'user.value': {
equals: user.id,
},
},
],
},
})) as unknown as { docs: { value: DocumentPreferences }[] }
if (preferencesResult?.docs?.[0]?.value) {
return preferencesResult.docs[0].value
}
}
return { fields: {} }
}

View File

@@ -0,0 +1,52 @@
import type { Locale, Payload, TypedUser, TypeWithID } from 'payload'
type Args = {
collectionSlug?: string
globalSlug?: string
id?: number | string
locale?: Locale
payload: Payload
user?: TypedUser
}
export const getDocumentData = async ({
id,
collectionSlug,
globalSlug,
locale,
payload,
user,
}: Args): Promise<null | Record<string, unknown> | TypeWithID> => {
let resolvedData: Record<string, unknown> | TypeWithID = null
try {
if (collectionSlug && id) {
resolvedData = await payload.findByID({
id,
collection: collectionSlug,
depth: 0,
draft: true,
fallbackLocale: null,
locale: locale?.code,
overrideAccess: false,
user,
})
}
if (globalSlug) {
resolvedData = await payload.findGlobal({
slug: globalSlug,
depth: 0,
draft: true,
fallbackLocale: null,
locale: locale?.code,
overrideAccess: false,
user,
})
}
} catch (_err) {
payload.logger.error(_err)
}
return resolvedData
}

View File

@@ -1,62 +0,0 @@
import type {
Data,
FormState,
Locale,
PayloadRequest,
SanitizedCollectionConfig,
SanitizedGlobalConfig,
} from 'payload'
import { buildFormState } from '@payloadcms/ui/utilities/buildFormState'
import { reduceFieldsToValues } from 'payload/shared'
export const getDocumentData = async (args: {
collectionConfig?: SanitizedCollectionConfig
globalConfig?: SanitizedGlobalConfig
id?: number | string
locale: Locale
req: PayloadRequest
schemaPath?: string
}): Promise<{
data: Data
formState: FormState
}> => {
const { id, collectionConfig, globalConfig, locale, req, schemaPath: schemaPathFromProps } = args
const schemaPath = schemaPathFromProps || collectionConfig?.slug || globalConfig?.slug
try {
const { state: formState } = await buildFormState({
req: {
...req,
data: {
id,
collectionSlug: collectionConfig?.slug,
globalSlug: globalConfig?.slug,
locale: locale?.code,
operation: (collectionConfig && id) || globalConfig ? 'update' : 'create',
schemaPath,
},
},
})
const data = reduceFieldsToValues(formState, true)
return {
data,
formState,
}
} catch (error) {
req.payload.logger.error({ err: error, msg: 'Error getting document data' })
return {
data: null,
formState: {
fields: {
initialValue: undefined,
valid: false,
value: undefined,
},
},
}
}
}

View File

@@ -0,0 +1,86 @@
import type {
Payload,
SanitizedCollectionConfig,
SanitizedGlobalConfig,
TypedUser,
Where,
} from 'payload'
type Args = {
collectionConfig?: SanitizedCollectionConfig
globalConfig?: SanitizedGlobalConfig
id?: number | string
isEditing: boolean
payload: Payload
user: TypedUser
}
type Result = Promise<{
currentEditor?: TypedUser
isLocked: boolean
lastUpdateTime?: number
}>
export const getIsLocked = async ({
id,
collectionConfig,
globalConfig,
isEditing,
payload,
user,
}: Args): Result => {
const entityConfig = collectionConfig || globalConfig
const entityHasLockingEnabled =
entityConfig?.lockDocuments !== undefined ? entityConfig?.lockDocuments : true
if (!entityHasLockingEnabled || !isEditing) {
return {
isLocked: false,
}
}
const where: Where = {}
if (globalConfig) {
where.globalSlug = {
equals: globalConfig.slug,
}
} else {
where.and = [
{
'document.value': {
equals: id,
},
},
{
'document.relationTo': {
equals: collectionConfig.slug,
},
},
]
}
const { docs } = await payload.find({
collection: 'payload-locked-documents',
depth: 1,
where,
})
if (docs.length > 0) {
const newEditor = docs[0].user?.value
const lastUpdateTime = new Date(docs[0].updatedAt).getTime()
if (newEditor?.id !== user.id) {
return {
currentEditor: newEditor,
isLocked: true,
lastUpdateTime,
}
}
}
return {
isLocked: false,
}
}

View File

@@ -0,0 +1,240 @@
import type {
DocumentPermissions,
Payload,
SanitizedCollectionConfig,
SanitizedGlobalConfig,
TypedUser,
} from 'payload'
type Args = {
collectionConfig?: SanitizedCollectionConfig
docPermissions: DocumentPermissions
globalConfig?: SanitizedGlobalConfig
id?: number | string
locale?: string
payload: Payload
user: TypedUser
}
type Result = Promise<{
hasPublishedDoc: boolean
mostRecentVersionIsAutosaved: boolean
unpublishedVersionCount: number
versionCount: number
}>
// TODO: in the future, we can parallelize some of these queries
// this will speed up the API by ~30-100ms or so
export const getVersions = async ({
id,
collectionConfig,
docPermissions,
globalConfig,
locale,
payload,
user,
}: Args): Result => {
let publishedQuery
let hasPublishedDoc = false
let mostRecentVersionIsAutosaved = false
let unpublishedVersionCount = 0
let versionCount = 0
const entityConfig = collectionConfig || globalConfig
const versionsConfig = entityConfig?.versions
const shouldFetchVersions = Boolean(versionsConfig && docPermissions?.readVersions?.permission)
if (!shouldFetchVersions) {
const hasPublishedDoc = Boolean((collectionConfig && id) || globalConfig)
return {
hasPublishedDoc,
mostRecentVersionIsAutosaved,
unpublishedVersionCount,
versionCount,
}
}
if (collectionConfig) {
if (!id) {
return {
hasPublishedDoc,
mostRecentVersionIsAutosaved,
unpublishedVersionCount,
versionCount,
}
}
if (versionsConfig?.drafts) {
publishedQuery = await payload.find({
collection: collectionConfig.slug,
depth: 0,
locale: locale || undefined,
user,
where: {
and: [
{
or: [
{
_status: {
equals: 'published',
},
},
{
_status: {
exists: false,
},
},
],
},
{
id: {
equals: id,
},
},
],
},
})
if (publishedQuery.docs?.[0]) {
hasPublishedDoc = true
}
if (versionsConfig.drafts?.autosave) {
const mostRecentVersion = await payload.findVersions({
collection: collectionConfig.slug,
depth: 0,
limit: 1,
user,
where: {
and: [
{
parent: {
equals: id,
},
},
],
},
})
if (
mostRecentVersion.docs[0] &&
'autosave' in mostRecentVersion.docs[0] &&
mostRecentVersion.docs[0].autosave
) {
mostRecentVersionIsAutosaved = true
}
}
if (publishedQuery.docs?.[0]?.updatedAt) {
;({ totalDocs: unpublishedVersionCount } = await payload.countVersions({
collection: collectionConfig.slug,
user,
where: {
and: [
{
parent: {
equals: id,
},
},
{
'version._status': {
equals: 'draft',
},
},
{
updatedAt: {
greater_than: publishedQuery.docs[0].updatedAt,
},
},
],
},
}))
}
}
;({ totalDocs: versionCount } = await payload.countVersions({
collection: collectionConfig.slug,
user,
where: {
and: [
{
parent: {
equals: id,
},
},
],
},
}))
}
if (globalConfig) {
if (versionsConfig?.drafts) {
publishedQuery = await payload.findGlobal({
slug: globalConfig.slug,
depth: 0,
locale,
user,
})
if (publishedQuery?._status === 'published') {
hasPublishedDoc = true
}
if (versionsConfig.drafts?.autosave) {
const mostRecentVersion = await payload.findGlobalVersions({
slug: globalConfig.slug,
limit: 1,
select: {
autosave: true,
},
user,
})
if (
mostRecentVersion.docs[0] &&
'autosave' in mostRecentVersion.docs[0] &&
mostRecentVersion.docs[0].autosave
) {
mostRecentVersionIsAutosaved = true
}
}
if (publishedQuery?.updatedAt) {
;({ totalDocs: unpublishedVersionCount } = await payload.countGlobalVersions({
depth: 0,
global: globalConfig.slug,
user,
where: {
and: [
{
'version._status': {
equals: 'draft',
},
},
{
updatedAt: {
greater_than: publishedQuery.updatedAt,
},
},
],
},
}))
}
}
;({ totalDocs: versionCount } = await payload.countGlobalVersions({
depth: 0,
global: globalConfig.slug,
user,
}))
}
return {
hasPublishedDoc,
mostRecentVersionIsAutosaved,
unpublishedVersionCount,
versionCount,
}
}

View File

@@ -10,8 +10,6 @@ import type {
} from 'payload'
import type React from 'react'
import { notFound } from 'next/navigation.js'
import { APIView as DefaultAPIView } from '../API/index.js'
import { EditView as DefaultEditView } from '../Edit/index.js'
import { LivePreviewView as DefaultLivePreviewView } from '../LivePreview/index.js'
@@ -23,7 +21,7 @@ import { getCustomViewByRoute } from './getCustomViewByRoute.js'
export type ViewFromConfig<TProps extends object> = {
Component?: React.FC<TProps>
payloadComponent?: PayloadComponent<TProps>
ComponentConfig?: PayloadComponent<TProps>
}
export const getViewsFromConfig = ({
@@ -81,7 +79,7 @@ export const getViewsFromConfig = ({
routeSegments
if (!overrideDocPermissions && !docPermissions?.read?.permission) {
notFound()
throw new Error('not-found')
} else {
// `../:id`, or `../create`
switch (routeSegments.length) {
@@ -94,7 +92,7 @@ export const getViewsFromConfig = ({
docPermissions?.create?.permission
) {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'default'),
ComponentConfig: getCustomViewByKey(views, 'default'),
}
DefaultView = {
Component: DefaultEditView,
@@ -132,11 +130,11 @@ export const getViewsFromConfig = ({
viewKey = customViewKey
CustomView = {
payloadComponent: CustomViewComponent,
ComponentConfig: CustomViewComponent,
}
} else {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'default'),
ComponentConfig: getCustomViewByKey(views, 'default'),
}
DefaultView = {
@@ -156,7 +154,7 @@ export const getViewsFromConfig = ({
case 'api': {
if (collectionConfig?.admin?.hideAPIURL !== true) {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'api'),
ComponentConfig: getCustomViewByKey(views, 'api'),
}
DefaultView = {
Component: DefaultAPIView,
@@ -171,7 +169,7 @@ export const getViewsFromConfig = ({
Component: DefaultLivePreviewView,
}
CustomView = {
payloadComponent: getCustomViewByKey(views, 'livePreview'),
ComponentConfig: getCustomViewByKey(views, 'livePreview'),
}
}
break
@@ -180,7 +178,7 @@ export const getViewsFromConfig = ({
case 'versions': {
if (!overrideDocPermissions && docPermissions?.readVersions?.permission) {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'versions'),
ComponentConfig: getCustomViewByKey(views, 'versions'),
}
DefaultView = {
Component: DefaultVersionsView,
@@ -218,7 +216,7 @@ export const getViewsFromConfig = ({
viewKey = customViewKey
CustomView = {
payloadComponent: CustomViewComponent,
ComponentConfig: CustomViewComponent,
}
}
@@ -233,7 +231,7 @@ export const getViewsFromConfig = ({
if (segment4 === 'versions') {
if (!overrideDocPermissions && docPermissions?.readVersions?.permission) {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'version'),
ComponentConfig: getCustomViewByKey(views, 'version'),
}
DefaultView = {
Component: DefaultVersionView,
@@ -269,7 +267,7 @@ export const getViewsFromConfig = ({
viewKey = customViewKey
CustomView = {
payloadComponent: CustomViewComponent,
ComponentConfig: CustomViewComponent,
}
}
}
@@ -284,12 +282,12 @@ export const getViewsFromConfig = ({
const [globalEntity, globalSlug, segment3, ...remainingSegments] = routeSegments
if (!overrideDocPermissions && !docPermissions?.read?.permission) {
notFound()
throw new Error('not-found')
} else {
switch (routeSegments.length) {
case 2: {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'default'),
ComponentConfig: getCustomViewByKey(views, 'default'),
}
DefaultView = {
Component: DefaultEditView,
@@ -303,7 +301,7 @@ export const getViewsFromConfig = ({
case 'api': {
if (globalConfig?.admin?.hideAPIURL !== true) {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'api'),
ComponentConfig: getCustomViewByKey(views, 'api'),
}
DefaultView = {
Component: DefaultAPIView,
@@ -318,7 +316,7 @@ export const getViewsFromConfig = ({
Component: DefaultLivePreviewView,
}
CustomView = {
payloadComponent: getCustomViewByKey(views, 'livePreview'),
ComponentConfig: getCustomViewByKey(views, 'livePreview'),
}
}
break
@@ -327,7 +325,7 @@ export const getViewsFromConfig = ({
case 'versions': {
if (!overrideDocPermissions && docPermissions?.readVersions?.permission) {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'versions'),
ComponentConfig: getCustomViewByKey(views, 'versions'),
}
DefaultView = {
@@ -362,7 +360,7 @@ export const getViewsFromConfig = ({
viewKey = customViewKey
CustomView = {
payloadComponent: CustomViewComponent,
ComponentConfig: CustomViewComponent,
}
} else {
DefaultView = {
@@ -385,7 +383,7 @@ export const getViewsFromConfig = ({
if (segment3 === 'versions') {
if (!overrideDocPermissions && docPermissions?.readVersions?.permission) {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'version'),
ComponentConfig: getCustomViewByKey(views, 'version'),
}
DefaultView = {
Component: DefaultVersionView,
@@ -416,7 +414,7 @@ export const getViewsFromConfig = ({
viewKey = customViewKey
CustomView = {
payloadComponent: CustomViewComponent,
ComponentConfig: CustomViewComponent,
}
}
}

View File

@@ -0,0 +1,195 @@
import type { I18nClient } from '@payloadcms/translations'
import type {
ClientConfig,
Data,
DocumentPreferences,
FormState,
PayloadRequest,
SanitizedConfig,
VisibleEntities,
} from 'payload'
import { headers as getHeaders } from 'next/headers.js'
import { createClientConfig, getAccessResults, isEntityHidden, parseCookies } from 'payload'
import { renderDocument } from './index.js'
let cachedClientConfig = global._payload_clientConfig
if (!cachedClientConfig) {
cachedClientConfig = global._payload_clientConfig = null
}
export const getClientConfig = (args: {
config: SanitizedConfig
i18n: I18nClient
}): ClientConfig => {
const { config, i18n } = args
if (cachedClientConfig && process.env.NODE_ENV !== 'development') {
return cachedClientConfig
}
cachedClientConfig = createClientConfig({
config,
i18n,
})
return cachedClientConfig
}
type RenderDocumentResult = {
data: any
Document: React.ReactNode
preferences: DocumentPreferences
}
export const renderDocumentHandler = async (args: {
collectionSlug: string
disableActions?: boolean
docID: string
drawerSlug?: string
initialData?: Data
initialState?: FormState
redirectAfterDelete: boolean
redirectAfterDuplicate: boolean
req: PayloadRequest
}): Promise<RenderDocumentResult> => {
const {
collectionSlug,
disableActions,
docID,
drawerSlug,
initialData,
redirectAfterDelete,
redirectAfterDuplicate,
req,
req: {
i18n,
payload,
payload: { config },
user,
},
} = args
const headers = await getHeaders()
const cookies = parseCookies(headers)
const incomingUserSlug = user?.collection
const adminUserSlug = config.admin.user
// If we have a user slug, test it against the functions
if (incomingUserSlug) {
const adminAccessFunction = payload.collections[incomingUserSlug].config.access?.admin
// Run the admin access function from the config if it exists
if (adminAccessFunction) {
const canAccessAdmin = await adminAccessFunction({ req })
if (!canAccessAdmin) {
throw new Error('Unauthorized')
}
// Match the user collection to the global admin config
} else if (adminUserSlug !== incomingUserSlug) {
throw new Error('Unauthorized')
}
} else {
const hasUsers = await payload.find({
collection: adminUserSlug,
depth: 0,
limit: 1,
pagination: false,
})
// If there are users, we should not allow access because of /create-first-user
if (hasUsers.docs.length) {
throw new Error('Unauthorized')
}
}
const clientConfig = getClientConfig({
config,
i18n,
})
let preferences: DocumentPreferences
if (docID) {
const preferencesKey = `${collectionSlug}-edit-${docID}`
preferences = await payload
.find({
collection: 'payload-preferences',
depth: 0,
limit: 1,
where: {
and: [
{
key: {
equals: preferencesKey,
},
},
{
'user.relationTo': {
equals: user.collection,
},
},
{
'user.value': {
equals: user.id,
},
},
],
},
})
.then((res) => res.docs[0]?.value as DocumentPreferences)
}
const visibleEntities: VisibleEntities = {
collections: payload.config.collections
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))
.filter(Boolean),
globals: payload.config.globals
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))
.filter(Boolean),
}
const permissions = await getAccessResults({
req,
})
const { data, Document } = await renderDocument({
clientConfig,
disableActions,
drawerSlug,
importMap: payload.importMap,
initialData,
initPageResult: {
collectionConfig: payload.config.collections.find(
(collection) => collection.slug === collectionSlug,
),
cookies,
docID,
globalConfig: payload.config.globals.find((global) => global.slug === collectionSlug),
languageOptions: undefined, // TODO
permissions,
req,
translations: undefined, // TODO
visibleEntities,
},
params: {
segments: ['collections', collectionSlug, docID],
},
redirectAfterDelete,
redirectAfterDuplicate,
searchParams: {},
})
return {
data,
Document,
preferences,
}
}

View File

@@ -1,37 +1,52 @@
import type {
AdminViewProps,
EditViewComponent,
MappedComponent,
Data,
PayloadComponent,
ServerProps,
ServerSideEditViewProps,
} from 'payload'
import { DocumentInfoProvider, EditDepthProvider, HydrateAuthProvider } from '@payloadcms/ui'
import {
formatAdminURL,
getCreateMappedComponent,
isEditing as getIsEditing,
RenderComponent,
} from '@payloadcms/ui/shared'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { formatAdminURL, isEditing as getIsEditing } from '@payloadcms/ui/shared'
import { buildFormState } from '@payloadcms/ui/utilities/buildFormState'
import { isRedirectError } from 'next/dist/client/components/redirect.js'
import { notFound, redirect } from 'next/navigation.js'
import React from 'react'
import type { GenerateEditViewMetadata } from './getMetaBySegment.js'
import type { ViewFromConfig } from './getViewsFromConfig.js'
import { DocumentHeader } from '../../elements/DocumentHeader/index.js'
import { NotFoundView } from '../NotFound/index.js'
import { getDocPreferences } from './getDocPreferences.js'
import { getDocumentData } from './getDocumentData.js'
import { getDocumentPermissions } from './getDocumentPermissions.js'
import { getIsLocked } from './getIsLocked.js'
import { getMetaBySegment } from './getMetaBySegment.js'
import { getVersions } from './getVersions.js'
import { getViewsFromConfig } from './getViewsFromConfig.js'
import { renderDocumentSlots } from './renderDocumentSlots.js'
export const generateMetadata: GenerateEditViewMetadata = async (args) => getMetaBySegment(args)
export const Document: React.FC<AdminViewProps> = async ({
// This function will be responsible for rendering an Edit Document view
// it will be called on the server for Edit page views as well as
// called on-demand from document drawers
export const renderDocument = async ({
disableActions,
drawerSlug,
importMap,
initialData,
initPageResult,
params,
redirectAfterDelete,
redirectAfterDuplicate,
searchParams,
}) => {
}: AdminViewProps): Promise<{
data: Data
Document: React.ReactNode
}> => {
const {
collectionConfig,
docID: id,
@@ -57,54 +72,108 @@ export const Document: React.FC<AdminViewProps> = async ({
const segments = Array.isArray(params?.segments) ? params.segments : []
const collectionSlug = collectionConfig?.slug || undefined
const globalSlug = globalConfig?.slug || undefined
const isEditing = getIsEditing({ id, collectionSlug, globalSlug })
let RootViewOverride: MappedComponent<ServerSideEditViewProps>
let CustomView: MappedComponent<ServerSideEditViewProps>
let DefaultView: MappedComponent<ServerSideEditViewProps>
let ErrorView: MappedComponent<AdminViewProps>
let RootViewOverride: PayloadComponent
let CustomView: ViewFromConfig<ServerSideEditViewProps>
let DefaultView: ViewFromConfig<ServerSideEditViewProps>
let ErrorView: ViewFromConfig<AdminViewProps>
let apiURL: string
const { data, formState } = await getDocumentData({
id,
collectionConfig,
globalConfig,
locale,
req,
})
// Fetch the doc required for the view
const doc =
initialData ||
(await getDocumentData({
id,
collectionSlug,
globalSlug,
locale,
payload,
user,
}))
if (!data) {
notFound()
if (isEditing && !doc) {
throw new Error('not-found')
}
const { docPermissions, hasPublishPermission, hasSavePermission } = await getDocumentPermissions({
id,
collectionConfig,
data,
globalConfig,
req,
})
const createMappedComponent = getCreateMappedComponent({
importMap,
serverProps: {
i18n,
initPageResult,
locale,
params,
const [
docPreferences,
{ docPermissions, hasPublishPermission, hasSavePermission },
{ currentEditor, isLocked, lastUpdateTime },
] = await Promise.all([
// Get document preferences
getDocPreferences({
id,
collectionSlug,
globalSlug,
payload,
permissions,
routeSegments: segments,
searchParams,
user,
},
})
}),
// Get permissions
getDocumentPermissions({
id,
collectionConfig,
data: doc,
globalConfig,
req,
}),
// Fetch document lock state
getIsLocked({
id,
collectionConfig,
globalConfig,
isEditing,
payload: req.payload,
user,
}),
])
const [
{ hasPublishedDoc, mostRecentVersionIsAutosaved, unpublishedVersionCount, versionCount },
{ state: formState },
] = await Promise.all([
getVersions({
id,
collectionConfig,
docPermissions,
globalConfig,
locale: locale?.code,
payload,
user,
}),
buildFormState({
id,
collectionSlug,
data: doc,
docPermissions,
docPreferences,
globalSlug,
locale: locale?.code,
operation: (collectionSlug && id) || globalSlug ? 'update' : 'create',
renderAllFields: true,
req,
schemaPath: collectionSlug || globalSlug,
}),
])
const serverProps: ServerProps = {
i18n,
initPageResult,
locale,
params,
payload,
permissions,
routeSegments: segments,
searchParams,
user,
}
if (collectionConfig) {
if (!visibleEntities?.collections?.find((visibleSlug) => visibleSlug === collectionSlug)) {
notFound()
throw new Error('not-found')
}
const params = new URLSearchParams()
@@ -122,12 +191,7 @@ export const Document: React.FC<AdminViewProps> = async ({
RootViewOverride =
collectionConfig?.admin?.components?.views?.edit?.root &&
'Component' in collectionConfig.admin.components.views.edit.root
? createMappedComponent(
collectionConfig?.admin?.components?.views?.edit?.root?.Component as EditViewComponent, // some type info gets lost from Config => SanitizedConfig due to our usage of Deep type operations from ts-essentials. Despite .Component being defined as EditViewComponent, this info is lost and we need cast it here.
undefined,
undefined,
'collectionConfig?.admin?.components?.views?.edit?.root',
)
? collectionConfig?.admin?.components?.views?.edit?.root?.Component
: null
if (!RootViewOverride) {
@@ -138,36 +202,21 @@ export const Document: React.FC<AdminViewProps> = async ({
routeSegments: segments,
})
CustomView = createMappedComponent(
collectionViews?.CustomView?.payloadComponent,
undefined,
collectionViews?.CustomView?.Component,
'collectionViews?.CustomView.payloadComponent',
)
DefaultView = createMappedComponent(
collectionViews?.DefaultView?.payloadComponent,
undefined,
collectionViews?.DefaultView?.Component,
'collectionViews?.DefaultView.payloadComponent',
)
ErrorView = createMappedComponent(
collectionViews?.ErrorView?.payloadComponent,
undefined,
collectionViews?.ErrorView?.Component,
'collectionViews?.ErrorView.payloadComponent',
)
CustomView = collectionViews?.CustomView
DefaultView = collectionViews?.DefaultView
ErrorView = collectionViews?.ErrorView
}
if (!CustomView && !DefaultView && !RootViewOverride && !ErrorView) {
ErrorView = createMappedComponent(undefined, undefined, NotFoundView, 'NotFoundView')
ErrorView = {
Component: NotFoundView,
}
}
}
if (globalConfig) {
if (!visibleEntities?.globals?.find((visibleSlug) => visibleSlug === globalSlug)) {
notFound()
throw new Error('not-found')
}
const params = new URLSearchParams({
@@ -189,12 +238,7 @@ export const Document: React.FC<AdminViewProps> = async ({
RootViewOverride =
globalConfig?.admin?.components?.views?.edit?.root &&
'Component' in globalConfig.admin.components.views.edit.root
? createMappedComponent(
globalConfig?.admin?.components?.views?.edit?.root?.Component as EditViewComponent, // some type info gets lost from Config => SanitizedConfig due to our usage of Deep type operations from ts-essentials. Despite .Component being defined as EditViewComponent, this info is lost and we need cast it here.
undefined,
undefined,
'globalConfig?.admin?.components?.views?.edit?.root',
)
? globalConfig?.admin?.components?.views?.edit?.root?.Component
: null
if (!RootViewOverride) {
@@ -205,29 +249,14 @@ export const Document: React.FC<AdminViewProps> = async ({
routeSegments: segments,
})
CustomView = createMappedComponent(
globalViews?.CustomView?.payloadComponent,
undefined,
globalViews?.CustomView?.Component,
'globalViews?.CustomView.payloadComponent',
)
DefaultView = createMappedComponent(
globalViews?.DefaultView?.payloadComponent,
undefined,
globalViews?.DefaultView?.Component,
'globalViews?.DefaultView.payloadComponent',
)
ErrorView = createMappedComponent(
globalViews?.ErrorView?.payloadComponent,
undefined,
globalViews?.ErrorView?.Component,
'globalViews?.ErrorView.payloadComponent',
)
CustomView = globalViews?.CustomView
DefaultView = globalViews?.DefaultView
ErrorView = globalViews?.ErrorView
if (!CustomView && !DefaultView && !RootViewOverride && !ErrorView) {
ErrorView = createMappedComponent(undefined, undefined, NotFoundView, 'NotFoundView')
ErrorView = {
Component: NotFoundView,
}
}
}
}
@@ -240,13 +269,14 @@ export const Document: React.FC<AdminViewProps> = async ({
hasSavePermission &&
((collectionConfig?.versions?.drafts && collectionConfig?.versions?.drafts?.autosave) ||
(globalConfig?.versions?.drafts && globalConfig?.versions?.drafts?.autosave))
const validateDraftData =
collectionConfig?.versions?.drafts && collectionConfig?.versions?.drafts?.validate
if (shouldAutosave && !validateDraftData && !id && collectionSlug) {
const doc = await payload.create({
collection: collectionSlug,
data: {},
data: initialData || {},
depth: 0,
draft: true,
fallbackLocale: null,
@@ -263,57 +293,96 @@ export const Document: React.FC<AdminViewProps> = async ({
})
redirect(redirectURL)
} else {
notFound()
throw new Error('not-found')
}
}
return (
<DocumentInfoProvider
apiURL={apiURL}
collectionSlug={collectionConfig?.slug}
disableActions={false}
docPermissions={docPermissions}
globalSlug={globalConfig?.slug}
hasPublishPermission={hasPublishPermission}
hasSavePermission={hasSavePermission}
id={id}
initialData={data}
initialState={formState}
isEditing={isEditing}
key={locale?.code}
>
{!RootViewOverride && (
<DocumentHeader
collectionConfig={collectionConfig}
globalConfig={globalConfig}
i18n={i18n}
payload={payload}
permissions={permissions}
/>
)}
<HydrateAuthProvider permissions={permissions} />
{/**
* After bumping the Next.js canary to 104, and React to 19.0.0-rc-06d0b89e-20240801" we have to deepCopy the permissions object (https://github.com/payloadcms/payload/pull/7541).
* If both HydrateClientUser and RenderCustomComponent receive the same permissions object (same object reference), we get a
* "TypeError: Cannot read properties of undefined (reading '$$typeof')" error when loading up some version views - for example a versions
* view in the draft-posts collection of the versions test suite. RenderCustomComponent is what renders the versions view.
*
* // TODO: Revisit this in the future and figure out why this is happening. Might be a React/Next.js bug. We don't know why it happens, and a future React/Next version might unbreak this (keep an eye on this and remove deepCopyObjectSimple if that's the case)
*/}
<EditDepthProvider
depth={1}
key={`${collectionSlug || globalSlug}${locale?.code ? `-${locale?.code}` : ''}`}
const documentSlots = renderDocumentSlots({
collectionConfig,
globalConfig,
hasSavePermission,
permissions: docPermissions,
req,
})
const clientProps = { formState, ...documentSlots }
return {
data: doc,
Document: (
<DocumentInfoProvider
apiURL={apiURL}
collectionSlug={collectionConfig?.slug}
currentEditor={currentEditor}
disableActions={disableActions ?? false}
docPermissions={docPermissions}
globalSlug={globalConfig?.slug}
hasPublishedDoc={hasPublishedDoc}
hasPublishPermission={hasPublishPermission}
hasSavePermission={hasSavePermission}
id={id}
initialData={doc}
initialState={formState}
isEditing={isEditing}
isLocked={isLocked}
key={locale?.code}
lastUpdateTime={lastUpdateTime}
mostRecentVersionIsAutosaved={mostRecentVersionIsAutosaved}
redirectAfterDelete={redirectAfterDelete}
redirectAfterDuplicate={redirectAfterDuplicate}
unpublishedVersionCount={unpublishedVersionCount}
versionCount={versionCount}
>
{ErrorView ? (
<RenderComponent mappedComponent={ErrorView} />
) : (
<RenderComponent
mappedComponent={
RootViewOverride ? RootViewOverride : CustomView ? CustomView : DefaultView
}
{!RootViewOverride && !drawerSlug && (
<DocumentHeader
collectionConfig={collectionConfig}
globalConfig={globalConfig}
i18n={i18n}
payload={payload}
permissions={permissions}
/>
)}
</EditDepthProvider>
</DocumentInfoProvider>
)
<HydrateAuthProvider permissions={permissions} />
<EditDepthProvider>
{ErrorView ? (
<RenderServerComponent
clientProps={clientProps}
Component={ErrorView.ComponentConfig || ErrorView.Component}
importMap={importMap}
serverProps={serverProps}
/>
) : (
<RenderServerComponent
clientProps={clientProps}
Component={
RootViewOverride
? RootViewOverride
: CustomView?.ComponentConfig || CustomView?.Component
? CustomView?.ComponentConfig || CustomView?.Component
: DefaultView?.ComponentConfig || DefaultView?.Component
}
importMap={importMap}
serverProps={serverProps}
/>
)}
</EditDepthProvider>
</DocumentInfoProvider>
),
}
}
export const Document: React.FC<AdminViewProps> = async (args) => {
try {
const { Document: RenderedDocument } = await renderDocument(args)
return RenderedDocument
} catch (error) {
if (isRedirectError(error)) {
throw error
}
args.initPageResult.req.payload.logger.error(error)
if (error.message === 'not-found') {
notFound()
}
}
}

View File

@@ -0,0 +1,136 @@
import type {
DefaultServerFunctionArgs,
DocumentPermissions,
DocumentSlots,
PayloadRequest,
SanitizedCollectionConfig,
SanitizedGlobalConfig,
StaticDescription,
} from 'payload'
import { ViewDescription } from '@payloadcms/ui'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import React from 'react'
import { getDocumentPermissions } from './getDocumentPermissions.js'
export const renderDocumentSlots: (args: {
collectionConfig?: SanitizedCollectionConfig
globalConfig?: SanitizedGlobalConfig
hasSavePermission: boolean
permissions: DocumentPermissions
req: PayloadRequest
}) => DocumentSlots = (args) => {
const { collectionConfig, globalConfig, hasSavePermission, req } = args
const components: DocumentSlots = {} as DocumentSlots
const unsavedDraftWithValidations = undefined
const isPreviewEnabled = collectionConfig?.admin?.preview || globalConfig?.admin?.preview
const CustomPreviewButton =
collectionConfig?.admin?.components?.edit?.PreviewButton ||
globalConfig?.admin?.components?.elements?.PreviewButton
if (isPreviewEnabled && CustomPreviewButton) {
components.PreviewButton = (
<RenderServerComponent Component={CustomPreviewButton} importMap={req.payload.importMap} />
)
}
const descriptionFromConfig =
collectionConfig?.admin?.description || globalConfig?.admin?.description
const staticDescription: StaticDescription =
typeof descriptionFromConfig === 'function'
? descriptionFromConfig({ t: req.i18n.t })
: descriptionFromConfig
const CustomDescription =
collectionConfig?.admin?.components?.Description ||
globalConfig?.admin?.components?.elements?.Description
const hasDescription = CustomDescription || staticDescription
if (hasDescription) {
components.Description = (
<RenderServerComponent
clientProps={{ description: staticDescription }}
Component={CustomDescription}
Fallback={ViewDescription}
importMap={req.payload.importMap}
/>
)
}
if (hasSavePermission) {
if (collectionConfig?.versions?.drafts || globalConfig?.versions?.drafts) {
const CustomPublishButton =
collectionConfig?.admin?.components?.edit?.PublishButton ||
globalConfig?.admin?.components?.elements?.PublishButton
if (CustomPublishButton) {
components.PublishButton = (
<RenderServerComponent
Component={CustomPublishButton}
importMap={req.payload.importMap}
/>
)
}
const CustomSaveDraftButton =
collectionConfig?.admin?.components?.edit?.SaveDraftButton ||
globalConfig?.admin?.components?.elements?.SaveDraftButton
const draftsEnabled =
(collectionConfig?.versions?.drafts && !collectionConfig?.versions?.drafts?.autosave) ||
(globalConfig?.versions?.drafts && !globalConfig?.versions?.drafts?.autosave)
if ((draftsEnabled || unsavedDraftWithValidations) && CustomSaveDraftButton) {
components.SaveDraftButton = (
<RenderServerComponent
Component={CustomSaveDraftButton}
importMap={req.payload.importMap}
/>
)
}
} else {
const CustomSaveButton =
collectionConfig?.admin?.components?.edit?.SaveButton ||
globalConfig?.admin?.components?.elements?.SaveButton
if (CustomSaveButton) {
components.SaveButton = (
<RenderServerComponent Component={CustomSaveButton} importMap={req.payload.importMap} />
)
}
}
}
return components
}
export const renderDocumentSlotsHandler = async (
args: { collectionSlug: string } & DefaultServerFunctionArgs,
) => {
const { collectionSlug, req } = args
const collectionConfig = req.payload.collections[collectionSlug]?.config
if (!collectionConfig) {
throw new Error(req.t('error:incorrectCollection'))
}
const { docPermissions, hasSavePermission } = await getDocumentPermissions({
collectionConfig,
data: {},
req,
})
return renderDocumentSlots({
collectionConfig,
hasSavePermission,
permissions: docPermissions,
req,
})
}

View File

@@ -1,33 +0,0 @@
'use client'
import type { ClientCollectionConfig, ClientGlobalConfig } from 'payload'
import { RenderComponent, SetViewActions, useConfig, useDocumentInfo } from '@payloadcms/ui'
import React, { Fragment } from 'react'
export const EditViewClient: React.FC = () => {
const { collectionSlug, globalSlug } = useDocumentInfo()
const { getEntityConfig } = useConfig()
const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig
const globalConfig = getEntityConfig({ globalSlug }) as ClientGlobalConfig
const Edit = (collectionConfig || globalConfig)?.admin?.components?.views?.edit?.default
?.Component
if (!Edit) {
return null
}
return (
<Fragment>
<SetViewActions
actions={
(collectionConfig || globalConfig)?.admin?.components?.views?.edit?.default?.actions
}
/>
<RenderComponent mappedComponent={Edit} />
</Fragment>
)
}

View File

@@ -1,9 +1,10 @@
import type { EditViewComponent, PayloadServerReactComponent } from 'payload'
'use client'
import type { ClientSideEditViewProps } from 'payload'
import { DefaultEditView } from '@payloadcms/ui'
import React from 'react'
import { EditViewClient } from './index.client.js'
export const EditView: PayloadServerReactComponent<EditViewComponent> = () => {
return <EditViewClient />
export const EditView: React.FC<ClientSideEditViewProps> = (props) => {
return <DefaultEditView {...props} />
}

View File

@@ -105,9 +105,11 @@ export const ForgotPasswordForm: React.FC = () => {
/>
) : (
<EmailField
autoComplete="email"
field={{
name: 'email',
admin: {
autoComplete: 'email',
},
label: t('general:email'),
required: true,
}}

View File

@@ -1,259 +0,0 @@
'use client'
import type { ClientCollectionConfig } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import {
Button,
DeleteMany,
EditMany,
Gutter,
ListControls,
ListHeader,
ListSelection,
Pagination,
PerPage,
PublishMany,
RelationshipProvider,
RenderComponent,
SelectionProvider,
SetViewActions,
StaggeredShimmers,
Table,
UnpublishMany,
useAuth,
useBulkUpload,
useConfig,
useEditDepth,
useListInfo,
useListQuery,
useModal,
useStepNav,
useTranslation,
useWindowInfo,
ViewDescription,
} from '@payloadcms/ui'
import LinkImport from 'next/link.js'
import { useRouter } from 'next/navigation.js'
import { formatFilesize, isNumber } from 'payload/shared'
import React, { Fragment, useEffect } from 'react'
import './index.scss'
const baseClass = 'collection-list'
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
export const DefaultListView: React.FC = () => {
const { user } = useAuth()
const {
beforeActions,
collectionSlug,
disableBulkDelete,
disableBulkEdit,
hasCreatePermission,
Header,
newDocumentURL,
} = useListInfo()
const router = useRouter()
const { data, defaultLimit, handlePageChange, handlePerPageChange, params } = useListQuery()
const { openModal } = useModal()
const { setCollectionSlug, setOnSuccess } = useBulkUpload()
const { drawerSlug } = useBulkUpload()
const { getEntityConfig } = useConfig()
const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig
const {
admin: {
components: {
afterList,
afterListTable,
beforeList,
beforeListTable,
Description,
views: {
list: { actions },
},
},
description,
},
fields,
labels,
} = collectionConfig
const { i18n, t } = useTranslation()
const drawerDepth = useEditDepth()
const { setStepNav } = useStepNav()
const {
breakpoints: { s: smallBreak },
} = useWindowInfo()
let docs = data.docs || []
const isUploadCollection = Boolean(collectionConfig.upload)
if (isUploadCollection) {
docs = docs?.map((doc) => {
return {
...doc,
filesize: formatFilesize(doc.filesize),
}
})
}
const openBulkUpload = React.useCallback(() => {
setCollectionSlug(collectionSlug)
openModal(drawerSlug)
setOnSuccess(() => router.refresh())
}, [router, collectionSlug, drawerSlug, openModal, setCollectionSlug, setOnSuccess])
useEffect(() => {
if (drawerDepth <= 1) {
setStepNav([
{
label: labels?.plural,
},
])
}
}, [setStepNav, labels, drawerDepth])
const isBulkUploadEnabled = isUploadCollection && collectionConfig.upload.bulkUpload
return (
<div className={`${baseClass} ${baseClass}--${collectionSlug}`}>
<SetViewActions actions={actions} />
<SelectionProvider docs={data.docs} totalDocs={data.totalDocs} user={user}>
<RenderComponent mappedComponent={beforeList} />
<Gutter className={`${baseClass}__wrap`}>
{Header || (
<ListHeader heading={getTranslation(labels?.plural, i18n)}>
{hasCreatePermission && (
<>
<Button
aria-label={i18n.t('general:createNewLabel', {
label: getTranslation(labels?.singular, i18n),
})}
buttonStyle="pill"
el={'link'}
Link={Link}
size="small"
to={newDocumentURL}
>
{i18n.t('general:createNew')}
</Button>
{isBulkUploadEnabled && (
<Button
aria-label={t('upload:bulkUpload')}
buttonStyle="pill"
onClick={openBulkUpload}
size="small"
>
{t('upload:bulkUpload')}
</Button>
)}
</>
)}
{!smallBreak && (
<ListSelection label={getTranslation(collectionConfig.labels.plural, i18n)} />
)}
{(description || Description) && (
<div className={`${baseClass}__sub-header`}>
<ViewDescription Description={Description} description={description} />
</div>
)}
</ListHeader>
)}
<ListControls collectionConfig={collectionConfig} fields={fields} />
<RenderComponent mappedComponent={beforeListTable} />
{!data.docs && (
<StaggeredShimmers
className={[`${baseClass}__shimmer`, `${baseClass}__shimmer--rows`].join(' ')}
count={6}
/>
)}
{data.docs && data.docs.length > 0 && (
<RelationshipProvider>
<Table
customCellContext={{
collectionSlug,
uploadConfig: collectionConfig.upload,
}}
data={docs}
fields={fields}
/>
</RelationshipProvider>
)}
{data.docs && data.docs.length === 0 && (
<div className={`${baseClass}__no-results`}>
<p>{i18n.t('general:noResults', { label: getTranslation(labels?.plural, i18n) })}</p>
{hasCreatePermission && newDocumentURL && (
<Button el="link" Link={Link} to={newDocumentURL}>
{i18n.t('general:createNewLabel', {
label: getTranslation(labels?.singular, i18n),
})}
</Button>
)}
</div>
)}
<RenderComponent mappedComponent={afterListTable} />
{data.docs && data.docs.length > 0 && (
<div className={`${baseClass}__page-controls`}>
<Pagination
hasNextPage={data.hasNextPage}
hasPrevPage={data.hasPrevPage}
limit={data.limit}
nextPage={data.nextPage}
numberOfNeighbors={1}
onChange={(page) => void handlePageChange(page)}
page={data.page}
prevPage={data.prevPage}
totalPages={data.totalPages}
/>
{data?.totalDocs > 0 && (
<Fragment>
<div className={`${baseClass}__page-info`}>
{data.page * data.limit - (data.limit - 1)}-
{data.totalPages > 1 && data.totalPages !== data.page
? data.limit * data.page
: data.totalDocs}{' '}
{i18n.t('general:of')} {data.totalDocs}
</div>
<PerPage
handleChange={(limit) => void handlePerPageChange(limit)}
limit={isNumber(params?.limit) ? Number(params.limit) : defaultLimit}
limits={collectionConfig?.admin?.pagination?.limits}
resetPage={data.totalDocs <= data.pagingCounter}
/>
{smallBreak && (
<div className={`${baseClass}__list-selection`}>
<ListSelection label={getTranslation(collectionConfig.labels.plural, i18n)} />
<div className={`${baseClass}__list-selection-actions`}>
{beforeActions && beforeActions}
{!disableBulkEdit && (
<Fragment>
<EditMany collection={collectionConfig} fields={fields} />
<PublishMany collection={collectionConfig} />
<UnpublishMany collection={collectionConfig} />
</Fragment>
)}
{!disableBulkDelete && <DeleteMany collection={collectionConfig} />}
</div>
</div>
)}
</Fragment>
)}
</div>
)}
</Gutter>
<RenderComponent mappedComponent={afterList} />
</SelectionProvider>
</div>
)
}

View File

@@ -0,0 +1,194 @@
import type { I18nClient } from '@payloadcms/translations'
import type { ListPreferences } from '@payloadcms/ui'
import type {
ClientConfig,
ListQuery,
PayloadRequest,
SanitizedConfig,
VisibleEntities,
} from 'payload'
import { headers as getHeaders } from 'next/headers.js'
import { createClientConfig, getAccessResults, isEntityHidden, parseCookies } from 'payload'
import { renderListView } from './index.js'
let cachedClientConfig = global._payload_clientConfig
if (!cachedClientConfig) {
cachedClientConfig = global._payload_clientConfig = null
}
export const getClientConfig = (args: {
config: SanitizedConfig
i18n: I18nClient
}): ClientConfig => {
const { config, i18n } = args
if (cachedClientConfig && process.env.NODE_ENV !== 'development') {
return cachedClientConfig
}
cachedClientConfig = createClientConfig({
config,
i18n,
})
return cachedClientConfig
}
type RenderListResult = {
List: React.ReactNode
preferences: ListPreferences
}
export const renderListHandler = async (args: {
collectionSlug: string
disableActions?: boolean
disableBulkDelete?: boolean
disableBulkEdit?: boolean
documentDrawerSlug: string
drawerSlug?: string
enableRowSelections: boolean
query: ListQuery
redirectAfterDelete: boolean
redirectAfterDuplicate: boolean
req: PayloadRequest
}): Promise<RenderListResult> => {
const {
collectionSlug,
disableActions,
disableBulkDelete,
disableBulkEdit,
drawerSlug,
enableRowSelections,
query,
redirectAfterDelete,
redirectAfterDuplicate,
req,
req: {
i18n,
payload,
payload: { config },
user,
},
} = args
const headers = await getHeaders()
const cookies = parseCookies(headers)
const incomingUserSlug = user?.collection
const adminUserSlug = config.admin.user
// If we have a user slug, test it against the functions
if (incomingUserSlug) {
const adminAccessFunction = payload.collections[incomingUserSlug].config.access?.admin
// Run the admin access function from the config if it exists
if (adminAccessFunction) {
const canAccessAdmin = await adminAccessFunction({ req })
if (!canAccessAdmin) {
throw new Error('Unauthorized')
}
// Match the user collection to the global admin config
} else if (adminUserSlug !== incomingUserSlug) {
throw new Error('Unauthorized')
}
} else {
const hasUsers = await payload.find({
collection: adminUserSlug,
depth: 0,
limit: 1,
pagination: false,
})
// If there are users, we should not allow access because of /create-first-user
if (hasUsers.docs.length) {
throw new Error('Unauthorized')
}
}
const clientConfig = getClientConfig({
config,
i18n,
})
const preferencesKey = `${collectionSlug}-list`
const preferences = await payload
.find({
collection: 'payload-preferences',
depth: 0,
limit: 1,
where: {
and: [
{
key: {
equals: preferencesKey,
},
},
{
'user.relationTo': {
equals: user.collection,
},
},
{
'user.value': {
equals: user.id,
},
},
],
},
})
.then((res) => res.docs[0]?.value as ListPreferences)
const visibleEntities: VisibleEntities = {
collections: payload.config.collections
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))
.filter(Boolean),
globals: payload.config.globals
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))
.filter(Boolean),
}
const permissions = await getAccessResults({
req,
})
const { List } = await renderListView({
clientConfig,
disableActions,
disableBulkDelete,
disableBulkEdit,
drawerSlug,
enableRowSelections,
importMap: payload.importMap,
initPageResult: {
collectionConfig: payload.config.collections.find(
(collection) => collection.slug === collectionSlug,
),
cookies,
globalConfig: payload.config.globals.find((global) => global.slug === collectionSlug),
languageOptions: undefined, // TODO
permissions,
req,
translations: undefined, // TODO
visibleEntities,
},
params: {
segments: ['collections', collectionSlug],
},
query,
redirectAfterDelete,
redirectAfterDuplicate,
searchParams: {},
})
return {
List,
preferences,
}
}

View File

@@ -1,32 +1,52 @@
import type { AdminViewProps, ClientCollectionConfig, Where } from 'payload'
import type { ListPreferences, ListViewClientProps } from '@payloadcms/ui'
import type { AdminViewProps, ListQuery, Where } from 'payload'
import {
HydrateAuthProvider,
ListInfoProvider,
ListQueryProvider,
TableColumnsProvider,
} from '@payloadcms/ui'
import { formatAdminURL, getCreateMappedComponent, RenderComponent } from '@payloadcms/ui/shared'
import { createClientCollectionConfig } from '@payloadcms/ui/utilities/createClientConfig'
import { DefaultListView, HydrateAuthProvider, ListQueryProvider } from '@payloadcms/ui'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { renderFilters, renderTable } from '@payloadcms/ui/rsc'
import { formatAdminURL, mergeListSearchAndWhere } from '@payloadcms/ui/shared'
import { notFound } from 'next/navigation.js'
import { deepCopyObjectSimple, mergeListSearchAndWhere } from 'payload'
import { isNumber } from 'payload/shared'
import React, { Fragment } from 'react'
import type { ListPreferences } from './Default/types.js'
import { DefaultEditView } from '../Edit/Default/index.js'
import { DefaultListView } from './Default/index.js'
import { renderListViewSlots } from './renderListViewSlots.js'
export { generateListMetadata } from './meta.js'
export const ListView: React.FC<AdminViewProps> = async ({
initPageResult,
params,
searchParams,
}) => {
type ListViewArgs = {
customCellProps?: Record<string, any>
disableBulkDelete?: boolean
disableBulkEdit?: boolean
enableRowSelections: boolean
query: ListQuery
} & AdminViewProps
export const renderListView = async (
args: ListViewArgs,
): Promise<{
List: React.ReactNode
}> => {
const {
clientConfig,
customCellProps,
disableBulkDelete,
disableBulkEdit,
drawerSlug,
enableRowSelections,
initPageResult,
params,
query: queryFromArgs,
searchParams,
} = args
const {
collectionConfig,
collectionConfig: {
slug: collectionSlug,
admin: { useAsTitle },
defaultSort,
fields,
},
locale: fullLocale,
permissions,
req,
@@ -35,18 +55,18 @@ export const ListView: React.FC<AdminViewProps> = async ({
locale,
payload,
payload: { config },
query,
query: queryFromReq,
user,
},
visibleEntities,
} = initPageResult
const collectionSlug = collectionConfig?.slug
if (!permissions?.collections?.[collectionSlug]?.read?.permission) {
notFound()
throw new Error('not-found')
}
const query = queryFromArgs || queryFromReq
let listPreferences: ListPreferences
const preferenceKey = `${collectionSlug}-list`
@@ -79,7 +99,7 @@ export const ListView: React.FC<AdminViewProps> = async ({
},
})
?.then((res) => res?.docs?.[0]?.value)) as ListPreferences
} catch (error) {} // eslint-disable-line no-empty
} catch (_err) {} // eslint-disable-line no-empty
const {
routes: { admin: adminRoute },
@@ -87,20 +107,21 @@ export const ListView: React.FC<AdminViewProps> = async ({
if (collectionConfig) {
if (!visibleEntities.collections.includes(collectionSlug)) {
return notFound()
throw new Error('not-found')
}
const page = isNumber(query?.page) ? Number(query.page) : 0
const whereQuery = mergeListSearchAndWhere({
collectionConfig,
query: {
search: typeof query?.search === 'string' ? query.search : undefined,
where: (query?.where as Where) || undefined,
},
search: typeof query?.search === 'string' ? query.search : undefined,
where: (query?.where as Where) || undefined,
})
const limit = isNumber(query?.limit)
? Number(query.limit)
: listPreferences?.limit || collectionConfig.admin.pagination.defaultLimit
const sort =
query?.sort && typeof query.sort === 'string'
? query.sort
@@ -125,89 +146,104 @@ export const ListView: React.FC<AdminViewProps> = async ({
where: whereQuery || {},
})
const createMappedComponent = getCreateMappedComponent({
importMap: payload.importMap,
serverProps: {
collectionConfig,
collectionSlug,
data,
hasCreatePermission: permissions?.collections?.[collectionSlug]?.create?.permission,
i18n,
limit,
listPreferences,
listSearchableFields: collectionConfig.admin.listSearchableFields,
locale: fullLocale,
newDocumentURL: formatAdminURL({
adminRoute,
path: `/collections/${collectionSlug}/create`,
}),
params,
payload,
permissions,
searchParams,
user,
},
const clientCollectionConfig = clientConfig.collections.find((c) => c.slug === collectionSlug)
const { columnState, Table } = renderTable({
collectionConfig: clientCollectionConfig,
columnPreferences: listPreferences?.columns,
customCellProps,
docs: data.docs,
drawerSlug,
enableRowSelections,
fields,
i18n: req.i18n,
payload,
useAsTitle,
})
const ListComponent = createMappedComponent(
collectionConfig?.admin?.components?.views?.list?.Component,
undefined,
DefaultListView,
'collectionConfig?.admin?.components?.views?.list?.Component',
)
const renderedFilters = renderFilters(fields, req.payload.importMap)
let clientCollectionConfig = deepCopyObjectSimple(
const staticDescription =
typeof collectionConfig.admin.description === 'function'
? collectionConfig.admin.description({ t: i18n.t })
: collectionConfig.admin.description
const listViewSlots = renderListViewSlots({
collectionConfig,
) as unknown as ClientCollectionConfig
clientCollectionConfig = createClientCollectionConfig({
clientCollection: clientCollectionConfig,
collection: collectionConfig,
createMappedComponent,
DefaultEditView,
DefaultListView,
i18n,
importMap: payload.importMap,
description: staticDescription,
payload,
})
return (
<Fragment>
<HydrateAuthProvider permissions={permissions} />
<ListInfoProvider
collectionConfig={clientCollectionConfig}
collectionSlug={collectionSlug}
hasCreatePermission={permissions?.collections?.[collectionSlug]?.create?.permission}
newDocumentURL={formatAdminURL({
adminRoute,
path: `/collections/${collectionSlug}/create`,
})}
>
const clientProps: ListViewClientProps = {
...listViewSlots,
collectionSlug,
columnState,
disableBulkDelete,
disableBulkEdit,
enableRowSelections,
hasCreatePermission: permissions?.collections?.[collectionSlug]?.create?.permission,
listPreferences,
newDocumentURL: formatAdminURL({
adminRoute,
path: `/collections/${collectionSlug}/create`,
}),
renderedFilters,
Table,
}
const isInDrawer = Boolean(drawerSlug)
return {
List: (
<Fragment>
<HydrateAuthProvider permissions={permissions} />
<ListQueryProvider
collectionSlug={collectionSlug}
data={data}
defaultLimit={limit || collectionConfig?.admin?.pagination?.defaultLimit}
defaultLimit={limit}
defaultSort={sort}
modifySearchParams
modifySearchParams={!isInDrawer}
preferenceKey={preferenceKey}
>
<TableColumnsProvider
collectionSlug={collectionSlug}
enableRowSelections
listPreferences={listPreferences}
preferenceKey={preferenceKey}
>
<RenderComponent
clientProps={{
collectionSlug,
listSearchableFields: collectionConfig?.admin?.listSearchableFields,
}}
mappedComponent={ListComponent}
/>
</TableColumnsProvider>
<RenderServerComponent
clientProps={clientProps}
Component={collectionConfig?.admin?.components?.views?.list?.Component}
Fallback={DefaultListView}
importMap={payload.importMap}
serverProps={{
collectionConfig,
collectionSlug,
data,
i18n,
limit,
listPreferences,
listSearchableFields: collectionConfig.admin.listSearchableFields,
locale: fullLocale,
params,
payload,
permissions,
searchParams,
user,
}}
/>
</ListQueryProvider>
</ListInfoProvider>
</Fragment>
)
</Fragment>
),
}
}
return notFound()
throw new Error('not-found')
}
export const ListView: React.FC<ListViewArgs> = async (args) => {
try {
const { List: RenderedList } = await renderListView({ ...args, enableRowSelections: true })
return RenderedList
} catch (error) {
if (error.message === 'not-found') {
notFound()
} else {
console.error(error) // eslint-disable-line no-console
}
}
}

View File

@@ -0,0 +1,66 @@
import type { ListViewSlots } from '@payloadcms/ui'
import type { Payload, SanitizedCollectionConfig, StaticDescription } from 'payload'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
export const renderListViewSlots = ({
collectionConfig,
description,
payload,
}: {
collectionConfig: SanitizedCollectionConfig
description?: StaticDescription
payload: Payload
}): ListViewSlots => {
const result: ListViewSlots = {} as ListViewSlots
if (collectionConfig.admin.components?.afterList) {
result.AfterList = (
<RenderServerComponent
Component={collectionConfig.admin.components.afterList}
importMap={payload.importMap}
/>
)
}
if (collectionConfig.admin.components?.afterListTable) {
result.AfterListTable = (
<RenderServerComponent
Component={collectionConfig.admin.components.afterListTable}
importMap={payload.importMap}
/>
)
}
if (collectionConfig.admin.components?.beforeList) {
result.BeforeList = (
<RenderServerComponent
Component={collectionConfig.admin.components.beforeList}
importMap={payload.importMap}
/>
)
}
if (collectionConfig.admin.components?.beforeListTable) {
result.BeforeListTable = (
<RenderServerComponent
Component={collectionConfig.admin.components.beforeListTable}
importMap={payload.importMap}
/>
)
}
if (collectionConfig.admin.components?.Description) {
result.Description = (
<RenderServerComponent
clientProps={{
description,
}}
Component={collectionConfig.admin.components.Description}
importMap={payload.importMap}
/>
)
}
return result
}

View File

@@ -13,29 +13,25 @@ import type {
import {
DocumentControls,
DocumentFields,
DocumentLocked,
DocumentTakeOver,
Form,
LeaveWithoutSaving,
OperationProvider,
SetViewActions,
SetDocumentStepNav,
SetDocumentTitle,
useAuth,
useConfig,
useDocumentDrawerContext,
useDocumentEvents,
useDocumentInfo,
useServerFunctions,
useTranslation,
} from '@payloadcms/ui'
import {
getFormState,
handleBackToDashboard,
handleGoBack,
handleTakeOver,
} from '@payloadcms/ui/shared'
import { handleBackToDashboard, handleGoBack, handleTakeOver } from '@payloadcms/ui/shared'
import { useRouter } from 'next/navigation.js'
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
import { DocumentLocked } from '../../elements/DocumentLocked/index.js'
import { DocumentTakeOver } from '../../elements/DocumentTakeOver/index.js'
import { LeaveWithoutSaving } from '../../elements/LeaveWithoutSaving/index.js'
import { SetDocumentStepNav } from '../Edit/Default/SetDocumentStepNav/index.js'
import { SetDocumentTitle } from '../Edit/Default/SetDocumentTitle/index.js'
import { useLivePreviewContext } from './Context/context.js'
import { LivePreviewProvider } from './Context/index.js'
import './index.scss'
@@ -55,13 +51,11 @@ type Props = {
}
const PreviewView: React.FC<Props> = ({
apiRoute,
collectionConfig,
config,
fields,
globalConfig,
schemaPath,
serverURL,
}) => {
const {
id,
@@ -69,7 +63,6 @@ const PreviewView: React.FC<Props> = ({
AfterDocument,
AfterFields,
apiURL,
BeforeDocument,
BeforeFields,
collectionSlug,
currentEditor,
@@ -86,13 +79,16 @@ const PreviewView: React.FC<Props> = ({
isEditing,
isInitializing,
lastUpdateTime,
onSave: onSaveFromProps,
setCurrentEditor,
setDocumentIsLocked,
unlockDocument,
updateDocumentEditor,
} = useDocumentInfo()
const { getFormState } = useServerFunctions()
const { onSave: onSaveFromProps } = useDocumentDrawerContext()
const operation = id ? 'update' : 'create'
const {
@@ -120,6 +116,8 @@ const PreviewView: React.FC<Props> = ({
const [isReadOnlyForIncomingUser, setIsReadOnlyForIncomingUser] = useState(false)
const [showTakeOverModal, setShowTakeOverModal] = useState(false)
const abortControllerRef = useRef(new AbortController())
const [editSessionStartTime, setEditSessionStartTime] = useState(Date.now())
const lockExpiryTime = lastUpdateTime + lockDurationInMilliseconds
@@ -178,6 +176,17 @@ const PreviewView: React.FC<Props> = ({
const onChange: FormProps['onChange'][0] = useCallback(
async ({ formState: prevFormState }) => {
if (abortControllerRef.current) {
try {
abortControllerRef.current.abort()
} catch (_err) {
// swallow error
}
}
const abortController = new AbortController()
abortControllerRef.current = abortController
const currentTime = Date.now()
const timeSinceLastUpdate = currentTime - editSessionStartTime
@@ -190,19 +199,17 @@ const PreviewView: React.FC<Props> = ({
const docPreferences = await getDocPreferences()
const { lockedState, state } = await getFormState({
apiRoute,
body: {
id,
collectionSlug,
docPreferences,
formState: prevFormState,
globalSlug,
operation,
returnLockStatus: isLockingEnabled ? true : false,
schemaPath,
updateLastEdited,
},
serverURL,
id,
collectionSlug,
docPermissions,
docPreferences,
formState: prevFormState,
globalSlug,
operation,
returnLockStatus: isLockingEnabled ? true : false,
schemaPath,
signal: abortController.signal,
updateLastEdited,
})
setDocumentIsLocked(true)
@@ -214,8 +221,13 @@ const PreviewView: React.FC<Props> = ({
: documentLockStateRef.current?.user
if (lockedState) {
if (!documentLockStateRef.current || lockedState.user.id !== previousOwnerId) {
if (previousOwnerId === user.id && lockedState.user.id !== user.id) {
const lockedUserID =
typeof lockedState.user === 'string' || typeof lockedState.user === 'number'
? lockedState.user
: lockedState.user.id
if (!documentLockStateRef.current || lockedUserID !== previousOwnerId) {
if (previousOwnerId === user.id && lockedUserID !== user.id) {
setShowTakeOverModal(true)
documentLockStateRef.current.hasShownLockedModal = true
}
@@ -223,9 +235,10 @@ const PreviewView: React.FC<Props> = ({
documentLockStateRef.current = documentLockStateRef.current = {
hasShownLockedModal: documentLockStateRef.current?.hasShownLockedModal || false,
isLocked: true,
user: lockedState.user,
user: lockedState.user as ClientUser,
}
setCurrentEditor(lockedState.user)
setCurrentEditor(lockedState.user as ClientUser)
}
}
}
@@ -233,25 +246,33 @@ const PreviewView: React.FC<Props> = ({
return state
},
[
collectionSlug,
editSessionStartTime,
globalSlug,
serverURL,
apiRoute,
id,
isLockingEnabled,
getDocPreferences,
getFormState,
id,
collectionSlug,
docPermissions,
globalSlug,
operation,
schemaPath,
getDocPreferences,
setCurrentEditor,
setDocumentIsLocked,
user,
user.id,
setCurrentEditor,
],
)
// Clean up when the component unmounts or when the document is unlocked
useEffect(() => {
return () => {
if (abortControllerRef.current) {
try {
abortControllerRef.current.abort()
} catch (_err) {
// swallow error
}
}
if (!isLockingEnabled) {
return
}
@@ -415,7 +436,6 @@ const PreviewView: React.FC<Props> = ({
.filter(Boolean)
.join(' ')}
>
{BeforeDocument}
<DocumentFields
AfterFields={AfterFields}
BeforeFields={BeforeFields}
@@ -423,7 +443,7 @@ const PreviewView: React.FC<Props> = ({
fields={fields}
forceSidebarWrap
readOnly={isReadOnlyForIncomingUser || !hasSavePermission}
schemaPath={collectionSlug || globalSlug}
schemaPathSegments={[collectionSlug || globalSlug]}
/>
{AfterDocument}
</div>
@@ -464,11 +484,6 @@ export const LivePreviewClient: React.FC<{
return (
<Fragment>
<SetViewActions
actions={
(collectionConfig || globalConfig)?.admin?.components?.views?.edit?.livePreview?.actions
}
/>
<LivePreviewProvider
breakpoints={breakpoints}
fieldSchema={collectionConfig?.fields || globalConfig?.fields}

View File

@@ -17,9 +17,11 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
if (type === 'email') {
return (
<EmailField
autoComplete="email"
field={{
name: 'email',
admin: {
autoComplete: 'email',
},
label: t('general:email'),
required,
}}

View File

@@ -1,6 +1,6 @@
import type { AdminViewProps } from 'payload'
import { getCreateMappedComponent, RenderComponent } from '@payloadcms/ui/shared'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { redirect } from 'next/navigation.js'
import React, { Fragment } from 'react'
@@ -28,23 +28,6 @@ export const LoginView: React.FC<AdminViewProps> = ({ initPageResult, params, se
routes: { admin },
} = config
const createMappedComponent = getCreateMappedComponent({
importMap: payload.importMap,
serverProps: {
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
},
})
const mappedBeforeLogins = createMappedComponent(beforeLogin, undefined, undefined, 'beforeLogin')
const mappedAfterLogins = createMappedComponent(afterLogin, undefined, undefined, 'afterLogin')
if (user) {
redirect((searchParams.redirect as string) || admin)
}
@@ -82,7 +65,19 @@ export const LoginView: React.FC<AdminViewProps> = ({ initPageResult, params, se
user={user}
/>
</div>
<RenderComponent mappedComponent={mappedBeforeLogins} />
<RenderServerComponent
Component={beforeLogin}
importMap={payload.importMap}
serverProps={{
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
}}
/>
{!collectionConfig?.auth?.disableLocalStrategy && (
<LoginForm
prefillEmail={prefillEmail}
@@ -91,7 +86,19 @@ export const LoginView: React.FC<AdminViewProps> = ({ initPageResult, params, se
searchParams={searchParams}
/>
)}
<RenderComponent mappedComponent={mappedAfterLogins} />
<RenderServerComponent
Component={afterLogin}
importMap={payload.importMap}
serverProps={{
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
}}
/>
</Fragment>
)
}

View File

@@ -75,13 +75,26 @@ export const ResetPasswordForm: React.FC<Args> = ({ token }) => {
label: i18n.t('authentication:newPassword'),
required: true,
}}
indexPath=""
parentPath=""
parentSchemaPath=""
path="password"
schemaPath={`${userSlug}.password`}
/>
<ConfirmPasswordField />
<HiddenField
field={{
name: 'token',
type: 'text',
admin: {
hidden: true,
},
}}
forceUsePathFromProps
indexPath=""
parentPath={userSlug}
parentSchemaPath={userSlug}
path="token"
schemaPath={`${userSlug}.token`}
value={token}
/>
</div>

View File

@@ -1,4 +1,11 @@
import type { AdminViewComponent, AdminViewProps, ImportMap, SanitizedConfig } from 'payload'
import type {
AdminViewComponent,
AdminViewProps,
CustomComponent,
EditConfig,
ImportMap,
SanitizedConfig,
} from 'payload'
import type React from 'react'
import { formatAdminURL } from '@payloadcms/ui/shared'
@@ -46,6 +53,20 @@ const oneSegmentViews: OneSegmentViews = {
unauthorized: UnauthorizedView,
}
function getViewActions({
editConfig,
viewKey,
}: {
editConfig: EditConfig
viewKey: keyof EditConfig
}): CustomComponent[] | undefined {
if (editConfig && viewKey in editConfig && 'actions' in editConfig[viewKey]) {
return editConfig[viewKey].actions
}
return undefined
}
export const getViewFromConfig = ({
adminRoute,
config,
@@ -65,8 +86,10 @@ export const getViewFromConfig = ({
}): {
DefaultView: ViewFromConfig
initPageOptions: Parameters<typeof initPage>[0]
serverProps: Record<string, unknown>
templateClassName: string
templateType: 'default' | 'minimal'
viewActions?: CustomComponent[]
} => {
let ViewToRender: ViewFromConfig = null
let templateClassName: string
@@ -79,10 +102,30 @@ export const getViewFromConfig = ({
searchParams,
}
const [segmentOne, segmentTwo] = segments
let viewActions: CustomComponent[] = config?.admin?.components?.actions || []
const [segmentOne, segmentTwo, segmentThree, segmentFour, segmentFive] = segments
const isGlobal = segmentOne === 'globals'
const isCollection = segmentOne === 'collections'
let matchedCollection: SanitizedConfig['collections'][number] = undefined
let matchedGlobal: SanitizedConfig['globals'][number] = undefined
let serverProps = {}
if (isCollection) {
matchedCollection = config.collections.find(({ slug }) => slug === segmentTwo)
serverProps = {
collectionConfig: matchedCollection,
}
}
if (isGlobal) {
matchedGlobal = config.globals.find(({ slug }) => slug === segmentTwo)
serverProps = {
globalConfig: matchedGlobal,
}
}
switch (segments.length) {
case 0: {
@@ -146,7 +189,7 @@ export const getViewFromConfig = ({
templateType = 'minimal'
}
if (isCollection) {
if (isCollection && matchedCollection) {
// --> /collections/:collectionSlug
ViewToRender = {
@@ -155,7 +198,8 @@ export const getViewFromConfig = ({
templateClassName = `${segmentTwo}-list`
templateType = 'default'
} else if (isGlobal) {
viewActions = viewActions.concat(matchedCollection.admin.components?.views?.list?.actions)
} else if (isGlobal && matchedGlobal) {
// --> /globals/:globalSlug
ViewToRender = {
@@ -164,6 +208,14 @@ export const getViewFromConfig = ({
templateClassName = 'global-edit'
templateType = 'default'
// add default view actions
viewActions = viewActions.concat(
getViewActions({
editConfig: matchedGlobal.admin?.components?.views?.edit,
viewKey: 'default',
}),
)
}
break
}
@@ -176,13 +228,13 @@ export const getViewFromConfig = ({
templateClassName = 'verify'
templateType = 'minimal'
} else if (isCollection) {
} else if (isCollection && matchedCollection) {
// Custom Views
// --> /collections/:collectionSlug/:id
// --> /collections/:collectionSlug/:id/api
// --> /collections/:collectionSlug/:id/preview
// --> /collections/:collectionSlug/:id/versions
// --> /collections/:collectionSlug/:id/versions/:versionId
// --> /collections/:collectionSlug/:id/api
ViewToRender = {
Component: DocumentView,
@@ -190,7 +242,65 @@ export const getViewFromConfig = ({
templateClassName = `collection-default-edit`
templateType = 'default'
} else if (isGlobal) {
// Adds view actions to the current collection view
if (matchedCollection.admin?.components?.views?.edit) {
if ('root' in matchedCollection.admin.components.views.edit) {
viewActions = viewActions.concat(
getViewActions({
editConfig: matchedCollection.admin?.components?.views?.edit,
viewKey: 'root',
}),
)
} else {
if (segmentFive) {
if (segmentFour === 'versions') {
// add version view actions
viewActions = viewActions.concat(
getViewActions({
editConfig: matchedCollection.admin?.components?.views?.edit,
viewKey: 'version',
}),
)
}
} else if (segmentFour) {
if (segmentFour === 'versions') {
// add versions view actions
viewActions = viewActions.concat(
getViewActions({
editConfig: matchedCollection.admin?.components?.views.edit,
viewKey: 'versions',
}),
)
} else if (segmentFour === 'preview') {
// add livePreview view actions
viewActions = viewActions.concat(
getViewActions({
editConfig: matchedCollection.admin?.components?.views.edit,
viewKey: 'livePreview',
}),
)
} else if (segmentFour === 'api') {
// add api view actions
viewActions = viewActions.concat(
getViewActions({
editConfig: matchedCollection.admin?.components?.views.edit,
viewKey: 'api',
}),
)
}
} else if (segmentThree) {
// add default view actions
viewActions = viewActions.concat(
getViewActions({
editConfig: matchedCollection.admin?.components?.views.edit,
viewKey: 'default',
}),
)
}
}
}
} else if (isGlobal && matchedGlobal) {
// Custom Views
// --> /globals/:globalSlug/versions
// --> /globals/:globalSlug/preview
@@ -203,6 +313,56 @@ export const getViewFromConfig = ({
templateClassName = `global-edit`
templateType = 'default'
// Adds view actions to the current global view
if (matchedGlobal.admin?.components?.views?.edit) {
if ('root' in matchedGlobal.admin.components.views.edit) {
viewActions = viewActions.concat(
getViewActions({
editConfig: matchedGlobal.admin.components?.views?.edit,
viewKey: 'root',
}),
)
} else {
if (segmentFour) {
if (segmentThree === 'versions') {
// add version view actions
viewActions = viewActions.concat(
getViewActions({
editConfig: matchedGlobal.admin?.components?.views?.edit,
viewKey: 'version',
}),
)
}
} else if (segmentThree) {
if (segmentThree === 'versions') {
// add versions view actions
viewActions = viewActions.concat(
getViewActions({
editConfig: matchedGlobal.admin?.components?.views?.edit,
viewKey: 'versions',
}),
)
} else if (segmentThree === 'preview') {
// add livePreview view actions
viewActions = viewActions.concat(
getViewActions({
editConfig: matchedGlobal.admin?.components?.views?.edit,
viewKey: 'livePreview',
}),
)
} else if (segmentThree === 'api') {
// add api view actions
viewActions = viewActions.concat(
getViewActions({
editConfig: matchedGlobal.admin?.components?.views?.edit,
viewKey: 'api',
}),
)
}
}
}
}
}
break
}
@@ -214,7 +374,9 @@ export const getViewFromConfig = ({
return {
DefaultView: ViewToRender,
initPageOptions,
serverProps,
templateClassName,
templateType,
viewActions: viewActions.reverse(),
}
}

View File

@@ -1,13 +1,15 @@
import type { I18nClient } from '@payloadcms/translations'
import type { Metadata } from 'next'
import type { ImportMap, MappedComponent, SanitizedConfig } from 'payload'
import type { ImportMap, SanitizedConfig } from 'payload'
import { formatAdminURL, getCreateMappedComponent, RenderComponent } from '@payloadcms/ui/shared'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { formatAdminURL } from '@payloadcms/ui/shared'
import { notFound, redirect } from 'next/navigation.js'
import React, { Fragment } from 'react'
import { DefaultTemplate } from '../../templates/Default/index.js'
import { MinimalTemplate } from '../../templates/Minimal/index.js'
import { getClientConfig } from '../../utilities/getClientConfig.js'
import { initPage } from '../../utilities/initPage/index.js'
import { getViewFromConfig } from './getViewFromConfig.js'
@@ -55,7 +57,14 @@ export const RootPage = async ({
const searchParams = await searchParamsPromise
const { DefaultView, initPageOptions, templateClassName, templateType } = getViewFromConfig({
const {
DefaultView,
initPageOptions,
serverProps,
templateClassName,
templateType,
viewActions,
} = getViewFromConfig({
adminRoute,
config,
currentRoute,
@@ -64,21 +73,22 @@ export const RootPage = async ({
segments,
})
let dbHasUser = false
const initPageResult = await initPage(initPageOptions)
dbHasUser = await initPageResult?.req.payload.db
.findOne({
collection: userSlug,
req: initPageResult?.req,
})
?.then((doc) => !!doc)
const dbHasUser =
initPageResult.req.user ||
(await initPageResult?.req.payload.db
.findOne({
collection: userSlug,
req: initPageResult?.req,
})
?.then((doc) => !!doc))
if (!DefaultView?.Component && !DefaultView?.payloadComponent) {
if (initPageResult?.req?.user) {
notFound()
}
if (dbHasUser) {
redirect(adminRoute)
}
@@ -111,27 +121,30 @@ export const RootPage = async ({
redirect(adminRoute)
}
const createMappedView = getCreateMappedComponent({
importMap,
serverProps: {
i18n: initPageResult?.req.i18n,
importMap,
initPageResult,
params,
payload: initPageResult?.req.payload,
searchParams,
},
const clientConfig = await getClientConfig({
config,
i18n: initPageResult?.req.i18n,
})
const MappedView: MappedComponent = createMappedView(
DefaultView.payloadComponent,
undefined,
DefaultView.Component,
'createMappedView',
const RenderedView = (
<RenderServerComponent
clientProps={{ clientConfig }}
Component={DefaultView.payloadComponent}
Fallback={DefaultView.Component}
importMap={importMap}
serverProps={{
...serverProps,
clientConfig,
i18n: initPageResult?.req.i18n,
importMap,
initPageResult,
params,
payload: initPageResult?.req.payload,
searchParams,
}}
/>
)
const RenderedView = <RenderComponent mappedComponent={MappedView} />
return (
<Fragment>
{!templateType && <Fragment>{RenderedView}</Fragment>}
@@ -147,6 +160,7 @@ export const RootPage = async ({
permissions={initPageResult?.permissions}
searchParams={searchParams}
user={initPageResult?.req.user}
viewActions={viewActions}
visibleEntities={{
// The reason we are not passing in initPageResult.visibleEntities directly is due to a "Cannot assign to read only property of object '#<Object>" error introduced in React 19
// which this caused as soon as initPageResult.visibleEntities is passed in

View File

@@ -1,14 +1,7 @@
'use client'
import type { ClientCollectionConfig, ClientGlobalConfig, OptionObject } from 'payload'
import {
Gutter,
SetViewActions,
useConfig,
useDocumentInfo,
usePayloadAPI,
useTranslation,
} from '@payloadcms/ui'
import { Gutter, useConfig, useDocumentInfo, usePayloadAPI, useTranslation } from '@payloadcms/ui'
import { formatDate } from '@payloadcms/ui/shared'
import React, { useState } from 'react'
@@ -80,11 +73,6 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
return (
<main className={baseClass}>
<SetViewActions
actions={
(collectionConfig || globalConfig)?.admin?.components?.views?.edit?.version?.actions
}
/>
<SetStepNav
collectionConfig={collectionConfig}
collectionSlug={collectionSlug}

View File

@@ -1,5 +1,11 @@
import type { I18n } from '@payloadcms/translations'
import type { SanitizedCollectionConfig, SanitizedConfig, SanitizedGlobalConfig } from 'payload'
import type {
PaginatedDocs,
SanitizedCollectionConfig,
SanitizedConfig,
SanitizedGlobalConfig,
TypeWithVersion,
} from 'payload'
import { type Column, SortColumn } from '@payloadcms/ui'
import React from 'react'
@@ -11,6 +17,7 @@ import { IDCell } from './cells/ID/index.js'
export const buildVersionColumns = ({
collectionConfig,
docID,
docs,
globalConfig,
i18n: { t },
latestDraftVersion,
@@ -19,6 +26,7 @@ export const buildVersionColumns = ({
collectionConfig?: SanitizedCollectionConfig
config: SanitizedConfig
docID?: number | string
docs: PaginatedDocs<TypeWithVersion<any>>['docs']
globalConfig?: SanitizedGlobalConfig
i18n: I18n
latestDraftVersion?: string
@@ -30,56 +38,37 @@ export const buildVersionColumns = ({
{
accessor: 'updatedAt',
active: true,
cellProps: {
field: {
name: '',
type: 'date',
admin: {
components: {
Cell: {
type: 'client',
Component: null,
RenderedComponent: (
<CreatedAtCell
collectionSlug={collectionConfig?.slug}
docID={docID}
globalSlug={globalConfig?.slug}
/>
),
},
Label: {
type: 'client',
Component: null,
},
},
},
},
field: {
name: '',
type: 'date',
},
Heading: <SortColumn Label={t('general:updatedAt')} name="updatedAt" />,
renderedCells: docs.map((doc, i) => {
return (
<CreatedAtCell
collectionSlug={collectionConfig?.slug}
docID={docID}
globalSlug={globalConfig?.slug}
key={i}
rowData={{
id: doc.id,
updatedAt: doc.updatedAt,
}}
/>
)
}),
},
{
accessor: 'id',
active: true,
cellProps: {
field: {
name: '',
type: 'text',
admin: {
components: {
Cell: {
type: 'client',
Component: null,
RenderedComponent: <IDCell />,
},
Label: {
type: 'client',
Component: null,
},
},
},
},
field: {
name: '',
type: 'text',
},
Heading: <SortColumn disable Label={t('version:versionID')} name="id" />,
renderedCells: docs.map((doc, i) => {
return <IDCell id={doc.id} key={i} />
}),
},
]
@@ -90,31 +79,21 @@ export const buildVersionColumns = ({
columns.push({
accessor: '_status',
active: true,
cellProps: {
field: {
name: '',
type: 'checkbox',
admin: {
components: {
Cell: {
type: 'client',
Component: null,
RenderedComponent: (
<AutosaveCell
latestDraftVersion={latestDraftVersion}
latestPublishedVersion={latestPublishedVersion}
/>
),
},
Label: {
type: 'client',
Component: null,
},
},
},
},
field: {
name: '',
type: 'checkbox',
},
Heading: <SortColumn disable Label={t('version:status')} name="status" />,
renderedCells: docs.map((doc, i) => {
return (
<AutosaveCell
key={i}
latestDraftVersion={latestDraftVersion}
latestPublishedVersion={latestPublishedVersion}
rowData={doc}
/>
)
}),
})
}

View File

@@ -1,10 +1,17 @@
'use client'
import { Pill, useConfig, useTableCell, useTranslation } from '@payloadcms/ui'
import { Pill, useConfig, useTranslation } from '@payloadcms/ui'
import React, { Fragment } from 'react'
type AutosaveCellProps = {
latestDraftVersion?: string
latestPublishedVersion?: string
rowData?: {
autosave?: boolean
publishedLocale?: string
version: {
_status?: string
}
}
}
export const renderPill = (data, latestVersion, currentLabel, previousLabel, pillStyle) => {
@@ -23,9 +30,10 @@ export const renderPill = (data, latestVersion, currentLabel, previousLabel, pil
export const AutosaveCell: React.FC<AutosaveCellProps> = ({
latestDraftVersion,
latestPublishedVersion,
rowData = { autosave: undefined, publishedLocale: undefined, version: undefined },
}) => {
const { i18n, t } = useTranslation()
const { rowData } = useTableCell()
const {
config: { localization },
} = useConfig()

View File

@@ -1,5 +1,5 @@
'use client'
import { useConfig, useTableCell, useTranslation } from '@payloadcms/ui'
import { useConfig, useTranslation } from '@payloadcms/ui'
import { formatAdminURL, formatDate } from '@payloadcms/ui/shared'
import LinkImport from 'next/link.js'
import React from 'react'
@@ -10,12 +10,17 @@ type CreatedAtCellProps = {
collectionSlug?: string
docID?: number | string
globalSlug?: string
rowData?: {
id: number | string
updatedAt: Date | number | string
}
}
export const CreatedAtCell: React.FC<CreatedAtCellProps> = ({
collectionSlug,
docID,
globalSlug,
rowData: { id, updatedAt } = {},
}) => {
const {
config: {
@@ -26,30 +31,25 @@ export const CreatedAtCell: React.FC<CreatedAtCellProps> = ({
const { i18n } = useTranslation()
const { cellData, rowData } = useTableCell()
const versionID = rowData.id
let to: string
if (collectionSlug) {
to = formatAdminURL({
adminRoute,
path: `/collections/${collectionSlug}/${docID}/versions/${versionID}`,
path: `/collections/${collectionSlug}/${docID}/versions/${id}`,
})
}
if (globalSlug) {
to = formatAdminURL({
adminRoute,
path: `/globals/${globalSlug}/versions/${versionID}`,
path: `/globals/${globalSlug}/versions/${id}`,
})
}
return (
<Link href={to} prefetch={false}>
{cellData &&
formatDate({ date: cellData as Date | number | string, i18n, pattern: dateFormat })}
{formatDate({ date: updatedAt, i18n, pattern: dateFormat })}
</Link>
)
}

View File

@@ -1,8 +1,6 @@
'use client'
import { useTableCell } from '@payloadcms/ui'
import React, { Fragment } from 'react'
export const IDCell: React.FC = () => {
const { cellData } = useTableCell()
return <Fragment>{cellData as number | string}</Fragment>
export function IDCell({ id }: { id: number | string }) {
return <Fragment>{id}</Fragment>
}

View File

@@ -1,15 +1,12 @@
'use client'
import type { ClientCollectionConfig, ClientGlobalConfig, SanitizedCollectionConfig } from 'payload'
import type { SanitizedCollectionConfig } from 'payload'
import {
type Column,
LoadingOverlayToggle,
Pagination,
PerPage,
SetViewActions,
Table,
useConfig,
useDocumentInfo,
useListQuery,
useTranslation,
} from '@payloadcms/ui'
@@ -24,14 +21,8 @@ export const VersionsViewClient: React.FC<{
}> = (props) => {
const { baseClass, columns, paginationLimits } = props
const { collectionSlug, globalSlug } = useDocumentInfo()
const { data, handlePageChange, handlePerPageChange } = useListQuery()
const { getEntityConfig } = useConfig()
const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig
const globalConfig = getEntityConfig({ globalSlug }) as ClientGlobalConfig
const searchParams = useSearchParams()
const limit = searchParams.get('limit')
@@ -41,11 +32,6 @@ export const VersionsViewClient: React.FC<{
return (
<React.Fragment>
<SetViewActions
actions={
(collectionConfig || globalConfig)?.admin?.components?.views?.edit?.versions?.actions
}
/>
<LoadingOverlayToggle name="versions" show={!data} />
{versionCount === 0 && (
<div className={`${baseClass}__no-versions`}>
@@ -54,11 +40,7 @@ export const VersionsViewClient: React.FC<{
)}
{versionCount > 0 && (
<React.Fragment>
<Table
columns={columns}
data={data?.docs}
fields={(collectionConfig || globalConfig)?.fields}
/>
<Table columns={columns} data={data?.docs} />
<div className={`${baseClass}__page-controls`}>
<Pagination
hasNextPage={data.hasNextPage}

View File

@@ -1,11 +1,10 @@
import type { EditViewComponent, PaginatedDocs, PayloadServerReactComponent } from 'payload'
import { Gutter, ListQueryProvider } from '@payloadcms/ui'
import { Gutter, ListQueryProvider, SetDocumentStepNav } from '@payloadcms/ui'
import { notFound } from 'next/navigation.js'
import { isNumber } from 'payload/shared'
import React from 'react'
import { SetDocumentStepNav } from '../Edit/Default/SetDocumentStepNav/index.js'
import { buildVersionColumns } from './buildColumns.js'
import { getLatestVersion } from './getLatestVersion.js'
import { VersionsViewClient } from './index.client.js'
@@ -165,6 +164,7 @@ export const VersionsView: PayloadServerReactComponent<EditViewComponent> = asyn
collectionConfig,
config,
docID: id,
docs: versionsData?.docs,
globalConfig,
i18n,
latestDraftVersion: latestDraftVersion?.id,
@@ -190,6 +190,7 @@ export const VersionsView: PayloadServerReactComponent<EditViewComponent> = asyn
<main className={baseClass}>
<Gutter className={`${baseClass}__wrap`}>
<ListQueryProvider
collectionSlug={collectionSlug}
data={versionsData}
defaultLimit={limitToUse}
defaultSort={sort as string}

View File

@@ -19,7 +19,7 @@
"src/**/*.ts",
"src/**/*.tsx",
"src/withPayload.js" /* Include the withPayload.js file in the build */
],
, "../ui/src/utilities/renderFields.tsx" ],
"references": [
{ "path": "../payload" },
{ "path": "../ui" },

View File

@@ -4,8 +4,8 @@ import type { JSONSchema4 } from 'json-schema'
import type { ImportMap } from '../bin/generateImportMap/index.js'
import type { SanitizedCollectionConfig, TypeWithID } from '../collections/config/types.js'
import type { Config, PayloadComponent, SanitizedConfig } from '../config/types.js'
import type { ValidationFieldError } from '../errors/ValidationError.js'
import type {
Field,
FieldAffectingData,
RichTextField,
RichTextFieldClient,
@@ -15,7 +15,7 @@ import type { SanitizedGlobalConfig } from '../globals/config/types.js'
import type { RequestContext } from '../index.js'
import type { JsonObject, Payload, PayloadRequest, PopulateType } from '../types/index.js'
import type { RichTextFieldClientProps } from './fields/RichText.js'
import type { CreateMappedComponent } from './types.js'
import type { FieldSchemaMap } from './types.js'
export type AfterReadRichTextHookArgs<
TData extends TypeWithID = any,
@@ -91,7 +91,7 @@ export type BeforeChangeRichTextHookArgs<
duplicate?: boolean
errors?: { field: string; message: string }[]
errors?: ValidationFieldError[]
/** Only available in `beforeChange` field hooks */
mergeLocaleActions?: (() => Promise<void>)[]
/** A string relating to which operation the field type is currently executing within. */
@@ -184,32 +184,19 @@ export type RichTextHooks = {
beforeChange?: BeforeChangeRichTextHook[]
beforeValidate?: BeforeValidateRichTextHook[]
}
export type RichTextGenerateComponentMap = (args: {
clientField: RichTextFieldClient
createMappedComponent: CreateMappedComponent
field: RichTextField
i18n: I18nClient
importMap: ImportMap
payload: Payload
schemaPath: string
}) => Map<string, unknown>
type RichTextAdapterBase<
Value extends object = object,
AdapterProps = any,
ExtraFieldProperties = {},
> = {
generateComponentMap: PayloadComponent<any, never>
generateImportMap?: Config['admin']['importMap']['generators'][0]
generateSchemaMap?: (args: {
config: SanitizedConfig
field: RichTextField
i18n: I18n<any, any>
schemaMap: Map<string, Field[]>
schemaMap: FieldSchemaMap
schemaPath: string
}) => Map<string, Field[]>
}) => FieldSchemaMap
/**
* Like an afterRead hook, but runs only for the GraphQL resolver. For populating data, this should be used, as afterRead hooks do not have a depth in graphQL.
*

View File

@@ -1,10 +1,15 @@
import type { ClientCollectionConfig } from '../../collections/config/client.js'
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
import type { ClientField } from '../../fields/config/client.js'
export type RowData = Record<string, any>
export type CellComponentProps<TField extends ClientField = ClientField> = {
export type DefaultCellComponentProps<TCellData = any, TField extends ClientField = ClientField> = {
readonly cellData: TCellData
readonly className?: string
readonly collectionConfig: ClientCollectionConfig
readonly columnIndex?: number
readonly customCellProps?: Record<string, any>
readonly field: TField
readonly link?: boolean
readonly onClick?: (args: {
@@ -12,13 +17,5 @@ export type CellComponentProps<TField extends ClientField = ClientField> = {
collectionSlug: SanitizedCollectionConfig['slug']
rowData: RowData
}) => void
}
export type DefaultCellComponentProps<TCellData = any, TField extends ClientField = ClientField> = {
readonly cellData: TCellData
readonly customCellContext?: {
collectionSlug?: SanitizedCollectionConfig['slug']
uploadConfig?: SanitizedCollectionConfig['upload']
}
readonly rowData: RowData
} & CellComponentProps<TField>
}

View File

@@ -1,6 +1,6 @@
import type { MarkOptional } from 'ts-essentials'
import type { ArrayField, ArrayFieldClient } from '../../fields/config/types.js'
import type { ArrayField, ArrayFieldClient, ClientField } from '../../fields/config/types.js'
import type { ArrayFieldValidation } from '../../fields/validations.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
@@ -14,15 +14,14 @@ import type {
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
MappedComponent,
} from '../types.js'
type ArrayFieldClientWithoutType = MarkOptional<ArrayFieldClient, 'type'>
type ArrayFieldBaseClientProps = {
readonly CustomRowLabel?: MappedComponent
readonly path?: string
readonly validate?: ArrayFieldValidation
}
} & Pick<ServerFieldBase, 'permissions'>
export type ArrayFieldClientProps = ArrayFieldBaseClientProps &
ClientFieldBase<ArrayFieldClientWithoutType>

View File

@@ -1,6 +1,6 @@
import type { MarkOptional } from 'ts-essentials'
import type { BlocksField, BlocksFieldClient } from '../../fields/config/types.js'
import type { BlocksField, BlocksFieldClient, ClientField } from '../../fields/config/types.js'
import type { BlocksFieldValidation } from '../../fields/validations.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
@@ -19,8 +19,9 @@ import type {
type BlocksFieldClientWithoutType = MarkOptional<BlocksFieldClient, 'type'>
type BlocksFieldBaseClientProps = {
readonly path?: string
readonly validate?: BlocksFieldValidation
}
} & Pick<ServerFieldBase, 'permissions'>
export type BlocksFieldClientProps = BlocksFieldBaseClientProps &
ClientFieldBase<BlocksFieldClientWithoutType>

View File

@@ -24,6 +24,7 @@ type CheckboxFieldBaseClientProps = {
readonly id?: string
readonly onChange?: (value: boolean) => void
readonly partialChecked?: boolean
readonly path?: string
readonly validate?: CheckboxFieldValidation
}

View File

@@ -20,7 +20,8 @@ type CodeFieldClientWithoutType = MarkOptional<CodeFieldClient, 'type'>
type CodeFieldBaseClientProps = {
readonly autoComplete?: string
readonly valiCode?: CodeFieldValidation
readonly path?: string
readonly validate?: CodeFieldValidation
}
export type CodeFieldClientProps = ClientFieldBase<CodeFieldClientWithoutType> &

View File

@@ -15,9 +15,14 @@ import type {
FieldLabelServerComponent,
} from '../types.js'
type CollapsibleFieldBaseClientProps = {
readonly path?: string
} & Pick<ServerFieldBase, 'permissions'>
type CollapsibleFieldClientWithoutType = MarkOptional<CollapsibleFieldClient, 'type'>
export type CollapsibleFieldClientProps = ClientFieldBase<CollapsibleFieldClientWithoutType>
export type CollapsibleFieldClientProps = ClientFieldBase<CollapsibleFieldClientWithoutType> &
CollapsibleFieldBaseClientProps
export type CollapsibleFieldServerProps = ServerFieldBase<
CollapsibleField,
@@ -29,8 +34,10 @@ export type CollapsibleFieldServerComponent = FieldServerComponent<
CollapsibleFieldClientWithoutType
>
export type CollapsibleFieldClientComponent =
FieldClientComponent<CollapsibleFieldClientWithoutType>
export type CollapsibleFieldClientComponent = FieldClientComponent<
CollapsibleFieldClientWithoutType,
CollapsibleFieldBaseClientProps
>
export type CollapsibleFieldLabelServerComponent = FieldLabelServerComponent<
CollapsibleField,

View File

@@ -19,6 +19,7 @@ import type {
type DateFieldClientWithoutType = MarkOptional<DateFieldClient, 'type'>
type DateFieldBaseClientProps = {
readonly path?: string
readonly validate?: DateFieldValidation
}

View File

@@ -19,7 +19,7 @@ import type {
type EmailFieldClientWithoutType = MarkOptional<EmailFieldClient, 'type'>
type EmailFieldBaseClientProps = {
readonly autoComplete?: string
readonly path?: string
readonly validate?: EmailFieldValidation
}

View File

@@ -17,7 +17,12 @@ import type {
type GroupFieldClientWithoutType = MarkOptional<GroupFieldClient, 'type'>
export type GroupFieldClientProps = ClientFieldBase<GroupFieldClientWithoutType>
export type GroupFieldBaseClientProps = {
readonly path?: string
} & Pick<ServerFieldBase, 'permissions'>
export type GroupFieldClientProps = ClientFieldBase<GroupFieldClientWithoutType> &
GroupFieldBaseClientProps
export type GroupFieldServerProps = ServerFieldBase<GroupField, GroupFieldClientWithoutType>
@@ -26,7 +31,10 @@ export type GroupFieldServerComponent = FieldServerComponent<
GroupFieldClientWithoutType
>
export type GroupFieldClientComponent = FieldClientComponent<GroupFieldClientWithoutType>
export type GroupFieldClientComponent = FieldClientComponent<
GroupFieldClientWithoutType,
GroupFieldBaseClientProps
>
export type GroupFieldLabelServerComponent = FieldLabelServerComponent<
GroupField,

View File

@@ -1,11 +1,12 @@
import type { ClientField } from '../../fields/config/client.js'
import type { FormFieldBase } from '../types.js'
import type { ClientField } from '../../fields/config/types.js'
import type { ClientFieldBase } from '../types.js'
export type HiddenFieldProps = {
type HiddenFieldBaseClientProps = {
readonly disableModifyingForm?: false
readonly field?: {
readonly name?: string
} & Pick<ClientField, '_path'>
readonly forceUsePathFromProps?: boolean
} & ClientField
readonly value?: unknown
} & FormFieldBase
}
export type HiddenFieldProps = ClientFieldBase & HiddenFieldBaseClientProps

View File

@@ -19,6 +19,7 @@ import type {
type JSONFieldClientWithoutType = MarkOptional<JSONFieldClient, 'type'>
type JSONFieldBaseClientProps = {
readonly path?: string
readonly validate?: JSONFieldValidation
}

View File

@@ -17,7 +17,9 @@ import type {
type JoinFieldClientWithoutType = MarkOptional<JoinFieldClient, 'type'>
export type JoinFieldClientProps = ClientFieldBase<JoinFieldClientWithoutType>
export type JoinFieldClientProps = {
path?: string
} & ClientFieldBase<JoinFieldClientWithoutType>
export type JoinFieldServerProps = ServerFieldBase<JoinField>

View File

@@ -20,6 +20,7 @@ type NumberFieldClientWithoutType = MarkOptional<NumberFieldClient, 'type'>
type NumberFieldBaseClientProps = {
readonly onChange?: (e: number) => void
readonly path?: string
readonly validate?: NumberFieldValidation
}

View File

@@ -19,6 +19,7 @@ import type {
type PointFieldClientWithoutType = MarkOptional<PointFieldClient, 'type'>
type PointFieldBaseClientProps = {
readonly path?: string
readonly validate?: PointFieldValidation
}

View File

@@ -24,6 +24,7 @@ type RadioFieldBaseClientProps = {
*/
readonly disableModifyingForm?: boolean
readonly onChange?: OnChange
readonly path?: string
readonly validate?: RadioFieldValidation
readonly value?: string
}

View File

@@ -19,6 +19,7 @@ import type {
type RelationshipFieldClientWithoutType = MarkOptional<RelationshipFieldClient, 'type'>
type RelationshipFieldBaseClientProps = {
readonly path?: string
readonly validate?: RelationshipFieldValidation
}

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