Compare commits
735 Commits
@nhost/das
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62b2de59d4 | ||
|
|
2a760593db | ||
|
|
9288873ce8 | ||
|
|
47014be8e3 | ||
|
|
49719f7a84 | ||
|
|
b3b64a3b74 | ||
|
|
3a56c12df4 | ||
|
|
5b15a4f235 | ||
|
|
83303017c3 | ||
|
|
e0739a5883 | ||
|
|
0a5a841cc8 | ||
|
|
3309835f06 | ||
|
|
32b221f944 | ||
|
|
e8a99badb8 | ||
|
|
1ea6e01963 | ||
|
|
958dec5dfe | ||
|
|
09257fbfb2 | ||
|
|
61e3497a13 | ||
|
|
a7b4e5606d | ||
|
|
34d77c9db1 | ||
|
|
4f1efd28a6 | ||
|
|
07a45fde0e | ||
|
|
9d0380eef3 | ||
|
|
ce3ec36b0a | ||
|
|
b62a9d19b5 | ||
|
|
c1472079c5 | ||
|
|
dd36971798 | ||
|
|
6199c1c555 | ||
|
|
f41fdc12af | ||
|
|
fc419ffa4d | ||
|
|
b7c102e876 | ||
|
|
873fc36e61 | ||
|
|
29743f0b71 | ||
|
|
d904ca2bbf | ||
|
|
80b22724de | ||
|
|
80e49f4459 | ||
|
|
b3d5ead508 | ||
|
|
77dcb8c964 | ||
|
|
3488da9dfd | ||
|
|
0e68a1fdfd | ||
|
|
8797b2bd17 | ||
|
|
5ef0b31573 | ||
|
|
86e5e0fb50 | ||
|
|
c2d589dd29 | ||
|
|
4b807d8134 | ||
|
|
ccdabb707f | ||
|
|
364bc87fd3 | ||
|
|
cc02902cbb | ||
|
|
0e838b9406 | ||
|
|
37ebf7d8e2 | ||
|
|
e23af24bdd | ||
|
|
90eb53cf19 | ||
|
|
7e516d7630 | ||
|
|
0861e41e70 | ||
|
|
057e7e2572 | ||
|
|
5a4e237a29 | ||
|
|
c7501c70ae | ||
|
|
6a45c1abad | ||
|
|
660d339e14 | ||
|
|
3dca08595d | ||
|
|
7c501c4e4f | ||
|
|
b9316bb668 | ||
|
|
5e1d5b737c | ||
|
|
bd4d0c2708 | ||
|
|
1d04ad6306 | ||
|
|
a4fa5f6f59 | ||
|
|
7e973d568a | ||
|
|
d81c52209b | ||
|
|
72744b3082 | ||
|
|
ff4efe2712 | ||
|
|
2982b90469 | ||
|
|
428a5df038 | ||
|
|
f79bf784b5 | ||
|
|
3b7449ac08 | ||
|
|
37bbfdb7ae | ||
|
|
eb570d2d09 | ||
|
|
c8c2a10b2d | ||
|
|
92c79eb2fb | ||
|
|
e70b45498d | ||
|
|
2e1ecfa731 | ||
|
|
8d323a7762 | ||
|
|
8aa0ff936a | ||
|
|
c6806d60c7 | ||
|
|
a13eb25ebc | ||
|
|
228d8a0686 | ||
|
|
0de1bc7ce3 | ||
|
|
6a94cad04b | ||
|
|
8643d25cc8 | ||
|
|
e820f11dda | ||
|
|
3555ab2b71 | ||
|
|
6e41d58131 | ||
|
|
6cf3beae1c | ||
|
|
022b76e784 | ||
|
|
2fbe88f806 | ||
|
|
9457bc32ca | ||
|
|
3de2639ae9 | ||
|
|
c43e549224 | ||
|
|
fc6fe5007b | ||
|
|
829febf33b | ||
|
|
ae99ba14b9 | ||
|
|
a158dc3a17 | ||
|
|
8420550990 | ||
|
|
156667cdbd | ||
|
|
7d388a8c91 | ||
|
|
d32a2fceae | ||
|
|
d690eb86bb | ||
|
|
d91271cce1 | ||
|
|
1e74a2da85 | ||
|
|
bc8837b961 | ||
|
|
78fdad8404 | ||
|
|
2e8a72d445 | ||
|
|
ce1ae32772 | ||
|
|
b51455d324 | ||
|
|
28a305d9be | ||
|
|
e23bf4500d | ||
|
|
d0457fe5c3 | ||
|
|
766d1e1c5a | ||
|
|
44d460cd01 | ||
|
|
adf934c871 | ||
|
|
0a963486e2 | ||
|
|
227d1704f2 | ||
|
|
2baef92988 | ||
|
|
2a10da128d | ||
|
|
62a51c9fc7 | ||
|
|
58977b173b | ||
|
|
b5e5dcf6de | ||
|
|
157e1b74b8 | ||
|
|
b3a475c60f | ||
|
|
3d62871db1 | ||
|
|
4f0368b95f | ||
|
|
0385093111 | ||
|
|
463cb50c27 | ||
|
|
a50174a0a1 | ||
|
|
21cbe7487e | ||
|
|
6e4b34126e | ||
|
|
fd3a1a44ef | ||
|
|
66e6021dc0 | ||
|
|
57fdba70e0 | ||
|
|
676c11f814 | ||
|
|
d8442a290b | ||
|
|
0db333353b | ||
|
|
7ea8120723 | ||
|
|
64a8f41d03 | ||
|
|
8e12ded94b | ||
|
|
564ce1ac2d | ||
|
|
b024817eb5 | ||
|
|
24f98630fd | ||
|
|
c1b024cf53 | ||
|
|
dbacbf140b | ||
|
|
eda9e57583 | ||
|
|
0a9af5075c | ||
|
|
f92d9d1fd2 | ||
|
|
15168539d8 | ||
|
|
0d74217a4c | ||
|
|
9721527324 | ||
|
|
fd4d024bfc | ||
|
|
c994c8f05b | ||
|
|
4c00a796eb | ||
|
|
2d3a77af76 | ||
|
|
ef05d69889 | ||
|
|
9b1d0f7a5b | ||
|
|
07abea4c16 | ||
|
|
8733961026 | ||
|
|
dfa8776b2b | ||
|
|
1b9f15cb67 | ||
|
|
b683615269 | ||
|
|
3dc97f17ae | ||
|
|
6d2963ffa7 | ||
|
|
d1ec8c0781 | ||
|
|
8b205e9c08 | ||
|
|
e2792cd453 | ||
|
|
a60ca2f6f5 | ||
|
|
14a2ead79f | ||
|
|
b625a6b4d4 | ||
|
|
fd12aa0a8d | ||
|
|
8871267b91 | ||
|
|
e3001ba4a5 | ||
|
|
1133b76a7e | ||
|
|
9b8a19a316 | ||
|
|
26b8519add | ||
|
|
f372b167d9 | ||
|
|
fbaea49b28 | ||
|
|
0b4a2041e2 | ||
|
|
444206b0f7 | ||
|
|
624ef79110 | ||
|
|
711ef45815 | ||
|
|
21e13db05a | ||
|
|
a1862b80ba | ||
|
|
42e8d102e9 | ||
|
|
aa3c629892 | ||
|
|
f016afed5d | ||
|
|
9d4ee99553 | ||
|
|
6cbe1f662f | ||
|
|
abd9c88e5a | ||
|
|
27e5ef8f5a | ||
|
|
788090e917 | ||
|
|
f16433ae67 | ||
|
|
33e3bba700 | ||
|
|
517bb6930a | ||
|
|
4b17964e8d | ||
|
|
ebc016377b | ||
|
|
eae670f4d1 | ||
|
|
c642649853 | ||
|
|
095f2c73b9 | ||
|
|
42753d2b1f | ||
|
|
328c0a6600 | ||
|
|
93101430c8 | ||
|
|
c14738b8c1 | ||
|
|
683ef4f2fe | ||
|
|
528f02874c | ||
|
|
5f72ba57d3 | ||
|
|
e71e3af530 | ||
|
|
aa05cd4b61 | ||
|
|
bd87a36c6e | ||
|
|
9c88947b86 | ||
|
|
1b03ce7259 | ||
|
|
de1137a876 | ||
|
|
770c4d1801 | ||
|
|
60e25b3425 | ||
|
|
f4d2a305a5 | ||
|
|
01eeef9de7 | ||
|
|
f395375cd0 | ||
|
|
ae3599d2b4 | ||
|
|
9a3c782fcb | ||
|
|
28c1633695 | ||
|
|
66822f8673 | ||
|
|
86b82bf5cf | ||
|
|
f77454a848 | ||
|
|
3426000edf | ||
|
|
7bd9da909c | ||
|
|
fcb84bfb3d | ||
|
|
991e4c0cc5 | ||
|
|
a7f03bc6ce | ||
|
|
5684fdbf0b | ||
|
|
39980e6c54 | ||
|
|
5b959cce0e | ||
|
|
117098d8da | ||
|
|
1fce4ca93d | ||
|
|
6a74a97cd0 | ||
|
|
7446fb30db | ||
|
|
291185e609 | ||
|
|
b1a2dd6ab3 | ||
|
|
d3f965048a | ||
|
|
9acecfd638 | ||
|
|
134773be86 | ||
|
|
88a4983f2e | ||
|
|
7526f573c3 | ||
|
|
9b0d4dde50 | ||
|
|
9934514dde | ||
|
|
c7084d1759 | ||
|
|
f0a122d39b | ||
|
|
34f1795e40 | ||
|
|
2032494b27 | ||
|
|
b5dce47370 | ||
|
|
60f1a03d33 | ||
|
|
881b2d752c | ||
|
|
db293808f3 | ||
|
|
7414b39f48 | ||
|
|
7ebc555e4c | ||
|
|
830e627da1 | ||
|
|
176e3d5ea6 | ||
|
|
759aa9c221 | ||
|
|
3acfd032bd | ||
|
|
b77711000a | ||
|
|
79fa77f28f | ||
|
|
bd6fe2f85f | ||
|
|
acb8a4f187 | ||
|
|
06d6ae0e86 | ||
|
|
1777c147b2 | ||
|
|
9d784b82c8 | ||
|
|
15d84a1966 | ||
|
|
8a1a1e06aa | ||
|
|
eb1d7137cf | ||
|
|
cf55e8e111 | ||
|
|
b47c797d73 | ||
|
|
dafc4b2635 | ||
|
|
97cfb5c514 | ||
|
|
0ccd9c82b1 | ||
|
|
f0be842325 | ||
|
|
30a0fd1698 | ||
|
|
ce45f3b707 | ||
|
|
8b9df13725 | ||
|
|
8f31258d11 | ||
|
|
877f857010 | ||
|
|
5773521b03 | ||
|
|
549dd83f3e | ||
|
|
e72c19c9da | ||
|
|
70d5dcf170 | ||
|
|
c439174821 | ||
|
|
2f7b2532ea | ||
|
|
faa21af7fb | ||
|
|
a5fdc46a57 | ||
|
|
04e8728753 | ||
|
|
34cf922643 | ||
|
|
0dd1883815 | ||
|
|
01ba429007 | ||
|
|
fdf5b3035c | ||
|
|
ae52ca6303 | ||
|
|
428222bf5f | ||
|
|
090ee51854 | ||
|
|
766c8431f1 | ||
|
|
4c62617472 | ||
|
|
01f3cbb07a | ||
|
|
62f5d8b69e | ||
|
|
88e2c05afc | ||
|
|
f1edbfdbf1 | ||
|
|
a9943eef3f | ||
|
|
431e61a684 | ||
|
|
87e7f7d4f5 | ||
|
|
5aab6b1896 | ||
|
|
e4e216da6d | ||
|
|
cd8ccdf59a | ||
|
|
9421dda73d | ||
|
|
f13dc75993 | ||
|
|
0a8b42d371 | ||
|
|
bd59d61e94 | ||
|
|
349622fca2 | ||
|
|
dfe8588a94 | ||
|
|
0167df5534 | ||
|
|
7c790b3afe | ||
|
|
1b3a8a1638 | ||
|
|
99edd0129a | ||
|
|
cfb759c34a | ||
|
|
6c5a645876 | ||
|
|
db459b09ec | ||
|
|
e4fa63a571 | ||
|
|
4ce552c8eb | ||
|
|
b847c6d003 | ||
|
|
daab7d8eeb | ||
|
|
1b4f074dfd | ||
|
|
cca7075721 | ||
|
|
4c1b96ccc6 | ||
|
|
809a2d35f8 | ||
|
|
084b7ce6d2 | ||
|
|
e17ec7fce7 | ||
|
|
241175b158 | ||
|
|
9b209ef419 | ||
|
|
a86d17c81f | ||
|
|
cc047b719a | ||
|
|
889b071134 | ||
|
|
51ceaf2696 | ||
|
|
eee3f5723e | ||
|
|
490b77cde4 | ||
|
|
7fea29a8b4 | ||
|
|
1a34e011ad | ||
|
|
395839f449 | ||
|
|
399009d66a | ||
|
|
12eb236c4a | ||
|
|
2218e5cd5b | ||
|
|
2dcf1b38c6 | ||
|
|
412520ac10 | ||
|
|
ad49c92879 | ||
|
|
13dd57eeb4 | ||
|
|
1345741b11 | ||
|
|
3cb63a6da9 | ||
|
|
985f648204 | ||
|
|
bbc3aa8896 | ||
|
|
3d51de4a60 | ||
|
|
b46afa37c6 | ||
|
|
8ad6358d76 | ||
|
|
f73f8366e4 | ||
|
|
511615f176 | ||
|
|
1198c201f1 | ||
|
|
cab803e5b7 | ||
|
|
59fea65eb6 | ||
|
|
f9b81a2ae9 | ||
|
|
23bac2d29c | ||
|
|
78739f77b1 | ||
|
|
dff0894f37 | ||
|
|
80f3645d57 | ||
|
|
7ddb9a654e | ||
|
|
71f3be15d8 | ||
|
|
96a9070836 | ||
|
|
329e5a91b9 | ||
|
|
6d559d6e23 | ||
|
|
f4f1450d06 | ||
|
|
a1eea9df7d | ||
|
|
26f2b665e6 | ||
|
|
8989202692 | ||
|
|
2ae463f11f | ||
|
|
18a786e880 | ||
|
|
c88fbe1e17 | ||
|
|
78c7109c46 | ||
|
|
c7b968868a | ||
|
|
77a3473166 | ||
|
|
224a5cc805 | ||
|
|
59125b3c77 | ||
|
|
91e2affa6f | ||
|
|
a8d747976b | ||
|
|
5b69e3efd8 | ||
|
|
c9a444d048 | ||
|
|
fc16fd5452 | ||
|
|
2eeac45718 | ||
|
|
afc9de7994 | ||
|
|
fe8ca8aba6 | ||
|
|
086ee46b08 | ||
|
|
37c1c18b43 | ||
|
|
67078b9a72 | ||
|
|
203bc97f51 | ||
|
|
b24af44aac | ||
|
|
cdaa6d4e73 | ||
|
|
09e2c8f5c7 | ||
|
|
4581677830 | ||
|
|
7bfa6c9f93 | ||
|
|
80ef430d70 | ||
|
|
bad8af0fd1 | ||
|
|
69caa34c43 | ||
|
|
1d898e2893 | ||
|
|
e87621cbde | ||
|
|
0d6fc42158 | ||
|
|
f6fbee6b13 | ||
|
|
1230b72222 | ||
|
|
6cc7704555 | ||
|
|
c0954dec09 | ||
|
|
6c25480a7a | ||
|
|
da03bf390c | ||
|
|
3b513be9f2 | ||
|
|
e450e9d636 | ||
|
|
ed1ee10879 | ||
|
|
a6120bf366 | ||
|
|
349aac369e | ||
|
|
5a84362c80 | ||
|
|
b23dc058a6 | ||
|
|
ad0dda7493 | ||
|
|
4063507d59 | ||
|
|
f59a77b1c8 | ||
|
|
30686bc4ce | ||
|
|
0c4ac8d368 | ||
|
|
85439307a9 | ||
|
|
0d8baa4065 | ||
|
|
7da0e5e256 | ||
|
|
4bca94425e | ||
|
|
9f948385c0 | ||
|
|
294c504b61 | ||
|
|
1469ec2969 | ||
|
|
8229101efe | ||
|
|
afad1778f8 | ||
|
|
28fc7b84c7 | ||
|
|
3f478a4e3c | ||
|
|
aa54666941 | ||
|
|
20fb69faba | ||
|
|
1caeb2a548 | ||
|
|
6356c5a2c8 | ||
|
|
6ec1dd3248 | ||
|
|
8a4b5031dc | ||
|
|
4790fee41f | ||
|
|
0a8033812d | ||
|
|
2b56ffc29e | ||
|
|
aa9b926cd7 | ||
|
|
575404ad62 | ||
|
|
3f6dfc7bcd | ||
|
|
682e64d7a3 | ||
|
|
30cee4f86c | ||
|
|
29dcc8c63e | ||
|
|
d926f15676 | ||
|
|
726c33d1b2 | ||
|
|
11b9cfbc0d | ||
|
|
d4a0aad2dd | ||
|
|
1030813279 | ||
|
|
917a14aa40 | ||
|
|
6381d1b095 | ||
|
|
8dbdc0bf50 | ||
|
|
8c072a4c6e | ||
|
|
fe341519f7 | ||
|
|
ea09384064 | ||
|
|
49b9972885 | ||
|
|
98c541ee52 | ||
|
|
79aaa91e67 | ||
|
|
df4d24320a | ||
|
|
757c888656 | ||
|
|
7c13eb5f9b | ||
|
|
a84608e086 | ||
|
|
e43c079b9c | ||
|
|
3f396a9ebb | ||
|
|
6ed605beb8 | ||
|
|
edd223d29c | ||
|
|
baa3ef794e | ||
|
|
da7ffbe523 | ||
|
|
15a985e079 | ||
|
|
8ff00a4258 | ||
|
|
7e27d7c0a1 | ||
|
|
49f9b8372a | ||
|
|
3ca70554c8 | ||
|
|
077b200510 | ||
|
|
925bf0f13f | ||
|
|
30d35f9607 | ||
|
|
755aa56f12 | ||
|
|
4c7e7c57a9 | ||
|
|
36708e2853 | ||
|
|
90c6031189 | ||
|
|
f044dbdb10 | ||
|
|
c2f3bce5f9 | ||
|
|
22d9877b97 | ||
|
|
628e96dcc3 | ||
|
|
3e9d3c42b6 | ||
|
|
a1e7b87c38 | ||
|
|
1bd800359e | ||
|
|
54a204a34e | ||
|
|
b17e8d6f3c | ||
|
|
12e2855f01 | ||
|
|
c1080d9e63 | ||
|
|
e4972b8307 | ||
|
|
2e7ec0697e | ||
|
|
2d9baec9d4 | ||
|
|
7a7750be0b | ||
|
|
0f34f0c6b9 | ||
|
|
d05253183a | ||
|
|
65df016bbc | ||
|
|
3e6ee1ae97 | ||
|
|
6042ed101f | ||
|
|
384bce59bf | ||
|
|
8da291ad4d | ||
|
|
f94eb3c467 | ||
|
|
9baf3f4ac7 | ||
|
|
9c406548e3 | ||
|
|
1c08cd1949 | ||
|
|
adc828a582 | ||
|
|
2f220db84a | ||
|
|
f1ec6b9a93 | ||
|
|
233b7e383e | ||
|
|
7ea469a1e3 | ||
|
|
ebd218c180 | ||
|
|
5ab1626f73 | ||
|
|
444c3b86ca | ||
|
|
7238412341 | ||
|
|
f6639ae05c | ||
|
|
d8ceccec5d | ||
|
|
6db257d4c7 | ||
|
|
93dab2d183 | ||
|
|
dfc18368be | ||
|
|
f7c6e80bf2 | ||
|
|
573cac1431 | ||
|
|
d72ae3f362 | ||
|
|
49ec7ec385 | ||
|
|
7d2b4083c2 | ||
|
|
696b493745 | ||
|
|
15a117a861 | ||
|
|
e7ff1f79f8 | ||
|
|
33c7368a2e | ||
|
|
664c182c8e | ||
|
|
c1ab4e0a77 | ||
|
|
4a4bd61757 | ||
|
|
b6d05289be | ||
|
|
5857458ca5 | ||
|
|
2fb1145fe0 | ||
|
|
546d710102 | ||
|
|
7756103476 | ||
|
|
fef9456c12 | ||
|
|
2d6d56f6b0 | ||
|
|
f54be0fefd | ||
|
|
4e76d388ab | ||
|
|
84b84ab785 | ||
|
|
ed66769688 | ||
|
|
899732f280 | ||
|
|
037b566e39 | ||
|
|
829f20c83c | ||
|
|
f1b5a944a3 | ||
|
|
5ccb764ae5 | ||
|
|
ef2b639734 | ||
|
|
a5b895a827 | ||
|
|
b441b4bae2 | ||
|
|
a6c67c1e4c | ||
|
|
7f1785ac0f | ||
|
|
a0298e0bdb | ||
|
|
3fd94b1cdf | ||
|
|
61d5f7d616 | ||
|
|
cde9a0a715 | ||
|
|
eae6349b04 | ||
|
|
211b930b84 | ||
|
|
4ae463074b | ||
|
|
1c5a4746f7 | ||
|
|
d6ae1fa44a | ||
|
|
a3abb81b37 | ||
|
|
ec74e7fe98 | ||
|
|
6713c198c6 | ||
|
|
35a6b9cf47 | ||
|
|
79f97fad76 | ||
|
|
2faf79077d | ||
|
|
4972b6feb6 | ||
|
|
23d5861c4c | ||
|
|
098ac5a71c | ||
|
|
3a15329cfd | ||
|
|
c3e798aa1d | ||
|
|
eec5e6a93d | ||
|
|
d964b689cd | ||
|
|
1e080c1af5 | ||
|
|
177bba7ec0 | ||
|
|
a593b45dc2 | ||
|
|
b384fb8bd8 | ||
|
|
abd8620ded | ||
|
|
e62ccdcaae | ||
|
|
46d01b09d6 | ||
|
|
ff74e712f8 | ||
|
|
770794ccad | ||
|
|
aa80d1795d | ||
|
|
eaa7720c65 | ||
|
|
7f447d1182 | ||
|
|
5d3dd84762 | ||
|
|
c625317342 | ||
|
|
117398f5dc | ||
|
|
4e421eb4bd | ||
|
|
771447b089 | ||
|
|
8ab75a4146 | ||
|
|
607f465616 | ||
|
|
668c877130 | ||
|
|
4bd870eb96 | ||
|
|
39b3161d91 | ||
|
|
ae090a6585 | ||
|
|
be4831ae62 | ||
|
|
4fb0c18c32 | ||
|
|
22cdd7f8d7 | ||
|
|
f3a91a1f76 | ||
|
|
1e9b92fcf8 | ||
|
|
6cc56066c2 | ||
|
|
99e80cea44 | ||
|
|
f2f1c01e3b | ||
|
|
2c0f98e85c | ||
|
|
a3ad84925c | ||
|
|
b8611b6a1c | ||
|
|
a0e3030005 | ||
|
|
0cf1f1d938 | ||
|
|
88f026066f | ||
|
|
185bef878d | ||
|
|
a1c7b00e74 | ||
|
|
6da4562e79 | ||
|
|
e44cfcb2f2 | ||
|
|
23fabaf8a6 | ||
|
|
f4dca9836f | ||
|
|
f2704ea149 | ||
|
|
dd1b053212 | ||
|
|
d4ccc65655 | ||
|
|
2c2570fc82 | ||
|
|
a60f26966b | ||
|
|
a988de2d61 | ||
|
|
de54ca460e | ||
|
|
afdffab743 | ||
|
|
4c61520397 | ||
|
|
f02cd444d5 | ||
|
|
7f45a51aca | ||
|
|
08e70b9df9 | ||
|
|
32f92489a4 | ||
|
|
20a83362ee | ||
|
|
20b800c3e4 | ||
|
|
94c9cd151a | ||
|
|
0e9eb18052 | ||
|
|
bfaa5b4c4a | ||
|
|
52ec6fe70c | ||
|
|
43b1b1442c | ||
|
|
b06239cc14 | ||
|
|
73dde87a65 | ||
|
|
7e7d810b74 | ||
|
|
b6b2403562 | ||
|
|
9a1f095a45 | ||
|
|
a1a00b33ad | ||
|
|
a3b1ffe77c | ||
|
|
4f22ab3a99 | ||
|
|
a269f4ca3f | ||
|
|
411cb65ba4 | ||
|
|
f691c1f753 | ||
|
|
b299cfc943 | ||
|
|
6157680963 | ||
|
|
1d4bdfa88b | ||
|
|
2755fc43b9 | ||
|
|
0c80d141aa | ||
|
|
f285883c88 | ||
|
|
39f9a325d3 | ||
|
|
e8f66e346f | ||
|
|
98c0535fc9 | ||
|
|
7a61c2e976 | ||
|
|
a15a4db210 | ||
|
|
11fcb8c72f | ||
|
|
a8a20cf5e2 | ||
|
|
2f84bf3251 | ||
|
|
368e0371e9 | ||
|
|
adb5209133 | ||
|
|
63bf405cdd | ||
|
|
d613d66a0a | ||
|
|
e7cb5070cd | ||
|
|
ee50051802 | ||
|
|
20e7bb4747 | ||
|
|
ba0d57ee91 | ||
|
|
98093c9023 | ||
|
|
2fda299736 | ||
|
|
3328ed059e | ||
|
|
cfb7199b79 | ||
|
|
1ad4bfe815 | ||
|
|
93910f27e1 | ||
|
|
04e2d19dda | ||
|
|
a2175f6df7 | ||
|
|
18d415a8fd | ||
|
|
feb195fd65 | ||
|
|
baaa510309 | ||
|
|
a84aa5ad68 | ||
|
|
4191b933c9 | ||
|
|
cf2264ce1d | ||
|
|
02dd9dd8c0 | ||
|
|
d4ff25df0f | ||
|
|
3d74374780 | ||
|
|
7063af678c | ||
|
|
2b44a1cf27 | ||
|
|
c4f60b3645 | ||
|
|
f86f658aa5 | ||
|
|
bd02bd3f3e | ||
|
|
a133faa797 | ||
|
|
bb0269691d | ||
|
|
8d6171d22d | ||
|
|
fff178d79f | ||
|
|
5e5e454ae7 | ||
|
|
ce005f6d9e | ||
|
|
85889ee882 | ||
|
|
351873059e | ||
|
|
8ccfc10522 | ||
|
|
82b02ca70b | ||
|
|
14fc132040 | ||
|
|
a35da349ed | ||
|
|
302e1d9d33 | ||
|
|
0db40184e8 | ||
|
|
d38649494e | ||
|
|
5f22f1b5e5 | ||
|
|
494f93a4bf | ||
|
|
84c8af232c | ||
|
|
7f101d54da | ||
|
|
75b497412e | ||
|
|
5bd774afbb | ||
|
|
cdfe203fe4 | ||
|
|
4c7d32e944 | ||
|
|
447c622fc0 | ||
|
|
03f22aed72 | ||
|
|
ede5abf2ac | ||
|
|
0bdd1d0e0c | ||
|
|
61de7b21fd | ||
|
|
4b6ead1b17 | ||
|
|
0b193e6310 | ||
|
|
b21a5403fe | ||
|
|
e8320be941 |
@@ -14,7 +14,7 @@ runs:
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 7.17.0
|
||||
version: 8.6.2
|
||||
run_install: false
|
||||
- name: Get pnpm cache directory
|
||||
id: pnpm-cache-dir
|
||||
@@ -26,7 +26,7 @@ runs:
|
||||
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: ${{ runner.os }}-node-
|
||||
- name: Use Node.js 16
|
||||
- name: Use Node.js v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
16
.github/actions/nhost-cli/action.yaml
vendored
16
.github/actions/nhost-cli/action.yaml
vendored
@@ -49,21 +49,9 @@ runs:
|
||||
if: ${{ inputs.start == 'true' }}
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.path }}
|
||||
run: nhost dev --no-browser &
|
||||
- name: Wait for the app to be ready
|
||||
id: wait
|
||||
if: ${{ inputs.start == 'true' && inputs.wait == 'true' }}
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.path }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
curl -sSf --connect-timeout 3 \
|
||||
--max-time 5 \
|
||||
--retry 300 \
|
||||
--retry-delay 1 \
|
||||
--retry-max-time 300 \
|
||||
--retry-connrefused \
|
||||
'http://localhost:9695' > /dev/null
|
||||
cp .secrets.example .secrets
|
||||
nhost up
|
||||
- name: Log on failure
|
||||
if: steps.wait.outcome == 'failure'
|
||||
shell: bash
|
||||
|
||||
16
.github/stale.yml
vendored
Normal file
16
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
daysUntilStale: 180
|
||||
daysUntilClose: 7
|
||||
limitPerRun: 30
|
||||
onlyLabels: []
|
||||
exemptLabels: []
|
||||
|
||||
exemptProjects: false
|
||||
exemptMilestones: false
|
||||
exemptAssignees: false
|
||||
staleLabel: stale
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
BUILD: 'all'
|
||||
- name: Check if the pnpm lockfile changed
|
||||
id: changed-lockfile
|
||||
uses: tj-actions/changed-files@v35
|
||||
uses: tj-actions/changed-files@v37
|
||||
with:
|
||||
files: pnpm-lock.yaml
|
||||
# * Determine a pnpm filter argument for packages that have been modified.
|
||||
|
||||
36
.github/workflows/test-nhost-cli-action.yaml
vendored
36
.github/workflows/test-nhost-cli-action.yaml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
path: packages/nhost-js
|
||||
start: true
|
||||
- name: should be running
|
||||
run: curl -sSf 'http://localhost:9695' > /dev/null
|
||||
run: curl -sSf 'https://local.hasura.nhost.run' > /dev/null
|
||||
|
||||
stop:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -48,28 +48,6 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
wait:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install the Nhost CLI and start the application
|
||||
uses: ./.github/actions/nhost-cli
|
||||
with:
|
||||
path: packages/nhost-js
|
||||
start: true
|
||||
wait: false
|
||||
- name: should not be ready
|
||||
run: curl -sSf -o /dev/null 'http://localhost:9695' > /dev/null && exit 1 || true
|
||||
- name: should eventually be ready
|
||||
run: |
|
||||
curl -sSf --connect-timeout 3 \
|
||||
--max-time 5 \
|
||||
--retry 300 \
|
||||
--retry-delay 1 \
|
||||
--retry-max-time 300 \
|
||||
--retry-connrefused \
|
||||
'http://localhost:9695' > /dev/null
|
||||
|
||||
config:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -79,14 +57,10 @@ jobs:
|
||||
with:
|
||||
path: packages/nhost-js
|
||||
start: true
|
||||
config: |
|
||||
services:
|
||||
auth:
|
||||
image: nhost/hasura-auth:0.15.0
|
||||
- name: should find the injected hasura-auth version
|
||||
run: |
|
||||
VERSION=$(curl -sSf 'http://localhost:1337/v1/auth/version')
|
||||
EXPECTED_VERSION='{"version":"v0.15.0"}'
|
||||
VERSION=$(curl -sSf 'https://local.auth.nhost.run/v1/version')
|
||||
EXPECTED_VERSION='{"version":"v0.20.1"}'
|
||||
if [ "$VERSION" != "$EXPECTED_VERSION" ]; then
|
||||
echo "Expected version $EXPECTED_VERSION but got $VERSION"
|
||||
exit 1
|
||||
@@ -99,6 +73,6 @@ jobs:
|
||||
- name: Install the Nhost CLI
|
||||
uses: ./.github/actions/nhost-cli
|
||||
with:
|
||||
version: v0.8.10
|
||||
version: v1.0.1
|
||||
- name: should find the correct version
|
||||
run: nhost version | head -n 1 | grep v0.8.10 || exit 1
|
||||
run: nhost --version | head -n 1 | grep v1.0.1 || exit 1
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -26,6 +26,7 @@ tmp/
|
||||
.pnpm-store
|
||||
.turbo
|
||||
.env
|
||||
.secrets
|
||||
out/
|
||||
|
||||
# Custom
|
||||
@@ -61,3 +62,7 @@ todo.md
|
||||
|
||||
# Nhost CLI data
|
||||
.nhost
|
||||
|
||||
# Nix
|
||||
.envrc
|
||||
.direnv/
|
||||
|
||||
103
DEVELOPERS.md
103
DEVELOPERS.md
@@ -1,16 +1,37 @@
|
||||
# Developer guide
|
||||
# Developer Guide
|
||||
|
||||
## Requirements
|
||||
|
||||
- This repository works with **Node 16**
|
||||
### Node.js v18
|
||||
|
||||
- We use [pnpm](https://pnpm.io/) as a package manager to speed up development and builds, and as a basis for our monorepo. You need to make sure it's installed on your machine. There are [several ways to install it](https://pnpm.io/installation), but the easiest way is with `npm`:
|
||||
_⚠️ Node.js v16 is also supported for the time being but support will be dropped in the near future_.
|
||||
|
||||
### [pnpm](https://pnpm.io/) package manager
|
||||
|
||||
The easiest way to install `pnpm` if it's not installed on your machine yet is to use `npm`:
|
||||
|
||||
```sh
|
||||
$ npm install -g pnpm
|
||||
```
|
||||
|
||||
- Our tests and examples use the Nhost CLI, to run the backend services locally. You can follow the installation instructions in [our documentation](https://docs.nhost.io/get-started/cli-workflow/install-cli).
|
||||
### [Nhost CLI](https://docs.nhost.io/cli)
|
||||
|
||||
- The CLI is primarily used for running the E2E tests
|
||||
- Please refer to the [installation guide](https://docs.nhost.io/get-started/cli-workflow/install-cli) if you have not installed it yet
|
||||
|
||||
## File Structure
|
||||
|
||||
The repository is organized as a monorepo, with the following structure (only relevant folders are shown):
|
||||
|
||||
```
|
||||
assets/ # Assets used in the README
|
||||
config/ # Configuration files for the monorepo
|
||||
dashboard/ # Dashboard
|
||||
docs/ # Documentation website
|
||||
examples/ # Example projects
|
||||
packages/ # Core packages
|
||||
integrations/ # These are packages that rely on the core packages
|
||||
```
|
||||
|
||||
## Get started
|
||||
|
||||
@@ -31,25 +52,25 @@ $ pnpm install
|
||||
|
||||
### Development
|
||||
|
||||
Although package references are correctly updated on the fly for TypeScript, example projects won't
|
||||
see the changes because they are depending on the build output. To fix this, you can run packages
|
||||
in development mode.
|
||||
Although package references are correctly updated on the fly for TypeScript, example projects and the dashboard won't see the changes because they are depending on the build output. To fix this, you can run packages in development mode.
|
||||
|
||||
Running packages in development mode is as simple as:
|
||||
Running packages in development mode from the root folder is as simple as:
|
||||
|
||||
```sh
|
||||
$ pnpm dev
|
||||
```
|
||||
|
||||
Our packages are linked together using [PNPM's workspace](https://pnpm.io/workspaces) feature. Vite automatically detects changes in the dependencies and rebuilds everything, so that the changes are immediately reflected in the other packages.
|
||||
Our packages are linked together using [PNPM's workspace](https://pnpm.io/workspaces) feature. Next.js and Vite automatically detect changes in the dependencies and rebuild everything, so the changes will be reflected in the examples and the dashboard.
|
||||
|
||||
### Use examples
|
||||
**Note:** It's possible that Next.js or Vite throw an error when you run `pnpm dev`. Restarting the process should fix it.
|
||||
|
||||
### Use Examples
|
||||
|
||||
Examples are a great way to test your changes in practice. Make sure you've `pnpm dev` running in your terminal and then run an example.
|
||||
|
||||
Let's follow the instructions to run [react-apollo example](https://github.com/nhost/nhost/blob/main/examples/react-apollo/README.md).
|
||||
|
||||
## Run the documentation website locally
|
||||
## Edit Documentation
|
||||
|
||||
The easier way to contribute to our documentation is to go to the `docs` folder and follow the [instructions to start local development](https://github.com/nhost/nhost/blob/main/docs/README.md):
|
||||
|
||||
@@ -60,9 +81,9 @@ $ pnpm install
|
||||
$ pnpm start
|
||||
```
|
||||
|
||||
## Run test suites
|
||||
## Run Test Suites
|
||||
|
||||
### Unit tests
|
||||
### Unit Tests
|
||||
|
||||
You can run the unit tests with the following command from the repository root:
|
||||
|
||||
@@ -70,7 +91,7 @@ You can run the unit tests with the following command from the repository root:
|
||||
$ pnpm test
|
||||
```
|
||||
|
||||
### End-to-end tests
|
||||
### E2E Tests
|
||||
|
||||
Each package that defines end-to-end tests embeds their own Nhost configuration, that will be automatically when running the tests. As a result, you must make sure you are not running the Nhost CLI before running the tests.
|
||||
|
||||
@@ -83,24 +104,60 @@ $ pnpm e2e
|
||||
## Changesets
|
||||
|
||||
If you've made changes to the packages, you must describe those changes so that they can be reflected in the next release.
|
||||
We use [changesets](https://github.com/changesets/changesets) to support our versioning and release workflows. When you submit a pull request, a bot checks if some changesets are present, and if not, it directs you to add them.
|
||||
We use [changesets](https://github.com/changesets/changesets) to support our versioning and release workflows. When you submit a pull request, a bot checks if changesets are present, and if not, it asks you to add them.
|
||||
|
||||
The most comprehensive way to add a changeset is to run the following command in the repository root:
|
||||
To create a changeset, run the following command from the repository root:
|
||||
|
||||
```sh
|
||||
$ pnpm changeset
|
||||
```
|
||||
|
||||
This will create a file in the `.changeset` directory. You can edit it to give more details about the change you just made.
|
||||
This command will guide you through the process of creating a changeset. It will create a file in the `.changeset` directory.
|
||||
|
||||
You can take a look at the changeset documentation: [How to add a changeset](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).
|
||||
|
||||
## Committing changes
|
||||
### Selecting the Version
|
||||
|
||||
You'll notice that `git commit` takes a few seconds to run. We set a commit hook that scans the changes in the code, automatically generates documentation from the inline [TSDoc](https://tsdoc.org/) annotations, and adds these generated documentation files to the commit. They automatically update the [reference documentation](https://docs.nhost.io/reference).
|
||||
When you create a changeset, you will be asked to select the version of the package that you are bumping. The versioning scheme is as follows:
|
||||
|
||||
- **major**
|
||||
- For breaking changes (e.g: changing the function signature, etc.)
|
||||
- Should be avoided as much as possible as it will require users to update their code. Instead, consider supporting both the old and the new API simultaneously for a while.
|
||||
- For example: `v1.5.8` -> `v2.0.0`
|
||||
- **minor**
|
||||
- For new features (e.g: adding a new page to the dashboard, etc.)
|
||||
- For example: `v1.5.8` -> `v1.6.0`
|
||||
- **patch**
|
||||
- For bug fixes (e.g: fixing a typo, etc.)
|
||||
- For example: `v1.5.8` -> `v1.5.9`
|
||||
|
||||
<!-- ## Good practices
|
||||
- lint
|
||||
- prettier
|
||||
- documentation -->
|
||||
### Writing Good Changesets
|
||||
|
||||
A concise summary that describes the changes should be added to each PR. This summary will be used as the changeset description.
|
||||
|
||||
The following structure is used for describing changes:
|
||||
|
||||
- **The type of the change**:
|
||||
|
||||
- fix
|
||||
- feat
|
||||
- chore
|
||||
- docs
|
||||
|
||||
- **The scope of the change** (_broader scopes (e.g: dashboard, hasura-storage-js, etc.) are not recommended as GitHub Releases already contain which project is being bumped_):
|
||||
|
||||
- projects
|
||||
- deployments
|
||||
- deps
|
||||
- etc.
|
||||
|
||||
- **A short summary of the changes that were made**
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `fix(deployments): use correct timestamp for deployment details`
|
||||
- `chore(deps): bump @types/react to v18.2.8`
|
||||
- `feat(secrets): enable secrets`
|
||||
- etc.
|
||||
|
||||
You can always take a look at examples of changesets in the [GitHub Releases section](https://github.com/nhost/nhost/releases).
|
||||
|
||||
@@ -36,6 +36,7 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
build: {
|
||||
target: 'es2019',
|
||||
sourcemap: true,
|
||||
lib: {
|
||||
entry,
|
||||
|
||||
@@ -28,7 +28,6 @@ module.exports = {
|
||||
'import/prefer-default-export': 'off',
|
||||
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
|
||||
curly: ['error', 'all'],
|
||||
'no-restricted-exports': 'off',
|
||||
'no-undef': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'@typescript-eslint/no-use-before-define': [
|
||||
@@ -67,21 +66,11 @@ module.exports = {
|
||||
{
|
||||
group: ['..*'],
|
||||
message:
|
||||
'Please use absolute imports instead. (e.g: @/ui/, @/hooks/, etc.)',
|
||||
},
|
||||
{
|
||||
group: ['@/components/ui', '@/components/ui/*'],
|
||||
message:
|
||||
'Please use shorthand imports instead. (e.g: @/ui/ActivityIndicator, @/ui/Button, etc.)',
|
||||
},
|
||||
{
|
||||
group: ['@/components/ui/v2*'],
|
||||
message:
|
||||
'Please use shorthand imports instead. (e.g: @/ui/v2/ActivityIndicator, @/ui/v2/Button, etc.)',
|
||||
'Please use absolute imports instead. (e.g: @/components/, @/hooks/, etc.)',
|
||||
},
|
||||
{
|
||||
group: ['@testing-library/react*'],
|
||||
message: 'Please use @/utils/testUtils instead.',
|
||||
message: 'Please use @/tests/testUtils instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -2,13 +2,14 @@ import '@fontsource/inter';
|
||||
import '@fontsource/inter/500.css';
|
||||
import '@fontsource/inter/700.css';
|
||||
import { CssBaseline, ThemeProvider } from '@mui/material';
|
||||
import { NhostClient, NhostProvider } from '@nhost/nextjs';
|
||||
import { NhostApolloProvider } from '@nhost/react-apollo';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { Buffer } from 'buffer';
|
||||
import { initialize, mswDecorator } from 'msw-storybook-addon';
|
||||
import { RouterContext } from 'next/dist/shared/lib/router-context';
|
||||
import { createTheme } from '../src/components/ui/v2/createTheme';
|
||||
import '../src/styles/globals.css';
|
||||
import createTheme from '../src/theme/createTheme';
|
||||
|
||||
global.Buffer = Buffer;
|
||||
|
||||
@@ -56,5 +57,10 @@ export const decorators = [
|
||||
<Story />
|
||||
</NhostApolloProvider>
|
||||
),
|
||||
(Story) => (
|
||||
<NhostProvider nhost={new NhostClient({ subdomain: 'local' })}>
|
||||
<Story />
|
||||
</NhostProvider>
|
||||
),
|
||||
mswDecorator,
|
||||
];
|
||||
|
||||
@@ -1,5 +1,302 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 0.17.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and `@types/react-dom` to `v18.2.6`
|
||||
- 32b221f94: chore(deps): bump `graphiql` to `v3`
|
||||
- 3a56c12df: chore(deps): bump `turbo` to `v1.10.6`
|
||||
- Updated dependencies [b3b64a3b7]
|
||||
- @nhost/react-apollo@5.0.29
|
||||
- @nhost/nextjs@1.13.31
|
||||
|
||||
## 0.17.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f41fdc12a: chore(deps): bump `turbo` to `1.10.5`
|
||||
- 6199c1c55: fix(projects): don't redirect to 404 page
|
||||
- Updated dependencies [07a45fde0]
|
||||
- @nhost/react-apollo@5.0.28
|
||||
- @nhost/nextjs@1.13.30
|
||||
|
||||
## 0.17.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 80b22724d: chore(deps): bump `@types/react` to `v18.2.13`, `@types/react-dom` to `v18.2.6` and `@storybook/testing-library` to `v0.2.0`
|
||||
|
||||
## 0.17.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cc02902cb: chore(docs): update environment variable documentation
|
||||
|
||||
## 0.17.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 660d339e1: fix(storybook): don't break storybook
|
||||
- 660d339e1: fix(tests): prevent warnings during tests
|
||||
- @nhost/react-apollo@5.0.27
|
||||
- @nhost/nextjs@1.13.29
|
||||
|
||||
## 0.17.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- bd4d0c270: chore(dashboard):add postgres 14.6-20230613-1 to the version selector
|
||||
|
||||
## 0.17.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c8c2a10b2: fix(database): don't break the password reset flow
|
||||
- e70b45498: chore(deps): bump `@types/react` to `v18.2.12` and `@types/react-dom` to `v18.2.5`
|
||||
|
||||
## 0.17.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 842055099: chore(deps): bump `turbo` to `v1.10.3` and `pnpm` to `v8.6.2`
|
||||
- fd12aa0a8: chore(projects): remove the postgres password input from the project creation screen
|
||||
- 022b76e78: chore(deps): bump `@types/react` to `v18.2.11`
|
||||
- 3555ab2b7: chore(deps): bump `vitest` monorepo to `v0.32.0`
|
||||
- c43e54922: feat(backups): add download button to backups
|
||||
|
||||
## 0.17.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d0457fe5c: feat(settings): improve the dashboard and config parity
|
||||
- @nhost/react-apollo@5.0.26
|
||||
- @nhost/nextjs@1.13.28
|
||||
|
||||
## 0.17.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4f0368b95: fix(account): don't break account settings page
|
||||
|
||||
## 0.17.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 64a8f41d0: chore(resources): lower the maximum allowed resources per service
|
||||
|
||||
## 0.17.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@5.0.25
|
||||
- @nhost/nextjs@1.13.27
|
||||
|
||||
## 0.17.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9b1d0f7a5: fix(deployments): use correct timestamp for deployment details
|
||||
- 6d2963ffa: chore(deps): bump `@types/react` to `v18.2.8`
|
||||
- 8871267b9: chore(deps): downgrade `pnpm` to `v8.5.1` because of no Turborepo support
|
||||
|
||||
## 0.17.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 01eeef9de: chore(misc): under the hood improvements
|
||||
- 21e13db05: chore(deps): bump `@types/react` to `v18.2.7` and `turbo` to `v1.10.1`
|
||||
- f16433ae6: chore(secrets): allow empty secrets and environment variables
|
||||
- aa3c62989: chore(cli): bump Nhost CLI version to v1.0
|
||||
- @nhost/react-apollo@5.0.24
|
||||
- @nhost/nextjs@1.13.26
|
||||
|
||||
## 0.17.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 88a4983f: chore(misc): under the hood improvements
|
||||
|
||||
## 0.17.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9b0d4dde: feat(secrets): enable secrets
|
||||
|
||||
## 0.17.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 15d84a19: Add postgres 14.6-20230525
|
||||
|
||||
## 0.16.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4c626174: chore: updated import paths, improved directory structure
|
||||
- cc047b71: chore(deps): bump `@fontsource` monorepo to `v5.0.0`
|
||||
- 99edd012: feat(account): add support for personal access tokens
|
||||
|
||||
## 0.16.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 78c7109c: feat(settings): allow selecting service versions
|
||||
|
||||
## 0.16.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 399009d6: fix(gql): don't enter an infinite loop when fetching remote app data
|
||||
- 329e5a91: fix(deployments): use the same sorting of deployments everywhere
|
||||
- 6d559d6e: chore(settings): add under the hood improvements to the settings page
|
||||
- 12eb236c: chore(deps): bump `prettier-plugin-tailwindcss` to `v0.3.0`
|
||||
- f9b81a2a: chore(deps): bump `turbo` to `v1.9.8`
|
||||
- 1345741b: fix(projects): don't redirect to 404 on project creation
|
||||
- Updated dependencies [7fea29a8]
|
||||
- @nhost/react-apollo@5.0.23
|
||||
- @nhost/nextjs@1.13.25
|
||||
|
||||
## 0.16.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1230b722: fix(projects): don't redirect to 404 on when the project is renamed
|
||||
- @nhost/react-apollo@5.0.22
|
||||
- @nhost/nextjs@1.13.24
|
||||
|
||||
## 0.16.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [da03bf39]
|
||||
- @nhost/react-apollo@5.0.21
|
||||
- @nhost/nextjs@1.13.23
|
||||
|
||||
## 0.16.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 349aac36: fix(settings): use region domain when constructing the postgres connection string
|
||||
|
||||
## 0.16.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 20fb69fa: chore(projects): change the way how API URLs are constructed
|
||||
|
||||
## 0.16.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 49f9b837: chore(docker): bump `pnpm` to `v8.4.0` and `turbo` to `v1.9.3`
|
||||
- 3f478a4e: chore(deps): bump `vitest` to `v0.31.0`, `@types/react` to `v18.2.6` and `@types/react-dom` to `v18.2.4`
|
||||
|
||||
## 0.16.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d926f156: fix(projects): redirect to 404 when an invalid project is opened
|
||||
- 49b99728: fix(projects): disable features for non-owner members of workspaces
|
||||
|
||||
## 0.16.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 12e2855f: chore(deps): bump `jsdom` to v22
|
||||
- e4972b83: feat(metrics): add Grafana page
|
||||
|
||||
## 0.16.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3f396a9e: fix(projects): unpause after upgrading a paused project to pro
|
||||
- 3f396a9e: fix(projects): don't redirect to 404 page after project creation
|
||||
|
||||
## 0.16.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [90c60311]
|
||||
- @nhost/react-apollo@5.0.20
|
||||
- @nhost/nextjs@1.13.22
|
||||
|
||||
## 0.16.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 0f34f0c6: fix(projects): disallow downgrading to free plan
|
||||
- 8da291ad: chore(deps): bump `@types/react` to v18.2.0 and `@types/react-dom` to v18.2.1
|
||||
|
||||
## 0.16.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- adc828a5: fix(gql): don't enter an infinite loop when fetching remote app data
|
||||
|
||||
## 0.16.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 2fb1145f: feat(compute): add support for replicas
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d8ceccec: chore(env): remove deprecated `NHOST_BACKEND_URL` environment variable
|
||||
|
||||
## 0.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 84b84ab7: fix(projects): filter projects by workspace
|
||||
|
||||
## 0.15.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2faf7907: chore(deps): bump `graphql-request` to v6
|
||||
- f1b5a944: chore(deps): bump `@vitejs/plugin-react` to v4
|
||||
- 7f1785ac: chore(deps): bump `@types/react` to v18.0.37
|
||||
- @nhost/react-apollo@5.0.19
|
||||
|
||||
## 0.15.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 85889ee8: feat(dashboard): add Compute management to the settings
|
||||
|
||||
## 0.14.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 668c8771: chore(dialogs): unify dialog management of payment dialogs
|
||||
|
||||
## 0.14.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d4ccc656: chore: cleanup unused code
|
||||
- @nhost/react-apollo@5.0.18
|
||||
- @nhost/nextjs@1.13.21
|
||||
|
||||
## 0.14.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b299cfc9: chore(deps): bump `vitest` to v0.30.0
|
||||
- 411cb65b: chore(projects): refactor workspace and project hooks
|
||||
- 43b1b144: chore(deps): bump `@types/react` to v18.0.34 and `@types/react-dom` to v18.0.11
|
||||
- Updated dependencies [43b1b144]
|
||||
- @nhost/react-apollo@5.0.17
|
||||
- @nhost/nextjs@1.13.20
|
||||
|
||||
## 0.14.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ba0d57ee: fix(i18n): revert i18n library
|
||||
- 3328ed05: feat(projects): improve overview when there is an error
|
||||
|
||||
## 0.14.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -3,7 +3,7 @@ RUN apk add --no-cache libc6-compat
|
||||
RUN apk update
|
||||
WORKDIR /app
|
||||
|
||||
RUN yarn global add turbo@1.8.6
|
||||
RUN yarn global add turbo@1.10.6
|
||||
COPY . .
|
||||
RUN turbo prune --scope="@nhost/dashboard" --docker
|
||||
|
||||
@@ -29,7 +29,7 @@ ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL_
|
||||
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
|
||||
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
|
||||
|
||||
RUN yarn global add pnpm@7.17.0
|
||||
RUN yarn global add pnpm@8.6.2
|
||||
COPY .gitignore .gitignore
|
||||
COPY --from=pruner /app/out/json/ .
|
||||
COPY --from=pruner /app/out/pnpm-*.yaml .
|
||||
|
||||
@@ -3,17 +3,27 @@
|
||||
This is the Nhost Dashboard, a web application that allows you to manage your Nhost projects.
|
||||
To get started, you need to have an Nhost project. If you don't have one, you can [create a project here](https://app.nhost.io).
|
||||
|
||||
First, install the dependencies:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Then, run the development server:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
## Environment
|
||||
|
||||
### Setup Environment Variables
|
||||
|
||||
Depending on the environment you wish to target you can configure environment variables in `.env.<target_environment>.local`.
|
||||
|
||||
- `.env.development`: This file is used if you run `nhost dev`
|
||||
- `.env.development`: This file is used if you run `nhost up`
|
||||
- `.env.development.local`: This file is used if you run `pnpm dev`. It takes precedence over `.env.local` if available.
|
||||
- `.env.production.local`: This file is used if you run `pnpm build`. It takes precedence over `.env.local` if available.
|
||||
- `.env.local`: This file is used if you run either `pnpm dev` or `pnpm build`.
|
||||
@@ -27,7 +37,7 @@ You can connect the Nhost Dashboard to your **locally running** Nhost backend in
|
||||
First, you need to run the following command to start your backend locally:
|
||||
|
||||
```bash
|
||||
cd <your_nhost_project> && nhost dev
|
||||
cd <your_nhost_project> && nhost up
|
||||
```
|
||||
|
||||
You can connect the Nhost Dashboard to your locally running backend by setting the following environment variables in `.env.development.local`:
|
||||
@@ -54,6 +64,12 @@ Components are documented using [Storybook](https://storybook.js.org/). To run S
|
||||
pnpm storybook
|
||||
```
|
||||
|
||||
By default, Storybook will run on port `6006`. You can change this by passing the `--port` flag:
|
||||
|
||||
```bash
|
||||
pnpm storybook --port 6007
|
||||
```
|
||||
|
||||
### General Environment Variables
|
||||
|
||||
| Name | Description |
|
||||
@@ -64,16 +80,15 @@ pnpm storybook
|
||||
|
||||
### Environment Variables for Local Development and Self-Hosting
|
||||
|
||||
| Name | Description |
|
||||
| ---- | ----------- |
|
||||
|
||||
| `NEXT_PUBLIC_NHOST_AUTH_URL` | The URL of the Auth service. When working locally, point it to the Auth service started by the CLI. When self-hosting, point it to the self-hosted Auth service. |
|
||||
| `NEXT_PUBLIC_NHOST_FUNCTIONS_URL` | The URL of the Functions service. When working locally, point it to the Functions service started by the CLI. When self-hosting, point it to the self-hosted Functions service. |
|
||||
| `NEXT_PUBLIC_NHOST_GRAPHQL_URL` | The URL of the GraphQL service. When working locally, point it to the GraphQL service started by the CLI. When self-hosting, point it to the self-hosted GraphQL service. |
|
||||
| `NEXT_PUBLIC_NHOST_STORAGE_URL` | The URL of the Storage service. When working locally, point it to the Storage service started by the CLI. When self-hosting, point it to the self-hosted Storage service. |
|
||||
| `NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL` | The URL of the Hasura Console. When working locally, point it to the Hasura Console started by the CLI. When self-hosting, point it to the self-hosted Hasura Console. |
|
||||
| `NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL` | The URL of Hasura's Migrations service. When working locally, point it to the Migrations service started by the CLI. |
|
||||
| `NEXT_PUBLIC_NHOST_HASURA_API_URL` | The URL of Hasura's Schema and Metadata API. When working locally, point it to the Schema and Metadata API started by the CLI. When self-hosting, point it to the self-hosted Schema and Metadata API. |
|
||||
| Name | Description |
|
||||
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `NEXT_PUBLIC_NHOST_AUTH_URL` | The URL of the Auth service. When working locally, point it to the Auth service started by the CLI. When self-hosting, point it to the self-hosted Auth service. |
|
||||
| `NEXT_PUBLIC_NHOST_FUNCTIONS_URL` | The URL of the Functions service. When working locally, point it to the Functions service started by the CLI. When self-hosting, point it to the self-hosted Functions service. |
|
||||
| `NEXT_PUBLIC_NHOST_GRAPHQL_URL` | The URL of the GraphQL service. When working locally, point it to the GraphQL service started by the CLI. When self-hosting, point it to the self-hosted GraphQL service. |
|
||||
| `NEXT_PUBLIC_NHOST_STORAGE_URL` | The URL of the Storage service. When working locally, point it to the Storage service started by the CLI. When self-hosting, point it to the self-hosted Storage service. |
|
||||
| `NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL` | The URL of the Hasura Console. When working locally, point it to the Hasura Console started by the CLI. When self-hosting, point it to the self-hosted Hasura Console. |
|
||||
| `NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL` | The URL of Hasura's Migrations service. When working locally, point it to the Migrations service started by the CLI. |
|
||||
| `NEXT_PUBLIC_NHOST_HASURA_API_URL` | The URL of Hasura's Schema and Metadata API. When working locally, point it to the Schema and Metadata API started by the CLI. When self-hosting, point it to the self-hosted Schema and Metadata API. |
|
||||
|
||||
### Other Environment Variables
|
||||
|
||||
@@ -93,7 +108,7 @@ pnpm storybook
|
||||
| Name | Description |
|
||||
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `react/react-in-jsx-scope` | Disabled because we don't need to import `React` anymore. |
|
||||
| `react/jsx-props-no-spreading` | Disabled because we heavily rely on props spreading in our `@/ui/v2` components. |
|
||||
| `react/jsx-props-no-spreading` | Disabled because we heavily rely on props spreading in our `@/components/ui/v2` components. |
|
||||
| `react/require-default-props` | Disabled because we use TypeScript instead of PropTypes. |
|
||||
| `react-hooks/exhaustive-deps` | Because we already had several rule violations when proper ESLint rules were introduced, we changed this rule to a warning. |
|
||||
| `import/extensions` | JS / TS files should be imported without file extensions. |
|
||||
@@ -102,7 +117,6 @@ pnpm storybook
|
||||
| `import/order` | Until we have a better auto-formatter, we disable this rule. |
|
||||
| `import/no-extraneous-dependencies` | `devDependencies` should be excluded from the list of disallowed imports. |
|
||||
| `curly` | By default it only enforces curly braces for multi-line blocks, but it should be enforced for single-line blocks as well. |
|
||||
| `no-restricted-exports` | `export { default } from './module'` is used heavily in `@/ui/v2` which is a restricted export by default. |
|
||||
| `@typescript-eslint/no-use-before-define` | Order of type references should be ignored. |
|
||||
| `no-undef` | [Official TypeScript ESLint packages](https://github.com/typescript-eslint/typescript-eslint/issues/4671#issuecomment-1065948494) are turning off this rule. |
|
||||
| `@typescript-eslint/no-shadow` | TypeScript specific implementation of `no-shadow`. |
|
||||
@@ -112,15 +126,19 @@ pnpm storybook
|
||||
| `@typescript-eslint/naming-convention` | Enforces a consistent naming convention. |
|
||||
| `no-restricted-imports` | Enforces absolute imports and consistent import paths for components from `src/components/ui` folder. |
|
||||
|
||||
### End-to-End Tests
|
||||
### Unit Tests
|
||||
|
||||
End-to-end tests are written using [Playwright](https://playwright.dev/). To run the tests, run the following command:
|
||||
Unit tests are written using [Vitest](https://vitest.dev/). To run the tests, run the following command:
|
||||
|
||||
```bash
|
||||
pnpm e2e
|
||||
pnpm test
|
||||
```
|
||||
|
||||
Most of the tests require access to the Nhost test user. To run these tests, you need to set the following environment variables in `.env.test`:
|
||||
### End-to-End Tests
|
||||
|
||||
Most of the end-to-end tests require access to an Nhost test user and a live project. You can register a user and create a test project on the [Nhost Dashboard](https://app.nhost.io/).
|
||||
|
||||
Next, you need to create a project. Create a `.env.test` file with the following variables:
|
||||
|
||||
```
|
||||
NHOST_TEST_DASHBOARD_URL=<test_dashboard_url>
|
||||
@@ -130,3 +148,20 @@ NHOST_TEST_WORKSPACE_NAME=<test_workspace_name>
|
||||
NHOST_TEST_PROJECT_NAME=<test_project_name>
|
||||
NHOST_TEST_PROJECT_ADMIN_SECRET=<test_project_admin_secret>
|
||||
```
|
||||
|
||||
**Required Variables**:
|
||||
|
||||
- `NHOST_TEST_DASHBOARD_URL`: The URL to run the tests against (e.g: http://localhost:3000 or https://staging.app.nhost.io)
|
||||
- `NHOST_TEST_USER_EMAIL`: Email address of the test user that owns the test project
|
||||
- `NHOST_TEST_USER_PASSWORD`: Password of the test user that owns the test project
|
||||
- `NHOST_TEST_WORKSPACE_NAME`: Name of the workspace that contains the test project
|
||||
- `NHOST_TEST_PROJECT_NAME`: Name of the test project
|
||||
- `NHOST_TEST_PROJECT_ADMIN_SECRET`: Admin secret of the test project
|
||||
|
||||
Make sure to copy the workspace and project information from the [Nhost Dashboard](https://app.nhost.io/).
|
||||
|
||||
End-to-end tests are written using [Playwright](https://playwright.dev/). To run the tests, run the following command:
|
||||
|
||||
```bash
|
||||
pnpm e2e
|
||||
```
|
||||
|
||||
51
dashboard/e2e/account/pat/manage-pat.test.ts
Normal file
51
dashboard/e2e/account/pat/manage-pat.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
let page: Page;
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('should be able to create then delete a personal access token', async () => {
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.getByRole('banner').getByRole('button').last().click();
|
||||
await page.getByRole('link', { name: /account settings/i }).click();
|
||||
await page
|
||||
.getByRole('button', { name: /create personal access token/i })
|
||||
.click();
|
||||
|
||||
const patName = faker.lorem.slug(3);
|
||||
|
||||
await page.getByRole('textbox', { name: /name/i }).fill(patName);
|
||||
await page.getByRole('button', { name: /expiration/i }).click();
|
||||
await page.getByRole('option', { name: /7 days/i }).click();
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText(
|
||||
/this token will not be shown again. make sure to copy it now./i,
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: /close/i }).click();
|
||||
|
||||
await expect(page.getByText(patName)).toBeVisible();
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: `More options for ${patName}`, exact: true })
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: /delete/i }).click();
|
||||
await page.getByRole('button', { name: /delete/i }).click();
|
||||
|
||||
await expect(page.getByText(patName)).not.toBeVisible();
|
||||
});
|
||||
@@ -30,7 +30,7 @@ test('should show a sidebar with menu items', async () => {
|
||||
const navLocator = page.getByRole('navigation', { name: /main navigation/i });
|
||||
await expect(navLocator).toBeVisible();
|
||||
await expect(navLocator.getByRole('list').getByRole('listitem')).toHaveCount(
|
||||
10,
|
||||
11,
|
||||
);
|
||||
await expect(
|
||||
navLocator.getByRole('link', { name: /overview/i }),
|
||||
@@ -53,6 +53,9 @@ test('should show a sidebar with menu items', async () => {
|
||||
navLocator.getByRole('link', { name: /backups/i }),
|
||||
).toBeVisible();
|
||||
await expect(navLocator.getByRole('link', { name: /logs/i })).toBeVisible();
|
||||
await expect(
|
||||
navLocator.getByRole('link', { name: /metrics/i }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
navLocator.getByRole('link', { name: /settings/i }),
|
||||
).toBeVisible();
|
||||
|
||||
@@ -46,7 +46,7 @@ async function globalTeardown() {
|
||||
await hasuraPage.locator('a', { hasText: /data/i }).click();
|
||||
await hasuraPage.getByRole('link', { name: /sql/i }).click();
|
||||
|
||||
await hasuraPage.getByRole('textbox').fill(`
|
||||
await hasuraPage.locator('#raw_sql > textarea').fill(`
|
||||
DO $$ DECLARE
|
||||
tablename text;
|
||||
BEGIN
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"locales": ["en"],
|
||||
"defaultLocale": "en",
|
||||
|
||||
"pages": {
|
||||
"*": ["common"],
|
||||
"/[workspaceSlug]/[appSlug]": ["overview"]
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"metrics": {
|
||||
"cpuUsageSeconds": {
|
||||
"label": "CPU Usage Seconds",
|
||||
"tooltip": "Total time the service has used the CPUs"
|
||||
},
|
||||
"totalRequests": {
|
||||
"label": "Total Requests",
|
||||
"tooltip": "Total amount of requests your services have received excluding functions"
|
||||
},
|
||||
"functionInvocations": {
|
||||
"label": "Function Invocations",
|
||||
"tooltip": "Number of times your functions have been called"
|
||||
},
|
||||
"egressVolume": {
|
||||
"label": "Egress Volume",
|
||||
"tooltip": "Amount of data your services have sent to users"
|
||||
},
|
||||
"logs": {
|
||||
"label": "Logs",
|
||||
"tooltip": "Amount of logs stored"
|
||||
},
|
||||
"helperText": "Your resource usage since the beginning of the month."
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
const path = require('path');
|
||||
const nextTranslate = require('next-translate-plugin');
|
||||
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
});
|
||||
const { version } = require('./package.json');
|
||||
|
||||
const configWithBundleAnalyzer = withBundleAnalyzer({
|
||||
module.exports = withBundleAnalyzer({
|
||||
reactStrictMode: true,
|
||||
swcMinify: false,
|
||||
output: 'standalone',
|
||||
@@ -79,5 +78,3 @@ const configWithBundleAnalyzer = withBundleAnalyzer({
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = nextTranslate(configWithBundleAnalyzer);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "0.14.4",
|
||||
"version": "0.17.16",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@@ -8,14 +8,14 @@
|
||||
"build": "next build --no-lint",
|
||||
"analyze": "ANALYZE=true pnpm build --no-lint",
|
||||
"start": "next start",
|
||||
"lint": "next lint --max-warnings 2",
|
||||
"lint": "next lint --max-warnings 0",
|
||||
"test": "vitest",
|
||||
"codegen": "graphql-codegen --config graphql.config.yaml --errors-only",
|
||||
"nhost:dev": "nhost dev -d",
|
||||
"format": "prettier --write \"src/**/*.{js,ts,tsx,jsx,json,md}\" --plugin-search-dir=.",
|
||||
"storybook": "start-storybook -p 6006 -s public",
|
||||
"build-storybook": "build-storybook",
|
||||
"e2e": "npx playwright@1.31.2 install --with-deps && playwright test"
|
||||
"install-browsers": "pnpm dlx playwright@1.31.0 install --with-deps",
|
||||
"e2e": "pnpm install-browsers && pnpm dlx playwright@1.31.0 test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.10",
|
||||
@@ -24,9 +24,9 @@
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/server": "^11.4.0",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@fontsource/inter": "^4.5.14",
|
||||
"@fontsource/roboto-mono": "^4.5.8",
|
||||
"@graphiql/react": "^0.17.0",
|
||||
"@fontsource/inter": "^5.0.0",
|
||||
"@fontsource/roboto-mono": "^5.0.0",
|
||||
"@graphiql/react": "^0.18.0",
|
||||
"@graphiql/toolkit": "^0.8.2",
|
||||
"@headlessui/react": "^1.6.5",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
@@ -49,16 +49,15 @@
|
||||
"clsx": "^1.2.1",
|
||||
"date-fns": "^2.29.3",
|
||||
"generate-password": "^1.7.0",
|
||||
"graphiql": "^2.4.0",
|
||||
"graphiql": "^3.0.0",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-request": "^4.3.0",
|
||||
"graphql-request": "^6.0.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"graphql-ws": "^5.11.2",
|
||||
"just-kebab-case": "^4.1.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"next": "^12.3.1",
|
||||
"next-seo": "^6.0.0",
|
||||
"next-translate": "^2.0.4",
|
||||
"node-pg-format": "^1.3.5",
|
||||
"pluralize": "^8.0.0",
|
||||
"react": "18.2.0",
|
||||
@@ -85,11 +84,10 @@
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@graphql-codegen/cli": "^3.0.0",
|
||||
"@graphql-codegen/typescript": "^3.0.0",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.1",
|
||||
"@graphql-codegen/typescript-operations": "^3.0.0",
|
||||
"@graphql-codegen/typescript-react-apollo": "^3.3.1",
|
||||
"@next/bundle-analyzer": "^12.3.1",
|
||||
"@playwright/test": "^1.31.2",
|
||||
"@playwright/test": "1.31.0",
|
||||
"@storybook/addon-actions": "^6.5.14",
|
||||
"@storybook/addon-essentials": "^6.5.14",
|
||||
"@storybook/addon-interactions": "^6.5.14",
|
||||
@@ -98,7 +96,7 @@
|
||||
"@storybook/builder-webpack5": "^6.5.14",
|
||||
"@storybook/manager-webpack5": "^6.5.14",
|
||||
"@storybook/react": "^6.5.14",
|
||||
"@storybook/testing-library": "^0.0.13",
|
||||
"@storybook/testing-library": "^0.2.0",
|
||||
"@testing-library/dom": "^9.0.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
@@ -106,15 +104,15 @@
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/node": "^16.11.7",
|
||||
"@types/pluralize": "^0.0.29",
|
||||
"@types/react": "18.0.33",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"@types/react": "^18.2.14",
|
||||
"@types/react-dom": "^18.2.6",
|
||||
"@types/react-table": "^7.7.12",
|
||||
"@types/testing-library__jest-dom": "^5.14.5",
|
||||
"@types/validator": "^13.7.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
||||
"@typescript-eslint/parser": "^5.43.0",
|
||||
"@vitejs/plugin-react": "^3.0.0",
|
||||
"@vitest/coverage-c8": "^0.29.0",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"@vitest/coverage-v8": "^0.32.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"babel-loader": "^8.3.0",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
@@ -130,16 +128,15 @@
|
||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||
"eslint-plugin-react": "^7.31.11",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"jsdom": "^21.0.0",
|
||||
"jsdom": "^22.0.0",
|
||||
"lint-staged": ">=13",
|
||||
"msw": "^1.0.1",
|
||||
"msw-storybook-addon": "^1.6.3",
|
||||
"next-translate-plugin": "^2.0.4",
|
||||
"node-fetch": "^3.3.0",
|
||||
"postcss": "^8.4.19",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-organize-imports": "^3.2.0",
|
||||
"prettier-plugin-tailwindcss": "^0.2.0",
|
||||
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||
"react-date-fns-hooks": "^0.9.4",
|
||||
"require-from-string": "^2.0.2",
|
||||
"snake-case": "^3.0.4",
|
||||
@@ -149,8 +146,7 @@
|
||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||
"vite": "^4.0.2",
|
||||
"vite-tsconfig-paths": "^4.0.3",
|
||||
"vitest": "^0.29.0",
|
||||
"webpack": "^5.75.0"
|
||||
"vitest": "^0.32.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
1
dashboard/public/assets/brands/bitbucket.svg
Normal file
1
dashboard/public/assets/brands/bitbucket.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#2684FF" fill-rule="evenodd" d="M3.41 4.393a.563.563 0 0 1 .434-.195l16.416.003a.562.562 0 0 1 .563.652l-2.388 14.66a.562.562 0 0 1-.563.472H6.417a.765.765 0 0 1-.748-.639L3.281 4.851a.562.562 0 0 1 .13-.458Zm6.832 10.282h3.656l.886-5.173H9.252l.99 5.173Z" clip-rule="evenodd"/><path fill="url(#a)" d="M20.063 9.502h-5.279l-.886 5.173h-3.656l-4.317 5.124a.762.762 0 0 0 .492.186h11.458a.562.562 0 0 0 .563-.473l1.625-10.01Z"/><defs><linearGradient id="a" x1="16.692" x2="10.594" y1="7.717" y2="16.375" gradientUnits="userSpaceOnUse"><stop offset=".18" stop-color="#0052CC"/><stop offset="1" stop-color="#2684FF"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 730 B |
1
dashboard/public/assets/brands/gitlab.svg
Normal file
1
dashboard/public/assets/brands/gitlab.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#E24329" fill-rule="evenodd" d="m12 19.996 3.223-9.917H8.777L12 19.995Z" clip-rule="evenodd"/><path fill="#FC6D26" fill-rule="evenodd" d="m12 19.996-3.223-9.917H4.261L12 19.996Z" clip-rule="evenodd"/><path fill="#FCA326" fill-rule="evenodd" d="m4.262 10.079-.98 3.013a.667.667 0 0 0 .243.746L12 19.996l-7.738-9.917Z" clip-rule="evenodd"/><path fill="#E24329" fill-rule="evenodd" d="M4.261 10.079h4.517L6.837 4.106a.333.333 0 0 0-.635 0l-1.94 5.973Z" clip-rule="evenodd"/><path fill="#FC6D26" fill-rule="evenodd" d="m12 19.996 3.222-9.917h4.516L12 19.996Z" clip-rule="evenodd"/><path fill="#FCA326" fill-rule="evenodd" d="m19.738 10.079.98 3.013a.667.667 0 0 1-.243.746L12 19.996l7.738-9.917Z" clip-rule="evenodd"/><path fill="#E24329" fill-rule="evenodd" d="M19.739 10.079h-4.517l1.941-5.973a.334.334 0 0 1 .635 0l1.94 5.973Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 941 B |
1
dashboard/public/assets/brands/strava.svg
Normal file
1
dashboard/public/assets/brands/strava.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#FE7203" d="M4.125 5.25c0-.621.504-1.125 1.125-1.125h13.5c.621 0 1.125.504 1.125 1.125v13.5c0 .621-.504 1.125-1.125 1.125H5.25a1.125 1.125 0 0 1-1.125-1.125V5.25Z"/><path fill="url(#a)" d="M4.125 5.25c0-.621.504-1.125 1.125-1.125h13.5c.621 0 1.125.504 1.125 1.125v13.5c0 .621-.504 1.125-1.125 1.125H5.25a1.125 1.125 0 0 1-1.125-1.125V5.25Z"/><path fill="#fff" fill-rule="evenodd" d="m10.917 12.787 2.461 4.43 2.363-4.43h-1.477l-.886 1.674-.984-1.674h-1.477Z" clip-rule="evenodd" opacity=".6"/><path fill="#fff" fill-rule="evenodd" d="m11.213 6.586 3.051 6.201H8.063l3.15-6.201Zm0 3.74 1.18 2.461h-2.46l1.28-2.46Z" clip-rule="evenodd"/><defs><linearGradient id="a" x1="12" x2="12" y1="4.125" y2="19.875" gradientUnits="userSpaceOnUse"><stop stop-color="#FB2F01" stop-opacity="0"/><stop offset="1" stop-color="#FB2F01"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 935 B |
1
dashboard/public/assets/grafana.svg
Normal file
1
dashboard/public/assets/grafana.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.3 KiB |
@@ -1,25 +1 @@
|
||||
<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 8C0 3.58172 3.58172 0 8 0H64C68.4183 0 72 3.58172 72 8V64C72 68.4183 68.4183 72 64 72H8C3.58172 72 0 68.4183 0 64V8Z" fill="#1EB4D4"/>
|
||||
<path d="M0 8C0 3.58172 3.58172 0 8 0H64C68.4183 0 72 3.58172 72 8V64C72 68.4183 68.4183 72 64 72H8C3.58172 72 0 68.4183 0 64V8Z" fill="url(#paint0_linear_1_85)" fill-opacity="0.2"/>
|
||||
<g filter="url(#filter0_d_1_85)">
|
||||
<circle cx="36" cy="39" r="16" fill="#35BCD8"/>
|
||||
</g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.3994 17.2212C52.831 21.3475 52.831 26.737 51.7362 29.3896C51.1889 30.6948 51.1467 32.2106 51.5678 33.558C52.1152 35.2001 52.4099 36.9264 52.4099 38.7791C52.4099 48.0001 44.9573 55.3685 35.7783 55.2001C27.0625 55.0738 19.6941 47.6633 19.4836 38.9896C19.4415 37.0948 19.7362 35.2001 20.3257 33.5159C20.7889 32.1685 20.7889 30.6948 20.2415 29.3896C19.1889 26.7791 19.1467 21.3896 20.5783 17.1791C20.9152 16.4212 22.0941 16.6738 22.0941 17.4738V17.7685C22.3467 21.7685 23.8625 24.1685 26.052 24.9685C26.3889 25.137 26.8099 25.0948 27.1467 24.8843C29.7152 23.3264 32.7046 22.358 35.9467 22.358C39.1889 22.358 42.2204 23.2843 44.7467 24.8843C45.1257 25.137 45.6731 25.137 46.0099 24.9685C48.1573 23.9159 49.5889 21.7685 49.8415 17.8106V17.5159C49.8836 16.7159 51.0204 16.4633 51.3994 17.2212ZM36.1994 26.2738C29.1257 26.1054 23.3152 31.9159 23.4836 39.0317C23.5678 45.7264 29.0836 51.158 35.7362 51.3264C42.852 51.4106 48.6204 45.6422 48.4941 38.5685C48.3678 31.8738 42.8941 26.358 36.1994 26.2738ZM34.9362 32.8844L37.8836 37.4318L40.7468 41.9792C40.9152 42.2318 40.9994 42.5265 40.9994 42.8213C40.9994 43.3686 40.7047 43.8739 40.2415 44.1686C39.6941 44.5055 38.9783 44.5055 38.431 44.1265C38.2204 44.0002 38.052 43.8739 37.9678 43.6634L36.3257 41.3055C36.1994 41.0528 35.9047 41.0528 35.6941 41.2634L33.3783 43.9581C33.0836 44.2528 32.7047 44.4634 32.2415 44.4634C31.8626 44.4634 31.4415 44.3371 31.1468 44.0844C30.5152 43.495 30.4731 42.4844 31.0626 41.8528L34.1783 38.4423C34.3047 38.2318 34.3889 37.9371 34.2204 37.6844L32.2415 34.5686C32.0731 34.316 31.9889 34.0213 31.9889 33.7265C31.9889 33.1792 32.2836 32.6739 32.7468 32.3792C33.5047 31.916 34.4731 32.1265 34.9362 32.8844Z" fill="white"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_1_85" x="17" y="23" width="38" height="41" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feMorphology radius="4" operator="erode" in="SourceAlpha" result="effect1_dropShadow_1_85"/>
|
||||
<feOffset dy="6"/>
|
||||
<feGaussianBlur stdDeviation="3.5"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.231373 0 0 0 0 0.278431 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_85"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_85" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_1_85" x1="0" y1="0" x2="72" y2="72" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="72" fill="none"><path fill="#1EB4D4" d="M0 8a8 8 0 0 1 8-8h56a8 8 0 0 1 8 8v56a8 8 0 0 1-8 8H8a8 8 0 0 1-8-8V8Z"/><path fill="url(#a)" fill-opacity=".2" d="M0 8a8 8 0 0 1 8-8h56a8 8 0 0 1 8 8v56a8 8 0 0 1-8 8H8a8 8 0 0 1-8-8V8Z"/><g filter="url(#b)"><circle cx="36" cy="39" r="16" fill="#35BCD8"/></g><path fill="#fff" fill-rule="evenodd" d="M51.4 17.221c1.431 4.127 1.431 9.516.336 12.169-.547 1.305-.59 2.82-.168 4.168.547 1.642.842 3.368.842 5.221 0 9.221-7.453 16.59-16.632 16.421-8.715-.126-16.084-7.537-16.294-16.21-.043-1.895.252-3.79.842-5.474.463-1.347.463-2.821-.085-4.126-1.052-2.61-1.094-8 .337-12.21.337-.759 1.516-.506 1.516.294v.294c.253 4 1.768 6.4 3.958 7.2.337.169.758.127 1.095-.084 2.568-1.558 5.558-2.526 8.8-2.526 3.242 0 6.273.926 8.8 2.526.379.253.926.253 1.263.084 2.147-1.052 3.579-3.2 3.832-7.157v-.295c.042-.8 1.178-1.053 1.557-.295Zm-15.2 9.053c-7.074-.169-12.885 5.642-12.716 12.758.084 6.694 5.6 12.126 12.252 12.294 7.116.085 12.884-5.684 12.758-12.758-.126-6.694-5.6-12.21-12.295-12.294Zm-1.264 6.61 2.948 4.548 2.863 4.547c.168.253.252.547.252.842 0 .548-.294 1.053-.758 1.348a1.662 1.662 0 0 1-1.81-.042c-.21-.127-.379-.253-.463-.464l-1.642-2.357c-.127-.253-.421-.253-.632-.043l-2.316 2.695c-.294.295-.673.505-1.136.505-.38 0-.8-.126-1.095-.379a1.59 1.59 0 0 1-.084-2.231l3.115-3.41c.127-.211.21-.506.042-.759l-1.978-3.115a1.518 1.518 0 0 1-.253-.843c0-.547.295-1.052.758-1.347.758-.463 1.726-.252 2.19.505Z" clip-rule="evenodd"/><defs><linearGradient id="a" x1="0" x2="72" y1="0" y2="72" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient><filter id="b" width="38" height="41" x="17" y="23" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feMorphology in="SourceAlpha" radius="4" result="effect1_dropShadow_1_85"/><feOffset dy="6"/><feGaussianBlur stdDeviation="3.5"/><feComposite in2="hardAlpha" operator="out"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0.231373 0 0 0 0 0.278431 0 0 0 0.25 0"/><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_1_85"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_1_85" result="shape"/></filter></defs></svg>
|
||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -1,206 +0,0 @@
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import Container from '@/components/layout/Container';
|
||||
import ProjectStatusInfo from '@/components/project/ProjectStatusInfo';
|
||||
import useProjectRedirectWhenReady from '@/hooks/common/useProjectRedirectWhenReady';
|
||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||
import { useInterval } from '@/hooks/useInterval';
|
||||
import { ApplicationStatus } from '@/types/application';
|
||||
import { Alert } from '@/ui/Alert';
|
||||
import Button from '@/ui/v2/Button';
|
||||
import Link from '@/ui/v2/Link';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import { discordAnnounce } from '@/utils/discordAnnounce';
|
||||
import { triggerToast } from '@/utils/toast';
|
||||
import {
|
||||
useInsertFeatureFlagMutation,
|
||||
useUpdateApplicationMutation,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { useUserEmail } from '@nhost/nextjs';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
/**
|
||||
* Number of minutes to wait before enabling the "Cancel Migration" button.
|
||||
*/
|
||||
const MIGRATION_CANCEL_TIMEOUT_MINUTES = 15;
|
||||
|
||||
function MigrationDialog() {
|
||||
const { closeAlertDialog } = useDialog();
|
||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||
const [countdownTimer, setCountdownTimer] = useState(-1);
|
||||
|
||||
const minutes = Math.floor(countdownTimer / 60);
|
||||
const seconds = Math.floor(countdownTimer % 60);
|
||||
|
||||
const countdownActive = countdownTimer > 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const rawTimestamp = localStorage.getItem(
|
||||
`migration-${currentApplication?.id}`,
|
||||
);
|
||||
|
||||
if (!rawTimestamp) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timestamp = new Date(rawTimestamp);
|
||||
const timeDifference =
|
||||
timestamp.getTime() +
|
||||
1000 * 60 * MIGRATION_CANCEL_TIMEOUT_MINUTES -
|
||||
Date.now();
|
||||
|
||||
if (timeDifference < 0) {
|
||||
setCountdownTimer(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setCountdownTimer(timeDifference / 1000);
|
||||
}, [currentApplication?.id]);
|
||||
|
||||
useInterval(
|
||||
() =>
|
||||
setCountdownTimer((prev) => {
|
||||
if (prev === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return prev - 1;
|
||||
}),
|
||||
1000,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (countdownTimer !== 0 || typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.removeItem(`migration-${currentApplication.id}`);
|
||||
}, [countdownTimer, currentApplication.id]);
|
||||
|
||||
const [updateApplication] = useUpdateApplicationMutation({
|
||||
refetchQueries: ['getOneUser'],
|
||||
});
|
||||
const [insertFeatureFlag] = useInsertFeatureFlagMutation();
|
||||
const userEmail = useUserEmail();
|
||||
|
||||
async function handleCancelMigration() {
|
||||
try {
|
||||
await updateApplication({
|
||||
variables: {
|
||||
appId: currentApplication.id,
|
||||
app: {
|
||||
desiredState: ApplicationStatus.Live,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await insertFeatureFlag({
|
||||
variables: {
|
||||
flag: {
|
||||
appId: currentApplication.id,
|
||||
name: 'fleetcontrol_use_rds',
|
||||
value: 'console',
|
||||
description: 'Use RDS',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
triggerToast(`${currentApplication.name} migration cancelled.`);
|
||||
} catch (e) {
|
||||
triggerToast(`Error trying to migrate ${currentApplication.name}`);
|
||||
await discordAnnounce(
|
||||
`Error trying to migrate app: ${currentApplication.subdomain} (${userEmail})`,
|
||||
);
|
||||
} finally {
|
||||
closeAlertDialog();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-flow-row gap-2 px-6">
|
||||
<Text>
|
||||
Cancelling this migration will revert your project to use the shared
|
||||
Postgres instance.
|
||||
</Text>
|
||||
|
||||
{!countdownActive && (
|
||||
<Alert severity="warning" className="px-3 text-left">
|
||||
Reach out to us at{' '}
|
||||
<Link
|
||||
underline="none"
|
||||
target="_blank"
|
||||
className="hover:underline focus:underline focus:outline-none"
|
||||
href="https://discord.com/channels/552499021260914688/1029043079946182676"
|
||||
>
|
||||
#migratedb
|
||||
</Link>{' '}
|
||||
if you think the migration should have finished by now.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="grid grid-flow-row gap-2 pb-1">
|
||||
<Button onClick={closeAlertDialog}>Continue Migration</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleCancelMigration}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
disabled={countdownActive}
|
||||
>
|
||||
{countdownActive
|
||||
? `Cancel in ${String(minutes).padStart(2, '0')}:${String(
|
||||
seconds,
|
||||
).padStart(2, '0')}`
|
||||
: 'Cancel Migration'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ApplicationMigrating() {
|
||||
const { openAlertDialog } = useDialog();
|
||||
|
||||
useProjectRedirectWhenReady({ pollInterval: 10000 });
|
||||
|
||||
return (
|
||||
<Container className="flex flex-col gap-6">
|
||||
<ProjectStatusInfo
|
||||
className="mx-auto max-w-sm"
|
||||
title="Migration in progress"
|
||||
description="Your project is being migrated to use a dedicated and more performant Postgres instance."
|
||||
imageProps={{
|
||||
src: '/assets/migrating.svg',
|
||||
alt: 'Application Migrating',
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="borderless"
|
||||
color="error"
|
||||
className="mx-auto"
|
||||
onClick={() =>
|
||||
openAlertDialog({
|
||||
title: 'Cancel Migration',
|
||||
payload: <MigrationDialog />,
|
||||
props: {
|
||||
titleProps: {
|
||||
className: 'px-6',
|
||||
},
|
||||
PaperProps: {
|
||||
className: 'py-6 px-0 max-w-sm w-full',
|
||||
},
|
||||
hidePrimaryAction: true,
|
||||
hideSecondaryAction: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
Cancel Migration
|
||||
</Button>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import Container from '@/components/layout/Container';
|
||||
import { useCheckProvisioning } from '@/hooks/useCheckProvisioning';
|
||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||
import { ApplicationStatus } from '@/types/application';
|
||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import Image from 'next/image';
|
||||
import ApplicationInfo from './ApplicationInfo';
|
||||
import { AppLoader } from './AppLoader';
|
||||
import { StagingMetadata } from './StagingMetadata';
|
||||
|
||||
export default function ApplicationProvisioning() {
|
||||
const currentApplicationState = useCheckProvisioning();
|
||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||
|
||||
return (
|
||||
<Container className="mx-auto mt-8 grid max-w-sm grid-flow-row gap-4 text-center">
|
||||
<div className="mx-auto flex w-centImage flex-col text-center">
|
||||
<Image
|
||||
src="/terminal-text.svg"
|
||||
alt="Terminal with a green dot"
|
||||
width={72}
|
||||
height={72}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{currentApplicationState.state === ApplicationStatus.Empty ? (
|
||||
<div className="grid grid-flow-row gap-1">
|
||||
<Text variant="h3" component="h1">
|
||||
Setting Up {currentApplication.name}
|
||||
</Text>
|
||||
<Text>This normally takes around 2 minutes</Text>
|
||||
<ActivityIndicator className="mx-auto" />
|
||||
</div>
|
||||
) : (
|
||||
<AppLoader startLoader date={currentApplicationState.createdAt} />
|
||||
)}
|
||||
|
||||
<StagingMetadata>
|
||||
<ApplicationInfo />
|
||||
</StagingMetadata>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import Container from '@/components/layout/Container';
|
||||
import { useCheckProvisioning } from '@/hooks/useCheckProvisioning';
|
||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||
import { ApplicationStatus } from '@/types/application';
|
||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import Image from 'next/image';
|
||||
import ApplicationInfo from './ApplicationInfo';
|
||||
import { AppLoader } from './AppLoader';
|
||||
import { StagingMetadata } from './StagingMetadata';
|
||||
|
||||
export default function ApplicationRestoring() {
|
||||
const currentApplicationState = useCheckProvisioning();
|
||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||
|
||||
return (
|
||||
<Container className="mx-auto mt-8 grid max-w-sm grid-flow-row gap-4 text-center">
|
||||
<div className="mx-auto flex w-centImage flex-col text-center">
|
||||
<Image
|
||||
src="/terminal-text.svg"
|
||||
alt="Terminal with a green dot"
|
||||
width={72}
|
||||
height={72}
|
||||
/>
|
||||
</div>
|
||||
{currentApplicationState.state === ApplicationStatus.Empty ? (
|
||||
<div className="grid grid-flow-row gap-1">
|
||||
<Text variant="h3" component="h1">
|
||||
Setting Up {currentApplication.name}
|
||||
</Text>
|
||||
|
||||
<Text>This normally takes around 2 minutes</Text>
|
||||
|
||||
<ActivityIndicator className="mx-auto" />
|
||||
</div>
|
||||
) : (
|
||||
<AppLoader
|
||||
startLoader
|
||||
restoring
|
||||
date={currentApplicationState.createdAt}
|
||||
/>
|
||||
)}
|
||||
<StagingMetadata>
|
||||
<ApplicationInfo />
|
||||
</StagingMetadata>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
import Box from '@/ui/v2/Box';
|
||||
import Button from '@/ui/v2/Button';
|
||||
import Input from '@/ui/v2/Input';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import { nhost } from '@/utils/nhost';
|
||||
import { triggerToast } from '@/utils/toast';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export function ChangePasswordModal({ close }: any) {
|
||||
const [formState, setFormState] = useState<{
|
||||
loading: boolean;
|
||||
error: null | string;
|
||||
}>({
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [newPasswordConfirm, setNewPasswordConfirm] = useState('');
|
||||
|
||||
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
setFormState({
|
||||
loading: true,
|
||||
error: null,
|
||||
});
|
||||
|
||||
if (newPassword !== newPasswordConfirm) {
|
||||
setFormState({
|
||||
loading: false,
|
||||
error: 'Passwords do not match',
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { error } = await nhost.auth.changePassword({ newPassword });
|
||||
|
||||
if (error) {
|
||||
setFormState({
|
||||
loading: false,
|
||||
error: error.message,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
triggerToast(`Your password has been updated`);
|
||||
close();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className="w-full max-w-md rounded-md px-6 py-6 text-left">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="grid grid-flow-row gap-4">
|
||||
<div className="grid grid-flow-row gap-2">
|
||||
<Text variant="h3">Choose New Password</Text>
|
||||
|
||||
<Text className="mt-2 font-normal">
|
||||
Make sure to pick a strong password with at least 8 characters.
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-flow-row gap-2">
|
||||
<Input
|
||||
type="password"
|
||||
id="newPassword"
|
||||
name="newPassword"
|
||||
label="New Password"
|
||||
placeholder="New password"
|
||||
autoFocus
|
||||
value={newPassword}
|
||||
onChange={(event) => setNewPassword(event.target.value)}
|
||||
hideEmptyHelperText
|
||||
fullWidth
|
||||
required
|
||||
/>
|
||||
|
||||
<Input
|
||||
type="password"
|
||||
id="confirmNewPassword"
|
||||
name="confirmNewPassword"
|
||||
label="Confirm New Password"
|
||||
placeholder="Confirm new password"
|
||||
value={newPasswordConfirm}
|
||||
onChange={(event) => setNewPasswordConfirm(event.target.value)}
|
||||
hideEmptyHelperText
|
||||
fullWidth
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-flow-row gap-2">
|
||||
{formState.error && (
|
||||
<Text className="w-full px-4 text-center" color="error">
|
||||
Error: {formState.error}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Button type="submit" loading={formState.loading}>
|
||||
Set New Password
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="reset"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChangePasswordModal;
|
||||
@@ -1,254 +0,0 @@
|
||||
import { BillingPaymentMethodForm } from '@/components/billing-payment-method/BillingPaymentMethodForm';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/context/UIContext';
|
||||
import {
|
||||
refetchGetApplicationPlanQuery,
|
||||
useGetAppPlanAndGlobalPlansQuery,
|
||||
useGetPaymentMethodsQuery,
|
||||
useUpdateAppMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||
import { Modal } from '@/ui/Modal';
|
||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||
import Box from '@/ui/v2/Box';
|
||||
import Button from '@/ui/v2/Button';
|
||||
import Checkbox from '@/ui/v2/Checkbox';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import { planDescriptions } from '@/utils/planDescriptions';
|
||||
import { triggerToast } from '@/utils/toast';
|
||||
import { useTheme } from '@mui/material';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
|
||||
function Plan({
|
||||
planName,
|
||||
price,
|
||||
setPlan,
|
||||
planId,
|
||||
selectedPlanId,
|
||||
currentPlan,
|
||||
}: any) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="my-4 grid w-full grid-flow-col items-center justify-between gap-2 px-1"
|
||||
onClick={setPlan}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<div className="grid grid-flow-row gap-y-0.5">
|
||||
<div className="grid grid-flow-col items-center justify-start gap-2">
|
||||
<Checkbox
|
||||
onChange={setPlan}
|
||||
checked={selectedPlanId === planId}
|
||||
aria-label={planName}
|
||||
/>
|
||||
|
||||
<Text
|
||||
variant="h3"
|
||||
component="p"
|
||||
className="self-center text-left font-medium"
|
||||
>
|
||||
{currentPlan.price > price ? 'Downgrade' : 'Upgrade'} to {planName}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Text variant="subtitle2" className="w-full max-w-[256px] text-start">
|
||||
{planDescriptions[planName]}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Text variant="h3" component="p">
|
||||
$ {price}/mo
|
||||
</Text>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChangePlanModalWithData({ app, plans, close }: any) {
|
||||
const theme = useTheme();
|
||||
const [selectedPlanId, setSelectedPlanId] = useState('');
|
||||
const { closeAlertDialog } = useDialog();
|
||||
|
||||
const { currentWorkspace, currentApplication } =
|
||||
useCurrentWorkspaceAndApplication();
|
||||
|
||||
// get workspace payment methods
|
||||
const { data } = useGetPaymentMethodsQuery({
|
||||
variables: {
|
||||
workspaceId: currentWorkspace.id,
|
||||
},
|
||||
});
|
||||
|
||||
const { openPaymentModal, closePaymentModal, paymentModal } = useUI();
|
||||
const paymentMethodAvailable = data?.paymentMethods.length > 0;
|
||||
|
||||
const currentPlan = plans.find((plan) => plan.id === app.plan.id);
|
||||
const selectedPlan = plans.find((plan) => plan.id === selectedPlanId);
|
||||
|
||||
const isDowngrade = currentPlan.price > selectedPlan?.price;
|
||||
|
||||
// graphql mutations
|
||||
const [updateApp] = useUpdateAppMutation({
|
||||
refetchQueries: [
|
||||
refetchGetApplicationPlanQuery({
|
||||
workspace: currentWorkspace.slug,
|
||||
slug: currentApplication.slug,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// function handlers
|
||||
const handleUpdateAppPlan = async () => {
|
||||
await updateApp({
|
||||
variables: {
|
||||
id: app.id,
|
||||
app: {
|
||||
planId: selectedPlan.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (isDowngrade) {
|
||||
if (close) {
|
||||
close();
|
||||
}
|
||||
|
||||
closeAlertDialog();
|
||||
}
|
||||
|
||||
triggerToast(
|
||||
`${currentApplication.name} plan changed to ${selectedPlan.name}.`,
|
||||
);
|
||||
};
|
||||
|
||||
const handleChangePlanClick = async () => {
|
||||
if (!selectedPlan) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!paymentMethodAvailable) {
|
||||
openPaymentModal();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await handleUpdateAppPlan();
|
||||
|
||||
if (close) {
|
||||
close();
|
||||
}
|
||||
|
||||
closeAlertDialog();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className="w-full max-w-xl rounded-lg p-6 text-left">
|
||||
<Modal
|
||||
showModal={paymentModal}
|
||||
close={closePaymentModal}
|
||||
dialogStyle={{ zIndex: theme.zIndex.modal + 1 }}
|
||||
>
|
||||
<BillingPaymentMethodForm
|
||||
close={closePaymentModal}
|
||||
onPaymentMethodAdded={handleUpdateAppPlan}
|
||||
workspaceId={currentWorkspace.id}
|
||||
/>
|
||||
</Modal>
|
||||
<div className="flex flex-col">
|
||||
<div className="mx-auto">
|
||||
<Image
|
||||
src="/assets/upgrade.svg"
|
||||
alt="Nhost Logo"
|
||||
width={72}
|
||||
height={72}
|
||||
/>
|
||||
</div>
|
||||
<Text variant="h3" component="h2" className="mt-2 text-center">
|
||||
Pick Your Plan
|
||||
</Text>
|
||||
<Text className="text-center">
|
||||
You're currently on the <strong>{app.plan.name}</strong> plan.
|
||||
</Text>
|
||||
|
||||
<div className="mt-5">
|
||||
{plans
|
||||
.filter((plan) => plan.id !== app.plan.id)
|
||||
.map((plan) => (
|
||||
<div className="mt-4" key={plan.id}>
|
||||
<Plan
|
||||
planName={plan.name}
|
||||
currentPlan={currentPlan}
|
||||
key={plan.id}
|
||||
planId={plan.id}
|
||||
selectedPlanId={selectedPlanId}
|
||||
price={plan.price}
|
||||
setPlan={() => setSelectedPlanId(plan.id)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-flow-row gap-2">
|
||||
<Button onClick={handleChangePlanClick} disabled={!selectedPlan}>
|
||||
{!selectedPlan && 'Change Plan'}
|
||||
{selectedPlan && isDowngrade && 'Downgrade'}
|
||||
{selectedPlan && !isDowngrade && 'Upgrade'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
if (close) {
|
||||
close();
|
||||
}
|
||||
|
||||
closeAlertDialog();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ChangePlanModalProps {
|
||||
/**
|
||||
* Function to close the modal if mounted on parent component.
|
||||
*
|
||||
* @deprecated Implement modal by using `openAlertDialog` hook instead.
|
||||
*/
|
||||
close?: () => void;
|
||||
}
|
||||
|
||||
export function ChangePlanModal({ close }: ChangePlanModalProps) {
|
||||
const {
|
||||
query: { workspaceSlug, appSlug },
|
||||
} = useRouter();
|
||||
|
||||
const { data, loading, error } = useGetAppPlanAndGlobalPlansQuery({
|
||||
variables: {
|
||||
workspaceSlug: workspaceSlug as string,
|
||||
appSlug: appSlug as string,
|
||||
},
|
||||
fetchPolicy: 'cache-first',
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator delay={500} label="Loading plans..." className="m-8" />
|
||||
);
|
||||
}
|
||||
|
||||
const { apps, plans } = data;
|
||||
const app = apps[0];
|
||||
|
||||
return <ChangePlanModalWithData app={app} plans={plans} close={close} />;
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import { LoadingScreen } from '@/components/common/LoadingScreen';
|
||||
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||
import Box from '@/ui/v2/Box';
|
||||
import Button from '@/ui/v2/Button';
|
||||
import IconButton from '@/ui/v2/IconButton';
|
||||
import ArrowSquareOutIcon from '@/ui/v2/icons/ArrowSquareOutIcon';
|
||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import generateAppServiceUrl, {
|
||||
defaultLocalBackendSlugs,
|
||||
defaultRemoteBackendSlugs,
|
||||
} from '@/utils/common/generateAppServiceUrl';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { getHasuraConsoleServiceUrl } from '@/utils/env';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface HasuraDataProps {
|
||||
close?: () => void;
|
||||
}
|
||||
|
||||
export function HasuraData({ close }: HasuraDataProps) {
|
||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||
const isPlatform = useIsPlatform();
|
||||
const projectAdminSecret = currentApplication?.config?.hasura.adminSecret;
|
||||
|
||||
if (!currentApplication?.subdomain || !projectAdminSecret) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
const hasuraUrl =
|
||||
process.env.NEXT_PUBLIC_ENV === 'dev' || !isPlatform
|
||||
? `${getHasuraConsoleServiceUrl()}`
|
||||
: generateAppServiceUrl(
|
||||
currentApplication?.subdomain,
|
||||
currentApplication?.region.awsName,
|
||||
'hasura',
|
||||
defaultLocalBackendSlugs,
|
||||
{ ...defaultRemoteBackendSlugs, hasura: '/console' },
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md px-6 py-4 text-left">
|
||||
<div className="grid grid-flow-row gap-1">
|
||||
<div className="mx-auto">
|
||||
<Image
|
||||
src="/assets/hasuramodal.svg"
|
||||
width={72}
|
||||
height={72}
|
||||
alt="Hasura"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Text variant="h3" component="h1" className="text-center">
|
||||
Open Hasura
|
||||
</Text>
|
||||
|
||||
<Text className="text-center">
|
||||
Hasura is the dashboard you'll use to edit your schema and
|
||||
permissions as well as browse data. Copy the admin secret to your
|
||||
clipboard and enter it in the next screen.
|
||||
</Text>
|
||||
|
||||
<Box className="mt-6 border-y-1">
|
||||
<div className="grid w-full grid-cols-1 place-content-between items-center py-2 sm:grid-cols-3">
|
||||
<Text className="col-span-1 text-center font-medium sm:justify-start sm:text-left">
|
||||
Admin Secret
|
||||
</Text>
|
||||
|
||||
<div className="col-span-1 grid grid-flow-col items-center justify-center gap-2 sm:col-span-2 sm:justify-end">
|
||||
<Text className="font-medium" variant="subtitle2">
|
||||
{Array(projectAdminSecret.length).fill('•').join('')}
|
||||
</Text>
|
||||
|
||||
<IconButton
|
||||
onClick={() => copy(projectAdminSecret, 'Hasura admin secret')}
|
||||
variant="borderless"
|
||||
color="secondary"
|
||||
className="min-w-0 p-1"
|
||||
aria-label="Copy admin secret"
|
||||
>
|
||||
<CopyIcon className="h-4 w-4" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div className="mt-6 grid grid-flow-row gap-2">
|
||||
<Button
|
||||
href={hasuraUrl}
|
||||
// Both `target` and `rel` are available when `href` is set. This is
|
||||
// a limitation of MUI.
|
||||
// @ts-ignore
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
endIcon={<ArrowSquareOutIcon className="h-4 w-4" />}
|
||||
>
|
||||
Open Hasura
|
||||
</Button>
|
||||
|
||||
{close && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
className="text-sm+ font-normal"
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default HasuraData;
|
||||
@@ -1,165 +0,0 @@
|
||||
import DeploymentStatusMessage from '@/components/deployments/DeploymentStatusMessage';
|
||||
import { FindOldApps } from '@/components/home';
|
||||
import type { UserData } from '@/hooks/useGetAllUserWorkspacesAndApplications';
|
||||
import type { ApplicationState } from '@/types/application';
|
||||
import { ApplicationStatus } from '@/types/application';
|
||||
import StateBadge from '@/ui/StateBadge';
|
||||
import type { DeploymentStatus } from '@/ui/StatusCircle';
|
||||
import { StatusCircle } from '@/ui/StatusCircle';
|
||||
import Divider from '@/ui/v2/Divider';
|
||||
import Link from '@/ui/v2/Link';
|
||||
import List from '@/ui/v2/List';
|
||||
import { ListItem } from '@/ui/v2/ListItem';
|
||||
import { getApplicationStatusString } from '@/utils/helpers';
|
||||
import Image from 'next/image';
|
||||
import NavLink from 'next/link';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
export function checkStatusOfTheApplication(
|
||||
stateHistory: ApplicationState[] | [],
|
||||
) {
|
||||
if (stateHistory.length === 0) {
|
||||
return ApplicationStatus.Empty;
|
||||
}
|
||||
|
||||
if (stateHistory[0].stateId === undefined) {
|
||||
return ApplicationStatus.Empty;
|
||||
}
|
||||
|
||||
return stateHistory[0].stateId;
|
||||
}
|
||||
|
||||
export function RenderWorkspacesWithApps({
|
||||
userData,
|
||||
query,
|
||||
}: {
|
||||
userData: UserData | null;
|
||||
query: string;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
{userData?.workspaces
|
||||
.filter((workspace) =>
|
||||
workspace.applications.map((app) =>
|
||||
app.name.toLowerCase().includes(query.toLowerCase()),
|
||||
),
|
||||
)
|
||||
.sort((w1, w2) =>
|
||||
// sort alphabetical order (A-Z)
|
||||
w1.name.localeCompare(w2.name),
|
||||
)
|
||||
.map((workspace) => {
|
||||
// early exit if no applications are available
|
||||
if (workspace.applications.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const workspaceProjects = workspace.applications
|
||||
.filter((app) =>
|
||||
app.name.toLowerCase().includes(query.toLowerCase()),
|
||||
)
|
||||
.sort((appA, appB) => {
|
||||
// sort apps based on either:
|
||||
// 1. When the app was recently deployed, if there is any deployments available
|
||||
// 2. When the app was created
|
||||
|
||||
const appASort =
|
||||
appA.deployments.length > 0
|
||||
? new Date(appA.deployments[0].deploymentEndedAt)
|
||||
: new Date(appA.createdAt);
|
||||
|
||||
const appBSort =
|
||||
appB.deployments.length > 0
|
||||
? new Date(appB.deployments[0].deploymentEndedAt)
|
||||
: new Date(appB.createdAt);
|
||||
|
||||
if (appASort > appBSort) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
|
||||
return (
|
||||
<div key={workspace.slug} className="my-8">
|
||||
<NavLink href={`/${workspace.slug}`} passHref>
|
||||
<Link
|
||||
href={`${workspace.slug}`}
|
||||
className="mb-1.5 block font-medium"
|
||||
underline="none"
|
||||
sx={{ color: 'text.primary' }}
|
||||
>
|
||||
{workspace.name}
|
||||
</Link>
|
||||
</NavLink>
|
||||
<List className="grid grid-flow-row border-y">
|
||||
{workspaceProjects.map((app, index) => {
|
||||
const [latestDeployment] = app.deployments;
|
||||
|
||||
return (
|
||||
<Fragment key={app.slug}>
|
||||
<ListItem.Root
|
||||
secondaryAction={
|
||||
<div className="grid grid-flow-col gap-px">
|
||||
{latestDeployment && (
|
||||
<div className="mr-2 flex self-center align-middle">
|
||||
<StatusCircle
|
||||
status={
|
||||
latestDeployment.deploymentStatus as DeploymentStatus
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<StateBadge
|
||||
state={checkStatusOfTheApplication(app.appStates)}
|
||||
desiredState={app.desiredState}
|
||||
title={getApplicationStatusString(
|
||||
checkStatusOfTheApplication(app.appStates),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<NavLink
|
||||
href={`${workspace?.slug}/${app.slug}`}
|
||||
passHref
|
||||
>
|
||||
<ListItem.Button className="rounded-none">
|
||||
<ListItem.Avatar>
|
||||
<div className="h-10 w-10 overflow-hidden rounded-lg">
|
||||
<Image
|
||||
src="/logos/new.svg"
|
||||
alt="Nhost Logo"
|
||||
width={40}
|
||||
height={40}
|
||||
/>
|
||||
</div>
|
||||
</ListItem.Avatar>
|
||||
|
||||
<ListItem.Text
|
||||
primary={app.name}
|
||||
secondary={
|
||||
<DeploymentStatusMessage
|
||||
appCreatedAt={app.createdAt}
|
||||
deployment={latestDeployment}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</ListItem.Button>
|
||||
</NavLink>
|
||||
</ListItem.Root>
|
||||
|
||||
{index < workspaceProjects.length - 1 && (
|
||||
<Divider component="li" />
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<FindOldApps />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||
import Box from '@/ui/v2/Box';
|
||||
import Button from '@/ui/v2/Button';
|
||||
import Checkbox from '@/ui/v2/Checkbox';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import { triggerToast } from '@/utils/toast';
|
||||
import { useRestoreApplicationDatabaseMutation } from '@/utils/__generated__/graphql';
|
||||
import { formatISO9075 } from 'date-fns';
|
||||
import { useState } from 'react';
|
||||
|
||||
export interface RestoreBackupModalModalProps {
|
||||
/**
|
||||
* Call this function to imperatively close the modal.
|
||||
*/
|
||||
close: any;
|
||||
/**
|
||||
* Arbitrary data passed down to the modal.
|
||||
*
|
||||
*/
|
||||
data: any;
|
||||
}
|
||||
|
||||
export function RestoreBackupModal({
|
||||
close,
|
||||
data,
|
||||
}: RestoreBackupModalModalProps) {
|
||||
const { id: backupId, createdAt } = data;
|
||||
|
||||
const [isSure, setIsSure] = useState(false);
|
||||
const [mutationIsCompleted, setMutationIsCompleted] = useState(false);
|
||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||
|
||||
const [restoreApplicationDatabase, { loading }] =
|
||||
useRestoreApplicationDatabaseMutation();
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setMutationIsCompleted(false);
|
||||
try {
|
||||
await restoreApplicationDatabase({
|
||||
variables: {
|
||||
backupId,
|
||||
appId: currentApplication.id,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
setMutationIsCompleted(false);
|
||||
triggerToast('Database backup restoration failed');
|
||||
return;
|
||||
}
|
||||
setMutationIsCompleted(true);
|
||||
triggerToast('Database backup successfully scheduled for restoration.');
|
||||
};
|
||||
|
||||
if (mutationIsCompleted) {
|
||||
return (
|
||||
<Box className="w-modal p-6 rounded-lg">
|
||||
<div className="flex flex-col">
|
||||
<Text className="text-center font-medium text-lg">
|
||||
The backup has been restored successfully.
|
||||
</Text>
|
||||
|
||||
<Button className="mt-5" onClick={close}>
|
||||
OK
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className="w-modal px-6 py-6 text-left rounded-lg">
|
||||
<div className="flex flex-col">
|
||||
<Text className="text-center text-lg font-medium">
|
||||
Restore Database Backup
|
||||
</Text>
|
||||
<Text className="mt-2 text-center font-normal">
|
||||
You current database will be deleted, and the backup created{' '}
|
||||
<span className="font-semibold">
|
||||
{formatISO9075(new Date(createdAt))}
|
||||
</span>{' '}
|
||||
will be restored.
|
||||
</Text>
|
||||
|
||||
<Box className="my-4 border-y py-2 px-2">
|
||||
<Checkbox
|
||||
checked={isSure}
|
||||
onChange={(_event, checked) => setIsSure(checked)}
|
||||
label="I'm sure I want to restore this backup."
|
||||
/>
|
||||
</Box>
|
||||
<Button onClick={handleSubmit} disabled={!isSure} loading={loading}>
|
||||
Restore
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import Status, { StatusEnum } from '@/ui/Status';
|
||||
import Box from '@/ui/v2/Box';
|
||||
import { isDevOrStaging } from '@/utils/helpers';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
export function StagingMetadata({ children }: PropsWithChildren<unknown>) {
|
||||
return (
|
||||
isDevOrStaging() && (
|
||||
<div className="mx-auto mt-10 max-w-sm">
|
||||
<Box className="mx-auto flex flex-col rounded-md border p-5 text-center">
|
||||
<Status status={StatusEnum.Deploying}>Internal info</Status>
|
||||
{children}
|
||||
</Box>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default StagingMetadata;
|
||||
@@ -1,49 +0,0 @@
|
||||
import { ChangePlanModal } from '@/components/applications/ChangePlanModal';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { Alert } from '@/ui/Alert';
|
||||
import Button from '@/ui/v2/Button';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import type { DetailedHTMLProps, HTMLProps } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export interface UnlockFeatureByUpgradingProps
|
||||
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {
|
||||
/**
|
||||
* Message to display in the alert.
|
||||
*/
|
||||
message: string;
|
||||
}
|
||||
|
||||
export function UnlockFeatureByUpgrading({
|
||||
message,
|
||||
className,
|
||||
...props
|
||||
}: UnlockFeatureByUpgradingProps) {
|
||||
const { openAlertDialog } = useDialog();
|
||||
|
||||
return (
|
||||
<div className={twMerge('flex', className)} {...props}>
|
||||
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">
|
||||
<Text className="text-left">{message}</Text>
|
||||
|
||||
<Button
|
||||
variant="borderless"
|
||||
onClick={() => {
|
||||
openAlertDialog({
|
||||
title: 'Upgrade your plan.',
|
||||
payload: <ChangePlanModal />,
|
||||
props: {
|
||||
PaperProps: { className: 'p-0 max-w-xl w-full' },
|
||||
hidePrimaryAction: true,
|
||||
hideSecondaryAction: true,
|
||||
hideTitle: true,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import Link from '@/ui/v2/Link';
|
||||
import Text from '@/ui/v2/Text';
|
||||
|
||||
export function CheckGithubConfiguration() {
|
||||
return (
|
||||
<Text className="mt-2 text-center text-xs">
|
||||
Do you miss a repository, or do you need to connect another GitHub
|
||||
account?{' '}
|
||||
<Link
|
||||
href={process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="text-xs font-medium"
|
||||
underline="hover"
|
||||
>
|
||||
Manage your GitHub configuration
|
||||
</Link>
|
||||
.
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
export default CheckGithubConfiguration;
|
||||
@@ -1,35 +0,0 @@
|
||||
import GithubIcon from '@/components/icons/GithubIcon';
|
||||
import Button from '@/ui/v2/Button';
|
||||
import ArrowSquareOutIcon from '@/ui/v2/icons/ArrowSquareOutIcon';
|
||||
import Text from '@/ui/v2/Text';
|
||||
|
||||
export default function GitHubInstallNhostApplication() {
|
||||
return (
|
||||
<div className="grid grid-flow-row justify-center gap-2 p-0.5">
|
||||
<GithubIcon className="mx-auto h-8 w-8" />
|
||||
|
||||
<div className="text-center">
|
||||
<Text variant="h3" component="h2">
|
||||
Install the Nhost GitHub Application
|
||||
</Text>
|
||||
|
||||
<Text variant="subtitle2">
|
||||
Install the Nhost application on your GitHub account and update
|
||||
permissions to automatically track repositories.
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
href={process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL}
|
||||
// Both `target` and `rel` are available when `href` is set. This is
|
||||
// a limitation of MUI.
|
||||
// @ts-ignore
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
endIcon={<ArrowSquareOutIcon className="h-4 w-4" />}
|
||||
>
|
||||
Configure the Nhost application on GitHub
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { Avatar } from '@/ui/Avatar';
|
||||
import Divider from '@/ui/v2/Divider';
|
||||
import PlusCircleIcon from '@/ui/v2/icons/PlusCircleIcon';
|
||||
import Link from '@/ui/v2/Link';
|
||||
import List from '@/ui/v2/List';
|
||||
import { ListItem } from '@/ui/v2/ListItem';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
export function GitHubNoRepositoriesAdded({
|
||||
filteredGitHubAppInstallations,
|
||||
}: any) {
|
||||
return (
|
||||
<div>
|
||||
<Text className="mt-1 text-center text-lg font-medium">
|
||||
No repositories found
|
||||
</Text>
|
||||
|
||||
<Text className="text-center text-xs">
|
||||
Check the Nhost app's settings on your GitHub account, or install
|
||||
the app on a new account.
|
||||
</Text>
|
||||
|
||||
<List className="my-2 border-y">
|
||||
{filteredGitHubAppInstallations.map((githubApp, index) => (
|
||||
<Fragment key={githubApp.id}>
|
||||
<ListItem.Root
|
||||
key={githubApp.id}
|
||||
className="grid grid-flow-col gap-2 py-2.5 justify-start items-center"
|
||||
>
|
||||
<ListItem.Avatar>
|
||||
<Avatar
|
||||
avatarUrl={githubApp.accountAvatarUrl as string}
|
||||
className="mr-1 h-5 w-5"
|
||||
/>
|
||||
</ListItem.Avatar>
|
||||
|
||||
<ListItem.Text primary={githubApp.accountLogin} />
|
||||
</ListItem.Root>
|
||||
|
||||
{index < filteredGitHubAppInstallations.length - 1 && (
|
||||
<Divider component="li" />
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<Link
|
||||
href={process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
underline="hover"
|
||||
className="grid grid-flow-col gap-1 items-center justify-start"
|
||||
>
|
||||
<PlusCircleIcon className="w-4 h-4" />
|
||||
Configure the Nhost application on GitHub.
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GitHubNoRepositoriesAdded;
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './Breadcrumbs';
|
||||
export { default } from './Breadcrumbs';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './ControlledAutocomplete';
|
||||
export { default } from './ControlledAutocomplete';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './ControlledCheckbox';
|
||||
export { default } from './ControlledCheckbox';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './ControlledSelect';
|
||||
export { default } from './ControlledSelect';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './ControlledSwitch';
|
||||
export { default } from './ControlledSwitch';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DataGrid';
|
||||
export { default } from './DataGrid';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DataGridBody';
|
||||
export { default } from './DataGridBody';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DataGridBooleanCell';
|
||||
export { default } from './DataGridBooleanCell';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DataGridDateCell';
|
||||
export { default } from './DataGridDateCell';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DataGridFrame';
|
||||
export { default } from './DataGridFrame';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DataGridHeader';
|
||||
export { default } from './DataGridHeader';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DataGridNumericCell';
|
||||
export { default } from './DataGridNumericCell';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DataGridPagination';
|
||||
export { default } from './DataGridPagination';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DataGridPreviewCell';
|
||||
export { default } from './DataGridPreviewCell';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DataGridTextCell';
|
||||
export { default } from './DataGridTextCell';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DesktopNav';
|
||||
export { default } from './DesktopNav';
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CommonDialogProps } from '@/components/ui/v2/Dialog';
|
||||
import type { DialogFormProps } from '@/types/common';
|
||||
import type { CommonDialogProps } from '@/ui/v2/Dialog';
|
||||
import type { ReactElement, ReactNode } from 'react';
|
||||
import { createContext } from 'react';
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface OpenDialogOptions {
|
||||
/**
|
||||
* Title of the dialog.
|
||||
*/
|
||||
title: ReactNode;
|
||||
title?: ReactNode;
|
||||
/**
|
||||
* Component to render inside the dialog skeleton.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
|
||||
import AlertDialog from '@/ui/v2/AlertDialog';
|
||||
import { BaseDialog } from '@/ui/v2/Dialog';
|
||||
import Drawer from '@/ui/v2/Drawer';
|
||||
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
|
||||
import { AlertDialog } from '@/components/ui/v2/AlertDialog';
|
||||
import { BaseDialog } from '@/components/ui/v2/Dialog';
|
||||
import { Drawer } from '@/components/ui/v2/Drawer';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { BaseSyntheticEvent, PropsWithChildren } from 'react';
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CommonDialogProps } from '@/ui/v2/Dialog';
|
||||
import type { CommonDialogProps } from '@/components/ui/v2/Dialog';
|
||||
import type { ReactElement, ReactNode } from 'react';
|
||||
import type { DialogConfig, OpenDialogOptions } from './DialogContext';
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './DialogContext';
|
||||
export { default as DialogContext } from './DialogContext';
|
||||
export { default as DialogProvider } from './DialogProvider';
|
||||
export { default as useDialog } from './useDialog';
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import ErrorMessage from '@/components/common/ErrorMessage';
|
||||
import type { FallbackProps } from 'react-error-boundary';
|
||||
|
||||
export default function ErrorBoundaryFallback({
|
||||
error,
|
||||
resetErrorBoundary,
|
||||
}: FallbackProps) {
|
||||
return (
|
||||
<ErrorMessage onReset={resetErrorBoundary}>{error.message}</ErrorMessage>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './ErrorBoundaryFallback';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './ErrorMessage';
|
||||
export { default } from './ErrorMessage';
|
||||
@@ -1,5 +1,11 @@
|
||||
import { FeedbackReceived } from '@/components/home/FeedbackReceived';
|
||||
import { SendFeedback } from '@/components/home/SendFeedback';
|
||||
import { Avatar } from '@/components/ui/v2/Avatar';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useInsertFeedbackOneMutation } from '@/utils/__generated__/graphql';
|
||||
import { useUserData } from '@nhost/nextjs';
|
||||
import Image from 'next/image';
|
||||
import type { DetailedHTMLProps, HTMLProps } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
@@ -7,25 +13,136 @@ import { twMerge } from 'tailwind-merge';
|
||||
export interface FeedbackFormProps
|
||||
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {}
|
||||
|
||||
// TODO: Use `react-hook-form` here instead of the custom solution
|
||||
// TODO: Use `react-hook-form` here instead of the custom form implementation
|
||||
export default function FeedbackForm({
|
||||
className,
|
||||
...props
|
||||
}: FeedbackFormProps) {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [insertFeedback, { loading }] = useInsertFeedbackOneMutation();
|
||||
const user = useUserData();
|
||||
|
||||
const [feedback, setFeedback] = useState('');
|
||||
const [feedbackSent, setFeedbackSent] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={twMerge('max-w-md py-4 px-5', className)} {...props}>
|
||||
{!feedbackSent ? (
|
||||
<SendFeedback
|
||||
setFeedbackSent={setFeedbackSent}
|
||||
feedback={feedback}
|
||||
setFeedback={setFeedback}
|
||||
function handleClose() {
|
||||
setTimeout(() => {
|
||||
setFeedbackSent(false);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
async function handleSubmit(e: React.SyntheticEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
|
||||
const feedbackWithProjectInfo = [
|
||||
currentProject && `Project ID: ${currentProject.id}`,
|
||||
typeof window !== 'undefined' && `URL: ${window.location.href}`,
|
||||
feedback,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n\n');
|
||||
|
||||
try {
|
||||
await insertFeedback({
|
||||
variables: {
|
||||
feedback: {
|
||||
feedback: feedbackWithProjectInfo,
|
||||
},
|
||||
},
|
||||
});
|
||||
setFeedbackSent(true);
|
||||
setFeedback('');
|
||||
} catch (error) {
|
||||
// TODO: Display error to user and use a logging solution
|
||||
}
|
||||
}
|
||||
|
||||
if (feedbackSent) {
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
'grid max-w-md grid-flow-row justify-center gap-4 py-4 px-5 text-center',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<Image
|
||||
src="/assets/FeedbackReceived.svg"
|
||||
alt="Light bulb with a checkmark"
|
||||
width={72}
|
||||
height={72}
|
||||
/>
|
||||
) : (
|
||||
<FeedbackReceived setFeedbackSent={setFeedbackSent} close={() => {}} />
|
||||
|
||||
<div className="grid grid-flow-row gap-2">
|
||||
<Text variant="h3" component="h2" className="text-center">
|
||||
Feedback Received
|
||||
</Text>
|
||||
|
||||
<Text>
|
||||
Thanks for sending us your thoughts! Feel free to send more feedback
|
||||
as you explore the beta, and stay tuned for updates.
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
className="mt-2 text-sm+ font-normal"
|
||||
onClick={handleClose}
|
||||
>
|
||||
Go Back
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
'grid max-w-md grid-flow-row gap-2 py-4 px-5',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<Text variant="h3" component="h2">
|
||||
Leave Feedback
|
||||
</Text>
|
||||
|
||||
<Text>
|
||||
Nhost is still in beta and not everything is in place yet, but we'd
|
||||
love to know what you think of it so far.
|
||||
</Text>
|
||||
|
||||
<form onSubmit={handleSubmit} className="grid grid-flow-row gap-2">
|
||||
<div className="grid grid-flow-col place-content-between gap-2">
|
||||
<Text className="font-medium">
|
||||
What do you think we should improve?
|
||||
</Text>
|
||||
|
||||
<Avatar
|
||||
className="h-6 w-6 rounded-full"
|
||||
alt={user?.displayName}
|
||||
src={user?.avatarUrl}
|
||||
>
|
||||
{user?.displayName}
|
||||
</Avatar>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
multiline
|
||||
value={feedback}
|
||||
onChange={(event) => setFeedback(event.target.value)}
|
||||
placeholder="Your feedback"
|
||||
rows={6}
|
||||
required
|
||||
fullWidth
|
||||
hideEmptyHelperText
|
||||
/>
|
||||
|
||||
<Button type="submit" disabled={!feedback} loading={loading}>
|
||||
Send Feedback
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './FeedbackForm';
|
||||
export { default } from './FeedbackForm';
|
||||
export { default as FeedbackForm } from './FeedbackForm';
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './Form';
|
||||
export { default } from './Form';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './FormActivityIndicator';
|
||||
export { default } from './FormActivityIndicator';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './Header';
|
||||
export { default } from './Header';
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './HighlightedText';
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NavLinkProps } from '@/components/common/NavLink';
|
||||
import NavLink from '@/components/common/NavLink';
|
||||
import type { SvgIconProps } from '@/ui/v2/icons/SvgIcon';
|
||||
import { NavLink } from '@/components/common/NavLink';
|
||||
import type { SvgIconProps } from '@/components/ui/v2/icons/SvgIcon';
|
||||
import type { ForwardedRef, PropsWithoutRef, ReactElement } from 'react';
|
||||
import { cloneElement, forwardRef, isValidElement } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './IconLink';
|
||||
export { default } from './IconLink';
|
||||
export { default as IconLink } from './IconLink';
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './InlineCode';
|
||||
export { default } from './InlineCode';
|
||||
@@ -1,19 +1,22 @@
|
||||
import { useGetWorkspaceMemberInvitesToManageQuery } from '@/generated/graphql';
|
||||
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetAllWorkspacesAndProjectsDocument,
|
||||
GetWorkspaceMemberInvitesToManageDocument,
|
||||
useGetWorkspaceMemberInvitesToManageQuery,
|
||||
} from '@/generated/graphql';
|
||||
import { useSubmitState } from '@/hooks/useSubmitState';
|
||||
import Box from '@/ui/v2/Box';
|
||||
import Button from '@/ui/v2/Button';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import { nhost } from '@/utils/nhost';
|
||||
import { triggerToast } from '@/utils/toast';
|
||||
import { updateOwnCache } from '@/utils/updateOwnCache';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { alpha } from '@mui/system';
|
||||
import { useUserData } from '@nhost/nextjs';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function InviteAnnounce() {
|
||||
export default function InviteNotification() {
|
||||
const user = useUserData();
|
||||
|
||||
const isPlatform = useIsPlatform();
|
||||
@@ -24,13 +27,18 @@ export function InviteAnnounce() {
|
||||
useSubmitState();
|
||||
|
||||
// @FIX: We probably don't want to poll every ten seconds for possible invites. (We can change later depending on how it works in production.) Maybe just on the workspace page?
|
||||
const { data, loading, error, refetch, startPolling } =
|
||||
useGetWorkspaceMemberInvitesToManageQuery({
|
||||
variables: {
|
||||
userId: user?.id,
|
||||
},
|
||||
skip: !isPlatform,
|
||||
});
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
refetch: refetchInvitations,
|
||||
startPolling,
|
||||
} = useGetWorkspaceMemberInvitesToManageQuery({
|
||||
variables: {
|
||||
userId: user?.id,
|
||||
},
|
||||
skip: !isPlatform || !user,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
startPolling(15000);
|
||||
@@ -75,9 +83,14 @@ export function InviteAnnounce() {
|
||||
});
|
||||
}
|
||||
|
||||
await updateOwnCache(client);
|
||||
await client.refetchQueries({
|
||||
include: [
|
||||
GetAllWorkspacesAndProjectsDocument,
|
||||
GetWorkspaceMemberInvitesToManageDocument,
|
||||
],
|
||||
});
|
||||
await router.push(`/${invite.workspace.slug}`);
|
||||
await refetch();
|
||||
await refetchInvitations();
|
||||
triggerToast('Workspace invite accepted');
|
||||
return setSubmitState({
|
||||
error: null,
|
||||
@@ -114,7 +127,10 @@ export function InviteAnnounce() {
|
||||
|
||||
// just refetch all data
|
||||
await client.refetchQueries({
|
||||
include: ['getOneUser', 'getWorkspaceMemberInvitesToManage'],
|
||||
include: [
|
||||
GetAllWorkspacesAndProjectsDocument,
|
||||
GetWorkspaceMemberInvitesToManageDocument,
|
||||
],
|
||||
});
|
||||
|
||||
setIgnoreState({
|
||||
@@ -182,5 +198,3 @@ export function InviteAnnounce() {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default InviteAnnounce;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as InviteNotification } from './InviteNotification';
|
||||
@@ -1,39 +0,0 @@
|
||||
import ThemeSwitcher from '@/components/common/ThemeSwitcher';
|
||||
import { Dropdown } from '@/ui/v2/Dropdown';
|
||||
import IconButton from '@/ui/v2/IconButton';
|
||||
import UserIcon from '@/ui/v2/icons/UserIcon';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import getConfig from 'next/config';
|
||||
|
||||
export default function LocalAccountMenu() {
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
|
||||
return (
|
||||
<Dropdown.Root className="justify-self-center">
|
||||
<Dropdown.Trigger hideChevron asChild>
|
||||
<IconButton
|
||||
variant="borderless"
|
||||
color="secondary"
|
||||
className="h-7 w-7 rounded-full"
|
||||
sx={{
|
||||
backgroundColor: (theme) => `${theme.palette.grey[300]} !important`,
|
||||
}}
|
||||
>
|
||||
<UserIcon className="h-4 w-4" />
|
||||
</IconButton>
|
||||
</Dropdown.Trigger>
|
||||
|
||||
<Dropdown.Content
|
||||
PaperProps={{
|
||||
className: 'mt-1 p-6 grid grid-flow-row gap-4 w-full max-w-xs',
|
||||
}}
|
||||
>
|
||||
<ThemeSwitcher label="Theme" />
|
||||
|
||||
<Text className="text-center text-xs" color="disabled">
|
||||
Dashboard Version: {publicRuntimeConfig?.version || 'n/a'}
|
||||
</Text>
|
||||
</Dropdown.Content>
|
||||
</Dropdown.Root>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './LocalAccountMenu';
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './MaintenanceAlert';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './MobileNav';
|
||||
export { default } from './MobileNav';
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { LinkProps } from '@/ui/v2/Link';
|
||||
import Link from '@/ui/v2/Link';
|
||||
import type { LinkProps } from '@/components/ui/v2/Link';
|
||||
import { Link } from '@/components/ui/v2/Link';
|
||||
import NextLink from 'next/link';
|
||||
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './NavLink';
|
||||
export { default } from './NavLink';
|
||||
export { default as NavLink } from './NavLink';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { ButtonProps } from '@/ui/v2/Button';
|
||||
import Button from '@/ui/v2/Button';
|
||||
import ChevronLeftIcon from '@/ui/v2/icons/ChevronLeftIcon';
|
||||
import ChevronRightIcon from '@/ui/v2/icons/ChevronRightIcon';
|
||||
import Input from '@/ui/v2/Input';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import type { ButtonProps } from '@/components/ui/v2/Button';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { ChevronLeftIcon } from '@/components/ui/v2/icons/ChevronLeftIcon';
|
||||
import { ChevronRightIcon } from '@/components/ui/v2/icons/ChevronRightIcon';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import type { DetailedHTMLProps, HTMLProps } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './Pagination';
|
||||
export { default } from './Pagination';
|
||||
|
||||
export { default as Pagination } from './Pagination';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './ReadOnlyToggle';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './RetryableErrorBoundary';
|
||||
export { default } from './RetryableErrorBoundary';
|
||||
@@ -1,7 +1,7 @@
|
||||
import Option from '@/ui/v2/Option';
|
||||
import type { SelectProps } from '@/ui/v2/Select';
|
||||
import Select from '@/ui/v2/Select';
|
||||
import useColorPreference from '@/ui/v2/useColorPreference';
|
||||
import { Option } from '@/components/ui/v2/Option';
|
||||
import type { SelectProps } from '@/components/ui/v2/Select';
|
||||
import { Select } from '@/components/ui/v2/Select';
|
||||
import { useColorPreference } from '@/components/ui/v2/useColorPreference';
|
||||
|
||||
export interface ThemeSwitcherProps extends SelectProps<any> {}
|
||||
|
||||
@@ -22,6 +22,7 @@ export default function ThemeSwitcher({
|
||||
onChange?.(event, value);
|
||||
}}
|
||||
slotProps={{
|
||||
...props?.slotProps,
|
||||
listbox: { className: 'min-w-0 w-full' },
|
||||
popper: {
|
||||
disablePortal: false,
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './ThemeSwitcher';
|
||||
export { default } from './ThemeSwitcher';
|
||||
export { default as ThemeSwitcher } from './ThemeSwitcher';
|
||||
|
||||
19
dashboard/src/components/common/UIProvider/UIContext.ts
Normal file
19
dashboard/src/components/common/UIProvider/UIContext.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export interface UIContextProps {
|
||||
/**
|
||||
* Determines whether or not the dashboard is in maintenance mode.
|
||||
*/
|
||||
maintenanceActive: boolean;
|
||||
/**
|
||||
* The date and time when maintenance mode will end.
|
||||
*/
|
||||
maintenanceEndDate: Date;
|
||||
}
|
||||
|
||||
const UIContext = createContext<UIContextProps>({
|
||||
maintenanceActive: false,
|
||||
maintenanceEndDate: null,
|
||||
});
|
||||
|
||||
export default UIContext;
|
||||
30
dashboard/src/components/common/UIProvider/UIProvider.tsx
Normal file
30
dashboard/src/components/common/UIProvider/UIProvider.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import type { UIContextProps } from './UIContext';
|
||||
import UIContext from './UIContext';
|
||||
|
||||
export default function UIProvider(props: PropsWithChildren<unknown>) {
|
||||
const router = useRouter();
|
||||
|
||||
const maintenanceUnlocked =
|
||||
process.env.NEXT_PUBLIC_MAINTENANCE_UNLOCK_SECRET &&
|
||||
process.env.NEXT_PUBLIC_MAINTENANCE_UNLOCK_SECRET ===
|
||||
router.query.maintenanceUnlockSecret;
|
||||
|
||||
const value: UIContextProps = useMemo(
|
||||
() => ({
|
||||
maintenanceActive: maintenanceUnlocked
|
||||
? false
|
||||
: process.env.NEXT_PUBLIC_MAINTENANCE_ACTIVE === 'true',
|
||||
maintenanceEndDate:
|
||||
process.env.NEXT_PUBLIC_MAINTENANCE_END_DATE &&
|
||||
!Number.isNaN(Date.parse(process.env.NEXT_PUBLIC_MAINTENANCE_END_DATE))
|
||||
? new Date(Date.parse(process.env.NEXT_PUBLIC_MAINTENANCE_END_DATE))
|
||||
: null,
|
||||
}),
|
||||
[maintenanceUnlocked],
|
||||
);
|
||||
|
||||
return <UIContext.Provider value={value} {...props} />;
|
||||
}
|
||||
4
dashboard/src/components/common/UIProvider/index.ts
Normal file
4
dashboard/src/components/common/UIProvider/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './UIContext';
|
||||
export { default as UIContext } from './UIContext';
|
||||
export { default as UIProvider } from './UIProvider';
|
||||
export { default as useUI } from './useUI';
|
||||
12
dashboard/src/components/common/UIProvider/useUI.ts
Normal file
12
dashboard/src/components/common/UIProvider/useUI.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { useContext } from 'react';
|
||||
import UIContext from './UIContext';
|
||||
|
||||
export default function useUI() {
|
||||
const context = useContext(UIContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useUI must be used within a UIProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import { ChangePasswordModal } from '@/components/applications/ChangePasswordModal';
|
||||
import ThemeSwitcher from '@/components/common/ThemeSwitcher';
|
||||
import { Avatar } from '@/ui/Avatar';
|
||||
import { Modal } from '@/ui/Modal';
|
||||
import Box from '@/ui/v2/Box';
|
||||
import Button from '@/ui/v2/Button';
|
||||
import { Dropdown, useDropdown } from '@/ui/v2/Dropdown';
|
||||
import PowerIcon from '@/ui/v2/icons/PowerIcon';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import { nhost } from '@/utils/nhost';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useUserData } from '@nhost/nextjs';
|
||||
import getConfig from 'next/config';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface AccountMenuContentProps {
|
||||
onChangePasswordClick: VoidFunction;
|
||||
}
|
||||
|
||||
function AccountMenuContent({
|
||||
onChangePasswordClick,
|
||||
}: AccountMenuContentProps) {
|
||||
const user = useUserData();
|
||||
const router = useRouter();
|
||||
const client = useApolloClient();
|
||||
const { handleClose } = useDropdown();
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
|
||||
return (
|
||||
<Box className="relative grid w-full grid-flow-row gap-5 p-6">
|
||||
<div className="grid grid-flow-row justify-center">
|
||||
<Avatar
|
||||
className="mx-auto mb-2 h-16 w-16 rounded-full"
|
||||
name={user?.displayName}
|
||||
avatarUrl={user?.avatarUrl}
|
||||
/>
|
||||
|
||||
<Text variant="h3" component="h2" className="text-center">
|
||||
{nhost.auth.getUser()?.displayName}
|
||||
</Text>
|
||||
|
||||
<Text className="text-center font-medium">
|
||||
{nhost.auth.getUser()?.email}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-flow-row gap-2">
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
onChangePasswordClick();
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
Change Password
|
||||
</Button>
|
||||
|
||||
<Button color="error" disabled>
|
||||
Remove Account
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={async () => {
|
||||
await nhost.auth.signOut();
|
||||
router.push('/signin');
|
||||
await client.resetStore();
|
||||
}}
|
||||
endIcon={<PowerIcon className="mr-1 h-4 w-4" />}
|
||||
>
|
||||
Sign Out
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<ThemeSwitcher label="Theme" />
|
||||
|
||||
<Text className="text-center text-xs" color="disabled">
|
||||
Dashboard Version: {publicRuntimeConfig?.version || 'n/a'}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccountMenu() {
|
||||
const user = useUserData();
|
||||
const [changePasswordModal, setChangePasswordModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (window.location.hash.search('type=passwordReset') !== -1) {
|
||||
setChangePasswordModal(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
showModal={changePasswordModal}
|
||||
close={() => setChangePasswordModal(false)}
|
||||
>
|
||||
<ChangePasswordModal close={() => setChangePasswordModal(false)} />
|
||||
</Modal>
|
||||
|
||||
<Dropdown.Root>
|
||||
<Dropdown.Trigger hideChevron className="rounded-full">
|
||||
<Avatar
|
||||
className="h-7 w-7 self-center rounded-full"
|
||||
name={user?.displayName}
|
||||
avatarUrl={user?.avatarUrl}
|
||||
/>
|
||||
</Dropdown.Trigger>
|
||||
|
||||
<Dropdown.Content PaperProps={{ className: 'mt-1 max-w-xs w-full' }}>
|
||||
<AccountMenuContent
|
||||
onChangePasswordClick={() => setChangePasswordModal(true)}
|
||||
/>
|
||||
</Dropdown.Content>
|
||||
</Dropdown.Root>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccountMenu;
|
||||
@@ -1,13 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
interface ContainerIndexApplicationsProps {
|
||||
children?: ReactNode | ReactNode[];
|
||||
}
|
||||
|
||||
export function ContainerIndexApplications({
|
||||
children,
|
||||
}: ContainerIndexApplicationsProps) {
|
||||
return <div className="flex flex-col font-display md:w-app">{children}</div>;
|
||||
}
|
||||
|
||||
export default ContainerIndexApplications;
|
||||
@@ -1,55 +0,0 @@
|
||||
import { useWorkspaceContext } from '@/context/workspace-context';
|
||||
import { useUserDataContext } from '@/context/workspace1-context';
|
||||
import Button from '@/ui/v2/Button';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import { darken } from '@mui/system';
|
||||
import Link from 'next/link';
|
||||
|
||||
export function NoApplications() {
|
||||
const { userContext } = useUserDataContext();
|
||||
const { workspaceContext } = useWorkspaceContext();
|
||||
|
||||
return (
|
||||
<div className="noapps mt-4 h-80 rounded-md text-center font-display font-normal">
|
||||
<div className="pt-12">
|
||||
<Text
|
||||
className="text-center text-2xl font-semibold"
|
||||
sx={{ color: 'common.white' }}
|
||||
>
|
||||
Welcome to Nhost!
|
||||
</Text>
|
||||
<Text className="mt-2" sx={{ color: 'common.white' }}>
|
||||
Let's set up your first backend - the Nhost way.
|
||||
</Text>
|
||||
<div className="inline-block pt-10">
|
||||
<Link href="/new" passHref>
|
||||
<Button
|
||||
sx={{
|
||||
backgroundColor: (theme) =>
|
||||
`${theme.palette.common.white} !important`,
|
||||
color: (theme) => `${theme.palette.common.black} !important`,
|
||||
'&:hover': {
|
||||
backgroundColor: (theme) =>
|
||||
`${darken(theme.palette.common.white, 0.1)} !important`,
|
||||
},
|
||||
}}
|
||||
disabled={
|
||||
!workspaceContext.id && userContext.workspaces.length === 0
|
||||
}
|
||||
>
|
||||
Create Your First Project
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="mt-9 opacity-40" sx={{ color: 'common.white' }}>
|
||||
Looking for your old projects? They're still on
|
||||
console.nhost.io during this beta.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NoApplications;
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './BaseColumnForm';
|
||||
export { default } from './BaseColumnForm';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './BaseForeignKeyForm';
|
||||
export { BaseForeignKeyForm as default } from './BaseForeignKeyForm';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './BaseRecordForm';
|
||||
export { default } from './BaseRecordForm';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './BaseTableForm';
|
||||
export { default } from './BaseTableForm';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './ColumnAutocomplete';
|
||||
export { default } from './ColumnAutocomplete';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './CreateColumnForm';
|
||||
export { default } from './CreateColumnForm';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './CreateForeignKeyForm';
|
||||
export { default } from './CreateForeignKeyForm';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './CreateRecordForm';
|
||||
export { default } from './CreateRecordForm';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './CreateTableForm';
|
||||
export { default } from './CreateTableForm';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user