Compare commits
682 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33c1f287f3 | ||
|
|
cd4861afda | ||
|
|
9f56ac182f | ||
|
|
3301f59822 | ||
|
|
f9ca3a9f96 | ||
|
|
3ba7594a65 | ||
|
|
6a1b25ab30 | ||
|
|
17dbbc7775 | ||
|
|
7e25abf87a | ||
|
|
0591dfd05b | ||
|
|
17610adf36 | ||
|
|
91814777b0 | ||
|
|
09d793926d | ||
|
|
a9f2f0ec03 | ||
|
|
66bf8c3cbd | ||
|
|
3967c1233f | ||
|
|
c929725dd5 | ||
|
|
9c6098b191 | ||
|
|
6daab398da | ||
|
|
36ef3789fb | ||
|
|
14cbf2f079 | ||
|
|
87bbf4416b | ||
|
|
785b992c3e | ||
|
|
b4695e10b6 | ||
|
|
0b0d971491 | ||
|
|
02af6b90b2 | ||
|
|
2181bc84a1 | ||
|
|
036cd5f831 | ||
|
|
da9825cd99 | ||
|
|
4a43f95952 | ||
|
|
9af9b73132 | ||
|
|
7f7d3dbeef | ||
|
|
8ef9206001 | ||
|
|
21ba237135 | ||
|
|
3bda163e7b | ||
|
|
e4e4ad1b08 | ||
|
|
f7352a7d08 | ||
|
|
6f6f2f8e7b | ||
|
|
5ca5abab42 | ||
|
|
9a7553099c | ||
|
|
55d0c917e6 | ||
|
|
f52daeccf0 | ||
|
|
6c871c57fc | ||
|
|
5322ada9e6 | ||
|
|
ee83a50ea9 | ||
|
|
f6b19e074c | ||
|
|
6cc1d9e41b | ||
|
|
74863f9462 | ||
|
|
fdcf029da2 | ||
|
|
3e3d151e4c | ||
|
|
5da204b152 | ||
|
|
3d6c3f7339 | ||
|
|
8d49517004 | ||
|
|
d1c0f2b97b | ||
|
|
1bc42ae098 | ||
|
|
c6edb7f53a | ||
|
|
1e048fe037 | ||
|
|
8fabdce584 | ||
|
|
5c1a3fabee | ||
|
|
fe6d30210b | ||
|
|
93f71e621c | ||
|
|
39d1a09d5a | ||
|
|
74ae6fd1d5 | ||
|
|
bbbcf8c869 | ||
|
|
b379666dec | ||
|
|
6f40b5c9ab | ||
|
|
b329be7dc1 | ||
|
|
c2ec54a7cb | ||
|
|
3641dfd38a | ||
|
|
5bf1354741 | ||
|
|
b894b809bf | ||
|
|
a4504ca15b | ||
|
|
7926083732 | ||
|
|
534cd5ae53 | ||
|
|
fb329a99ba | ||
|
|
9e726d9b90 | ||
|
|
8d065d619d | ||
|
|
cbff1776e7 | ||
|
|
e517695000 | ||
|
|
4370cfca0c | ||
|
|
4135b618ef | ||
|
|
1cfce87549 | ||
|
|
c48283ac1d | ||
|
|
328be3e4bc | ||
|
|
b4becd1493 | ||
|
|
95fac0bd62 | ||
|
|
a30d9dc1d7 | ||
|
|
7bfcefbfea | ||
|
|
131b2796e7 | ||
|
|
debcb003bb | ||
|
|
6e1dfff1b8 | ||
|
|
a9ebb71a09 | ||
|
|
3e34e5216f | ||
|
|
2400c58219 | ||
|
|
90d504526c | ||
|
|
c97d4f9545 | ||
|
|
09a8144f3c | ||
|
|
00ef1700ae | ||
|
|
3e03b2b5df | ||
|
|
974f79e57e | ||
|
|
34f42083b5 | ||
|
|
c0cae1e834 | ||
|
|
3ce8ee4661 | ||
|
|
f9feff58d6 | ||
|
|
73848b6037 | ||
|
|
7fd8124df6 | ||
|
|
1c77455403 | ||
|
|
051a0fad84 | ||
|
|
8e53ef47a0 | ||
|
|
918130486e | ||
|
|
b454811698 | ||
|
|
f87c68f310 | ||
|
|
25006d44e8 | ||
|
|
d8e51dd200 | ||
|
|
f54210a528 | ||
|
|
96dab15cd1 | ||
|
|
4126843619 | ||
|
|
e0238ad393 | ||
|
|
aa0302c05e | ||
|
|
1040ad2cfe | ||
|
|
c64f15d4d9 | ||
|
|
22ea98ca33 | ||
|
|
75bab716d1 | ||
|
|
52a8e9624c | ||
|
|
52cd3b4a7e | ||
|
|
cc63167307 | ||
|
|
314671b3b7 | ||
|
|
ef83bdb709 | ||
|
|
686085496a | ||
|
|
76ae20aa16 | ||
|
|
a1a55386f0 | ||
|
|
b3bb421c6c | ||
|
|
eabb981243 | ||
|
|
4be14a12d0 | ||
|
|
ce174878f3 | ||
|
|
14966796ae | ||
|
|
7b756f3421 | ||
|
|
9fea2b4e08 | ||
|
|
f9b1b1fe7f | ||
|
|
205404a88a | ||
|
|
813c46c86d | ||
|
|
8bfe253157 | ||
|
|
ed8e581629 | ||
|
|
7cf66b081a | ||
|
|
64503f8267 | ||
|
|
d82c12eab6 | ||
|
|
9c4f2b68b0 | ||
|
|
bcfda707ff | ||
|
|
66b77f7dca | ||
|
|
dcc8dad53b | ||
|
|
bb1477e08b | ||
|
|
4e165cf52e | ||
|
|
a1083727ef | ||
|
|
d5ccd45b53 | ||
|
|
94d355ba5e | ||
|
|
892a774998 | ||
|
|
78f39af5cf | ||
|
|
813fa1571c | ||
|
|
c40e232ac6 | ||
|
|
834f4ebd38 | ||
|
|
ca434b8a92 | ||
|
|
7acf944a28 | ||
|
|
391c9d8682 | ||
|
|
8e47961da1 | ||
|
|
016f3e47ab | ||
|
|
21eb19edd1 | ||
|
|
ad4f7a5fff | ||
|
|
cd209379b2 | ||
|
|
14a16dc05f | ||
|
|
9a461b8536 | ||
|
|
59af8725b4 | ||
|
|
dffeaf6a69 | ||
|
|
c7851f8189 | ||
|
|
ada1871993 | ||
|
|
78ccd2ad9b | ||
|
|
4cb69039ed | ||
|
|
c8b37f40cb | ||
|
|
4963f10a18 | ||
|
|
6f77fe7c27 | ||
|
|
c584c2ed47 | ||
|
|
5a19f6915a | ||
|
|
48f0c06edc | ||
|
|
50cc3c5a21 | ||
|
|
6674a0df6b | ||
|
|
cbbace21ca | ||
|
|
87346a97ab | ||
|
|
4357c5491d | ||
|
|
03847703b0 | ||
|
|
3c46851426 | ||
|
|
8115855e5a | ||
|
|
ddb2a0a4f5 | ||
|
|
a45577f182 | ||
|
|
eb963066f7 | ||
|
|
a99d9c98c3 | ||
|
|
cdfc0dec70 | ||
|
|
eb1ff7efce | ||
|
|
55843f6c55 | ||
|
|
7856f66b31 | ||
|
|
47ac86b4b7 | ||
|
|
13dc39dc6d | ||
|
|
65653bd1d3 | ||
|
|
299ee82ccf | ||
|
|
b38b6427b8 | ||
|
|
784696f9a6 | ||
|
|
8bd2a0e6c9 | ||
|
|
0f671b1b35 | ||
|
|
d56882cc20 | ||
|
|
6d13ae6846 | ||
|
|
91000d7fda | ||
|
|
888734dcdf | ||
|
|
e81da57f61 | ||
|
|
05d1b141b2 | ||
|
|
b7e5828adc | ||
|
|
2ee4c7ad72 | ||
|
|
77ab11bce8 | ||
|
|
cd8edbaa1f | ||
|
|
5e21048457 | ||
|
|
c0c093d16c | ||
|
|
117153fad7 | ||
|
|
5ae666b0e0 | ||
|
|
afa03789b8 | ||
|
|
44b0073834 | ||
|
|
254636167d | ||
|
|
29e82ec845 | ||
|
|
c3a0bd8625 | ||
|
|
32a4e8e9b9 | ||
|
|
482cbe71c7 | ||
|
|
7dbcd9ca89 | ||
|
|
a1f2dcab8f | ||
|
|
21417c6598 | ||
|
|
f1a272f407 | ||
|
|
064f797023 | ||
|
|
26c2020b12 | ||
|
|
af1a483e64 | ||
|
|
f517cb5e93 | ||
|
|
c8a8914ea7 | ||
|
|
4f834c6bf8 | ||
|
|
d588843121 | ||
|
|
51c7770b10 | ||
|
|
18ff5d29b0 | ||
|
|
e31098eaa5 | ||
|
|
0873050b32 | ||
|
|
8df9ee7b2d | ||
|
|
a9ef557ca4 | ||
|
|
689fa008fb | ||
|
|
1a4ce65e6c | ||
|
|
f6dff04cfe | ||
|
|
a73c391c2c | ||
|
|
e88c7ca4b2 | ||
|
|
0cd8446735 | ||
|
|
6a3cfced9a | ||
|
|
0586d7aa7d | ||
|
|
3736755a12 | ||
|
|
d727fc8e24 | ||
|
|
50b0303ab3 | ||
|
|
6bc6e7bb61 | ||
|
|
cf2eb3e482 | ||
|
|
4aa5500593 | ||
|
|
c7432fb6e6 | ||
|
|
f895443e33 | ||
|
|
32cebb56ff | ||
|
|
4a6b01d094 | ||
|
|
f8295f7577 | ||
|
|
a2fa99d06f | ||
|
|
c508ac6dee | ||
|
|
4900fa799f | ||
|
|
135b1bcdd7 | ||
|
|
25f5d68b74 | ||
|
|
1ed867ce0c | ||
|
|
5ef5f09a5c | ||
|
|
bc97d3d6f2 | ||
|
|
b21a56fdf7 | ||
|
|
97f2a9b484 | ||
|
|
4ef6801230 | ||
|
|
8e21a91b2c | ||
|
|
38a1a38c0c | ||
|
|
2b7785d101 | ||
|
|
2cf9d35fed | ||
|
|
4e1f9c7280 | ||
|
|
f45d5a0421 | ||
|
|
39586d3cdb | ||
|
|
5e66e3ee78 | ||
|
|
a284f6c066 | ||
|
|
fdc0ad8a3c | ||
|
|
fa01b353ef | ||
|
|
dac3308441 | ||
|
|
11d8fc71e8 | ||
|
|
771bbaedbc | ||
|
|
040833ead8 | ||
|
|
7c2acb4324 | ||
|
|
88fe797007 | ||
|
|
5825d7e64f | ||
|
|
97ff49f2bf | ||
|
|
ccada2e8c9 | ||
|
|
1d8bcd6e16 | ||
|
|
1b58094e6f | ||
|
|
1437bfe0c9 | ||
|
|
171da32b00 | ||
|
|
a3edbf4fef | ||
|
|
8ab4ec8d54 | ||
|
|
f8365abf1b | ||
|
|
078e8dcc51 | ||
|
|
b1a1575122 | ||
|
|
201a8e1053 | ||
|
|
ff4c1a1c01 | ||
|
|
7c449e0d30 | ||
|
|
2ca526bd22 | ||
|
|
48f929f3ab | ||
|
|
f2bec90663 | ||
|
|
1c7445dc7f | ||
|
|
78630cafa2 | ||
|
|
0a40dd43cb | ||
|
|
32fb72a5bb | ||
|
|
cbb1c84be7 | ||
|
|
145e1db05d | ||
|
|
4115045c15 | ||
|
|
d0744f3702 | ||
|
|
e7caaf57a9 | ||
|
|
fba0847f0f | ||
|
|
566c6ba3a9 | ||
|
|
b860959fea | ||
|
|
9e4e4b231c | ||
|
|
b8421ddc0c | ||
|
|
9165b25fd6 | ||
|
|
f615abc9b1 | ||
|
|
9237dd67da | ||
|
|
e1347d5a39 | ||
|
|
ca852e8cb2 | ||
|
|
d0da3d7962 | ||
|
|
2ae33b603a | ||
|
|
6a6a69190f | ||
|
|
a7b882c03d | ||
|
|
a83921a2fe | ||
|
|
4b7b04d5fa | ||
|
|
b403f5228b | ||
|
|
dd289ca24a | ||
|
|
daf5fc83d8 | ||
|
|
08271086c3 | ||
|
|
30da37becd | ||
|
|
df1491de74 | ||
|
|
420fd67905 | ||
|
|
17a55e4a3a | ||
|
|
1b3a328388 | ||
|
|
060ee40bab | ||
|
|
103966d36e | ||
|
|
1ce2dc8e6d | ||
|
|
2f4f075441 | ||
|
|
50972b98a1 | ||
|
|
874c001d3b | ||
|
|
63519a8f54 | ||
|
|
63a0336e98 | ||
|
|
ae51836187 | ||
|
|
888e756c6e | ||
|
|
a8d2e09952 | ||
|
|
23d2ef5027 | ||
|
|
3735a28b0c | ||
|
|
112defe846 | ||
|
|
804f3ced95 | ||
|
|
209b02b069 | ||
|
|
a5918059a8 | ||
|
|
c32dfea356 | ||
|
|
3b2061e54a | ||
|
|
61673907ca | ||
|
|
1e72301cdf | ||
|
|
055d1b9ea1 | ||
|
|
8d968b7690 | ||
|
|
2989ec5100 | ||
|
|
c4d7df8bba | ||
|
|
d6cd5b1f0d | ||
|
|
e9b3f3f060 | ||
|
|
ed80c30c74 | ||
|
|
3343adb952 | ||
|
|
a8aaa6deea | ||
|
|
3c8fa72dc8 | ||
|
|
91c4f4f980 | ||
|
|
b2406b3696 | ||
|
|
b8504ffb25 | ||
|
|
0294c02aed | ||
|
|
31680dcdea | ||
|
|
d91b69c465 | ||
|
|
9c0c606b20 | ||
|
|
6791352ce8 | ||
|
|
d17d227fad | ||
|
|
c175476e74 | ||
|
|
f0ff1c7c99 | ||
|
|
d0d498e9c7 | ||
|
|
da2a262208 | ||
|
|
2f86a39e38 | ||
|
|
408b66590a | ||
|
|
663cae4788 | ||
|
|
3a66860ab2 | ||
|
|
9abb74abf7 | ||
|
|
bf47f69258 | ||
|
|
0b57e45ee4 | ||
|
|
bf942bf015 | ||
|
|
b2ae5da62a | ||
|
|
858b1afa54 | ||
|
|
bc83153b60 | ||
|
|
1997153549 | ||
|
|
b479013f00 | ||
|
|
211b960440 | ||
|
|
7157f70790 | ||
|
|
84611aff2c | ||
|
|
bd4dd45f8e | ||
|
|
f190ad3576 | ||
|
|
9813216ea9 | ||
|
|
d5e88cc1a9 | ||
|
|
5dc7caf356 | ||
|
|
a8a21e119b | ||
|
|
2c9d45c59c | ||
|
|
d176633804 | ||
|
|
f65594be43 | ||
|
|
f0466f885f | ||
|
|
309a8cde95 | ||
|
|
61f0e8ea9f | ||
|
|
af00eb9cc4 | ||
|
|
61e8a4d6ee | ||
|
|
55a273d982 | ||
|
|
0993223e5c | ||
|
|
08a38b03b9 | ||
|
|
b79ae00603 | ||
|
|
c96985be0c | ||
|
|
36e9acc637 | ||
|
|
e541713282 | ||
|
|
5d57bfa438 | ||
|
|
c5bcd1e341 | ||
|
|
cfb5540e64 | ||
|
|
38d1794ee2 | ||
|
|
8feed39fb9 | ||
|
|
3140f9c3b8 | ||
|
|
e13c6fa955 | ||
|
|
b201168bd0 | ||
|
|
e5e675c14a | ||
|
|
9589f0798f | ||
|
|
e1c35b5220 | ||
|
|
f22f56e73c | ||
|
|
61280e36c4 | ||
|
|
3b4d5afd41 | ||
|
|
3a3026cd63 | ||
|
|
2a1f387bcc | ||
|
|
6f8b8d0fb8 | ||
|
|
8a81d0b274 | ||
|
|
e2c366f536 | ||
|
|
4415f9db48 | ||
|
|
f6ff308dc4 | ||
|
|
546011d8a9 | ||
|
|
60c0f0fb08 | ||
|
|
4b6f451022 | ||
|
|
3fb2bcfac7 | ||
|
|
66c820c863 | ||
|
|
09f57e9a4c | ||
|
|
6f748f1adb | ||
|
|
893772ebd8 | ||
|
|
b987cb8dc4 | ||
|
|
aa322b2cf2 | ||
|
|
4c68f64829 | ||
|
|
cc360191b5 | ||
|
|
1d06117619 | ||
|
|
446a4e1d8a | ||
|
|
428edb05c4 | ||
|
|
21708bcc1c | ||
|
|
23d605c5aa | ||
|
|
b4ffa22885 | ||
|
|
d2bc4b72a0 | ||
|
|
27c352c2ff | ||
|
|
19a354863f | ||
|
|
33f4a1322d | ||
|
|
563f98e2fb | ||
|
|
1e4b94c508 | ||
|
|
cd8d1c7ace | ||
|
|
2ea545f0cf | ||
|
|
e8a1cdafba | ||
|
|
1f8b5d8e1a | ||
|
|
793dfe96b9 | ||
|
|
6533af9c0e | ||
|
|
87ead2f89b | ||
|
|
28e3928094 | ||
|
|
dc41372acb | ||
|
|
6a9ea638d5 | ||
|
|
2a7651eb9c | ||
|
|
9021d5352d | ||
|
|
dc5c9ee3a0 | ||
|
|
079623f40a | ||
|
|
b59ba230de | ||
|
|
6826e44072 | ||
|
|
878fd4ab92 | ||
|
|
abfb1868ab | ||
|
|
7319a8e90d | ||
|
|
d2be893a5a | ||
|
|
cc8b636248 | ||
|
|
a103665bd9 | ||
|
|
0d1603b467 | ||
|
|
d6a7b8c319 | ||
|
|
f5ad7a163a | ||
|
|
d433351dbd | ||
|
|
3601ef90bc | ||
|
|
76b6c736e1 | ||
|
|
a194a64845 | ||
|
|
b00d8584f3 | ||
|
|
39d075b999 | ||
|
|
e57a5741e5 | ||
|
|
a6a9ab15b7 | ||
|
|
4280a59c77 | ||
|
|
3259e34e27 | ||
|
|
3132d35e27 | ||
|
|
7376246592 | ||
|
|
69ce6d78da | ||
|
|
b43e8c5db0 | ||
|
|
e348bdcc21 | ||
|
|
039c3ec110 | ||
|
|
f98ef62ad3 | ||
|
|
17ebcc6925 | ||
|
|
6347a2febf | ||
|
|
2ef8a1e35a | ||
|
|
df11478905 | ||
|
|
e7d2bdb45a | ||
|
|
1564fcaeb3 | ||
|
|
cf38e8d520 | ||
|
|
bb9f28fb08 | ||
|
|
d8a28acfa8 | ||
|
|
b0eccdd12e | ||
|
|
1427fb4078 | ||
|
|
1812be2e16 | ||
|
|
db87179576 | ||
|
|
0d8d41ce7e | ||
|
|
a2f95f6cea | ||
|
|
fa9bd6191c | ||
|
|
bc9de859c4 | ||
|
|
73d04262ad | ||
|
|
6e7d547da5 | ||
|
|
6060e4cb27 | ||
|
|
b5ce54c7ae | ||
|
|
492a1308cc | ||
|
|
147a4f8624 | ||
|
|
eac982398e | ||
|
|
9afd51b7dd | ||
|
|
dd810a3593 | ||
|
|
75a4c52071 | ||
|
|
2707af1d45 | ||
|
|
b7d49db536 | ||
|
|
3a98c6ad53 | ||
|
|
79f3de8042 | ||
|
|
81024e4753 | ||
|
|
e2c333ac02 | ||
|
|
1bdacb9bda | ||
|
|
2fa680cdf8 | ||
|
|
3a17a8a001 | ||
|
|
afbb014cbd | ||
|
|
c75c67ec86 | ||
|
|
e8b72f186f | ||
|
|
a777032894 | ||
|
|
624e25c077 | ||
|
|
34d0b89376 | ||
|
|
fcd3b0d2cb | ||
|
|
5512022a10 | ||
|
|
28e31fee72 | ||
|
|
779a60a42c | ||
|
|
ba29898d97 | ||
|
|
a636c65e34 | ||
|
|
ff3e137c78 | ||
|
|
8c4e0fa7b2 | ||
|
|
3ae1c26a07 | ||
|
|
7a6f2392fa | ||
|
|
07949525ef | ||
|
|
3b9fdf3ffd | ||
|
|
2694c0d5bd | ||
|
|
4f8b5b9f85 | ||
|
|
e75cca4512 | ||
|
|
68e7c41fdc | ||
|
|
735e385537 | ||
|
|
c995f797fe | ||
|
|
54162b52cc | ||
|
|
66fc06895a | ||
|
|
85b7a490eb | ||
|
|
4b0a257246 | ||
|
|
29f1af7eb4 | ||
|
|
ba79b4446c | ||
|
|
40b6afff2d | ||
|
|
a664b627f6 | ||
|
|
3a4657e368 | ||
|
|
4cb565f1cc | ||
|
|
31ca363982 | ||
|
|
4a3588e965 | ||
|
|
11600930b7 | ||
|
|
4bb0d3994f | ||
|
|
5fc4f3adad | ||
|
|
41a0ba5780 | ||
|
|
f7a81d70ac | ||
|
|
baf4664073 | ||
|
|
48700a93e3 | ||
|
|
49d09a349f | ||
|
|
31bc4c6532 | ||
|
|
aa89251a3b | ||
|
|
057846e250 | ||
|
|
5a5e3b589c | ||
|
|
270dd22f08 | ||
|
|
beec04a1bc | ||
|
|
60bfb1c3b8 | ||
|
|
98676bea69 | ||
|
|
8589fdefda | ||
|
|
8ba25e8602 | ||
|
|
de43e21ebc | ||
|
|
90ba15f9bd | ||
|
|
b9f9f15d77 | ||
|
|
cac5266c79 | ||
|
|
420afc6838 | ||
|
|
4e3c4e9c0c | ||
|
|
2c27812ba1 | ||
|
|
2f57a983cd | ||
|
|
91c4ef226b | ||
|
|
38b52bf67b | ||
|
|
281985970d | ||
|
|
03f28a4804 | ||
|
|
c0acba94c6 | ||
|
|
8d550d411e | ||
|
|
e8064371b0 | ||
|
|
166bd31506 | ||
|
|
4055908bc8 | ||
|
|
d68bb8c292 | ||
|
|
c8be171f24 | ||
|
|
43eafd4b9f | ||
|
|
ce1c99b01c | ||
|
|
4b2bc36f89 | ||
|
|
58587525e5 | ||
|
|
df76f60e7f | ||
|
|
2c66ad8689 | ||
|
|
6016e2346c | ||
|
|
56cdd943fd | ||
|
|
6d02f7d3ac | ||
|
|
18f8790062 | ||
|
|
854b63ec34 | ||
|
|
beccbbd3e8 | ||
|
|
91d0d84c65 | ||
|
|
7dd67a8d39 | ||
|
|
ad43cbc808 | ||
|
|
2b2a562d83 | ||
|
|
d9e7696384 | ||
|
|
01bc1fef1e | ||
|
|
567d8c19bf | ||
|
|
b722bed24f | ||
|
|
0af66d68a7 | ||
|
|
f3b7dcff57 | ||
|
|
67331eb975 | ||
|
|
d9ef803d20 | ||
|
|
9fd171b26d | ||
|
|
91e33d1c1c | ||
|
|
601e69ab0d | ||
|
|
81aa74bbbe | ||
|
|
e84b7a9c58 | ||
|
|
b6b0ffb674 | ||
|
|
62bd2db5f9 | ||
|
|
74342a4dea | ||
|
|
c78d77446a | ||
|
|
6eb4fc04b1 | ||
|
|
f1b00e85fc | ||
|
|
7c73f2c1d9 | ||
|
|
704e543c7e | ||
|
|
fcaa1454dc | ||
|
|
9d388f7a89 | ||
|
|
918062de2f | ||
|
|
375738d99c | ||
|
|
1c37ec3902 | ||
|
|
7eb804daf9 | ||
|
|
d99a67ca95 | ||
|
|
3d5ed93fce | ||
|
|
b80006be45 | ||
|
|
70edb0ba38 | ||
|
|
a1fe17d05d | ||
|
|
7083225abd | ||
|
|
af6479bf34 | ||
|
|
44c12325b4 | ||
|
|
f2bf2399fa | ||
|
|
7fe2fece6c | ||
|
|
bf0fe090ab | ||
|
|
397dba4a30 | ||
|
|
593d6a596a | ||
|
|
82a6db8b4f | ||
|
|
28aa3dd17b | ||
|
|
3d4d807370 | ||
|
|
c18cc23c71 | ||
|
|
7ee374ea1a | ||
|
|
20bbda95c6 |
24
.eslintrc.js
24
.eslintrc.js
@@ -21,6 +21,28 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['test/**/int.spec.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/consistent-type-imports': 'warn',
|
||||
'jest/prefer-strict-equal': 'off',
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['test/**/e2e.spec.ts'],
|
||||
extends: [
|
||||
'plugin:playwright/playwright-test'
|
||||
],
|
||||
rules: {
|
||||
'jest/consistent-test-it': 'off',
|
||||
'jest/require-top-level-describe': 'off',
|
||||
'jest/no-test-callback': 'off',
|
||||
'jest/prefer-strict-equal': 'off',
|
||||
'jest/expect-expect': 'off',
|
||||
'jest-dom/prefer-to-have-attribute': 'off',
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
@@ -66,7 +88,7 @@ module.exports = {
|
||||
'no-underscore-dangle': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'arrow-body-style': 0,
|
||||
'@typescript-eslint/no-use-before-define': ['error'],
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'ignorePackages',
|
||||
|
||||
13
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
13
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
@@ -1,27 +1,28 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a bug report for Payload
|
||||
labels: 'bug'
|
||||
labels: 'possible-bug'
|
||||
---
|
||||
|
||||
# Bug Report
|
||||
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
<!--- Tell us what should happen -->
|
||||
|
||||
## Current Behavior
|
||||
|
||||
<!--- Tell us what happens instead of the expected behavior -->
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
<!--- Tell us what you expected happen -->
|
||||
|
||||
## Possible Solution
|
||||
|
||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||
<!--- Optional. If familiar with the codebase, suggest a fix/reason for the bug. -->
|
||||
|
||||
## Steps to Reproduce
|
||||
<!--- Steps to reproduce this bug. Include any code, if relevant -->
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
33
.github/workflows/tests.yml
vendored
33
.github/workflows/tests.yml
vendored
@@ -1,13 +1,17 @@
|
||||
name: build
|
||||
|
||||
on: [push]
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, edited, synchronize]
|
||||
push:
|
||||
branches: ['master']
|
||||
|
||||
jobs:
|
||||
build_yarn:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x, 16.x]
|
||||
node-version: [14.x, 16.x, 18.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -31,11 +35,26 @@ jobs:
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
- run: yarn build
|
||||
- run: yarn test:client
|
||||
- run: yarn test:int # In-memory db + api tests
|
||||
- run: yarn demo:generate:types
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Component Tests
|
||||
run: yarn test:components
|
||||
- name: Integration Tests
|
||||
run: yarn test:int
|
||||
|
||||
- name: Generate Payload Types
|
||||
run: yarn dev:generate-types fields
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
- name: E2E Tests
|
||||
run: yarn test:e2e --bail
|
||||
|
||||
# - uses: actions/upload-artifact@v2
|
||||
# if: always()
|
||||
# with:
|
||||
# name: playwright-report
|
||||
# path: playwright-report/
|
||||
# retention-days: 30
|
||||
install_npm:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -2,8 +2,6 @@ coverage
|
||||
package-lock.json
|
||||
dist
|
||||
.idea
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
|
||||
# Created by https://www.gitignore.io/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
|
||||
|
||||
@@ -88,6 +86,15 @@ typings/
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# Yarn Berry
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
@@ -233,3 +240,4 @@ components/styles.css
|
||||
|
||||
# Ignore generated
|
||||
demo/generated-types.ts
|
||||
demo/generated-schema.graphql
|
||||
|
||||
1
.node-version
Normal file
1
.node-version
Normal file
@@ -0,0 +1 @@
|
||||
v16.14.2
|
||||
44
.vscode/launch.json
vendored
44
.vscode/launch.json
vendored
@@ -29,55 +29,17 @@
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"env": {
|
||||
"PAYLOAD_CONFIG_PATH": "demo/payload.config.ts",
|
||||
"BABEL_ENV": "development"
|
||||
},
|
||||
"program": "${workspaceFolder}/demo/index.js",
|
||||
"program": "${workspaceFolder}/test/dev.js",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/babel-node",
|
||||
"runtimeArgs": [
|
||||
"--nolazy"
|
||||
],
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program - Production",
|
||||
"env": {
|
||||
"PAYLOAD_CONFIG_PATH": "demo/payload.config.ts",
|
||||
"NODE_ENV": "production",
|
||||
"BABEL_ENV": "development"
|
||||
},
|
||||
"program": "${workspaceFolder}/demo/index.js",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/babel-node",
|
||||
"outputCapture": "std",
|
||||
"runtimeArgs": [
|
||||
"--nolazy"
|
||||
],
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against Localhost",
|
||||
"url": "http://localhost:3000/admin",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Payload Generate Types",
|
||||
"program": "${workspaceFolder}/src/bin/generateTypes.ts",
|
||||
"env": {
|
||||
"PAYLOAD_CONFIG_PATH": "demo/payload.config.ts",
|
||||
},
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/dist/**/*.js",
|
||||
"!**/node_modules/**"
|
||||
"args": [
|
||||
"fields"
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
953
CHANGELOG.md
953
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
10
README.md
10
README.md
@@ -1,6 +1,9 @@
|
||||
<h1 align="center">Payload</h1>
|
||||
<p align="center">A free and open-source TypeScript headless CMS & application framework built with Express, MongoDB and React.</p>
|
||||
<p align="center">
|
||||
<a href="https://opensource.org/licenses/MIT">
|
||||
<img src="https://img.shields.io/badge/License-MIT-blue.svg" />
|
||||
</a>
|
||||
<a href="https://github.com/payloadcms/payload/actions">
|
||||
<img src="https://github.com/payloadcms/payload/workflows/build/badge.svg" />
|
||||
</a>
|
||||
@@ -45,10 +48,10 @@
|
||||
|
||||
Payload is a CMS that has been designed for developers from the ground up to deliver them what they need to build great digital products. If you know JavaScript, you know Payload. It's a _code-first_ CMS, which allows us to do a lot of things right:
|
||||
|
||||
- Payload gives you everything you need, but then steps back and lets you build what you want in JavaScript or TypeScript - with no unnecessary complexity brought by GUIs. You'll understand how your CMS works, because you will have written it exactly how you want it.
|
||||
- Payload gives you everything you need, but then steps back and lets you build what you want in JavaScript or TypeScript - with no unnecessary complexity brought by GUIs. You'll understand how your CMS works because you will have written it exactly how you want it.
|
||||
- Bring your own Express server and do whatever you need on top of Payload. Payload doesn't impose anything on you or your app.
|
||||
- Completely control the Admin panel by using your own React components. Swap out fields or even entire views with ease.
|
||||
- Use your data however and wherever you need thanks to auto-generated, yet fully extensible REST, GraphQL and Local Node APIs.
|
||||
- Use your data however and wherever you need thanks to auto-generated, yet fully extensible REST, GraphQL, and Local Node APIs.
|
||||
|
||||
### Quick Start
|
||||
|
||||
@@ -65,6 +68,9 @@ Alternatively, it only takes about five minutes to [create an app from scratch](
|
||||
### Documentation
|
||||
|
||||
Check out the [Payload website](https://payloadcms.com/docs/getting-started/what-is-payload) to find in-depth documentation for everything that Payload offers.
|
||||
### Contributing
|
||||
|
||||
If you want to add contributions to this repository, please follow the instructions in [contributing.md](./contributing.md).
|
||||
|
||||
### Other Resources
|
||||
|
||||
|
||||
@@ -2,3 +2,4 @@ export { default as Button } from '../dist/admin/components/elements/Button';
|
||||
export { default as Card } from '../dist/admin/components/elements/Card';
|
||||
export { default as Eyebrow } from '../dist/admin/components/elements/Eyebrow';
|
||||
export { default as Nav } from '../dist/admin/components/elements/Nav';
|
||||
export { Gutter } from '../dist/admin/components/elements/Gutter';
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
export {
|
||||
useForm,
|
||||
/**
|
||||
* @deprecated useWatchForm is no longer preferred. If you need all form fields, prefer `useAllFormFields`.
|
||||
*/
|
||||
useWatchForm,
|
||||
useFormFields,
|
||||
useAllFormFields,
|
||||
useFormSubmitted,
|
||||
useFormProcessing,
|
||||
useFormModified,
|
||||
} from '../dist/admin/components/forms/Form/context';
|
||||
|
||||
export { default as useField } from '../dist/admin/components/forms/useField';
|
||||
|
||||
/**
|
||||
* @deprecated This method is now called useField. The useFieldType alias will be removed in an upcoming version.
|
||||
*/
|
||||
export { default as useFieldType } from '../dist/admin/components/forms/useField';
|
||||
|
||||
export { default as Form } from '../dist/admin/components/forms/Form';
|
||||
@@ -24,5 +33,6 @@ export { default as Submit } from '../dist/admin/components/forms/Submit';
|
||||
export { default as Label } from '../dist/admin/components/forms/Label';
|
||||
|
||||
export { default as reduceFieldsToValues } from '../dist/admin/components/forms/Form/reduceFieldsToValues';
|
||||
export { default as getSiblingData } from '../dist/admin/components/forms/Form/getSiblingData';
|
||||
|
||||
export { default as withCondition } from '../dist/admin/components/forms/withCondition';
|
||||
|
||||
@@ -20,9 +20,39 @@ Payload documentation can be found directly within its codebase and you can feel
|
||||
|
||||
If you're an incredibly awesome person and want to help us make Payload even better through new features or additions, we would be thrilled to work with you.
|
||||
|
||||
To help us work on new features, you can reach out to our Development team at [`dev@payloadcms.com`](mailto:dev@payloadcms.com). Be as complete and descriptive as possible regarding your vision and we'll go from there!
|
||||
### Before Starting
|
||||
|
||||
To help us work on new features, you can create a new feature request post in [GitHub Discussion](https://github.com/payloadcms/payload/discussions) or discuss it in our [Discord](https://discord.com/invite/r6sCXqVk3v). New functionality often has large implications across the entire Payload repo, so it is best to discuss the architecture and approach before starting work on a pull request.
|
||||
|
||||
### Code
|
||||
|
||||
Most new functionality should keep testing in mind. With 1.0, testability of new features has been vastly improved. All top-level directories within the `test/` directory are for testing a specific category: `fields`, `collections`, etc.
|
||||
|
||||
If it makes sense to add your feature to an existing test directory, please do so.
|
||||
|
||||
A typical directory with `test/` will be structured like this:
|
||||
|
||||
```text
|
||||
.
|
||||
├── config.ts
|
||||
├── int.spec.ts
|
||||
├── e2e.spec.ts
|
||||
└── payload-types.ts
|
||||
```
|
||||
|
||||
- `config.ts` - This is the _granular_ Payload config for testing. It should be as lightweight as possible. Reference existing configs for an example
|
||||
- `int.spec.ts` - This is the test file run by jest. Any test file must have a `*int.spec.ts` suffix.
|
||||
- `e2e.spec.ts` - This is the end-to-end test file that will load up the admin UI using the above config and run Playwright tests. These tests are typically only needed if a large change is being made to the Admin UI.
|
||||
- `payload-types.ts` - Generated types from `config.ts`. Generate this file by running `yarn dev:generate-types my-test-dir`.
|
||||
|
||||
The directory split up in this way specifically to reduce friction when creating tests and to add the ability to boot up Payload with that specific config.
|
||||
|
||||
The following command will start Payload with your config: `yarn dev my-test-dir`. This command will start up Payload using your config and refresh a test database on every restart.
|
||||
|
||||
When switching between test directories, you will want to remove your `node_modules/.cache ` manually or by running `yarn clean:cache`.
|
||||
|
||||
NOTE: It is recommended to add the test credentials (located in `test/credentials.ts`) to your autofill for `localhost:3000/admin` as this will be required on every nodemon restart.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
For all Pull Requests, you should be extremely descriptive about both your problem and proposed solution. If there are any affected open or closed issues, please leave the issue number in your PR message.
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"$schema": "https://on.cypress.io/cypress.schema.json",
|
||||
"testFiles": "**/*e2e.ts",
|
||||
"ignoreTestFiles": "**/examples/*spec.js",
|
||||
"viewportWidth": 1440,
|
||||
"viewportHeight": 900,
|
||||
"baseUrl": "http://localhost:3000"
|
||||
}
|
||||
16
cypress/cypress.d.ts
vendored
16
cypress/cypress.d.ts
vendored
@@ -1,16 +0,0 @@
|
||||
export { };
|
||||
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable<Subject> {
|
||||
visitAdmin(): Chainable<any>
|
||||
/**
|
||||
* Login
|
||||
*
|
||||
* Creates user if not exists
|
||||
*/
|
||||
login(): Chainable<any>
|
||||
apiLogin(): Chainable<any>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { adminURL } from './common/constants';
|
||||
import { credentials } from './common/credentials';
|
||||
|
||||
describe('Collections', () => {
|
||||
const collectionName = 'Admins';
|
||||
|
||||
before(() => {
|
||||
cy.apiLogin();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visitAdmin();
|
||||
});
|
||||
|
||||
it('can view collection', () => {
|
||||
cy.contains(collectionName).click();
|
||||
|
||||
cy.get('.collection-list__wrap')
|
||||
.should('be.visible');
|
||||
cy.get('.collection-list__header')
|
||||
.contains(collectionName)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get('.table')
|
||||
.contains(credentials.email)
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
it('can create new', () => {
|
||||
cy.contains(collectionName).click();
|
||||
|
||||
cy.contains('Create New').click();
|
||||
cy.url().should('contain', `${adminURL}/collections/${collectionName.toLowerCase()}/create`);
|
||||
});
|
||||
it('can create new - plus button', () => {
|
||||
cy.contains(collectionName)
|
||||
.get('.card__actions')
|
||||
.first()
|
||||
.click();
|
||||
|
||||
cy.url().should('contain', `${adminURL}/collections/${collectionName.toLowerCase()}/create`);
|
||||
});
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
export const adminURL = 'http://localhost:3000/admin';
|
||||
@@ -1,5 +0,0 @@
|
||||
export const credentials = {
|
||||
email: 'test@test.com',
|
||||
password: 'test123',
|
||||
roles: ['admin'],
|
||||
};
|
||||
@@ -1,76 +0,0 @@
|
||||
describe('Fields', () => {
|
||||
before(() => {
|
||||
cy.apiLogin();
|
||||
});
|
||||
|
||||
describe('Array', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/admin/collections/all-fields/create');
|
||||
});
|
||||
|
||||
it('can add and remove rows', () => {
|
||||
cy.contains('Add Array').click();
|
||||
|
||||
cy.contains('Array Text 1')
|
||||
.should('be.visible');
|
||||
|
||||
cy.get('.action-panel__add-row').first().click();
|
||||
|
||||
cy.get('.field-type.array')
|
||||
.filter(':contains("Editable Array")')
|
||||
.should('contain', '02');
|
||||
|
||||
cy.get('.action-panel__remove-row').first().click();
|
||||
|
||||
cy.get('.field-type.array')
|
||||
.filter(':contains("Editable Array")')
|
||||
.should('not.contain', '02')
|
||||
.should('contain', '01');
|
||||
});
|
||||
|
||||
it('can be readOnly', () => {
|
||||
cy.get('.field-type.array')
|
||||
.filter(':contains("readOnly Array")')
|
||||
.should('not.contain', 'Add Row');
|
||||
|
||||
cy.get('.field-type.array')
|
||||
.filter(':contains("readOnly Array")')
|
||||
.children('.action-panel__add-row')
|
||||
.should('not.exist');
|
||||
|
||||
cy.get('.field-type.array')
|
||||
.filter(':contains("readOnly Array")')
|
||||
.children('.position-panel__move-backward')
|
||||
.should('not.exist');
|
||||
|
||||
cy.get('.field-type.array')
|
||||
.filter(':contains("readOnly Array")')
|
||||
.children('.position-panel__move-forward')
|
||||
.should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Admin', () => {
|
||||
describe('Conditions', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/admin/collections/conditions/create');
|
||||
});
|
||||
|
||||
it('can see conditional fields', () => {
|
||||
cy.get('#simpleCondition')
|
||||
.should('not.exist');
|
||||
|
||||
cy.get('#customComponent')
|
||||
.should('not.exist');
|
||||
|
||||
cy.contains('Enable Test').click();
|
||||
|
||||
cy.get('#simpleCondition')
|
||||
.should('be.visible');
|
||||
|
||||
cy.get('#customComponent')
|
||||
.should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,99 +0,0 @@
|
||||
/* eslint-disable jest/expect-expect */
|
||||
import { adminURL } from './common/constants';
|
||||
import { credentials } from './common/credentials';
|
||||
|
||||
// running login more than one time is not working
|
||||
const viewportSizes: Cypress.ViewportPreset[] = [
|
||||
'macbook-15',
|
||||
// 'iphone-x',
|
||||
// 'ipad-2',
|
||||
];
|
||||
|
||||
// intermittent failure
|
||||
describe.skip('Payload Login', () => {
|
||||
beforeEach(() => {
|
||||
cy.clearCookies();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.apiLogin();
|
||||
});
|
||||
|
||||
viewportSizes.forEach((viewportSize) => {
|
||||
describe(`Login (${viewportSize})`, () => {
|
||||
beforeEach(() => {
|
||||
cy.visit(adminURL);
|
||||
});
|
||||
|
||||
it('success', () => {
|
||||
cy.viewport(viewportSize);
|
||||
|
||||
cy.url().should('include', '/admin/login');
|
||||
|
||||
cy.get('.field-type.email input').type(credentials.email);
|
||||
cy.get('.field-type.password input').type(credentials.password);
|
||||
cy.get('form')
|
||||
.contains('form', 'Login')
|
||||
.should('be.visible')
|
||||
.submit();
|
||||
cy.get('.template-default')
|
||||
.find('h3.dashboard__label')
|
||||
.should('have.length', 2); // TODO: Should assert label content
|
||||
cy.url().should('eq', adminURL);
|
||||
});
|
||||
|
||||
// skip due to issue with cookies not being reset between tests
|
||||
it.skip('bad Password', () => {
|
||||
cy.viewport(viewportSize);
|
||||
|
||||
cy.visit(adminURL);
|
||||
cy.get('#email').type(credentials.email);
|
||||
cy.get('#password').type('badpassword');
|
||||
cy.get('form')
|
||||
.contains('form', 'Login')
|
||||
.should('be.visible')
|
||||
.submit();
|
||||
|
||||
cy.get('.Toastify')
|
||||
.contains('The email or password provided is incorrect.')
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
// skip due to issue with cookies not being reset between tests
|
||||
it.skip('bad Password - Retry Success', () => {
|
||||
cy.viewport(viewportSize);
|
||||
|
||||
cy.visit(adminURL);
|
||||
cy.get('#email').type(credentials.email);
|
||||
cy.get('#password').type('badpassword');
|
||||
cy.get('form')
|
||||
.contains('form', 'Login')
|
||||
.should('be.visible')
|
||||
.submit();
|
||||
|
||||
cy.get('.Toastify')
|
||||
.contains('The email or password provided is incorrect.')
|
||||
.should('be.visible');
|
||||
|
||||
// Dismiss notification
|
||||
cy.wait(500);
|
||||
cy.get('.Toastify__toast-body').click();
|
||||
cy.wait(200);
|
||||
cy.get('.Toastify__toast-body').should('not.be.visible');
|
||||
cy.url().should('eq', `${adminURL}/login`);
|
||||
|
||||
cy.get('#password').clear().type(credentials.password);
|
||||
cy.get('form')
|
||||
.contains('form', 'Login')
|
||||
.should('be.visible')
|
||||
.submit();
|
||||
|
||||
cy.get('.template-default')
|
||||
.find('h3.dashboard__label')
|
||||
.should('have.length', 2); // TODO: Should assert label content
|
||||
|
||||
cy.url().should('eq', adminURL);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
require('isomorphic-fetch');
|
||||
const { credentials } = require('../integration/common/credentials');
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
module.exports = (on) => {
|
||||
on('before:run', () => {
|
||||
return fetch('http://localhost:3000/api/admins/first-register', {
|
||||
body: JSON.stringify(credentials),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'post',
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
import { adminURL } from '../integration/common/constants';
|
||||
import { credentials } from '../integration/common/credentials';
|
||||
|
||||
Cypress.Commands.add('visitAdmin', () => {
|
||||
cy.visit(adminURL);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('login', () => {
|
||||
cy.clearCookies();
|
||||
cy.visit(adminURL);
|
||||
cy.get('#email').type(credentials.email);
|
||||
cy.get('#password').type(credentials.password);
|
||||
|
||||
cy.get('body')
|
||||
.then((body) => {
|
||||
if (body.find('.dashboard__card-list').length) {
|
||||
cy.get('.dashboard__card-list')
|
||||
.should('be.visible');
|
||||
}
|
||||
|
||||
if (body.find('#confirm-password').length) {
|
||||
cy.get('#confirm-password').type(credentials.password);
|
||||
cy.get('.rs__indicators').first()
|
||||
.click();
|
||||
cy.get('.rs__menu').first().contains('admin')
|
||||
.click();
|
||||
|
||||
cy.get('form')
|
||||
.contains('form', 'Create')
|
||||
.should('be.visible')
|
||||
.submit();
|
||||
}
|
||||
|
||||
if (body.find('form').length) {
|
||||
cy.get('form')
|
||||
.contains('form', 'Login')
|
||||
.should('be.visible')
|
||||
.submit();
|
||||
}
|
||||
cy.get('.dashboard__card-list')
|
||||
.should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('apiLogin', () => {
|
||||
cy.api({
|
||||
url: '/api/admins/login',
|
||||
method: 'POST',
|
||||
body: credentials,
|
||||
failOnStatusCode: true,
|
||||
}).should(({ status }) => {
|
||||
cy.wrap(status).should('equal', 200);
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
import '@bahmutov/cy-api';
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands';
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
Cypress.Cookies.defaults({
|
||||
preserve: 'payload-token',
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom"
|
||||
],
|
||||
"types": [
|
||||
"cypress"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* authorize a request by comparing the current user with one or more roles
|
||||
* @param allRoles
|
||||
* @param user
|
||||
* @returns {Function}
|
||||
*/
|
||||
const checkRole = (allRoles, user) => {
|
||||
if (user) {
|
||||
if (allRoles.some((role) => user.roles && user.roles.some((individualRole) => individualRole === role))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export default checkRole;
|
||||
@@ -1,7 +0,0 @@
|
||||
export default [
|
||||
'admin',
|
||||
'editor',
|
||||
'moderator',
|
||||
'user',
|
||||
'viewer',
|
||||
];
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Block } from '../../src/fields/config/types';
|
||||
|
||||
const CTA: Block = {
|
||||
slug: 'cta',
|
||||
labels: {
|
||||
singular: 'Call to Action',
|
||||
plural: 'Calls to Action',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
label: 'Label',
|
||||
type: 'text',
|
||||
maxLength: 100,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
label: 'URL',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default CTA;
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Block } from '../../src/fields/config/types';
|
||||
|
||||
const Email: Block = {
|
||||
slug: 'email',
|
||||
labels: {
|
||||
singular: 'Email',
|
||||
plural: 'Emails',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'testEmail',
|
||||
label: 'Test Email Field',
|
||||
type: 'email',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default Email;
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Block } from '../../src/fields/config/types';
|
||||
|
||||
const NumberBlock: Block = {
|
||||
slug: 'number',
|
||||
labels: {
|
||||
singular: 'Number',
|
||||
plural: 'Numbers',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'testNumber',
|
||||
label: 'Test Number Field',
|
||||
type: 'number',
|
||||
max: 100,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default NumberBlock;
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Block } from '../../src/fields/config/types';
|
||||
|
||||
const Quote: Block = {
|
||||
imageURL: '/static/assets/images/generic-block-image.svg',
|
||||
slug: 'quote',
|
||||
labels: {
|
||||
singular: 'Quote',
|
||||
plural: 'Quotes',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'quote',
|
||||
label: 'Quote',
|
||||
type: 'textarea',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'color',
|
||||
label: 'Color',
|
||||
type: 'text',
|
||||
maxLength: 7,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default Quote;
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
// As this is the demo folder, we import Payload SCSS functions relatively.
|
||||
@import '../../../../scss';
|
||||
|
||||
// In your own projects, you'd import as follows:
|
||||
// @import '~payload/scss';
|
||||
|
||||
.after-dashboard {
|
||||
margin-top: base(2);
|
||||
|
||||
&__cards {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useConfig } from '../../../../src/admin/components/utilities/Config';
|
||||
|
||||
// As this is the demo project, we import our dependencies from the `src` directory.
|
||||
import Card from '../../../../src/admin/components/elements/Card';
|
||||
|
||||
// In your projects, you can import as follows:
|
||||
// import { Card } from 'payload/components/elements';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'after-dashboard';
|
||||
|
||||
const AfterDashboard: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const { routes: { admin: adminRoute } } = useConfig();
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<h3>Custom Routes & Dashboard Components</h3>
|
||||
<p>This is a custom component that is rendered within the built-in dashboard component after its contents are rendered. Below, there are two cards that link to custom routes.</p>
|
||||
<ul className="dashboard__card-list">
|
||||
<li>
|
||||
<Card
|
||||
title="Default Template"
|
||||
onClick={() => history.push(`${adminRoute}/custom-default-route`)}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<Card
|
||||
title="Minimal Template"
|
||||
onClick={() => history.push(`${adminRoute}/custom-minimal-route`)}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AfterDashboard;
|
||||
@@ -1,14 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="portal"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,9 +0,0 @@
|
||||
<svg width="82" height="53" viewBox="0 0 82 53" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect x="0.713013" width="80.574" height="52.7791" fill="url(#pattern0)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0" transform="scale(0.00387597 0.00591716)"/>
|
||||
</pattern>
|
||||
<image id="image0" width="258" height="169" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQIAAACpCAYAAADA4zPJAAALpklEQVR4Ae2dSY/UOhSF+///DCSGHQuEgAVI7BBITAI2zCCGZuhmpqEBP53W8yOvK1UdO3Zyr/NFilJdXalyTo4/X984zlZgQQEUWLwCW4tXAAFQAAUCIMAEKIACgAAPoAAKBECACVAABQABHkABFAiAABOgAAoAAjyAAiggBbhqgA9QAAUAAR5AARQgIsADKIACdA3wAAqggBQgR4APUAAFAAEeQAEUICLAAyiAAnQN8AAKoIAUIEeAD1AABQABHkABFCAiwAMogAJ0DfAACqCAFCBHgA9QAAUAAR5AARQgIsADKIACdA3wAAqggBQgR4APUAAFAAEeQAEUICLAAyiAAnQN8AAKoIAUIEeAD1AABQABHkABFCAiwAMogAJ0DfAACqCAFCBHgA9QAAUAAR5AARQgIsADKIACdA3wAAqggBQgR4APUAAFAAEeQAEUICLAAyiAAnQN8AAKoIAUIEeAD1AABQABHkABFCAiwAMLVmB/fz98+/YtfPr0KXz48CHs7u6GnZ2dg+3Hjx/D169fw48fP8KfP3+aV4muQfOnmAPsKqDKr4r/9u3bsL29PXgVKPb29pqFAiDouoTXzSrw8+fPg5Y+pfL3ffbdu3fh+/fvzekECJo7pRxQVwGF9YoA+ir1mPfUhVB00coCCFo5kxzHigKqqGrBx1T4Tfu+efOmmegAEKzYhzdaUEBdAVXUTRW51P+UVPS+AALvZ5DyryigSGAqCESYeIcBIFixEW94VuDXr1/JVwRiZR671VUFrwsg8HrmKPeKAkoMKok3tkLn7q8oRCDyuAACj2eNMvcqoMFBuZW41H4alORxAQQezxplXlHg9+/fk+cF1sHDYxcBEKxYijc8KvDly5fZo4EIhvfv37uTEBC4O2UU+LACyg2kDhmOlbbWVpcvPS2AwNPZoqy9CigUr1Whc79Xoxk9LYDA09mirL0K1BhCnAuAuJ8iFE8LIPB0tihrrwI1hxHHip2z9XQpERD0Wos3vSig/EBOJZ1iH09XDwCBF8dTzl4FNJx4ikqd8xu6kuFlAQRezhTl7FVAMwjlVNIp9vn8+XNvmS2+aR4E6md56mtZPMktl8niFYMIGU9XDkyDQCdZ47e1eupvtVzxrB0bEUGZM2ISBEoAafLISNa4FWH1PxYUiApo4E70h7UtOYJ4ljK2OrGbLgfpfy1NEZUhEbt0FNA9BtYAEMvjaW5DMxGBWnpN7hBFPGrrfSKIjpd5OVIBa8OLo3c9DTM2AQJRXbdvRgGHbrWP9mVZtgKaanyoZ6b6nPJanrqxs4MgJgRzTxCJxGVDQEevEDzXP7X2E5w8LbOBQLQsOUZ8TCLx9evX4fHjx+HRo0esTjW4efNmKL3eu3cvyBs5sPB2lWsWENSaZlr3gacmEi9fvhyOHTvGiga9Hjh79mwyDJSz8NQtUOQyOQhSEoI5JNY+mrJqyPLy5cvekw8YAGPXA7du3UqKCob6b4hHp/rMZCDITQjmwmBIIvH+/fuAgEjgSA9cuXJlMAh0edtbNCDYTAICjf6a4xKPflO/vW4BBLT83ZZ/3esUEHi6ZNitF1VBIDLqxovcVr3UfipDH6UBASBYV/m77w8FgaeRhF0I6HU1EChpp+Rdqco89nv6EomAABB0K/y610NAoCHxfY3N4Qpn9e8qILAwv3wfODTmoJvIef78+ZH9w3Xm4P3lQOTGjRsbGzSNGfAMgeIRgRKCFkd5HYaCyqiyaj1//jwwIGG41gOnT58Ourp02EPx7zHjVyxFB8UigrkSgvGEpG5jIlEw0ECiO3fusDaowe3bt8PVq1eDwvvU9fr162sh0NqI1tEgsJIQTAVB/Py6RKIlWlOWcQrIo+oSlrpypShADUhLyygQaOYgSwnBWLlTtzoGZkFqydb9xxKBkONZQUSNRqs+yQaBbvRQeJRa6ax+Xsfi6f7xfqvz7lAFVKEVJSjbLzCookc/a6uBQcol6ZKgxgZ4TwYepUsyCBQSeUgI5gJHxmgt7DvKBPwfBZJAIDKW6mflVtQp9tMxeh0hhqVRIEeBQSBQWKQQaYpKaOk3dMyth4Q5pmGf9hQ4EgTqS+3s7CwOAhFIOvZWE0Tt2ZkjylVgIwhaSwjGyp26JZGYay/286JALwiULOubTjy1ArX2ee/jyb2YknJOr8AKCJQk06WT1ipxqeORNiQSpzcqv1hXgf9AsNSEYC4gSCTWNSbfPq0CByBYekIwFwYkEqc1K79WT4GtsdOJ51aiVvZTItHbjLX17MQ3e1Vgq5UKOfdxtHI7ajSy7iYlFxLVaH8LCLa3iyVGlUhMnU7dmsWUK+pOL6dZpxlUZe0slS8PICgIghiV6F4M3ct+6dIlV+vFixcPJmo5d+5c6K4XLlwI+p/l47l27RpdtBF8AAQVQHDmzJm1M94wxVm9Kc6kOzeM5dEAEBQGgR6dRmWvV9mP0lbdM5Z0BQBBYRA8ePAAEMw4B6KeVciSrgAgAARNgQsQpENAewACQAAI8upOU3sBgsIgePjwYVMV66g+ubX/ExHk8QkQFAaB5sA/ceIEMJghT3D8+HHmnczjAF2DeO2/5Pbu3bvh1KlTwGBCGJw8eTI8efIksxqwGxFB4YggAuXVq1fh2bNn4enTp0euL168OJj/QaP4Sq26O1JzLw75/Rqf0czApY5lyPcwfmAczABBJRBEIAzd6ual7nMZx5xWK3eTxkfLjTkW9p1GAUBgBAQRGGMrj7W7SRWV6AYmFtsKAAJjIBAQciqPbgyyPL0cj5YDBMXu7out5lK2QyuPl+nleLScXRgQERiMCLqg2zQLkqIAJdK6n7f+mhmhbcIAEBgHgSp2X+VRlnx3d9cVBLqQGpsLsVmd/JYKEDgAQaxA8bmM1hKCsXypWx4tZwccgMARCFTRFB2kVjjrn2dG6PmBAAicgcB6pc4t36ZcyPzVpP0SAAJAYCbC6MuFtF8FbRwhIAAEZkAQowkeLTc9HAABIDAHAgGBR8tNCwNAAAhMgiBGByQSpwECIAAEpkEgIJBIrA8DQAAIzINAMFAikUfL1QMCIAAELkAQuwqtPVquXtVO+2ZAAAhcgUBAaOHRcmnVtP6nAQEgcAeCGB3ohiuWMgoAAkDgFgQCgm68Ypqy8TAABIDANQgEAxKJgMC9iWOYy3b84+lJJOYDgYiAiKApmGoWpP39/fwasdA9AQEgaAoEMbIqNSP0UrgACABBkyAQEEgkDscYIAAEzYJAMMiZEXp49Wnnk4AAEDQNgthVGDojdDtVO+1IAAEgWAQIBAQSievhAAgAwWJAIBhozAGJxFUgAAJAsCgQxK4C06n/HwaAABAsEgQCAonEvzAABIBgsSCI0QGJxBAAASBYPAgEhKU/lxEQAAJA8K8HljydOiAABIDgkAfio+X+9qDbfwUIDpkg9hvZjr8b0LOGS3suIyAABEQEGzywlOnUAcEGE3hu0Sh7uYhmCdOpAwJAQEQwwAOtJxIBwQAT0LqWa129a9nqcxkBASAgIkj0QIvPZQQEiSbw3qJR/nLRTUuJREAACIgIRniglUQiIBhhAlrXcq2rZy1bmE4dEAACIoJCHvA8nTogKGQCzy0aZS8X2Xh9LiMgAAREBBU84O25jICgggloYcu1sJ619DSdOiAABEQEFT3gJZEICCqawHNrRtnLRjXWE4mAABAQEUzkAcvTqQOCiUxAC1u2hfWsp8Xp1AEBICAimMED1hKJgGAGE3huzSh7ucjG0nTqgAAQEBHM7AEL06kDgplNQAtbroX1rOXciURAAAiICIx4QGMO5kokAgIjJvDcmlH2slHNHM9lBASAgIjAoAemTiQCAoMmoIUt28J61nOqRCIgAAREBMY9MMUsSIDAuAk8t2aUvVxkU3s6dUAACIgIHHmg1nMZAYEjE9DClmthPWtZYzp1QAAIiAiceqDkdOqAwKkJPLdolL1cZFMqkbilkUysaIAH/Hpgb28vjF3+AbSb48mcXO9tAAAAAElFTkSuQmCC"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB |
@@ -1,67 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
import roles from '../access/roles';
|
||||
import checkRole from '../access/checkRole';
|
||||
|
||||
const access = ({ req: { user } }) => {
|
||||
const result = checkRole(['admin'], user);
|
||||
return result;
|
||||
};
|
||||
|
||||
const Admin: CollectionConfig = {
|
||||
slug: 'admins',
|
||||
labels: {
|
||||
singular: 'Admin',
|
||||
plural: 'Admins',
|
||||
},
|
||||
access: {
|
||||
create: access,
|
||||
read: access,
|
||||
update: access,
|
||||
delete: access,
|
||||
admin: () => true,
|
||||
},
|
||||
auth: {
|
||||
tokenExpiration: 7200, // 2 hours
|
||||
verify: false,
|
||||
maxLoginAttempts: 5,
|
||||
lockTime: 600 * 1000, // lock time in ms
|
||||
useAPIKey: true,
|
||||
depth: 0,
|
||||
cookies: {
|
||||
secure: false,
|
||||
sameSite: 'lax',
|
||||
domain: undefined,
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'roles',
|
||||
label: 'Role',
|
||||
type: 'select',
|
||||
options: roles,
|
||||
defaultValue: 'user',
|
||||
required: true,
|
||||
saveToJWT: true,
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: 'publicUser',
|
||||
type: 'relationship',
|
||||
hasMany: true,
|
||||
relationTo: 'public-users',
|
||||
},
|
||||
{
|
||||
name: 'apiKey',
|
||||
type: 'text',
|
||||
access: {
|
||||
read: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
},
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
admin: {
|
||||
useAsTitle: 'email',
|
||||
},
|
||||
};
|
||||
|
||||
export default Admin;
|
||||
@@ -1,363 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
import checkRole from '../access/checkRole';
|
||||
import Email from '../blocks/Email';
|
||||
import Quote from '../blocks/Quote';
|
||||
import NumberBlock from '../blocks/Number';
|
||||
import CallToAction from '../blocks/CallToAction';
|
||||
import CollectionDescription from '../customComponents/CollectionDescription';
|
||||
import DemoUIField from '../client/components/DemoUIField/Field';
|
||||
import DemoUIFieldCell from '../client/components/DemoUIField/Cell';
|
||||
|
||||
const AllFields: CollectionConfig = {
|
||||
slug: 'all-fields',
|
||||
labels: {
|
||||
singular: 'All Fields',
|
||||
plural: 'All Fields',
|
||||
},
|
||||
admin: {
|
||||
defaultColumns: ['text', 'demo', 'createdAt'],
|
||||
useAsTitle: 'text',
|
||||
preview: (doc, { token }) => {
|
||||
const { text } = doc;
|
||||
|
||||
if (doc && text) {
|
||||
return `http://localhost:3000/previewable-posts/${text}?preview=true&token=${token}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
description: CollectionDescription,
|
||||
},
|
||||
versions: {
|
||||
maxPerDoc: 20,
|
||||
retainDeleted: true,
|
||||
drafts: false,
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
label: 'Text',
|
||||
required: true,
|
||||
defaultValue: 'Default Value',
|
||||
unique: true,
|
||||
access: {
|
||||
create: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
update: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'descriptionText',
|
||||
type: 'text',
|
||||
label: 'Text with text description',
|
||||
defaultValue: 'Default Value',
|
||||
admin: {
|
||||
description: 'This text describes the field',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'descriptionFunction',
|
||||
type: 'text',
|
||||
label: 'Text with function description',
|
||||
defaultValue: 'Default Value',
|
||||
maxLength: 20,
|
||||
admin: {
|
||||
description: ({ value }) => (typeof value === 'string' ? `${20 - value.length} characters left` : ''),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
label: 'Image',
|
||||
relationTo: 'media',
|
||||
admin: {
|
||||
description: 'No selfies',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'select',
|
||||
label: 'Select',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}, {
|
||||
value: 'option-4',
|
||||
label: 'Option 4 Label',
|
||||
}],
|
||||
defaultValue: 'option-1',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'selectMany',
|
||||
label: 'Select w/ hasMany',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}, {
|
||||
value: 'option-4',
|
||||
label: 'Option 4 Label',
|
||||
}],
|
||||
defaultValue: 'option-1',
|
||||
required: true,
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: 'dayOnlyDateFieldExample',
|
||||
label: 'Day Only',
|
||||
type: 'date',
|
||||
required: true,
|
||||
admin: {
|
||||
date: {
|
||||
pickerAppearance: 'dayOnly',
|
||||
monthsToShow: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'timeOnlyDateFieldExample',
|
||||
label: 'Time Only',
|
||||
type: 'date',
|
||||
admin: {
|
||||
date: {
|
||||
pickerAppearance: 'timeOnly',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'point',
|
||||
label: 'Point Field (GeoJSON)',
|
||||
type: 'point',
|
||||
},
|
||||
{
|
||||
name: 'radioGroupExample',
|
||||
label: 'Radio Group Example',
|
||||
type: 'radio',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Options 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}],
|
||||
defaultValue: 'option-2',
|
||||
required: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
type: 'email',
|
||||
}, {
|
||||
name: 'number',
|
||||
label: 'Number',
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Group',
|
||||
name: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'nestedText1',
|
||||
label: 'Nested Text 1',
|
||||
type: 'text',
|
||||
}, {
|
||||
name: 'nestedText2',
|
||||
label: 'Nested Text 2',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
label: 'Editable Array',
|
||||
name: 'array',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'arrayText1',
|
||||
label: 'Array Text 1',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'arrayText2',
|
||||
label: 'Array Text 2',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
update: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'arrayText3',
|
||||
label: 'Array Text 3',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
type: 'checkbox',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
name: 'readOnlyArray',
|
||||
label: 'readOnly Array',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
defaultValue: [{
|
||||
text: 'text in readOnly array one',
|
||||
},
|
||||
{
|
||||
text: 'text in readOnly array two',
|
||||
}],
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'blocks',
|
||||
label: 'Blocks Content',
|
||||
name: 'blocks',
|
||||
minRows: 2,
|
||||
blocks: [Email, NumberBlock, Quote, CallToAction],
|
||||
localized: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship to One Collection',
|
||||
name: 'relationship',
|
||||
relationTo: 'conditions',
|
||||
admin: {
|
||||
description: 'Relates to description',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship hasMany',
|
||||
name: 'relationshipHasMany',
|
||||
relationTo: 'localized-posts',
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship to Multiple Collections',
|
||||
name: 'relationshipMultipleCollections',
|
||||
relationTo: ['localized-posts', 'conditions'],
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
label: 'Textarea',
|
||||
name: 'textarea',
|
||||
admin: {
|
||||
description: 'Hello textarea description',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
label: 'Rich Text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'ui',
|
||||
name: 'demo',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
components: {
|
||||
Field: DemoUIField,
|
||||
Cell: DemoUIFieldCell,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
label: 'Slug',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
localized: true,
|
||||
unique: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
type: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dateFieldExample',
|
||||
label: 'Day and Time',
|
||||
type: 'date',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
date: {
|
||||
timeIntervals: 30,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default AllFields;
|
||||
@@ -1,102 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const AutoLabel: CollectionConfig = {
|
||||
slug: 'auto-label',
|
||||
admin: {
|
||||
useAsTitle: 'autoLabelField',
|
||||
enableRichTextRelationship: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'autoLabelField',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'noLabel',
|
||||
type: 'text',
|
||||
label: false,
|
||||
},
|
||||
{
|
||||
name: 'labelOverride',
|
||||
type: 'text',
|
||||
label: 'Custom Label',
|
||||
},
|
||||
{
|
||||
name: 'testRelationship',
|
||||
type: 'relationship',
|
||||
relationTo: 'all-fields',
|
||||
},
|
||||
{
|
||||
name: 'specialBlock',
|
||||
type: 'blocks',
|
||||
minRows: 1,
|
||||
maxRows: 20,
|
||||
// Will auto-label
|
||||
// labels: {
|
||||
// singular: 'Special Block',
|
||||
// plural: 'Special Blocks',
|
||||
// },
|
||||
blocks: [
|
||||
{
|
||||
slug: 'number',
|
||||
// Will auto-label
|
||||
// labels: {
|
||||
// singular: 'Number',
|
||||
// plural: 'Numbers',
|
||||
// },
|
||||
fields: [
|
||||
{
|
||||
name: 'testNumber',
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'noLabelBlock',
|
||||
type: 'blocks',
|
||||
label: false,
|
||||
minRows: 1,
|
||||
maxRows: 20,
|
||||
blocks: [
|
||||
{
|
||||
slug: 'number',
|
||||
// labels: {
|
||||
// singular: 'Number',
|
||||
// plural: 'Numbers',
|
||||
// },
|
||||
fields: [
|
||||
{
|
||||
name: 'testNumber',
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'itemName',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'noLabelArray',
|
||||
type: 'array',
|
||||
label: false,
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'textField',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default AutoLabel;
|
||||
@@ -1,46 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
import Email from '../blocks/Email';
|
||||
import Quote from '../blocks/Quote';
|
||||
import NumberBlock from '../blocks/Number';
|
||||
import CallToAction from '../blocks/CallToAction';
|
||||
|
||||
const Blocks: CollectionConfig = {
|
||||
slug: 'blocks',
|
||||
labels: {
|
||||
singular: 'Blocks',
|
||||
plural: 'Blocks',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'layout',
|
||||
label: 'Layout Blocks',
|
||||
labels: {
|
||||
singular: 'Block',
|
||||
plural: 'Blocks',
|
||||
},
|
||||
type: 'blocks',
|
||||
blocks: [Email, NumberBlock, Quote, CallToAction],
|
||||
localized: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'nonLocalizedLayout',
|
||||
label: 'Non Localized Layout',
|
||||
labels: {
|
||||
singular: 'Layout',
|
||||
plural: 'Layouts',
|
||||
},
|
||||
type: 'blocks',
|
||||
blocks: [Email, NumberBlock, Quote, CallToAction],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default Blocks;
|
||||
@@ -1,23 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const Code: CollectionConfig = {
|
||||
slug: 'code',
|
||||
labels: {
|
||||
singular: 'Code',
|
||||
plural: 'Codes',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'code',
|
||||
type: 'code',
|
||||
label: 'Code',
|
||||
required: true,
|
||||
admin: {
|
||||
language: 'js',
|
||||
description: 'javascript example',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default Code;
|
||||
@@ -1,86 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
import Email from '../blocks/Email';
|
||||
import Quote from '../blocks/Quote';
|
||||
import NumberBlock from '../blocks/Number';
|
||||
import CallToAction from '../blocks/CallToAction';
|
||||
import Text from './CustomComponents/components/fields/Text/Field';
|
||||
|
||||
const Conditions: CollectionConfig = {
|
||||
slug: 'conditions',
|
||||
labels: {
|
||||
singular: 'Conditions',
|
||||
plural: 'Conditions',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Title',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'enableTest',
|
||||
type: 'checkbox',
|
||||
label: 'Enable Test',
|
||||
},
|
||||
{
|
||||
name: 'number',
|
||||
type: 'number',
|
||||
label: 'Number Field',
|
||||
},
|
||||
{
|
||||
name: 'simpleCondition',
|
||||
type: 'text',
|
||||
label: 'Enable Test is checked',
|
||||
required: true,
|
||||
admin: {
|
||||
condition: (_, siblings) => siblings.enableTest === true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'orCondition',
|
||||
type: 'text',
|
||||
label: 'Number is greater than 20 OR enableTest is checked',
|
||||
required: true,
|
||||
admin: {
|
||||
condition: (_, siblings) => siblings.number > 20 || siblings.enableTest === true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'nestedConditions',
|
||||
type: 'text',
|
||||
label: 'Number is either greater than 20 AND enableTest is checked, OR number is less than 20 and enableTest is NOT checked',
|
||||
admin: {
|
||||
condition: (_, siblings) => (siblings.number > 20 && siblings.enableTest === true) || (siblings.number < 20 && siblings.enableTest === false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
label: 'Blocks',
|
||||
labels: {
|
||||
singular: 'Block',
|
||||
plural: 'Blocks',
|
||||
},
|
||||
type: 'blocks',
|
||||
blocks: [Email, NumberBlock, Quote, CallToAction],
|
||||
required: true,
|
||||
admin: {
|
||||
condition: (_, siblings) => siblings?.enableTest === true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'customComponent',
|
||||
type: 'text',
|
||||
label: 'Custom Component with Enable Test is checked',
|
||||
required: true,
|
||||
admin: {
|
||||
condition: (_, siblings) => siblings?.enableTest === true,
|
||||
components: {
|
||||
Field: Text,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default Conditions;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const Cell: React.FC = () => <div className="description">fake description cell</div>;
|
||||
|
||||
export default Cell;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const Description: React.FC = () => <div className="description">fake description field</div>;
|
||||
|
||||
export default Description;
|
||||
@@ -1,3 +0,0 @@
|
||||
.custom-description-filter {
|
||||
background: lightgray;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Props } from './types';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const Filter: React.FC<Props> = ({ onChange, value }) => (
|
||||
<input
|
||||
className="custom-description-filter"
|
||||
type="text"
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
|
||||
export default Filter;
|
||||
@@ -1,4 +0,0 @@
|
||||
export type Props = {
|
||||
value?: string
|
||||
onChange?: (value: string) => void
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Group } from '../../../../../../../components/forms';
|
||||
|
||||
const CustomGroup: React.FC = (props) => (
|
||||
<div className="custom-group">
|
||||
<Group {...props} />
|
||||
</div>
|
||||
);
|
||||
|
||||
CustomGroup.defaultProps = {
|
||||
value: '',
|
||||
};
|
||||
|
||||
CustomGroup.propTypes = {
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
||||
export default CustomGroup;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const NestedArrayCustomField = () => <div className="nested-array-custom-field">Nested array custom field</div>;
|
||||
|
||||
export default NestedArrayCustomField;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const NestedGroupCustomField = () => <div className="nested-group-custom-field">Nested group custom field</div>;
|
||||
|
||||
export default NestedGroupCustomField;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const NestedText1 = () => <div className="nested-text-1">Nested Text 1</div>;
|
||||
|
||||
export default NestedText1;
|
||||
@@ -1,61 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import SelectInput from '../../../../../../../src/admin/components/forms/field-types/Select/Input';
|
||||
import { Props as SelectFieldType } from '../../../../../../../src/admin/components/forms/field-types/Select/types';
|
||||
import useField from '../../../../../../../src/admin/components/forms/useField';
|
||||
|
||||
const Select: React.FC<SelectFieldType> = (props) => {
|
||||
const {
|
||||
path,
|
||||
name,
|
||||
label,
|
||||
options,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
showError,
|
||||
value,
|
||||
setValue,
|
||||
} = useField({
|
||||
path,
|
||||
});
|
||||
|
||||
const onChange = useCallback((incomingOption) => {
|
||||
const { value: incomingValue } = incomingOption;
|
||||
|
||||
const sendToCRM = async () => {
|
||||
try {
|
||||
const req = await fetch('https://fake-crm.com', {
|
||||
method: 'post',
|
||||
body: JSON.stringify({
|
||||
someKey: incomingValue,
|
||||
}),
|
||||
});
|
||||
|
||||
const res = await req.json();
|
||||
if (res.ok) {
|
||||
console.log('Successfully synced to CRM.'); // eslint-disable-line no-console
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
sendToCRM();
|
||||
setValue(incomingValue);
|
||||
}, [
|
||||
setValue,
|
||||
]);
|
||||
|
||||
return (
|
||||
<SelectInput
|
||||
name={name}
|
||||
label={label}
|
||||
options={options}
|
||||
value={value as string}
|
||||
onChange={onChange}
|
||||
showError={showError}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Select;
|
||||
@@ -1,44 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import TextInput from '../../../../../../../src/admin/components/forms/field-types/Text/Input';
|
||||
import { Props as TextFieldType } from '../../../../../../../src/admin/components/forms/field-types/Text/types';
|
||||
import useField from '../../../../../../../src/admin/components/forms/useField';
|
||||
|
||||
const Text: React.FC<TextFieldType> = (props) => {
|
||||
const {
|
||||
path,
|
||||
name,
|
||||
label,
|
||||
} = props;
|
||||
|
||||
const field = useField({
|
||||
path,
|
||||
enableDebouncedValue: true,
|
||||
});
|
||||
|
||||
const {
|
||||
showError,
|
||||
value,
|
||||
setValue,
|
||||
} = field;
|
||||
|
||||
const onChange = useCallback((e) => {
|
||||
const { value: incomingValue } = e.target;
|
||||
const valueWithoutSpaces = incomingValue.replace(/\s/g, '');
|
||||
setValue(valueWithoutSpaces);
|
||||
}, [
|
||||
setValue,
|
||||
]);
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
path={path}
|
||||
name={name}
|
||||
value={value as string || ''}
|
||||
label={label}
|
||||
onChange={onChange}
|
||||
showError={showError}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Text;
|
||||
@@ -1,41 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import TextAreaInput from '../../../../../../../src/admin/components/forms/field-types/Textarea/Input';
|
||||
import { Props as TextFieldType } from '../../../../../../../src/admin/components/forms/field-types/Text/types';
|
||||
import useField from '../../../../../../../src/admin/components/forms/useField';
|
||||
|
||||
const TextArea: React.FC<TextFieldType> = (props) => {
|
||||
const {
|
||||
path,
|
||||
name,
|
||||
label,
|
||||
} = props;
|
||||
|
||||
const field = useField({
|
||||
path,
|
||||
});
|
||||
|
||||
const {
|
||||
showError,
|
||||
value,
|
||||
setValue,
|
||||
} = field;
|
||||
|
||||
const onChange = useCallback((e) => {
|
||||
const { value: incomingValue } = e.target;
|
||||
setValue(incomingValue);
|
||||
}, [
|
||||
setValue,
|
||||
]);
|
||||
|
||||
return (
|
||||
<TextAreaInput
|
||||
name={name}
|
||||
value={value as string || ''}
|
||||
label={label}
|
||||
onChange={onChange}
|
||||
showError={showError}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextArea;
|
||||
@@ -1,50 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import TextInput from '../../../../../../../src/admin/components/forms/field-types/Text';
|
||||
import { UIField as UIFieldType } from '../../../../../../../src/fields/config/types';
|
||||
import SelectInput from '../../../../../../../src/admin/components/forms/field-types/Select';
|
||||
|
||||
const UIField: React.FC<UIFieldType> = () => {
|
||||
const [textValue, setTextValue] = React.useState('');
|
||||
const [selectValue, setSelectValue] = React.useState('');
|
||||
|
||||
const onTextChange = useCallback((incomingValue) => {
|
||||
setTextValue(incomingValue);
|
||||
}, [])
|
||||
|
||||
const onSelectChange = useCallback((incomingValue) => {
|
||||
setSelectValue(incomingValue);
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TextInput
|
||||
name="ui-text"
|
||||
label="Presentation-only text field (does not submit)"
|
||||
value={textValue as string}
|
||||
onChange={onTextChange}
|
||||
/>
|
||||
<SelectInput
|
||||
name="ui-select"
|
||||
label="Presentation-only select field (does not submit)"
|
||||
options={[
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: 'option-1'
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: 'option-2'
|
||||
},
|
||||
{
|
||||
label: 'Option 3',
|
||||
value: 'option-4'
|
||||
}
|
||||
]}
|
||||
value={selectValue as string}
|
||||
onChange={onSelectChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export default UIField;
|
||||
@@ -1,57 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import UploadInput from '../../../../../../../src/admin/components/forms/field-types/Upload/Input';
|
||||
import { Props as UploadFieldType } from '../../../../../../../src/admin/components/forms/field-types/Upload/types';
|
||||
import useField from '../../../../../../../src/admin/components/forms/useField';
|
||||
import { SanitizedCollectionConfig } from '../../../../../../../src/collections/config/types';
|
||||
import { useConfig } from '../../../../../../../src/admin/components/utilities/Config';
|
||||
|
||||
const Text: React.FC<UploadFieldType> = (props) => {
|
||||
const {
|
||||
path,
|
||||
name,
|
||||
label,
|
||||
relationTo,
|
||||
fieldTypes,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
value,
|
||||
setValue,
|
||||
showError,
|
||||
} = useField({
|
||||
path,
|
||||
});
|
||||
|
||||
const onChange = useCallback((incomingValue) => {
|
||||
const incomingID = incomingValue?.id || incomingValue;
|
||||
setValue(incomingID);
|
||||
}, [setValue]);
|
||||
|
||||
const {
|
||||
collections,
|
||||
serverURL,
|
||||
routes: {
|
||||
api,
|
||||
},
|
||||
} = useConfig();
|
||||
|
||||
const collection = collections.find((coll) => coll.slug === relationTo) || undefined;
|
||||
|
||||
return (
|
||||
<UploadInput
|
||||
path={path}
|
||||
relationTo={relationTo}
|
||||
fieldTypes={fieldTypes}
|
||||
name={name}
|
||||
label={label}
|
||||
value={value as string}
|
||||
onChange={onChange}
|
||||
showError={showError}
|
||||
collection={collection as SanitizedCollectionConfig}
|
||||
serverURL={serverURL}
|
||||
api={api}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Text;
|
||||
@@ -1,5 +0,0 @@
|
||||
$color: purple;
|
||||
|
||||
.custom-list {
|
||||
color: $color;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import React from 'react';
|
||||
import DefaultList from '../../../../../../src/admin/components/views/collections/List/Default';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const CustomListView: React.FC = (props) => (
|
||||
<div className="custom-list">
|
||||
<p>This is a custom Pages list view</p>
|
||||
<p>Sup</p>
|
||||
<DefaultList {...props} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CustomListView;
|
||||
@@ -1,224 +0,0 @@
|
||||
import { CollectionConfig } from '../../../src/collections/config/types';
|
||||
import DescriptionField from './components/fields/Description/Field';
|
||||
import TextField from './components/fields/Text/Field';
|
||||
import SelectField from './components/fields/Select/Field';
|
||||
import TextAreaField from './components/fields/TextArea/Field';
|
||||
import UploadField from './components/fields/Upload/Field';
|
||||
import DescriptionCell from './components/fields/Description/Cell';
|
||||
import DescriptionFilter from './components/fields/Description/Filter';
|
||||
import NestedArrayField from './components/fields/NestedArrayCustomField/Field';
|
||||
import GroupField from './components/fields/Group/Field';
|
||||
import NestedGroupField from './components/fields/NestedGroupCustomField/Field';
|
||||
import NestedText1Field from './components/fields/NestedText1/Field';
|
||||
import UIField from './components/fields/UI/Field';
|
||||
import ListView from './components/views/List';
|
||||
import CustomDescriptionComponent from '../../customComponents/Description';
|
||||
|
||||
const CustomComponents: CollectionConfig = {
|
||||
slug: 'custom-components',
|
||||
labels: {
|
||||
singular: 'Custom Component',
|
||||
plural: 'Custom Components',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
type: 'text',
|
||||
maxLength: 100,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
{
|
||||
name: 'normalText',
|
||||
label: 'Normal text field',
|
||||
type: 'text',
|
||||
// required: true,
|
||||
},
|
||||
{
|
||||
name: 'customText',
|
||||
label: 'Custom text field (removes whitespace)',
|
||||
type: 'text',
|
||||
// required: true,
|
||||
admin: {
|
||||
components: {
|
||||
Field: TextField,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'normalSelect',
|
||||
label: 'Normal select field',
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: 'Option 3',
|
||||
value: '3',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'customSelect',
|
||||
label: 'Custom select field (syncs value with crm)',
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: 'Option 3',
|
||||
value: '3',
|
||||
},
|
||||
],
|
||||
admin: {
|
||||
components: {
|
||||
Field: SelectField,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'normalTextarea',
|
||||
label: 'Normal textarea field',
|
||||
type: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'customTextarea',
|
||||
label: 'Custom textarea field',
|
||||
type: 'textarea',
|
||||
admin: {
|
||||
components: {
|
||||
Field: TextAreaField,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ui',
|
||||
label: 'UI',
|
||||
type: 'ui',
|
||||
admin: {
|
||||
components: {
|
||||
Field: UIField,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'normalUpload',
|
||||
label: 'Normal upload field',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
},
|
||||
{
|
||||
name: 'customUpload',
|
||||
label: 'Custom upload field',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
admin: {
|
||||
components: {
|
||||
Field: UploadField,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: 'textarea',
|
||||
admin: {
|
||||
components: {
|
||||
Field: DescriptionField,
|
||||
Cell: DescriptionCell,
|
||||
Filter: DescriptionFilter,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'componentDescription',
|
||||
label: 'Component ViewDescription',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: CustomDescriptionComponent,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'array',
|
||||
label: 'Array',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedArrayCustomField',
|
||||
label: 'Nested Array Custom Field',
|
||||
admin: {
|
||||
components: {
|
||||
Field: NestedArrayField,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'group',
|
||||
label: 'Group',
|
||||
type: 'group',
|
||||
admin: {
|
||||
components: {
|
||||
Field: GroupField,
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedGroupCustomField',
|
||||
label: 'Nested Group Custom Field',
|
||||
admin: {
|
||||
components: {
|
||||
Field: NestedGroupField,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'nestedText1',
|
||||
label: 'Nested Text 1',
|
||||
type: 'text',
|
||||
admin: {
|
||||
components: {
|
||||
Field: NestedText1Field,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: 'nestedText2',
|
||||
label: 'Nested Text 2',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
components: {
|
||||
views: {
|
||||
List: ListView,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default CustomComponents;
|
||||
@@ -1,22 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const CustomID: CollectionConfig = {
|
||||
slug: 'custom-id',
|
||||
labels: {
|
||||
singular: 'CustomID',
|
||||
plural: 'CustomIDs',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default CustomID;
|
||||
@@ -1,329 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
import checkRole from '../access/checkRole';
|
||||
import Email from '../blocks/Email';
|
||||
import Quote from '../blocks/Quote';
|
||||
import NumberBlock from '../blocks/Number';
|
||||
import CallToAction from '../blocks/CallToAction';
|
||||
|
||||
const DefaultValues: CollectionConfig = {
|
||||
slug: 'default-values',
|
||||
labels: {
|
||||
singular: 'Default Value Test',
|
||||
plural: 'Default Value Tests',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'text',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
label: 'Text',
|
||||
defaultValue: 'Default Value',
|
||||
unique: true,
|
||||
access: {
|
||||
create: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
update: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
label: 'Image',
|
||||
relationTo: 'media',
|
||||
},
|
||||
{
|
||||
name: 'select',
|
||||
label: 'Select',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}, {
|
||||
value: 'option-4',
|
||||
label: 'Option 4 Label',
|
||||
}],
|
||||
defaultValue: 'option-1',
|
||||
},
|
||||
{
|
||||
name: 'selectMany',
|
||||
label: 'Select w/ hasMany',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}, {
|
||||
value: 'option-4',
|
||||
label: 'Option 4 Label',
|
||||
}],
|
||||
defaultValue: ['option-1', 'option-4'],
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: 'radioGroupExample',
|
||||
label: 'Radio Group Example',
|
||||
type: 'radio',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Options 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}],
|
||||
defaultValue: 'option-2',
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
type: 'email',
|
||||
defaultValue: 'some@email.com',
|
||||
}, {
|
||||
name: 'number',
|
||||
label: 'Number',
|
||||
type: 'number',
|
||||
defaultValue: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Group',
|
||||
name: 'group',
|
||||
defaultValue: {
|
||||
nestedText2: 'nested default text 2',
|
||||
nestedText3: 'neat',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'nestedText1',
|
||||
label: 'Nested Text 1',
|
||||
type: 'text',
|
||||
defaultValue: 'this should take priority',
|
||||
},
|
||||
{
|
||||
name: 'nestedText2',
|
||||
label: 'Nested Text 2',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'nestedText3',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
label: 'Array',
|
||||
name: 'array',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
defaultValue: [
|
||||
{
|
||||
arrayText1: 'Get out',
|
||||
arrayText2: 'Get in',
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'arrayText1',
|
||||
label: 'Array Text 1',
|
||||
type: 'text',
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
defaultValue: 'default array text',
|
||||
},
|
||||
{
|
||||
name: 'arrayText2',
|
||||
label: 'Array Text 2',
|
||||
type: 'text',
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
update: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'arrayText3',
|
||||
label: 'Array Text 3',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'blocks',
|
||||
label: 'Blocks Content',
|
||||
name: 'blocks',
|
||||
labels: {
|
||||
singular: 'Block',
|
||||
plural: 'Blocks',
|
||||
},
|
||||
blocks: [Email, NumberBlock, Quote, CallToAction],
|
||||
localized: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
defaultValue: [
|
||||
{
|
||||
blockType: 'email',
|
||||
testEmail: 'dev@payloadcms.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship to One Collection',
|
||||
name: 'relationship',
|
||||
relationTo: 'conditions',
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship hasMany',
|
||||
name: 'relationshipHasMany',
|
||||
relationTo: 'localized-posts',
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship to Multiple Collections',
|
||||
name: 'relationshipMultipleCollections',
|
||||
relationTo: ['localized-posts', 'conditions'],
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
label: 'Textarea',
|
||||
name: 'textarea',
|
||||
defaultValue: 'my textarea text',
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
label: 'Slug',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
localized: true,
|
||||
unique: true,
|
||||
defaultValue: 'my-slug',
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
type: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
label: 'Rich Text',
|
||||
admin: {
|
||||
elements: [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'blockquote',
|
||||
'ul',
|
||||
'ol',
|
||||
'link',
|
||||
],
|
||||
leaves: [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strikethrough',
|
||||
],
|
||||
},
|
||||
defaultValue: [{
|
||||
children: [{ text: 'Cookin now' }],
|
||||
}],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
name: 'asyncArray',
|
||||
defaultValue: () => {
|
||||
return [{ child: 'ok' }];
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'child',
|
||||
type: 'text',
|
||||
defaultValue: () => {
|
||||
return 'async child';
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'asyncText',
|
||||
type: 'text',
|
||||
defaultValue: async (): Promise<string> => {
|
||||
return new Promise((resolve) => setTimeout(() => {
|
||||
resolve('asyncFunction');
|
||||
}, 50));
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'function',
|
||||
type: 'text',
|
||||
defaultValue: (args): string => {
|
||||
const { locale } = args;
|
||||
if (locale === 'en') {
|
||||
return 'function';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
export default DefaultValues;
|
||||
@@ -1,70 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
import checkRole from '../access/checkRole';
|
||||
|
||||
const access = ({ req: { user } }) => {
|
||||
const isAdmin = checkRole(['admin'], user);
|
||||
|
||||
if (isAdmin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (user) {
|
||||
return {
|
||||
owner: {
|
||||
equals: user.id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const Files: CollectionConfig = {
|
||||
slug: 'files',
|
||||
labels: {
|
||||
singular: 'File',
|
||||
plural: 'Files',
|
||||
},
|
||||
upload: {
|
||||
staticURL: '/files',
|
||||
staticDir: './files',
|
||||
},
|
||||
access: {
|
||||
create: () => true,
|
||||
read: access,
|
||||
update: access,
|
||||
delete: access,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'type',
|
||||
label: 'Type',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'Type 1',
|
||||
label: 'Type 1 Label',
|
||||
}, {
|
||||
value: 'Type 2',
|
||||
label: 'Type 2 Label',
|
||||
}, {
|
||||
value: 'Type 3',
|
||||
label: 'Type 3 Label',
|
||||
}],
|
||||
defaultValue: 'Type 1',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'owner',
|
||||
label: 'Owner',
|
||||
type: 'relationship',
|
||||
relationTo: 'admins',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
admin: {
|
||||
useAsTitle: 'filename',
|
||||
},
|
||||
};
|
||||
|
||||
export default Files;
|
||||
@@ -1,43 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const HiddenFields: CollectionConfig = {
|
||||
slug: 'hidden-fields',
|
||||
labels: {
|
||||
singular: 'Hidden Fields',
|
||||
plural: 'Hidden Fields',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Title - Not Hidden',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'hiddenAdmin',
|
||||
type: 'text',
|
||||
label: 'Hidden on Admin',
|
||||
admin: {
|
||||
hidden: true,
|
||||
},
|
||||
required: true,
|
||||
defaultValue: 'should be hidden from admin, visible in API',
|
||||
},
|
||||
{
|
||||
name: 'hiddenAPI',
|
||||
type: 'text',
|
||||
label: 'Hidden on API',
|
||||
hidden: true,
|
||||
required: true,
|
||||
hooks: {
|
||||
beforeValidate: [
|
||||
({ value }) => {
|
||||
return value || 'should be hidden from API';
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default HiddenFields;
|
||||
@@ -1,98 +0,0 @@
|
||||
/* eslint-disable no-param-reassign, no-console */
|
||||
// If importing outside of demo project, should import CollectionAfterReadHook, CollectionBeforeChangeHook, etc
|
||||
import { AfterChangeHook, AfterDeleteHook, AfterReadHook, BeforeChangeHook, BeforeDeleteHook, BeforeReadHook, CollectionConfig } from '../../src/collections/config/types';
|
||||
import { FieldHook } from '../../src/fields/config/types';
|
||||
import { Hook } from '../payload-types';
|
||||
|
||||
const Hooks: CollectionConfig = {
|
||||
slug: 'hooks',
|
||||
labels: {
|
||||
singular: 'Hook',
|
||||
plural: 'Hooks',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
access: {
|
||||
create: () => true,
|
||||
read: () => true,
|
||||
update: () => true,
|
||||
delete: () => true,
|
||||
},
|
||||
hooks: {
|
||||
beforeRead: [
|
||||
((operation) => {
|
||||
if (operation.req.headers.hook === 'beforeRead') {
|
||||
console.log('before reading Hooks document');
|
||||
}
|
||||
}) as BeforeReadHook<Hook>,
|
||||
],
|
||||
beforeChange: [
|
||||
((operation) => {
|
||||
if (operation.req.headers.hook === 'beforeChange') {
|
||||
operation.data.description += '-beforeChangeSuffix';
|
||||
}
|
||||
return operation.data;
|
||||
}) as BeforeChangeHook<Hook>,
|
||||
],
|
||||
beforeDelete: [
|
||||
((operation) => {
|
||||
if (operation.req.headers.hook === 'beforeDelete') {
|
||||
// TODO: Find a better hook operation to assert against in tests
|
||||
operation.req.headers.hook = 'afterDelete';
|
||||
}
|
||||
}) as BeforeDeleteHook,
|
||||
],
|
||||
afterRead: [
|
||||
((operation) => {
|
||||
const { doc, findMany } = operation;
|
||||
doc.afterReadHook = true;
|
||||
doc.findMany = findMany;
|
||||
|
||||
return doc;
|
||||
}) as AfterReadHook<Hook & { afterReadHook: boolean, findMany: boolean }>,
|
||||
],
|
||||
afterChange: [
|
||||
((operation) => {
|
||||
if (operation.req.headers.hook === 'afterChange') {
|
||||
operation.doc.afterChangeHook = true;
|
||||
}
|
||||
return operation.doc;
|
||||
}) as AfterChangeHook<Hook & { afterChangeHook: boolean }>,
|
||||
],
|
||||
afterDelete: [
|
||||
((operation) => {
|
||||
if (operation.req.headers.hook === 'afterDelete') {
|
||||
operation.doc.afterDeleteHook = true;
|
||||
}
|
||||
return operation.doc;
|
||||
}) as AfterDeleteHook,
|
||||
],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
type: 'text',
|
||||
maxLength: 100,
|
||||
required: true,
|
||||
unique: true,
|
||||
localized: true,
|
||||
hooks: {
|
||||
afterRead: [
|
||||
({ value }) => (value ? value.toUpperCase() : null) as FieldHook<Hook, 'title'>,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: 'textarea',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default Hooks;
|
||||
@@ -1,22 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const Images: CollectionConfig = {
|
||||
slug: 'images',
|
||||
admin: {
|
||||
description: 'Used to test upload relationship queries',
|
||||
},
|
||||
labels: {
|
||||
singular: 'Image',
|
||||
plural: 'Images',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'upload',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default Images;
|
||||
@@ -1,38 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const LocalOperations: CollectionConfig = {
|
||||
slug: 'local-operations',
|
||||
labels: {
|
||||
singular: 'Local Operation',
|
||||
plural: 'Local Operations',
|
||||
},
|
||||
hooks: {
|
||||
afterRead: [
|
||||
async ({ req, doc }) => {
|
||||
const formattedData = { ...doc };
|
||||
const localizedPosts = await req.payload.find({
|
||||
collection: 'localized-posts',
|
||||
});
|
||||
|
||||
const blocksGlobal = await req.payload.findGlobal({
|
||||
slug: 'blocks-global',
|
||||
});
|
||||
|
||||
formattedData.localizedPosts = localizedPosts;
|
||||
formattedData.blocksGlobal = blocksGlobal;
|
||||
|
||||
return formattedData;
|
||||
},
|
||||
],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'title',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default LocalOperations;
|
||||
@@ -1,146 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
import { PayloadRequest } from '../../src/express/types';
|
||||
import { Block } from '../../src/fields/config/types';
|
||||
|
||||
const validateLocalizationTransform = (hook: string, value, req: PayloadRequest) => {
|
||||
if (req.locale !== 'all' && value !== undefined && typeof value !== 'string' && value !== null) {
|
||||
console.error(hook, value);
|
||||
throw new Error('Locale transformation should happen before hook is called');
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const RichTextBlock: Block = {
|
||||
slug: 'richTextBlock',
|
||||
labels: {
|
||||
singular: 'Rich Text Block',
|
||||
plural: 'Rich Text Blocks',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'content',
|
||||
localized: true,
|
||||
type: 'richText',
|
||||
admin: {
|
||||
hideGutter: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const LocalizedPosts: CollectionConfig = {
|
||||
slug: 'localized-posts',
|
||||
labels: {
|
||||
singular: 'Localized Post',
|
||||
plural: 'Localized Posts',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: [
|
||||
'title',
|
||||
'priority',
|
||||
'createdAt',
|
||||
],
|
||||
enableRichTextRelationship: true,
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
type: 'text',
|
||||
maxLength: 100,
|
||||
required: true,
|
||||
unique: true,
|
||||
localized: true,
|
||||
hooks: {
|
||||
beforeValidate: [({ value, req }) => validateLocalizationTransform('beforeValidate', value, req)],
|
||||
beforeChange: [({ value, req }) => validateLocalizationTransform('beforeChange', value, req)],
|
||||
afterChange: [({ value, req }) => validateLocalizationTransform('afterChange', value, req)],
|
||||
afterRead: [({ value, req }) => validateLocalizationTransform('afterRead', value, req)],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'summary',
|
||||
label: 'Summary',
|
||||
type: 'text',
|
||||
index: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: 'textarea',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
type: 'richText',
|
||||
name: 'richText',
|
||||
label: 'Rich Text',
|
||||
},
|
||||
{
|
||||
name: 'priority',
|
||||
label: 'Priority',
|
||||
type: 'number',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'localizedGroup',
|
||||
label: 'Localized Group',
|
||||
type: 'group',
|
||||
localized: true,
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'text',
|
||||
label: 'Text',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'demoHiddenField',
|
||||
hidden: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'nonLocalizedGroup',
|
||||
label: 'Non-Localized Group',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'text',
|
||||
label: 'Text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
label: 'Non-Localized Array',
|
||||
name: 'nonLocalizedArray',
|
||||
maxRows: 3,
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'localizedEmbeddedText',
|
||||
label: 'Localized Embedded Text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Blocks',
|
||||
name: 'richTextBlocks',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
RichTextBlock,
|
||||
],
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default LocalizedPosts;
|
||||
@@ -1,79 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
import { FieldAccess } from '../../src/fields/config/types';
|
||||
import checkRole from '../access/checkRole';
|
||||
|
||||
const PublicReadabilityAccess: FieldAccess = ({ req: { user }, siblingData }) => {
|
||||
if (checkRole(['admin'], user)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (siblingData?.allowPublicReadability) return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const LocalizedArrays: CollectionConfig = {
|
||||
slug: 'localized-arrays',
|
||||
labels: {
|
||||
singular: 'Localized Array',
|
||||
plural: 'Localized Arrays',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'array',
|
||||
label: false,
|
||||
name: 'array',
|
||||
localized: true,
|
||||
required: true,
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'allowPublicReadability',
|
||||
label: 'Allow Public Readability',
|
||||
type: 'checkbox',
|
||||
},
|
||||
{
|
||||
name: 'arrayText1',
|
||||
label: 'Array Text 1',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
access: {
|
||||
read: PublicReadabilityAccess,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'arrayText2',
|
||||
label: 'Array Text 2',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'arrayText3',
|
||||
label: 'Array Text 3',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default LocalizedArrays;
|
||||
@@ -1,81 +0,0 @@
|
||||
import { CollectionConfig, BeforeChangeHook } from '../../src/collections/config/types';
|
||||
|
||||
const checkForUploadSizesHook: BeforeChangeHook = ({ req: { payloadUploadSizes }, data }) => {
|
||||
if (typeof payloadUploadSizes === 'object') {
|
||||
return {
|
||||
...data,
|
||||
foundUploadSizes: true,
|
||||
};
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const Media: CollectionConfig = {
|
||||
slug: 'media',
|
||||
labels: {
|
||||
singular: 'Media',
|
||||
plural: 'Media',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
admin: {
|
||||
enableRichTextRelationship: true,
|
||||
description: 'No selfies please',
|
||||
},
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
checkForUploadSizesHook,
|
||||
],
|
||||
},
|
||||
upload: {
|
||||
staticURL: '/media',
|
||||
staticDir: './media',
|
||||
adminThumbnail: ({ doc }) => `/media/${doc.filename}`,
|
||||
imageSizes: [
|
||||
{
|
||||
name: 'maintainedAspectRatio',
|
||||
width: 1024,
|
||||
height: null,
|
||||
crop: 'center',
|
||||
},
|
||||
{
|
||||
name: 'tablet',
|
||||
width: 640,
|
||||
height: 480,
|
||||
crop: 'left top',
|
||||
},
|
||||
{
|
||||
name: 'mobile',
|
||||
width: 320,
|
||||
height: 240,
|
||||
crop: 'left top',
|
||||
},
|
||||
{
|
||||
name: 'icon',
|
||||
width: 16,
|
||||
height: 16,
|
||||
},
|
||||
],
|
||||
staticOptions: {
|
||||
maxAge: 21600000, // 6 hours in milliseconds
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'alt',
|
||||
label: 'Alt Text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'foundUploadSizes',
|
||||
type: 'checkbox',
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default Media;
|
||||
@@ -1,71 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const NestedArray: CollectionConfig = {
|
||||
slug: 'nested-arrays',
|
||||
labels: {
|
||||
singular: 'Nested Array',
|
||||
plural: 'Nested Arrays',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'array',
|
||||
label: 'Array',
|
||||
name: 'array',
|
||||
labels: {
|
||||
singular: 'Parent Row',
|
||||
plural: 'Parent Rows',
|
||||
},
|
||||
required: true,
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
fields: [
|
||||
{
|
||||
name: 'parentIdentifier',
|
||||
label: 'Parent Identifier',
|
||||
defaultValue: ' ',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
name: 'nestedArray',
|
||||
labels: {
|
||||
singular: 'Child Row',
|
||||
plural: 'Child Rows',
|
||||
},
|
||||
required: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'childIdentifier',
|
||||
label: 'Child Identifier',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
name: 'deeplyNestedArray',
|
||||
labels: {
|
||||
singular: 'Grandchild Row',
|
||||
plural: 'Grandchild Rows',
|
||||
},
|
||||
required: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'grandchildIdentifier',
|
||||
label: 'Grandchild Identifier',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default NestedArray;
|
||||
@@ -1,37 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const Preview: CollectionConfig = {
|
||||
slug: 'previewable-post',
|
||||
labels: {
|
||||
singular: 'Previewable Post',
|
||||
plural: 'Previewable Posts',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
preview: async (doc, { token }) => {
|
||||
const { title } = doc;
|
||||
if (title) {
|
||||
const mockAsyncReq = await fetch(`http://localhost:3000/api/previewable-post?depth=0`)
|
||||
const mockJSON = await mockAsyncReq.json();
|
||||
const mockParam = mockJSON?.docs?.[0]?.title || '';
|
||||
return `http://localhost:3000/previewable-posts/${title}?preview=true&token=${token}&mockParam=${mockParam}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
type: 'text',
|
||||
maxLength: 100,
|
||||
required: true,
|
||||
unique: true,
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default Preview;
|
||||
@@ -1,61 +0,0 @@
|
||||
import checkRole from '../access/checkRole';
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const access = ({ req: { user } }) => checkRole(['admin'], user);
|
||||
|
||||
const PublicUsers: CollectionConfig = {
|
||||
slug: 'public-users',
|
||||
labels: {
|
||||
singular: 'Public User',
|
||||
plural: 'Public Users',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'email',
|
||||
},
|
||||
access: {
|
||||
admin: () => false,
|
||||
create: () => true,
|
||||
read: () => true,
|
||||
update: ({ req: { user } }) => {
|
||||
if (checkRole(['admin'], user)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (user) {
|
||||
return {
|
||||
id: user.id,
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
delete: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
},
|
||||
auth: {
|
||||
tokenExpiration: 300,
|
||||
verify: true,
|
||||
maxLoginAttempts: 5,
|
||||
lockTime: 600 * 1000, // lock time in ms
|
||||
cookies: {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
domain: undefined,
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'adminOnly',
|
||||
label: 'This field should only be readable and editable by Admins with "admin" role',
|
||||
type: 'text',
|
||||
defaultValue: 'test',
|
||||
access: {
|
||||
create: access,
|
||||
read: access,
|
||||
update: access,
|
||||
},
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default PublicUsers;
|
||||
@@ -1,86 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const RelationshipA: CollectionConfig = {
|
||||
slug: 'relationship-a',
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
labels: {
|
||||
singular: 'Relationship A',
|
||||
plural: 'Relationship A',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'post',
|
||||
label: 'Post',
|
||||
type: 'relationship',
|
||||
relationTo: 'relationship-b',
|
||||
},
|
||||
{
|
||||
name: 'LocalizedPost',
|
||||
label: 'Localized Post',
|
||||
type: 'relationship',
|
||||
relationTo: 'localized-posts',
|
||||
hasMany: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'postLocalizedMultiple',
|
||||
label: 'Localized Post Multiple',
|
||||
type: 'relationship',
|
||||
relationTo: ['localized-posts', 'all-fields', 'custom-id'],
|
||||
hasMany: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'postManyRelationships',
|
||||
label: 'Post Many Relationships',
|
||||
type: 'relationship',
|
||||
relationTo: ['relationship-b'],
|
||||
localized: true,
|
||||
hasMany: false,
|
||||
},
|
||||
{
|
||||
name: 'postMaxDepth',
|
||||
maxDepth: 0,
|
||||
label: 'Post With MaxDepth',
|
||||
type: 'relationship',
|
||||
relationTo: 'relationship-b',
|
||||
hasMany: false,
|
||||
},
|
||||
{
|
||||
name: 'customID',
|
||||
label: 'CustomID Relation',
|
||||
type: 'relationship',
|
||||
relationTo: 'custom-id',
|
||||
hasMany: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'filterRelationship',
|
||||
type: 'relationship',
|
||||
relationTo: 'relationship-b',
|
||||
filterOptions: {
|
||||
disableRelation: {
|
||||
not_equals: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'files',
|
||||
type: 'upload',
|
||||
relationTo: 'files',
|
||||
filterOptions: {
|
||||
type: { equals: 'Type 2' },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'demoHiddenField',
|
||||
type: 'text',
|
||||
hidden: true,
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default RelationshipA;
|
||||
@@ -1,65 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const RelationshipB: CollectionConfig = {
|
||||
slug: 'relationship-b',
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
labels: {
|
||||
singular: 'Relationship B',
|
||||
plural: 'Relationship B',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'disableRelation', // used on RelationshipA.filterRelationship field
|
||||
type: 'checkbox',
|
||||
required: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'post',
|
||||
label: 'Post',
|
||||
type: 'relationship',
|
||||
relationTo: 'relationship-a',
|
||||
localized: false,
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: 'postManyRelationships',
|
||||
label: 'Post Many Relationships',
|
||||
type: 'relationship',
|
||||
relationTo: ['relationship-a', 'media'],
|
||||
localized: true,
|
||||
hasMany: false,
|
||||
},
|
||||
{
|
||||
name: 'localizedPosts',
|
||||
label: 'Localized Posts',
|
||||
type: 'relationship',
|
||||
hasMany: true,
|
||||
relationTo: ['localized-posts', 'previewable-post'],
|
||||
},
|
||||
{
|
||||
name: 'nonLocalizedRelationToMany',
|
||||
type: 'relationship',
|
||||
relationTo: ['localized-posts', 'relationship-a'],
|
||||
},
|
||||
{
|
||||
name: 'strictAccess',
|
||||
type: 'relationship',
|
||||
relationTo: 'strict-access',
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default RelationshipB;
|
||||
@@ -1,112 +0,0 @@
|
||||
import Button from '../client/components/richText/elements/Button';
|
||||
import PurpleBackground from '../client/components/richText/leaves/PurpleBackground';
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const RichText: CollectionConfig = {
|
||||
slug: 'rich-text',
|
||||
labels: {
|
||||
singular: 'Rich Text',
|
||||
plural: 'Rich Texts',
|
||||
},
|
||||
access: {
|
||||
read: ({ req }) => {
|
||||
if (req.user) return true;
|
||||
|
||||
return {
|
||||
_status: {
|
||||
equals: 'published',
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
versions: {
|
||||
drafts: {
|
||||
autosave: false,
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
preview: () => 'https://payloadcms.com',
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'defaultRichText',
|
||||
type: 'richText',
|
||||
label: 'Default Rich Text',
|
||||
required: true,
|
||||
admin: {
|
||||
upload: {
|
||||
collections: {
|
||||
media: {
|
||||
fields: [
|
||||
{
|
||||
type: 'richText',
|
||||
name: 'caption',
|
||||
label: 'Caption',
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
type: 'relationship',
|
||||
relationTo: 'admins',
|
||||
name: 'linkToAdmin',
|
||||
label: 'Link to Admin',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'imageAlignment',
|
||||
label: 'Image Alignment',
|
||||
options: [
|
||||
{
|
||||
label: 'Left',
|
||||
value: 'left',
|
||||
},
|
||||
{
|
||||
label: 'Center',
|
||||
value: 'center',
|
||||
},
|
||||
{
|
||||
label: 'Right',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'wrapText',
|
||||
label: 'Wrap Text',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'customRichText',
|
||||
type: 'richText',
|
||||
label: 'Customized Rich Text',
|
||||
required: true,
|
||||
admin: {
|
||||
elements: [
|
||||
'h2',
|
||||
'h3',
|
||||
Button,
|
||||
],
|
||||
leaves: [
|
||||
'bold',
|
||||
PurpleBackground,
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default RichText;
|
||||
@@ -1,99 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const Select: CollectionConfig = {
|
||||
slug: 'select',
|
||||
labels: {
|
||||
singular: 'Select',
|
||||
plural: 'Selects',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'select',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'one',
|
||||
label: 'One',
|
||||
}, {
|
||||
value: 'two',
|
||||
label: 'Two',
|
||||
}, {
|
||||
value: 'three',
|
||||
label: 'Three',
|
||||
}],
|
||||
label: 'Select From',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'selectHasMany',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'one',
|
||||
label: 'One',
|
||||
}, {
|
||||
value: 'two',
|
||||
label: 'Two',
|
||||
}, {
|
||||
value: 'three',
|
||||
label: 'Three',
|
||||
}],
|
||||
label: 'Select HasMany',
|
||||
required: true,
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: 'selectJustStrings',
|
||||
type: 'select',
|
||||
options: ['blue', 'green', 'yellow'],
|
||||
label: 'Select Just Strings',
|
||||
required: true,
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: 'selectWithEmptyString',
|
||||
type: 'select',
|
||||
defaultValue: '',
|
||||
options: [{
|
||||
value: '',
|
||||
label: 'None',
|
||||
}, {
|
||||
value: 'option',
|
||||
label: 'Option',
|
||||
}],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'radio',
|
||||
type: 'radio',
|
||||
options: [{
|
||||
value: 'one',
|
||||
label: 'One',
|
||||
}, {
|
||||
value: 'two',
|
||||
label: 'Two',
|
||||
}, {
|
||||
value: 'three',
|
||||
label: 'Three',
|
||||
}],
|
||||
label: 'Choose From',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'radioWithEmptyString',
|
||||
type: 'radio',
|
||||
defaultValue: '',
|
||||
options: [{
|
||||
value: '',
|
||||
label: 'None',
|
||||
}, {
|
||||
value: 'one',
|
||||
label: 'One',
|
||||
}, {
|
||||
value: 'two',
|
||||
label: 'Two',
|
||||
}],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default Select;
|
||||
@@ -1,76 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
import checkRole from '../access/checkRole';
|
||||
|
||||
const StrictAccess: CollectionConfig = {
|
||||
slug: 'strict-access',
|
||||
labels: {
|
||||
singular: 'Strict Access',
|
||||
plural: 'Strict Access',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'address',
|
||||
},
|
||||
access: {
|
||||
create: () => true,
|
||||
read: ({ req: { user } }) => {
|
||||
if (checkRole(['admin'], user)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (user) {
|
||||
return {
|
||||
owner: {
|
||||
equals: user.id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
update: ({ req: { user } }) => {
|
||||
if (checkRole(['admin'], user)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (user) {
|
||||
return {
|
||||
owner: {
|
||||
equals: user.id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
delete: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'address',
|
||||
type: 'text',
|
||||
label: 'Address',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'city',
|
||||
type: 'text',
|
||||
label: 'City',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
type: 'text',
|
||||
label: 'State',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'zip',
|
||||
type: 'number',
|
||||
label: 'ZIP Code',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default StrictAccess;
|
||||
@@ -1,25 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const Uniques: CollectionConfig = {
|
||||
slug: 'uniques',
|
||||
labels: {
|
||||
singular: 'Unique',
|
||||
plural: 'Uniques',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Title',
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Description',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default Uniques;
|
||||
@@ -1,35 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const UnstoredMedia: CollectionConfig = {
|
||||
slug: 'unstored-media',
|
||||
labels: {
|
||||
singular: 'Unstored Media',
|
||||
plural: 'Unstored Media',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
upload: {
|
||||
staticURL: '/unstored-media',
|
||||
disableLocalStorage: true,
|
||||
imageSizes: [
|
||||
{
|
||||
name: 'tablet',
|
||||
width: 640,
|
||||
height: 480,
|
||||
crop: 'left top',
|
||||
},
|
||||
],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'alt',
|
||||
label: 'Alt Text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default UnstoredMedia;
|
||||
@@ -1,141 +0,0 @@
|
||||
import { CollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const Validations: CollectionConfig = {
|
||||
slug: 'validations',
|
||||
labels: {
|
||||
singular: 'Validation',
|
||||
plural: 'Validations',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'validationOptions',
|
||||
type: 'text',
|
||||
label: 'Text with siblingData Validation',
|
||||
required: true,
|
||||
validate: (value: string, { data, siblingData, id, operation, user }) => {
|
||||
if (typeof value === 'undefined') {
|
||||
return 'Validation is missing value';
|
||||
}
|
||||
if (data?.text !== 'test') {
|
||||
return 'The next field should be test';
|
||||
}
|
||||
if (siblingData?.text !== 'test') {
|
||||
return 'The next field should be test';
|
||||
}
|
||||
if (!user) {
|
||||
return 'ValidationOptions is missing "user"';
|
||||
}
|
||||
if (typeof operation === 'undefined') {
|
||||
return 'ValidationOptions is missing "operation"';
|
||||
}
|
||||
if (operation === 'update' && typeof id === 'undefined') {
|
||||
return 'ValidationOptions is missing "id"';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
label: 'Text',
|
||||
required: true,
|
||||
validate: (value) => {
|
||||
const result = value === 'test';
|
||||
|
||||
if (!result) {
|
||||
return 'The only accepted value of this field is "test".';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'lessThan10',
|
||||
label: 'Less than 10',
|
||||
type: 'number',
|
||||
required: true,
|
||||
validate: (value) => {
|
||||
const result = parseInt(value, 10) < 10;
|
||||
|
||||
if (!result) {
|
||||
return 'The value of this field needs to be less than 10.';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
}, {
|
||||
name: 'greaterThan10LessThan50',
|
||||
label: 'Greater than 10, Less than 50',
|
||||
type: 'number',
|
||||
required: true,
|
||||
min: 10,
|
||||
max: 50,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
label: 'Should have at least 3 rows',
|
||||
name: 'atLeast3Rows',
|
||||
required: true,
|
||||
validate: (value) => {
|
||||
const result = value >= 3;
|
||||
|
||||
if (!result) {
|
||||
return 'This array needs to have at least 3 rows.';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'number',
|
||||
name: 'greaterThan30',
|
||||
label: 'Number should be greater than 30',
|
||||
required: true,
|
||||
validate: (value) => {
|
||||
const result = value > 30;
|
||||
|
||||
if (!result) {
|
||||
return 'This value of this field needs to be greater than 30.';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
label: 'Default array validation',
|
||||
name: 'array',
|
||||
required: true,
|
||||
fields: [
|
||||
{
|
||||
type: 'number',
|
||||
name: 'lessThan20',
|
||||
label: 'Number should be less than 20',
|
||||
required: true,
|
||||
validate: (value) => {
|
||||
const result = value < 20;
|
||||
|
||||
if (!result) {
|
||||
return 'This value of this field needs to be less than 20.';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default Validations;
|
||||
@@ -1,15 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="application-name" content="My Payload Application" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="portal"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,9 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const CollectionDescription: React.FC = () => (
|
||||
<div>
|
||||
Collection description
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CollectionDescription;
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const CustomDescriptionComponent: React.FC = ({ value }) => (
|
||||
<div>
|
||||
Character count:
|
||||
{' '}
|
||||
{ value?.length || 0 }
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CustomDescriptionComponent;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const CustomAccountView: React.FC = () => <div>fake account view</div>;
|
||||
|
||||
export default CustomAccountView;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const CustomDashboardView: React.FC = () => <div>fake dashboard view</div>;
|
||||
|
||||
export default CustomDashboardView;
|
||||
@@ -1,43 +0,0 @@
|
||||
import checkRole from '../access/checkRole';
|
||||
import Quote from '../blocks/Quote';
|
||||
import CallToAction from '../blocks/CallToAction';
|
||||
import { GlobalConfig } from '../../src/globals/config/types';
|
||||
|
||||
const BlocksGlobal: GlobalConfig = {
|
||||
slug: 'blocks-global',
|
||||
label: 'Blocks Global',
|
||||
versions: {
|
||||
max: 20,
|
||||
drafts: {
|
||||
autosave: true,
|
||||
},
|
||||
},
|
||||
access: {
|
||||
update: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
read: ({ draft, req: { user } }) => {
|
||||
// To read a draft of this global, you need to be authenticated
|
||||
if (draft) {
|
||||
return Boolean(user);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
label: 'Blocks',
|
||||
type: 'blocks',
|
||||
blocks: [Quote, CallToAction],
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default BlocksGlobal;
|
||||
@@ -1,37 +0,0 @@
|
||||
import { GlobalConfig } from '../../src/globals/config/types';
|
||||
import checkRole from '../access/checkRole';
|
||||
|
||||
const GlobalWithAccess: GlobalConfig = {
|
||||
slug: 'global-with-access',
|
||||
label: 'Global with Strict Access',
|
||||
access: {
|
||||
update: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
read: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Site Title',
|
||||
type: 'text',
|
||||
maxLength: 100,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'relationship',
|
||||
label: 'Test Relationship',
|
||||
type: 'relationship',
|
||||
relationTo: 'localized-posts',
|
||||
hasMany: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'singleRelationship',
|
||||
label: 'Test Single Relationship',
|
||||
type: 'relationship',
|
||||
relationTo: 'localized-posts',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default GlobalWithAccess;
|
||||
@@ -1,32 +0,0 @@
|
||||
import { GlobalConfig } from '../../src/globals/config/types';
|
||||
import checkRole from '../access/checkRole';
|
||||
|
||||
const NavigationArray: GlobalConfig = {
|
||||
slug: 'navigation-array',
|
||||
access: {
|
||||
update: ({ req: { user } }) => checkRole(['admin', 'user'], user),
|
||||
read: () => true,
|
||||
},
|
||||
admin: {
|
||||
description: 'A description for the editor',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'array',
|
||||
label: 'Array',
|
||||
type: 'array',
|
||||
localized: true,
|
||||
fields: [{
|
||||
name: 'text',
|
||||
label: 'Text',
|
||||
type: 'text',
|
||||
}, {
|
||||
name: 'textarea',
|
||||
label: 'Textarea',
|
||||
type: 'textarea',
|
||||
}],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default NavigationArray;
|
||||
@@ -1,14 +0,0 @@
|
||||
const babelConfig = require('../babel.config');
|
||||
|
||||
require('@babel/register')({
|
||||
...babelConfig,
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
||||
env: {
|
||||
development: {
|
||||
sourceMaps: 'inline',
|
||||
retainLines: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
require('./server.ts');
|
||||
@@ -1,747 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/**
|
||||
* This file was automatically generated by Payload CMS.
|
||||
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
export interface Config {}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "blocks-global".
|
||||
*/
|
||||
export interface BlocksGlobal {
|
||||
id: string;
|
||||
_status?: 'draft' | 'published';
|
||||
title: string;
|
||||
blocks?: (
|
||||
| {
|
||||
quote: string;
|
||||
color: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'quote';
|
||||
}
|
||||
| {
|
||||
label: string;
|
||||
url: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'cta';
|
||||
}
|
||||
)[];
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "navigation-array".
|
||||
*/
|
||||
export interface NavigationArray {
|
||||
id: string;
|
||||
array?: {
|
||||
text?: string;
|
||||
textarea?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "global-with-access".
|
||||
*/
|
||||
export interface GlobalWithStrictAccess {
|
||||
id: string;
|
||||
title: string;
|
||||
relationship: (string | LocalizedPost)[];
|
||||
singleRelationship: string | LocalizedPost;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localized-posts".
|
||||
*/
|
||||
export interface LocalizedPost {
|
||||
id: string;
|
||||
title: string;
|
||||
summary?: string;
|
||||
description: string;
|
||||
richText?: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
priority?: number;
|
||||
localizedGroup?: {
|
||||
text?: string;
|
||||
demoHiddenField?: string;
|
||||
};
|
||||
nonLocalizedGroup?: {
|
||||
text?: string;
|
||||
};
|
||||
nonLocalizedArray?: {
|
||||
localizedEmbeddedText?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
richTextBlocks?: {
|
||||
content?: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'richTextBlock';
|
||||
}[];
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "admins".
|
||||
*/
|
||||
export interface Admin {
|
||||
id: string;
|
||||
email?: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpiration?: string;
|
||||
enableAPIKey?: boolean;
|
||||
apiKey?: string;
|
||||
apiKeyIndex?: string;
|
||||
loginAttempts?: number;
|
||||
lockUntil?: string;
|
||||
roles: ('admin' | 'editor' | 'moderator' | 'user' | 'viewer')[];
|
||||
publicUser?: (string | PublicUser)[];
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "public-users".
|
||||
*/
|
||||
export interface PublicUser {
|
||||
id: string;
|
||||
email?: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpiration?: string;
|
||||
_verified?: boolean;
|
||||
_verificationToken?: string;
|
||||
loginAttempts?: number;
|
||||
lockUntil?: string;
|
||||
adminOnly?: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "all-fields".
|
||||
*/
|
||||
export interface AllFields {
|
||||
id: string;
|
||||
text: string;
|
||||
descriptionText?: string;
|
||||
descriptionFunction?: string;
|
||||
image?: string | Media;
|
||||
select: 'option-1' | 'option-2' | 'option-3' | 'option-4';
|
||||
selectMany: ('option-1' | 'option-2' | 'option-3' | 'option-4')[];
|
||||
dayOnlyDateFieldExample: string;
|
||||
timeOnlyDateFieldExample?: string;
|
||||
point?: [number, number];
|
||||
radioGroupExample: 'option-1' | 'option-2' | 'option-3';
|
||||
email?: string;
|
||||
number?: number;
|
||||
group?: {
|
||||
nestedText1?: string;
|
||||
nestedText2?: string;
|
||||
};
|
||||
array?: {
|
||||
arrayText1: string;
|
||||
arrayText2: string;
|
||||
arrayText3?: string;
|
||||
checkbox?: boolean;
|
||||
id?: string;
|
||||
}[];
|
||||
blocks: (
|
||||
| {
|
||||
testEmail: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'email';
|
||||
}
|
||||
| {
|
||||
testNumber: number;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'number';
|
||||
}
|
||||
| {
|
||||
quote: string;
|
||||
color: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'quote';
|
||||
}
|
||||
| {
|
||||
label: string;
|
||||
url: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'cta';
|
||||
}
|
||||
)[];
|
||||
relationship?: string | Conditions;
|
||||
relationshipHasMany?: (string | LocalizedPost)[];
|
||||
relationshipMultipleCollections?:
|
||||
| {
|
||||
value: string | LocalizedPost;
|
||||
relationTo: 'localized-posts';
|
||||
}
|
||||
| {
|
||||
value: string | Conditions;
|
||||
relationTo: 'conditions';
|
||||
};
|
||||
textarea?: string;
|
||||
richText: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
slug: string;
|
||||
checkbox?: boolean;
|
||||
dateFieldExample?: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media".
|
||||
*/
|
||||
export interface Media {
|
||||
id: string;
|
||||
url?: string;
|
||||
filename?: string;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
sizes?: {
|
||||
maintainedAspectRatio?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
tablet?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
mobile?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
icon?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
};
|
||||
alt: string;
|
||||
foundUploadSizes?: boolean;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "conditions".
|
||||
*/
|
||||
export interface Conditions {
|
||||
id: string;
|
||||
title: string;
|
||||
enableTest?: boolean;
|
||||
number?: number;
|
||||
simpleCondition: string;
|
||||
orCondition: string;
|
||||
nestedConditions?: string;
|
||||
blocks: (
|
||||
| {
|
||||
testEmail: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'email';
|
||||
}
|
||||
| {
|
||||
testNumber: number;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'number';
|
||||
}
|
||||
| {
|
||||
quote: string;
|
||||
color: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'quote';
|
||||
}
|
||||
| {
|
||||
label: string;
|
||||
url: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'cta';
|
||||
}
|
||||
)[];
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "auto-label".
|
||||
*/
|
||||
export interface AutoLabel {
|
||||
id: string;
|
||||
autoLabelField?: string;
|
||||
noLabel?: string;
|
||||
labelOverride?: string;
|
||||
testRelationship?: string | AllFields;
|
||||
specialBlock?: {
|
||||
testNumber?: number;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'number';
|
||||
}[];
|
||||
noLabelBlock?: {
|
||||
testNumber?: number;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'number';
|
||||
}[];
|
||||
items?: {
|
||||
itemName?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
noLabelArray?: {
|
||||
textField?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "autosave-posts".
|
||||
*/
|
||||
export interface AutosavePost {
|
||||
id: string;
|
||||
_status?: 'draft' | 'published';
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "blocks".
|
||||
*/
|
||||
export interface Blocks {
|
||||
id: string;
|
||||
_status?: 'draft' | 'published';
|
||||
layout: (
|
||||
| {
|
||||
testEmail: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'email';
|
||||
}
|
||||
| {
|
||||
testNumber: number;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'number';
|
||||
}
|
||||
| {
|
||||
quote: string;
|
||||
color: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'quote';
|
||||
}
|
||||
| {
|
||||
label: string;
|
||||
url: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'cta';
|
||||
}
|
||||
)[];
|
||||
nonLocalizedLayout: (
|
||||
| {
|
||||
testEmail: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'email';
|
||||
}
|
||||
| {
|
||||
testNumber: number;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'number';
|
||||
}
|
||||
| {
|
||||
quote: string;
|
||||
color: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'quote';
|
||||
}
|
||||
| {
|
||||
label: string;
|
||||
url: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'cta';
|
||||
}
|
||||
)[];
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "code".
|
||||
*/
|
||||
export interface Code {
|
||||
id: string;
|
||||
code: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "custom-id".
|
||||
*/
|
||||
export interface CustomID {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "default-values".
|
||||
*/
|
||||
export interface DefaultValueTest {
|
||||
id: string;
|
||||
text?: string;
|
||||
image?: string | Media;
|
||||
select?: 'option-1' | 'option-2' | 'option-3' | 'option-4';
|
||||
selectMany?: ('option-1' | 'option-2' | 'option-3' | 'option-4')[];
|
||||
radioGroupExample?: 'option-1' | 'option-2' | 'option-3';
|
||||
email?: string;
|
||||
number?: number;
|
||||
group?: {
|
||||
nestedText1?: string;
|
||||
nestedText2?: string;
|
||||
nestedText3?: string;
|
||||
};
|
||||
array?: {
|
||||
arrayText1?: string;
|
||||
arrayText2?: string;
|
||||
arrayText3?: string;
|
||||
checkbox?: boolean;
|
||||
id?: string;
|
||||
}[];
|
||||
blocks?: (
|
||||
| {
|
||||
testEmail: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'email';
|
||||
}
|
||||
| {
|
||||
testNumber: number;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'number';
|
||||
}
|
||||
| {
|
||||
quote: string;
|
||||
color: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'quote';
|
||||
}
|
||||
| {
|
||||
label: string;
|
||||
url: string;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'cta';
|
||||
}
|
||||
)[];
|
||||
relationship?: string | Conditions;
|
||||
relationshipHasMany?: (string | LocalizedPost)[];
|
||||
relationshipMultipleCollections?:
|
||||
| {
|
||||
value: string | LocalizedPost;
|
||||
relationTo: 'localized-posts';
|
||||
}
|
||||
| {
|
||||
value: string | Conditions;
|
||||
relationTo: 'conditions';
|
||||
};
|
||||
textarea?: string;
|
||||
slug?: string;
|
||||
checkbox?: boolean;
|
||||
richText?: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
asyncArray?: {
|
||||
child?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
asyncText?: string;
|
||||
function?: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "files".
|
||||
*/
|
||||
export interface File {
|
||||
id: string;
|
||||
url?: string;
|
||||
filename?: string;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
type: 'Type 1' | 'Type 2' | 'Type 3';
|
||||
owner: string | Admin;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "geolocation".
|
||||
*/
|
||||
export interface Geolocation {
|
||||
id: string;
|
||||
location?: [number, number];
|
||||
localizedPoint?: [number, number];
|
||||
group?: {
|
||||
point?: [number, number];
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "hidden-fields".
|
||||
*/
|
||||
export interface HiddenFields {
|
||||
id: string;
|
||||
title: string;
|
||||
hiddenAdmin: string;
|
||||
hiddenAPI: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "hooks".
|
||||
*/
|
||||
export interface Hook {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localized-arrays".
|
||||
*/
|
||||
export interface LocalizedArray {
|
||||
id: string;
|
||||
array: {
|
||||
allowPublicReadability?: boolean;
|
||||
arrayText1: string;
|
||||
arrayText2: string;
|
||||
arrayText3?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "local-operations".
|
||||
*/
|
||||
export interface LocalOperation {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "images".
|
||||
*/
|
||||
export interface Image {
|
||||
id: string;
|
||||
upload?: string | Media;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "nested-arrays".
|
||||
*/
|
||||
export interface NestedArray {
|
||||
id: string;
|
||||
array: {
|
||||
parentIdentifier: string;
|
||||
nestedArray: {
|
||||
childIdentifier: string;
|
||||
deeplyNestedArray: {
|
||||
grandchildIdentifier?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
id?: string;
|
||||
}[];
|
||||
id?: string;
|
||||
}[];
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "previewable-post".
|
||||
*/
|
||||
export interface PreviewablePost {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "relationship-a".
|
||||
*/
|
||||
export interface RelationshipA {
|
||||
id: string;
|
||||
post?: string | RelationshipB;
|
||||
LocalizedPost?: (string | LocalizedPost)[];
|
||||
postLocalizedMultiple?: (
|
||||
| {
|
||||
value: string | LocalizedPost;
|
||||
relationTo: 'localized-posts';
|
||||
}
|
||||
| {
|
||||
value: string | AllFields;
|
||||
relationTo: 'all-fields';
|
||||
}
|
||||
| {
|
||||
value: number | CustomID;
|
||||
relationTo: 'custom-id';
|
||||
}
|
||||
)[];
|
||||
postManyRelationships?: {
|
||||
value: string | RelationshipB;
|
||||
relationTo: 'relationship-b';
|
||||
};
|
||||
postMaxDepth?: string | RelationshipB;
|
||||
customID?: (number | CustomID)[];
|
||||
filterRelationship?: string | RelationshipB;
|
||||
files?: string | File;
|
||||
demoHiddenField?: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "relationship-b".
|
||||
*/
|
||||
export interface RelationshipB {
|
||||
id: string;
|
||||
title?: string;
|
||||
disableRelation: boolean;
|
||||
post?: (string | RelationshipA)[];
|
||||
postManyRelationships?:
|
||||
| {
|
||||
value: string | RelationshipA;
|
||||
relationTo: 'relationship-a';
|
||||
}
|
||||
| {
|
||||
value: string | Media;
|
||||
relationTo: 'media';
|
||||
};
|
||||
localizedPosts?: (
|
||||
| {
|
||||
value: string | LocalizedPost;
|
||||
relationTo: 'localized-posts';
|
||||
}
|
||||
| {
|
||||
value: string | PreviewablePost;
|
||||
relationTo: 'previewable-post';
|
||||
}
|
||||
)[];
|
||||
nonLocalizedRelationToMany?:
|
||||
| {
|
||||
value: string | LocalizedPost;
|
||||
relationTo: 'localized-posts';
|
||||
}
|
||||
| {
|
||||
value: string | RelationshipA;
|
||||
relationTo: 'relationship-a';
|
||||
};
|
||||
strictAccess?: string | StrictAccess;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "strict-access".
|
||||
*/
|
||||
export interface StrictAccess {
|
||||
id: string;
|
||||
address: string;
|
||||
city: string;
|
||||
state: string;
|
||||
zip: number;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "rich-text".
|
||||
*/
|
||||
export interface RichText {
|
||||
id: string;
|
||||
_status?: 'draft' | 'published';
|
||||
title?: string;
|
||||
defaultRichText: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
customRichText: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "select".
|
||||
*/
|
||||
export interface Select {
|
||||
id: string;
|
||||
select: 'one' | 'two' | 'three';
|
||||
selectHasMany: ('one' | 'two' | 'three')[];
|
||||
selectJustStrings: ('blue' | 'green' | 'yellow')[];
|
||||
selectWithEmptyString: '' | 'option';
|
||||
radio: 'one' | 'two' | 'three';
|
||||
radioWithEmptyString: '' | 'one' | 'two';
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "validations".
|
||||
*/
|
||||
export interface Validation {
|
||||
id: string;
|
||||
validationOptions: string;
|
||||
text: string;
|
||||
lessThan10: number;
|
||||
greaterThan10LessThan50: number;
|
||||
atLeast3Rows: {
|
||||
greaterThan30: number;
|
||||
id?: string;
|
||||
}[];
|
||||
array: {
|
||||
lessThan20: number;
|
||||
id?: string;
|
||||
}[];
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "uniques".
|
||||
*/
|
||||
export interface Unique {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "unstored-media".
|
||||
*/
|
||||
export interface UnstoredMedia {
|
||||
id: string;
|
||||
url?: string;
|
||||
filename?: string;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
sizes?: {
|
||||
tablet?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
};
|
||||
alt: string;
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
import path from 'path';
|
||||
import { buildConfig } from '../src/config/build';
|
||||
|
||||
import Admin from './collections/Admin';
|
||||
import AllFields from './collections/AllFields';
|
||||
import AutoLabel from './collections/AutoLabel';
|
||||
import Autosave from './collections/Autosave';
|
||||
import Code from './collections/Code';
|
||||
import Conditions from './collections/Conditions';
|
||||
// import CustomComponents from './collections/CustomComponents';
|
||||
import File from './collections/File';
|
||||
import Blocks from './collections/Blocks';
|
||||
import CustomID from './collections/CustomID';
|
||||
import DefaultValues from './collections/DefaultValues';
|
||||
import HiddenFields from './collections/HiddenFields';
|
||||
import Hooks from './collections/Hooks';
|
||||
import Localized from './collections/Localized';
|
||||
import LocalizedArray from './collections/LocalizedArray';
|
||||
import LocalOperations from './collections/LocalOperations';
|
||||
import Media from './collections/Media';
|
||||
import Images from './collections/Images';
|
||||
import NestedArrays from './collections/NestedArrays';
|
||||
import Preview from './collections/Preview';
|
||||
import PublicUsers from './collections/PublicUsers';
|
||||
import RelationshipA from './collections/RelationshipA';
|
||||
import RelationshipB from './collections/RelationshipB';
|
||||
import RichText from './collections/RichText';
|
||||
import Select from './collections/Select';
|
||||
import StrictAccess from './collections/StrictAccess';
|
||||
import Validations from './collections/Validations';
|
||||
import Uniques from './collections/Uniques';
|
||||
import Geolocation from './collections/Geolocation';
|
||||
|
||||
import BlocksGlobal from './globals/BlocksGlobal';
|
||||
import NavigationArray from './globals/NavigationArray';
|
||||
import GlobalWithStrictAccess from './globals/GlobalWithStrictAccess';
|
||||
import UnstoredMedia from './collections/UnstoredMedia';
|
||||
import CustomRouteWithMinimalTemplate from './client/components/views/CustomMinimal';
|
||||
import CustomRouteWithDefaultTemplate from './client/components/views/CustomDefault';
|
||||
import AfterDashboard from './client/components/AfterDashboard';
|
||||
import AfterNavLinks from './client/components/AfterNavLinks';
|
||||
import BeforeLogin from './client/components/BeforeLogin';
|
||||
// import CustomProvider from './client/components/CustomProvider';
|
||||
|
||||
export default buildConfig({
|
||||
cookiePrefix: 'payload',
|
||||
serverURL: 'http://localhost:3000',
|
||||
typescript: {
|
||||
outputFile: path.resolve(__dirname, './payload-types.ts'),
|
||||
},
|
||||
admin: {
|
||||
user: 'admins',
|
||||
indexHTML: path.resolve(__dirname, './client/index.html'),
|
||||
// meta: {
|
||||
// titleSuffix: '- Payload Demo',
|
||||
// // ogImage: '/static/find-image-here.jpg',
|
||||
// // favicon: '/img/whatever.png',
|
||||
// },
|
||||
// disable: true,
|
||||
scss: path.resolve(__dirname, './client/scss/overrides.scss'),
|
||||
components: {
|
||||
// providers: [CustomProvider, CustomProvider],
|
||||
routes: [
|
||||
{
|
||||
path: '/custom-minimal-route',
|
||||
Component: CustomRouteWithMinimalTemplate,
|
||||
},
|
||||
{
|
||||
path: '/custom-default-route',
|
||||
Component: CustomRouteWithDefaultTemplate,
|
||||
},
|
||||
],
|
||||
afterDashboard: [
|
||||
AfterDashboard,
|
||||
],
|
||||
beforeLogin: [
|
||||
BeforeLogin,
|
||||
],
|
||||
afterNavLinks: [
|
||||
AfterNavLinks,
|
||||
],
|
||||
// Nav: () => (
|
||||
// <div>Hello</div>
|
||||
// ),
|
||||
views: {
|
||||
// Dashboard: CustomDashboardView,
|
||||
// Account: CustomAccountView,
|
||||
},
|
||||
},
|
||||
webpack: (config) => config,
|
||||
},
|
||||
collections: [
|
||||
Admin,
|
||||
AllFields,
|
||||
AutoLabel,
|
||||
Autosave,
|
||||
Blocks,
|
||||
Code,
|
||||
Conditions,
|
||||
// CustomComponents,
|
||||
CustomID,
|
||||
DefaultValues,
|
||||
File,
|
||||
Geolocation,
|
||||
HiddenFields,
|
||||
Hooks,
|
||||
Localized,
|
||||
LocalizedArray,
|
||||
LocalOperations,
|
||||
Media,
|
||||
Images,
|
||||
NestedArrays,
|
||||
Preview,
|
||||
PublicUsers,
|
||||
RelationshipA,
|
||||
RelationshipB,
|
||||
RichText,
|
||||
Select,
|
||||
StrictAccess,
|
||||
Validations,
|
||||
Uniques,
|
||||
UnstoredMedia,
|
||||
],
|
||||
globals: [
|
||||
BlocksGlobal,
|
||||
NavigationArray,
|
||||
GlobalWithStrictAccess,
|
||||
],
|
||||
// cors: [
|
||||
// 'http://localhost',
|
||||
// 'http://localhost:3000',
|
||||
// 'http://localhost:8080',
|
||||
// 'http://localhost:8081',
|
||||
// ],
|
||||
// csrf: [
|
||||
// 'http://localhost:3000',
|
||||
// 'https://other-app-here.com',
|
||||
// ],
|
||||
routes: {
|
||||
api: '/api',
|
||||
admin: '/admin',
|
||||
graphQL: '/graphql',
|
||||
graphQLPlayground: '/graphql-playground',
|
||||
},
|
||||
defaultDepth: 2,
|
||||
graphQL: {
|
||||
maxComplexity: 1000,
|
||||
disablePlaygroundInProduction: false,
|
||||
disable: false,
|
||||
},
|
||||
// rateLimit: {
|
||||
// window: 15 * 60 * 100,
|
||||
// max: 100,
|
||||
// trustProxy: true,
|
||||
// skip: (req) => req.ip === '127.0.0.1',
|
||||
// },
|
||||
maxDepth: 10,
|
||||
localization: {
|
||||
locales: [
|
||||
'en',
|
||||
'es',
|
||||
],
|
||||
defaultLocale: 'en',
|
||||
fallback: true,
|
||||
},
|
||||
// indexSortableFields: true,
|
||||
hooks: {
|
||||
afterError: (err) => {
|
||||
if (process.env.DISABLE_LOGGING !== 'true') {
|
||||
console.error('global error config handler', err);
|
||||
}
|
||||
},
|
||||
},
|
||||
upload: {
|
||||
limits: {
|
||||
fileSize: 10000000, // 10MB
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,40 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import express from 'express';
|
||||
import path from 'path';
|
||||
import payload from '../src';
|
||||
|
||||
const expressApp = express();
|
||||
|
||||
expressApp.use('/static', express.static(path.resolve(__dirname, 'client/static')));
|
||||
|
||||
payload.init({
|
||||
secret: 'SECRET_KEY',
|
||||
mongoURL: process.env.MONGO_URL || 'mongodb://localhost/payload',
|
||||
express: expressApp,
|
||||
email: {
|
||||
fromName: 'Payload',
|
||||
fromAddress: 'hello@payloadcms.com',
|
||||
},
|
||||
onInit: (app) => {
|
||||
app.logger.info('Payload Demo Initialized');
|
||||
},
|
||||
});
|
||||
|
||||
const externalRouter = express.Router();
|
||||
|
||||
externalRouter.use(payload.authenticate);
|
||||
|
||||
externalRouter.get('/', (req, res) => {
|
||||
if (req.user) {
|
||||
return res.send(`Authenticated successfully as ${req.user.email}.`);
|
||||
}
|
||||
|
||||
return res.send('Not authenticated');
|
||||
});
|
||||
|
||||
expressApp.use('/external-route', externalRouter);
|
||||
|
||||
expressApp.listen(3000, async () => {
|
||||
payload.logger.info(`Admin URL on ${payload.getAdminURL()}`);
|
||||
payload.logger.info(`API URL on ${payload.getAPIURL()}`);
|
||||
});
|
||||
@@ -27,8 +27,10 @@ If a Collection supports [`Authentication`](/docs/authentication/overview), the
|
||||
| **[`unlock`](#unlock)** | Used to restrict which users can access the `unlock` operation |
|
||||
|
||||
**Example Collection config:**
|
||||
```js
|
||||
export default {
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
|
||||
const Posts: CollectionConfig = {
|
||||
slug: "posts",
|
||||
// highlight-start
|
||||
access: {
|
||||
@@ -40,58 +42,135 @@ export default {
|
||||
},
|
||||
// highlight-end
|
||||
};
|
||||
|
||||
export default Categories;
|
||||
```
|
||||
|
||||
### Create
|
||||
|
||||
Returns a boolean which allows/denies access to the `create` request.
|
||||
|
||||
**Available argument properties :**
|
||||
**Available argument properties:**
|
||||
|
||||
| Option | Description |
|
||||
| ---------- | ----------- |
|
||||
| **`req`** | The Express `request` object containing the currently authenticated `user` |
|
||||
| **`data`** | The data passed to create the document with. |
|
||||
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
const PublicUsers = {
|
||||
slug: 'public-users',
|
||||
access: {
|
||||
// highlight-start
|
||||
// allow guest users to self-registration
|
||||
create: () => true,
|
||||
// highlight-end
|
||||
...
|
||||
},
|
||||
fields: [ ... ],
|
||||
}
|
||||
```
|
||||
|
||||
### Read
|
||||
|
||||
Read access functions can return a boolean result or optionally return a [query constraint](/docs/queries/overview) which limits the documents that are returned to only those that match the constraint you provide. This can be helpful to restrict users' access to only certain documents however you specify.
|
||||
|
||||
**Available argument properties :**
|
||||
**Available argument properties:**
|
||||
|
||||
| Option | Description |
|
||||
| --------- | ----------- |
|
||||
| **`req`** | The Express `request` object containing the currently authenticated `user` |
|
||||
| **`id`** | `id` of document requested, if within `findByID`. |
|
||||
| **`id`** | `id` of document requested, if within `findByID` |
|
||||
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
import { Access } from 'payload/config';
|
||||
|
||||
const canReadPage: Access = ({ req: { user } }) => {
|
||||
// allow authenticated users
|
||||
if (user) {
|
||||
return true;
|
||||
}
|
||||
// using a query constraint, guest users can access when a field named 'isPublic' is set to true
|
||||
return {
|
||||
// assumes we have a checkbox field named 'isPublic'
|
||||
isPublic: {
|
||||
equals: true,
|
||||
},
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Update
|
||||
|
||||
Update access functions can return a boolean result or optionally return a [query constraint](/docs/queries/overview) to limit the document(s) that can be updated by the currently authenticated user. For example, returning a `query` from the `update` Access Control is helpful in cases where you would like to restrict a user to only being able to update the documents containing a `createdBy` relationship field equal to the user's ID.
|
||||
|
||||
**Available argument properties :**
|
||||
**Available argument properties:**
|
||||
|
||||
| Option | Description |
|
||||
| ---------- | ----------- |
|
||||
| **`req`** | The Express `request` object containing the currently authenticated `user` |
|
||||
| **`id`** | `id` of document requested to update |
|
||||
| **`data`** | The data passed to update the document with. |
|
||||
| **`data`** | The data passed to update the document with |
|
||||
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
import { Access } from 'payload/config';
|
||||
|
||||
const canUpdateUser: Access = ({ req: { user }, id }) => {
|
||||
// allow users with a role of 'admin'
|
||||
if (user.roles && user.roles.some(role => role === 'admin')) {
|
||||
return true;
|
||||
}
|
||||
// allow any other users to update only oneself
|
||||
return user.id === id;
|
||||
};
|
||||
```
|
||||
|
||||
### Delete
|
||||
|
||||
Similarly to the Update function, returns a boolean or a [query constraint](/docs/queries/overview) to limit which documents can be deleted by which users.
|
||||
|
||||
**Available argument properties :**
|
||||
**Available argument properties:**
|
||||
|
||||
| Option | Description |
|
||||
| --------- | ----------- |
|
||||
| **`req`** | The Express `request` object with additional `user` property, which is the currently logged in user |
|
||||
| **`id`** | `id` of document requested to delete |
|
||||
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
import { Access } from 'payload/config'
|
||||
|
||||
const canDeleteCustomer: Access = async ({ req, id }) => {
|
||||
if (!id) {
|
||||
// allow the admin UI to show controls to delete since it is indeterminate without the id
|
||||
return true;
|
||||
}
|
||||
// query another collection using the id
|
||||
const result = await req.payload.find({
|
||||
collection: 'contracts',
|
||||
limit: 0,
|
||||
depth: 0,
|
||||
where: {
|
||||
customer: { equals: id },
|
||||
},
|
||||
});
|
||||
|
||||
return result.totalDocs === 0;
|
||||
};
|
||||
```
|
||||
|
||||
### Admin
|
||||
|
||||
If the Collection is [used to access the Payload Admin panel](/docs/admin/overview#the-admin-user-collection), the `Admin` Access Control function determines whether or not the currently logged in user can access the admin UI.
|
||||
|
||||
**Available argument properties :**
|
||||
**Available argument properties:**
|
||||
|
||||
| Option | Description |
|
||||
| --------- | ----------- |
|
||||
@@ -101,7 +180,7 @@ If the Collection is [used to access the Payload Admin panel](/docs/admin/overvi
|
||||
|
||||
Determines which users can [unlock](/docs/authentication/operations#unlock) other users who may be blocked from authenticating successfully due to [failing too many login attempts](/docs/authentication/config#options).
|
||||
|
||||
**Available argument properties :**
|
||||
**Available argument properties:**
|
||||
|
||||
| Option | Description |
|
||||
| --------- | ----------- |
|
||||
|
||||
@@ -17,8 +17,10 @@ Field Access Control is specified with functions inside a field's config. All fi
|
||||
| **[`update`](#update)** | Allows or denies the ability to update a field's value |
|
||||
|
||||
**Example Collection config:**
|
||||
```js
|
||||
export default {
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
|
||||
const Posts: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
fields: [
|
||||
{
|
||||
@@ -33,7 +35,7 @@ export default {
|
||||
// highlight-end
|
||||
};
|
||||
],
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Create
|
||||
|
||||
@@ -18,16 +18,20 @@ You can define Global-level Access Control within each Global's `access` propert
|
||||
| **[`update`](#update)** | Used in the `update` Global operation |
|
||||
|
||||
**Example Global config:**
|
||||
```js
|
||||
export default {
|
||||
```ts
|
||||
import { GlobalConfig } from 'payload/types';
|
||||
|
||||
const Header: GlobalConfig = {
|
||||
slug: "header",
|
||||
// highlight-start
|
||||
access: {
|
||||
read: ({ req: { user } }) => { ... },
|
||||
update: ({ req: { user } }) => { ... },
|
||||
read: ({ req: { user } }) => { /* */ },
|
||||
update: ({ req: { user } }) => { /* */ },
|
||||
},
|
||||
// highlight-end
|
||||
};
|
||||
|
||||
export default Header;
|
||||
```
|
||||
|
||||
### Read
|
||||
|
||||
@@ -8,6 +8,11 @@ keywords: overview, access control, permissions, documentation, Content Manageme
|
||||
|
||||
Access control within Payload is extremely powerful while remaining easy and intuitive to manage. Declaring who should have access to what documents is no more complex than writing a simple JavaScript function that either returns a `boolean` or a [`query`](/docs/queries/overview) constraint to restrict which documents users can interact with.
|
||||
|
||||
<YouTube
|
||||
id="DoPLyXG26Dg"
|
||||
title="Overview of Payload Access Control"
|
||||
/>
|
||||
|
||||
**Example use cases:**
|
||||
|
||||
- Allowing anyone `read` access to all `Post`s
|
||||
@@ -23,7 +28,7 @@ Access control within Payload is extremely powerful while remaining easy and int
|
||||
|
||||
**Default Access function:**
|
||||
|
||||
```js
|
||||
```ts
|
||||
const defaultPayloadAccess = ({ req: { user } }) => {
|
||||
// Return `true` if a user is found
|
||||
// and `false` if it is undefined or null
|
||||
@@ -50,7 +55,7 @@ You can manage access within Payload on three different levels:
|
||||
<Banner type="success">
|
||||
<strong>Note:</strong><br/>
|
||||
Access control functions are utilized in two places. It's important to understand how and when your access control is executed.
|
||||
<Banner />
|
||||
</Banner>
|
||||
|
||||
#### As you execute operations
|
||||
|
||||
@@ -67,6 +72,6 @@ To accomplish this, Payload ships with an `Access` operation, which is executed
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong><br/>
|
||||
When your access control functions are executed via the <strong>access</strong> operation, the <strong>id</strong> and <strong>data</strong> arguments will be <strong>undefined</strong>, because Payload is executing your functions without referencing a specific document.
|
||||
<Banner />
|
||||
</Banner>
|
||||
|
||||
If you use `id` or `data` within your access control functions, make sure to check that they are defined first. If they are not, then you can assume that your access control is being executed via the `access` operation, to determine solely what the user can do within the Admin UI.
|
||||
|
||||
@@ -22,8 +22,8 @@ You can override a set of admin panel-wide components by providing a component t
|
||||
| Path | Description |
|
||||
| --------------------- | -------------|
|
||||
| **`Nav`** | Contains the sidebar and mobile Nav in its entirety. |
|
||||
| **`BeforeDashboard`** | Array of components to inject into the built-in Dashboard, _before_ the default dashboard contents. [Demo](https://github.com/payloadcms/payload/tree/master/demo/client/components/AfterDashboard) |
|
||||
| **`AfterDashboard`** | Array of components to inject into the built-in Dashboard, _after_ the default dashboard contents. |
|
||||
| **`BeforeDashboard`** | Array of components to inject into the built-in Dashboard, _before_ the default dashboard contents. |
|
||||
| **`AfterDashboard`** | Array of components to inject into the built-in Dashboard, _after_ the default dashboard contents. [Demo](https://github.com/payloadcms/payload/tree/master/test/admin/components/AfterDashboard/index.tsx)|
|
||||
| **`BeforeLogin`** | Array of components to inject into the built-in Login, _before_ the default login form. |
|
||||
| **`AfterLogin`** | Array of components to inject into the built-in Login, _after_ the default login form. |
|
||||
| **`BeforeNavLinks`** | Array of components to inject into the built-in Nav, _before_ the links themselves. |
|
||||
@@ -38,9 +38,16 @@ You can override a set of admin panel-wide components by providing a component t
|
||||
#### Full example:
|
||||
|
||||
`payload.config.js`
|
||||
```js
|
||||
import { buildConfig } from 'payload/config';
|
||||
import { MyCustomNav, MyCustomLogo, MyCustomIcon, MyCustomAccount, MyCustomDashboard, MyProvider } from './customComponents.js';
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import {
|
||||
MyCustomNav,
|
||||
MyCustomLogo,
|
||||
MyCustomIcon,
|
||||
MyCustomAccount,
|
||||
MyCustomDashboard,
|
||||
MyProvider,
|
||||
} from './customComponents';
|
||||
|
||||
export default buildConfig({
|
||||
admin: {
|
||||
@@ -55,12 +62,12 @@ export default buildConfig({
|
||||
Dashboard: MyCustomDashboard,
|
||||
},
|
||||
providers: [MyProvider],
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
*For more examples regarding how to customize components, look at the [demo app](https://github.com/payloadcms/payload/tree/master/demo).*
|
||||
*For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/master/test/admin/components).*
|
||||
|
||||
### Collections
|
||||
|
||||
@@ -90,78 +97,73 @@ All Payload fields support the ability to swap in your own React components. So,
|
||||
|
||||
**Fields support the following custom components:**
|
||||
|
||||
| Component | Description |
|
||||
| --------------- | -------------|
|
||||
| **`Filter`** | Override the text input that is presented in the `List` view when a user is filtering documents by the customized field. |
|
||||
| **`Cell`** | Used in the `List` view's table to represent a table-based preview of the data stored in the field. |
|
||||
| **`Field`** | Swap out the field itself within all `Edit` views. |
|
||||
| Component | Description |
|
||||
| --------------- |------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`Filter`** | Override the text input that is presented in the `List` view when a user is filtering documents by the customized field. |
|
||||
| **`Cell`** | Used in the `List` view's table to represent a table-based preview of the data stored in the field. [More](#cell-component) |
|
||||
| **`Field`** | Swap out the field itself within all `Edit` views. [More](#field-component) |
|
||||
|
||||
## Cell Component
|
||||
|
||||
These are the props that will be passed to your custom Cell to use in your own components.
|
||||
|
||||
| Property | Description |
|
||||
|--------------|-------------------------------------------------------------------|
|
||||
| **`field`** | An object that includes the field configuration. |
|
||||
| **`colIndex`** | A unique number for the column in the list. |
|
||||
| **`collection`** | An object with the config of the collection that the field is in. |
|
||||
| **`cellData`** | The data for the field that the cell represents. |
|
||||
| **`rowData`** | An object with all the field values for the row. |
|
||||
|
||||
#### Example
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import './index.scss';
|
||||
const baseClass = 'custom-cell';
|
||||
|
||||
const CustomCell: React.FC<Props> = (props) => {
|
||||
const {
|
||||
field,
|
||||
colIndex,
|
||||
collection,
|
||||
cellData,
|
||||
rowData,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<span className={baseClass}>
|
||||
{ cellData }
|
||||
</span>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Field Component
|
||||
|
||||
When writing your own custom components you can make use of a number of hooks to set data, get reactive changes to other fields, get the id of the document or interact with a context from a custom provider.
|
||||
|
||||
### Sending and receiving values from the form
|
||||
|
||||
When swapping out the `Field` component, you'll be responsible for sending and receiving the field's `value` from the form itself. To do so, import the `useField` hook as follows:
|
||||
|
||||
```js
|
||||
import { useField } from 'payload/components/forms';
|
||||
```tsx
|
||||
import { useField } from 'payload/components/forms'
|
||||
|
||||
const CustomTextField = ({ path }) => {
|
||||
type Props = { path: string }
|
||||
|
||||
const CustomTextField: React.FC<Props> = ({ path }) => {
|
||||
// highlight-start
|
||||
const { value, setValue } = useField({ path });
|
||||
const { value, setValue } = useField<Props>({ path })
|
||||
// highlight-end
|
||||
|
||||
return (
|
||||
<input
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
value={value}
|
||||
/>
|
||||
)
|
||||
return <input onChange={e => setValue(e.target.value)} value={value.path} />
|
||||
}
|
||||
```
|
||||
|
||||
### Getting other field values from the form
|
||||
|
||||
There are times when a custom field component needs to have access to data from other fields. This can be done using `getDataByPath` from `useWatchForm` as follows:
|
||||
|
||||
```js
|
||||
import { useWatchForm } from 'payload/components/forms';
|
||||
|
||||
const DisplayFee = () => {
|
||||
const { getDataByPath } = useWatchForm();
|
||||
|
||||
const amount = getDataByPath('amount');
|
||||
const feePercentage = getDataByPath('feePercentage');
|
||||
|
||||
if (amount && feePercentage) {
|
||||
return (
|
||||
<span>The fee is ${ amount * feePercentage / 100 }</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Getting the document ID
|
||||
|
||||
The document ID can be very useful for certain custom components. You can get the `id` from the `useDocumentInfo` hook. Here is an example of a `UI` field using `id` to link to related collections:
|
||||
|
||||
```js
|
||||
import { useDocumentInfo } from 'payload/components/utilities';
|
||||
|
||||
const LinkFromCategoryToPosts = () => {
|
||||
// highlight-start
|
||||
const { id } = useDocumentInfo();
|
||||
// highlight-end
|
||||
|
||||
// id will be undefined on the create form
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<a href={`/admin/collections/posts?where[or][0][and][0][category][in][0]=[${id}]`} >
|
||||
View posts
|
||||
</a>
|
||||
)
|
||||
};
|
||||
```
|
||||
<Banner type="success">
|
||||
For more information regarding the hooks that are available to you while you build custom components, including the <strong>useField</strong> hook, <a href="/docs/admin/hooks" style={{color: 'black'}}>click here</a>.
|
||||
</Banner>
|
||||
|
||||
## Custom routes
|
||||
|
||||
@@ -195,12 +197,12 @@ Your custom route components will be given all the props that a React Router `<R
|
||||
|
||||
#### Example
|
||||
|
||||
You can find examples of custom route views in the [Payload source code `/demo/client/components/views` folder](https://github.com/payloadcms/payload/tree/master/demo/client/components/views). There, you'll find two custom routes:
|
||||
You can find examples of custom route views in the [Payload source code `/test/admin/components/views` folder](https://github.com/payloadcms/payload/tree/master/test/admin/components/views). There, you'll find two custom routes:
|
||||
|
||||
1. A custom view that uses the `DefaultTemplate`, which is the built-in Payload template that displays the sidebar and "eyebrow nav"
|
||||
1. A custom view that uses the `MinimalTemplate` - which is just a centered template used for things like logging in or out
|
||||
|
||||
To see how to pass in your custom views to create custom routes of your own, take a look at the `admin.components.routes` property of the [Payload demo config](https://github.com/payloadcms/payload/blob/master/demo/payload.config.ts).
|
||||
To see how to pass in your custom views to create custom routes of your own, take a look at the `admin.components.routes` property of the [Payload test admin config](https://github.com/payloadcms/payload/blob/master/test/admin/config.ts).
|
||||
|
||||
## Custom providers
|
||||
|
||||
@@ -222,10 +224,10 @@ To make use of Payload SCSS variables / mixins to use directly in your own compo
|
||||
|
||||
In any custom component you can get the selected locale with the `useLocale` hook. Here is a simple example:
|
||||
|
||||
```js
|
||||
```tsx
|
||||
import { useLocale } from 'payload/components/utilities';
|
||||
|
||||
const Greeting = () => {
|
||||
const Greeting: React.FC = () => {
|
||||
// highlight-start
|
||||
const locale = useLocale();
|
||||
// highlight-end
|
||||
@@ -237,6 +239,6 @@ const Greeting = () => {
|
||||
|
||||
return (
|
||||
<span> { trans[locale] } </span>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Customizing CSS & SCSS
|
||||
label: Customizing CSS
|
||||
order: 30
|
||||
order: 40
|
||||
desc: Customize your Payload admin panel further by adding your own CSS or SCSS style sheet to the configuration, powerful theme and design options are waiting for you.
|
||||
keywords: admin, css, scss, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
@@ -13,7 +13,7 @@ You can add your own CSS by providing your base Payload config with a path to yo
|
||||
To do so, provide your base Payload config with a path to your own stylesheet. It can be either a CSS or SCSS file.
|
||||
|
||||
**Example in payload.config.js:**
|
||||
```js
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config';
|
||||
import path from 'path';
|
||||
|
||||
@@ -21,51 +21,29 @@ const config = buildConfig({
|
||||
admin: {
|
||||
css: path.resolve(__dirname, 'relative/path/to/stylesheet.scss'),
|
||||
},
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### Overriding SCSS variables
|
||||
### Overriding built-in styles
|
||||
|
||||
You can specify your own SCSS variable stylesheet that will allow for the override of Payload's base theme. This unlocks a ton of powerful theming and design options such as:
|
||||
To make it as easy as possible for you to override our styles, Payload uses [BEM naming conventions](http://getbem.com/) for all CSS within the Admin UI. If you provide your own CSS, you can override any built-in styles easily.
|
||||
|
||||
- Changing dashboard font families
|
||||
- Modifying color palette
|
||||
- Creating a dark theme
|
||||
- Etc.
|
||||
In addition to adding your own style definitions, you can also override Payload's built-in CSS variables. We use as much as possible behind the scenes, and you can override any of them that you'd like to.
|
||||
|
||||
To do so, provide your base Payload config with a path to your own SCSS variable sheet.
|
||||
You can find the built-in Payload CSS variables within [`./src/admin/scss/app.scss`](https://github.com/payloadcms/payload/blob/master/src/admin/scss/app.scss) and [`./src/admin/scss/colors.scss`](https://github.com/payloadcms/payload/blob/master/src/admin/scss/colors.scss). The following variables are defined and can be overridden:
|
||||
|
||||
**NOTE: Before you start editing your SCSS file, make sure you install 'sass-loader' & 'node-sass' as dev dependencies.**
|
||||
**Node**
|
||||
```
|
||||
npm install sass-loader node-sass --save-dev
|
||||
```
|
||||
**YARN**
|
||||
```
|
||||
yarn add sass-loader node-sass --save-dev
|
||||
```
|
||||
- Breakpoints
|
||||
- Base color shades (white to black by default)
|
||||
- Success / warning / error color shades
|
||||
- Theme-specific colors (background, input background, text color, etc.)
|
||||
- Elevation colors (used to determine how "bright" something should be when compared to the background)
|
||||
- Fonts
|
||||
- Horizontal gutter
|
||||
|
||||
**Example in payload.config.js:**
|
||||
```js
|
||||
import { buildConfig } from 'payload/config';
|
||||
import path from 'path';
|
||||
#### Dark mode
|
||||
|
||||
const config = buildConfig({
|
||||
admin: {
|
||||
scss: path.resolve(__dirname, 'relative/path/to/vars.scss'),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Example stylesheet override:**
|
||||
```scss
|
||||
$font-body: 'Papyrus';
|
||||
$style-radius-m: 10px;
|
||||
```
|
||||
|
||||
To reference all Sass variables that you can override, look at the default [SCSS variable stylesheet](https://github.com/payloadcms/payload/blob/master/src/admin/scss/vars.scss) within the Payload source code.
|
||||
|
||||
<Banner type="error">
|
||||
<strong>Warning:</strong><br />
|
||||
Only SCSS variables, mixins, functions, and extends are allowed in <strong>your SCSS overrides</strong>. Do not attempt to add any CSS declarations to this file, as this variable stylesheet is imported by many components throughout the Payload Admin panel and will result in your CSS definition(s) being duplicated many times. If you need to add real CSS definitions, see "Adding your own CSS / SCSS" the top of this page.
|
||||
<Banner type="warning">
|
||||
If you're overriding colors or theme elevations, make sure to consider how your changes will affect dark mode.
|
||||
</Banner>
|
||||
|
||||
By default, Payload automatically overrides all `--theme-elevation`s and inverts all success / warning / error shades to suit dark mode. We also update some base theme variables like `--theme-bg`, `--theme-text`, etc.
|
||||
|
||||
271
docs/admin/hooks.mdx
Normal file
271
docs/admin/hooks.mdx
Normal file
@@ -0,0 +1,271 @@
|
||||
---
|
||||
title: React Hooks
|
||||
label: React Hooks
|
||||
order: 30
|
||||
desc: Make use of all of the powerful React hooks that Payload provides.
|
||||
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
Payload provides a variety of powerful hooks that can be used within your own React components. With them, you can interface with Payload itself and build just about any type of complex customization you can think of—directly in familiar React code.
|
||||
|
||||
### useField
|
||||
|
||||
The `useField` hook is used internally within every applicable Payload field component, and it manages sending and receiving a field's state from its parent form.
|
||||
|
||||
Outside of internal use, its most common use-case is in custom `Field` components. When you build a custom React `Field` component, you'll be responsible for sending and receiving the field's `value` from the form itself. To do so, import the `useField` hook as follows:
|
||||
|
||||
```tsx
|
||||
import { useField } from 'payload/components/forms'
|
||||
|
||||
type Props = { path: string }
|
||||
|
||||
const CustomTextField: React.FC<Props> = ({ path }) => {
|
||||
// highlight-start
|
||||
const { value, setValue } = useField<string>({ path })
|
||||
// highlight-end
|
||||
|
||||
return <input onChange={e => setValue(e.target.value)} value={value.path} />
|
||||
}
|
||||
```
|
||||
|
||||
The `useField` hook accepts an `args` object and sends back information and helpers for you to make use of:
|
||||
|
||||
```ts
|
||||
const field = useField<string>({
|
||||
path: 'fieldPathHere', // required
|
||||
validate: myValidateFunc, // optional
|
||||
disableFormData?: false, // if true, the field's data will be ignored
|
||||
condition?: myConditionHere, // optional, used to skip validation if condition fails
|
||||
})
|
||||
|
||||
// Here is what `useField` sends back
|
||||
const {
|
||||
showError, // whether or not the field should show as errored
|
||||
errorMessage, // the error message to show, if showError
|
||||
value, // the current value of the field from the form
|
||||
formSubmitted, // if the form has been submitted
|
||||
formProcessing, // if the form is currently processing
|
||||
setValue, // method to set the field's value in form state
|
||||
initialValue, // the initial value that the field mounted with
|
||||
} = field;
|
||||
|
||||
// The rest of your component goes here
|
||||
```
|
||||
|
||||
### useFormFields
|
||||
|
||||
There are times when a custom field component needs to have access to data from other fields, and you have a few options to do so. The `useFormFields` hook is a powerful and highly performant way to retrieve a form's field state, as well as to retrieve the `dispatchFields` method, which can be helpful for setting other fields' form states from anywhere within a form.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>This hook is great for retrieving only certain fields from form state</strong> because it ensures that it will only cause a rerender when the items that you ask for change.
|
||||
</Banner>
|
||||
|
||||
Thanks to the awesome package [`use-context-selector`](https://github.com/dai-shi/use-context-selector), you can retrieve a specific field's state easily. This is ideal because you can ensure you have an up-to-date field state, and your component will only re-render when _that field's state_ changes.
|
||||
|
||||
You can pass a Redux-like selector into the hook, which will ensure that you retrieve only the field that you want. The selector takes an argument with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
|
||||
|
||||
```tsx
|
||||
import { useFormFields } from 'payload/components/forms';
|
||||
|
||||
const MyComponent: React.FC = () => {
|
||||
// Get only the `amount` field state, and only cause a rerender when that field changes
|
||||
const amount = useFormFields(([fields, dispatch]) => fields.amount);
|
||||
|
||||
// Do the same thing as above, but to the `feePercentage` field
|
||||
const feePercentage = useFormFields(([fields, dispatch]) => fields.feePercentage);
|
||||
|
||||
if (typeof amount?.value !== 'undefined' && typeof feePercentage?.value !== 'undefined') {
|
||||
return (
|
||||
<span>The fee is ${(amount.value * feePercentage.value) / 100}</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### useAllFormFields
|
||||
|
||||
**To retrieve more than one field**, you can use the `useAllFormFields` hook. Your component will re-render when _any_ field changes, so use this hook only if you absolutely need to. Unlike the `useFormFields` hook, this hook does not accept a "selector", and it always returns an array with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
|
||||
|
||||
You can do lots of powerful stuff by retrieving the full form state, like using built-in helper functions to reduce field state to values only, or to retrieve sibling data by path.
|
||||
|
||||
```tsx
|
||||
import { useAllFormFields, reduceFieldsToValues, getSiblingData } from 'payload/components/forms';
|
||||
|
||||
const ExampleComponent: React.FC = () => {
|
||||
// the `fields` const will be equal to all fields' state,
|
||||
// and the `dispatchFields` method is usable to send field state up to the form
|
||||
const [fields, dispatchFields] = useAllFormFields();
|
||||
|
||||
// Pass in fields, and indicate if you'd like to "unflatten" field data.
|
||||
// The result below will reflect the data stored in the form at the given time
|
||||
const formData = reduceFieldsToValues(fields, true);
|
||||
|
||||
// Pass in field state and a path,
|
||||
// and you will be sent all sibling data of the path that you've specified
|
||||
const siblingData = getSiblingData(fields, 'someFieldName');
|
||||
|
||||
return (
|
||||
// return some JSX here if necessary
|
||||
)
|
||||
};
|
||||
```
|
||||
|
||||
##### Updating other fields' values
|
||||
|
||||
If you are building a custom component, then you should use `setValue` which is returned from the `useField` hook to programmatically set your field's value. But if you're looking to update _another_ field's value, you can use `dispatchFields` returned from `useFormFields`.
|
||||
|
||||
You can send the following actions to the `dispatchFields` function.
|
||||
|
||||
| Action | Description |
|
||||
|------------------------|----------------------------------------------------------------------------|
|
||||
| **`ADD_ROW`** | Adds a row of data (useful in array / block field data) |
|
||||
| **`DUPLICATE_ROW`** | Duplicates a row of data (useful in array / block field data) |
|
||||
| **`MODIFY_CONDITION`** | Updates a field's conditional logic result (true / false) |
|
||||
| **`MOVE_ROW`** | Moves a row of data (useful in array / block field data) |
|
||||
| **`REMOVE`** | Removes a field from form state |
|
||||
| **`REMOVE_ROW`** | Removes a row of data from form state (useful in array / block field data) |
|
||||
| **`REPLACE_STATE`** | Completely replaces form state |
|
||||
| **`UPDATE`** | Update any property of a specific field's state |
|
||||
|
||||
To see types for each action supported within the `dispatchFields` hook, check out the Form types [here](https://github.com/payloadcms/payload/blob/master/src/admin/components/forms/Form/types.ts).
|
||||
|
||||
### useForm
|
||||
|
||||
The `useForm` hook can be used to interact with the form itself, and sends back many methods that can be used to reactively fetch form state without causing rerenders within your components each time a field is changed. This is useful if you have action-based callbacks that your components fire, and need to interact with form state _based on a user action_.
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Warning:</strong><br/>
|
||||
This hook is optimized to avoid causing rerenders when fields change, and as such, its `fields` property will be out of date. You should only leverage this hook if you need to perform actions against the form in response to your users' actions. Do not rely on its returned "fields" as being up-to-date. They will be removed from this hook's response in an upcoming version.
|
||||
</Banner>
|
||||
|
||||
The `useForm` hook returns an object with the following properties:
|
||||
|
||||
| Action | Description |
|
||||
|----------------------|---------------------------------------------------------------------|
|
||||
| **`fields`** | Deprecated. This property cannot be relied on as up-to-date. |
|
||||
| **`submit`** | Method to trigger the form to submit |
|
||||
| **`dispatchFields`** | Dispatch actions to the form field state |
|
||||
| **`validateForm`** | Trigger a validation of the form state |
|
||||
| **`createFormData`** | Create a `multipart/form-data` object from the current form's state |
|
||||
| **`disabled`** | Boolean denoting whether or not the form is disabled |
|
||||
| **`getFields`** | Gets all fields from state |
|
||||
| **`getField`** | Gets a single field from state by path |
|
||||
| **`getData`** | Returns the data stored in the form |
|
||||
| **`getSiblingData`** | Returns form sibling data for the given field path |
|
||||
| **`setModified`** | Set the form's `modified` state |
|
||||
| **`setProcessing`** | Set the form's `processing` state |
|
||||
| **`setSubmitted`** | Set the form's `submitted` state |
|
||||
| **`formRef`** | The ref from the form HTML element |
|
||||
| **`reset`** | Method to reset the form to its initial state |
|
||||
|
||||
### useDocumentInfo
|
||||
|
||||
The `useDocumentInfo` hook provides lots of information about the document currently being edited, including the following:
|
||||
|
||||
| Property | Description |
|
||||
|---------------------------|------------------------------------------------------------------------------------|
|
||||
| **`collection`** | If the doc is a collection, its collection config will be returned |
|
||||
| **`global`** | If the doc is a global, its global config will be returned |
|
||||
| **`type`** | The type of document being edited (collection or global) |
|
||||
| **`id`** | If the doc is a collection, its ID will be returned |
|
||||
| **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences |
|
||||
| **`versions`** | Versions of the current doc |
|
||||
| **`unpublishedVersions`** | Unpublished versions of the current doc |
|
||||
| **`publishedDoc`** | The currently published version of the doc being edited |
|
||||
| **`getVersions`** | Method to trigger the retrieval of document versions |
|
||||
|
||||
**Example:**
|
||||
|
||||
```tsx
|
||||
import { useDocumentInfo } from 'payload/components/utilities';
|
||||
|
||||
const LinkFromCategoryToPosts: React.FC = () => {
|
||||
// highlight-start
|
||||
const { id } = useDocumentInfo();
|
||||
// highlight-end
|
||||
|
||||
// id will be undefined on the create form
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<a href={`/admin/collections/posts?where[or][0][and][0][category][in][0]=[${id}]`} >
|
||||
View posts
|
||||
</a>
|
||||
)
|
||||
};
|
||||
```
|
||||
|
||||
### useLocale
|
||||
|
||||
In any custom component you can get the selected locale with the `useLocale` hook. Here is a simple example:
|
||||
|
||||
```tsx
|
||||
import { useLocale } from 'payload/components/utilities';
|
||||
|
||||
const Greeting: React.FC = () => {
|
||||
// highlight-start
|
||||
const locale = useLocale();
|
||||
// highlight-end
|
||||
|
||||
const trans = {
|
||||
en: 'Hello',
|
||||
es: 'Hola',
|
||||
};
|
||||
|
||||
return (
|
||||
<span> { trans[locale] } </span>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### useAuth
|
||||
|
||||
Useful to retrieve info about the currently logged in user as well as methods for interacting with it. It sends back an object with the following properties:
|
||||
|
||||
| Property | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------------------|
|
||||
| **`user`** | The currently logged in user |
|
||||
| **`logOut`** | A method to log out the currently logged in user |
|
||||
| **`refreshCookie`** | A method to trigger the silent refreshing of a user's auth token |
|
||||
| **`setToken`** | Set the token of the user, to be decoded and used to reset the user and token in memory |
|
||||
| **`token`** | The logged in user's token (useful for creating preview links, etc.) |
|
||||
| **`permissions`** | The permissions of the current user |
|
||||
|
||||
```tsx
|
||||
import { useAuth } from 'payload/components/utilities';
|
||||
import { User } from '../payload-types.ts';
|
||||
|
||||
const Greeting: React.FC = () => {
|
||||
// highlight-start
|
||||
const { user } = useConfig<User>();
|
||||
// highlight-end
|
||||
|
||||
return (
|
||||
<span>Hi, {user.email}!</span>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### useConfig
|
||||
|
||||
Used to easily fetch the full Payload config.
|
||||
|
||||
```tsx
|
||||
import { useConfig } from 'payload/components/utilities';
|
||||
|
||||
const MyComponent: React.FC = () => {
|
||||
// highlight-start
|
||||
const config = useConfig();
|
||||
// highlight-end
|
||||
|
||||
return (
|
||||
<span>{config.serverURL}</span>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### usePreferences
|
||||
|
||||
Returns methods to set and get user preferences. More info can be found [here](https://payloadcms.com/docs/admin/preferences).
|
||||
@@ -30,7 +30,8 @@ All options for the Admin panel are defined in your base Payload config file.
|
||||
| `indexHTML` | Optionally replace the entirety of the `index.html` file used by the Admin panel. Reference the [base index.html file](https://github.com/payloadcms/payload/blob/master/src/admin/index.html) to ensure your replacement has the appropriate HTML elements. |
|
||||
| `css` | Absolute path to a stylesheet that you can use to override / customize the Admin panel styling. [More](/docs/admin/customizing-css). |
|
||||
| `scss` | Absolute path to a Sass variables / mixins stylesheet meant to override Payload styles to make for an easy re-skinning of the Admin panel. [More](/docs/admin/customizing-css#overriding-scss-variables). |
|
||||
| `dateFormat` | Global date format that will be used for all dates in the Admin panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used.
|
||||
| `dateFormat` | Global date format that will be used for all dates in the Admin panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
|
||||
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
|
||||
| `components` | Component overrides that affect the entirety of the Admin panel. [More](/docs/admin/components) |
|
||||
| `webpack` | Customize the Webpack config that's used to generate the Admin panel. [More](/docs/admin/webpack) |
|
||||
|
||||
@@ -45,14 +46,14 @@ All options for the Admin panel are defined in your base Payload config file.
|
||||
To specify which Collection to use to log in to the Admin panel, pass the `admin` options a `user` key equal to the slug of the Collection that you'd like to use.
|
||||
|
||||
`payload.config.js`:
|
||||
```js
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config';
|
||||
|
||||
const config = buildConfig({
|
||||
admin: {
|
||||
user: 'admins', // highlight-line
|
||||
},
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
By default, if you have not specified a Collection, Payload will automatically provide you with a `User` Collection which will be used to access the Admin panel. You can customize or override the fields and settings of the default `User` Collection by passing your own collection using `users` as its `slug` to Payload. When this is done, Payload will use your provided `User` Collection instead of its default version.
|
||||
@@ -66,6 +67,10 @@ For example, you may wish to have two Collections that both support `Authenticat
|
||||
|
||||
This is totally possible. For the above scenario, by specifying `admin: { user: 'admins' }`, your Payload Admin panel will use `admins`. Any users logged in as `customers` will not be able to log in via the Admin panel.
|
||||
|
||||
### Light and dark modes
|
||||
|
||||
Users in the admin panel have access to choosing between light mode and dark mode for their editing experience. The setting is managed while logged into the admin UI within the user account page and will be stored with the browser. By default, the operating system preference is detected and used.
|
||||
|
||||
### Restricting user access
|
||||
|
||||
If you would like to restrict which users from a single Collection can access the Admin panel, you can use the `admin` access control function. [Click here](/docs/access-control/overview#admin) to learn more.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Managing User Preferences
|
||||
label: Preferences
|
||||
order: 40
|
||||
order: 50
|
||||
desc: Store the preferences of your users as they interact with the Admin panel.
|
||||
keywords: admin, preferences, custom, customize, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Webpack
|
||||
label: Webpack
|
||||
order: 50
|
||||
order: 60
|
||||
desc: The Payload admin panel uses Webpack 5 and supports many common functionalities such as SCSS and Typescript out of the box to give you more freedom.
|
||||
keywords: admin, webpack, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
@@ -10,8 +10,8 @@ Payload uses Webpack 5 to build the Admin panel. It comes with support for many
|
||||
|
||||
To extend the Webpack config, add the `webpack` key to your base Payload config, and provide a function that accepts the default Webpack config as its only argument:
|
||||
|
||||
`payload.config.js`
|
||||
```js
|
||||
`payload.config.ts`
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config';
|
||||
|
||||
export default buildConfig({
|
||||
@@ -24,7 +24,7 @@ export default buildConfig({
|
||||
}
|
||||
// highlight-end
|
||||
}
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### Aliasing server-only modules
|
||||
@@ -52,16 +52,17 @@ You may rely on server-only packages such as the above to perform logic in acces
|
||||
<br/><br/>
|
||||
|
||||
`collections/Subscriptions/index.js`
|
||||
```js
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
import createStripeSubscription from './hooks/createStripeSubscription';
|
||||
|
||||
const Subscription = {
|
||||
const Subscription: CollectionConfig = {
|
||||
slug: 'subscriptions',
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
createStripeSubscription,
|
||||
]
|
||||
}
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'stripeSubscriptionID',
|
||||
@@ -69,7 +70,7 @@ const Subscription = {
|
||||
required: true,
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
export default Subscription;
|
||||
```
|
||||
@@ -154,6 +155,11 @@ export default {};
|
||||
|
||||
Now, when Webpack sees that you're attempting to import your `createStripeSubscriptionPath` file, it'll disregard that actual file and load your mock file instead. Not only will your Admin panel now bundle successfully, you will have optimized its filesize by removing unnecessary code! And you might have learned something about Webpack, too.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong><br/>
|
||||
If changes to your Webpack aliases are not surfacing, they might be [cached](https://webpack.js.org/configuration/cache/) in `node_modules/.cache/webpack`. Try deleting that folder and restarting your server.
|
||||
</Banner>
|
||||
|
||||
## Admin environment vars
|
||||
|
||||
<Banner type="warning">
|
||||
|
||||
@@ -12,16 +12,18 @@ To enable Authentication on a collection, define an `auth` property and set it t
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | -------------|
|
||||
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More](/docs/authentication/config#api-keys) |
|
||||
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
|
||||
| **`maxLoginAttempts`** | Only allow a user to attempt logging in X amount of times. Automatically locks out a user from authenticating if this limit is passed. Set to `0` to disable. |
|
||||
| **`lockTime`** | Set the time that a user should be locked out if they fail authentication more times than `maxLoginAttempts` allows for. |
|
||||
| **`depth`** | How many levels deep a `user` document should be populated when creating the JWT and binding the `user` to the express `req`. Defaults to `0` and should only be modified if absolutely necessary, as this will affect performance. |
|
||||
| **`cookies`** | Set cookie options, including `secure`, `sameSite`, and `domain`. For advanced users. |
|
||||
| **`forgotPassword`** | Customize the way that the `forgotPassword` operation functions. [More](/docs/authentication/config#forgot-password) |
|
||||
| **`verify`** | Set to `true` or pass an object with verification options to require users to verify by email before they are allowed to log into your app. [More](/docs/authentication/config#email-verification) |
|
||||
| Option | Description |
|
||||
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More](/docs/authentication/config#api-keys) |
|
||||
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
|
||||
| **`maxLoginAttempts`** | Only allow a user to attempt logging in X amount of times. Automatically locks out a user from authenticating if this limit is passed. Set to `0` to disable. |
|
||||
| **`lockTime`** | Set the time that a user should be locked out if they fail authentication more times than `maxLoginAttempts` allows for. |
|
||||
| **`depth`** | How many levels deep a `user` document should be populated when creating the JWT and binding the `user` to the express `req`. Defaults to `0` and should only be modified if absolutely necessary, as this will affect performance. |
|
||||
| **`cookies`** | Set cookie options, including `secure`, `sameSite`, and `domain`. For advanced users. |
|
||||
| **`forgotPassword`** | Customize the way that the `forgotPassword` operation functions. [More](/docs/authentication/config#forgot-password) |
|
||||
| **`verify`** | Set to `true` or pass an object with verification options to require users to verify by email before they are allowed to log into your app. [More](/docs/authentication/config#email-verification) |
|
||||
| **`disableLocalStrategy`** | Advanced - disable Payload's built-in local auth strategy. Only use this property if you have replaced Payload's auth mechanisms with your own. |
|
||||
| **`strategies`** | Advanced - an array of PassportJS authentication strategies to extend this collection's authentication with. [More](/docs/authentication/config#strategies) |
|
||||
|
||||
### API keys
|
||||
|
||||
@@ -37,7 +39,8 @@ Technically, both of these options will work for third-party integrations but th
|
||||
To enable API keys on a collection, set the `useAPIKey` auth option to `true`. From there, a new interface will appear in the Admin panel for each document within the collection that allows you to generate an API key for each user in the Collection.
|
||||
|
||||
<Banner type="success">
|
||||
User API keys are encrypted within the database, meaning that if your database is compromised, your API keys will not be.
|
||||
User API keys are encrypted within the database, meaning that if your database
|
||||
is compromised, your API keys will not be.
|
||||
</Banner>
|
||||
|
||||
##### Authenticating via API Key
|
||||
@@ -45,14 +48,17 @@ To enable API keys on a collection, set the `useAPIKey` auth option to `true`. F
|
||||
To utilize your API key while interacting with the REST or GraphQL API, add the `Authorization` header.
|
||||
|
||||
**For example, using Fetch:**
|
||||
```js
|
||||
const response = await fetch('http://localhost:3000/api/pages', {
|
||||
|
||||
```ts
|
||||
const response = await fetch("http://localhost:3000/api/pages", {
|
||||
headers: {
|
||||
Authorization: `${collection.labels.singular} API-Key ${YOUR_API_KEY}`,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Note: The label portion of the header is case-sensitive and will likely have a capitalized first character unless the label has been customized.
|
||||
|
||||
### Forgot Password
|
||||
|
||||
You can customize how the Forgot Password workflow operates with the following options on the `auth.forgotPassword` property:
|
||||
@@ -62,14 +68,21 @@ You can customize how the Forgot Password workflow operates with the following o
|
||||
Function that accepts one argument, containing `{ req, token, user }`, that allows for overriding the HTML within emails that are sent to users attempting to reset their password. The function should return a string that supports HTML, which can be a full HTML email.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong><br />
|
||||
HTML templating can be used to create custom email templates, inline CSS automatically, and more. You can make a reusable function that standardizes all email sent from Payload, which makes sending custom emails more DRY. Payload doesn't ship with an HTML templating engine, so you are free to choose your own.
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
HTML templating can be used to create custom email templates, inline CSS
|
||||
automatically, and more. You can make a reusable function that standardizes
|
||||
all email sent from Payload, which makes sending custom emails more DRY.
|
||||
Payload doesn't ship with an HTML templating engine, so you are free to choose
|
||||
your own.
|
||||
</Banner>
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
{
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
|
||||
const Customers: CollectionConfig = {
|
||||
slug: 'customers',
|
||||
auth: {
|
||||
forgotPassword: {
|
||||
@@ -95,12 +108,17 @@ Example:
|
||||
// highlight-end
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong><br />
|
||||
If you specify a different URL to send your users to for resetting their password, such as a page on the frontend of your app or similar, you need to handle making the call to the Payload REST or GraphQL reset-password operation yourself on your frontend, using the token that was provided for you. Above, it was passed via query parameter.
|
||||
<strong>Important:</strong>
|
||||
<br />
|
||||
If you specify a different URL to send your users to for resetting their
|
||||
password, such as a page on the frontend of your app or similar, you need to
|
||||
handle making the call to the Payload REST or GraphQL reset-password operation
|
||||
yourself on your frontend, using the token that was provided for you. Above,
|
||||
it was passed via query parameter.
|
||||
</Banner>
|
||||
|
||||
**`generateEmailSubject`**
|
||||
@@ -109,7 +127,7 @@ Similarly to the above `generateEmailHTML`, you can also customize the subject o
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
```ts
|
||||
{
|
||||
slug: 'customers',
|
||||
auth: {
|
||||
@@ -134,8 +152,11 @@ Function that accepts one argument, containing `{ req, token, user }`, that allo
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
{
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
|
||||
|
||||
const Customers: CollectionConfig = {
|
||||
slug: 'customers',
|
||||
auth: {
|
||||
verify: {
|
||||
@@ -149,12 +170,17 @@ Example:
|
||||
// highlight-end
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong><br />
|
||||
If you specify a different URL to send your users to for email verification, such as a page on the frontend of your app or similar, you need to handle making the call to the Payload REST or GraphQL verification operation yourself on your frontend, using the token that was provided for you. Above, it was passed via query parameter.
|
||||
<strong>Important:</strong>
|
||||
<br />
|
||||
If you specify a different URL to send your users to for email verification,
|
||||
such as a page on the frontend of your app or similar, you need to handle
|
||||
making the call to the Payload REST or GraphQL verification operation yourself
|
||||
on your frontend, using the token that was provided for you. Above, it was
|
||||
passed via query parameter.
|
||||
</Banner>
|
||||
|
||||
**`generateEmailSubject`**
|
||||
@@ -163,7 +189,7 @@ Similarly to the above `generateEmailHTML`, you can also customize the subject o
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
```ts
|
||||
{
|
||||
slug: 'customers',
|
||||
auth: {
|
||||
@@ -177,3 +203,29 @@ Example:
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Strategies
|
||||
|
||||
As of Payload `1.0.0`, you can add additional authentication strategies to Payload easily by passing them to your collection's `auth.strategies` array.
|
||||
|
||||
Behind the scenes, Payload uses PassportJS to power its local authentication strategy, so most strategies listed on the PassportJS website will work seamlessly. Combined with adding custom components to the admin panel's `Login` view, you can create advanced authentication strategies directly within Payload.
|
||||
|
||||
<Banner type="warning">
|
||||
This is an advanced feature, so only attempt this if you are an experienced
|
||||
developer. Otherwise, just let Payload's built-in authentication handle user
|
||||
auth for you.
|
||||
</Banner>
|
||||
|
||||
The `strategies` property is an array that takes objects with the following properties:
|
||||
|
||||
**`strategy`**
|
||||
|
||||
This property can accept a Passport strategy directly, or you can pass a function that takes a `payload` argument, and returns a Passport strategy.
|
||||
|
||||
**`name`**
|
||||
|
||||
If you pass a strategy to the `strategy` property directly, the `name` property is optional and allows you to override the strategy's built-in name.
|
||||
|
||||
However, if you pass a function to `strategy`, `name` is a required property.
|
||||
|
||||
In either case, Payload will prefix the strategy name with the collection `slug` that the strategy is passed to.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user