Compare commits
30 Commits
fix/api-ke
...
feat/onboa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a31885e265 | ||
|
|
5c5d208fc4 | ||
|
|
cb76926041 | ||
|
|
6dfee580f7 | ||
|
|
e20104a292 | ||
|
|
dbf53717db | ||
|
|
0dc5092ebc | ||
|
|
e81f5d7fe6 | ||
|
|
f4bb7f8443 | ||
|
|
ac2d675934 | ||
|
|
edc9e1254e | ||
|
|
a713d09c46 | ||
|
|
385bc0de61 | ||
|
|
1e93d85318 | ||
|
|
abaf2badb0 | ||
|
|
603cc46b7d | ||
|
|
e9d338384a | ||
|
|
8a2a9ecef5 | ||
|
|
3a44957662 | ||
|
|
20e6c99e32 | ||
|
|
c0aae5449f | ||
|
|
596f346181 | ||
|
|
7b59e2ac07 | ||
|
|
2a9b1fd2b5 | ||
|
|
a2d4314e14 | ||
|
|
f94e4d6da2 | ||
|
|
97f4defc0e | ||
|
|
dd29dc89fd | ||
|
|
486cdf8313 | ||
|
|
67cf523ee8 |
@@ -103,6 +103,9 @@ checksums:
|
||||
tMYIu7/message: 8b1f0bb96a905646ecfad1cfdfa42168
|
||||
tMYIu7/origin/0/0: d77eb1f90f2fa86a55181629d88d237a
|
||||
tMYIu7/translation: 8b1f0bb96a905646ecfad1cfdfa42168
|
||||
UGCIzB/translation: f7b6699c16726afc48f1e1d1339b2e7e
|
||||
UGCIzB/message: f7b6699c16726afc48f1e1d1339b2e7e
|
||||
UGCIzB/origin/0/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
B3xxao/message: 9d80becc4fe65a74e0004e4fd290735d
|
||||
B3xxao/origin/0/0: 6812b79a8fdc3527586b523d4c137340
|
||||
B3xxao/translation: 9d80becc4fe65a74e0004e4fd290735d
|
||||
@@ -516,23 +519,27 @@ checksums:
|
||||
dEgA5A/message: 2e2a849c2223911717de8caa2c71bade
|
||||
dEgA5A/origin/0/0: cf5db0532c4f1cf49cebe78926c03385
|
||||
dEgA5A/origin/1/0: a50c4fbde8da6e0a77c4e0aef0b44dea
|
||||
dEgA5A/origin/2/0: 522726bb62051656dc0d165185d6b451
|
||||
dEgA5A/origin/3/0: b2059fa4b8b99cc10440da26475b234c
|
||||
dEgA5A/origin/4/0: 2a382c1cdf010f01a8e95c3e05f6df53
|
||||
dEgA5A/origin/5/0: d55f532bb5ae247e3a018978be0da463
|
||||
dEgA5A/origin/6/0: 4b4f55c033add8db69178563832227c6
|
||||
dEgA5A/origin/7/0: 68450df287fb46ea629e818732c1291a
|
||||
dEgA5A/origin/8/0: 03c00909b41be01231cd3e843c8ee44e
|
||||
dEgA5A/origin/9/0: 0627e0040ca4939c9a57349e98c51797
|
||||
dEgA5A/origin/10/0: 15792234f408027822ee71ccddde6f9b
|
||||
dEgA5A/origin/11/0: 5c60161e173f5eae9276d8e4c70b4ebb
|
||||
dEgA5A/origin/12/0: a1fde3fcce608870830f168f027727ff
|
||||
dEgA5A/origin/13/0: 83911e3eacbad4583e2b1647a784d154
|
||||
dEgA5A/origin/14/0: dbad0c8d7863cb41f5495264f82e7081
|
||||
dEgA5A/origin/2/0: 97bd206a05414e2a571020d64e166093
|
||||
dEgA5A/origin/3/0: 522726bb62051656dc0d165185d6b451
|
||||
dEgA5A/origin/4/0: b2059fa4b8b99cc10440da26475b234c
|
||||
dEgA5A/origin/5/0: 2a382c1cdf010f01a8e95c3e05f6df53
|
||||
dEgA5A/origin/6/0: d55f532bb5ae247e3a018978be0da463
|
||||
dEgA5A/origin/7/0: 4b4f55c033add8db69178563832227c6
|
||||
dEgA5A/origin/8/0: 68450df287fb46ea629e818732c1291a
|
||||
dEgA5A/origin/9/0: 03c00909b41be01231cd3e843c8ee44e
|
||||
dEgA5A/origin/10/0: 0627e0040ca4939c9a57349e98c51797
|
||||
dEgA5A/origin/11/0: 15792234f408027822ee71ccddde6f9b
|
||||
dEgA5A/origin/12/0: 5c60161e173f5eae9276d8e4c70b4ebb
|
||||
dEgA5A/origin/13/0: a1fde3fcce608870830f168f027727ff
|
||||
dEgA5A/origin/14/0: 83911e3eacbad4583e2b1647a784d154
|
||||
dEgA5A/origin/15/0: dbad0c8d7863cb41f5495264f82e7081
|
||||
dEgA5A/translation: 2e2a849c2223911717de8caa2c71bade
|
||||
kryGs%2B/message: bba0beaced7ea954ceb980f2b022ffee
|
||||
kryGs%2B/origin/0/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
kryGs%2B/translation: bba0beaced7ea954ceb980f2b022ffee
|
||||
sU2uWc/translation: 3fe52244676faf64e5266b6713d01e07
|
||||
sU2uWc/message: 3fe52244676faf64e5266b6713d01e07
|
||||
sU2uWc/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
fEY2vP/message: 91509e2f92b0b3b11330b6983139fdbf
|
||||
fEY2vP/origin/0/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
fEY2vP/origin/1/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
@@ -541,8 +548,9 @@ checksums:
|
||||
ZH7TVS/origin/0/0: 984122104bcc6d8fa6d4c8a35e7751d2
|
||||
ZH7TVS/translation: 7c34f59f4005e6cb3a6ff546ea0b96e3
|
||||
rQXTmd/message: 9387a4702b302f23f2f7c20b73a2b3dd
|
||||
rQXTmd/origin/0/0: 1b05beba6bdd908015fa8e798717f024
|
||||
rQXTmd/origin/1/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
rQXTmd/origin/0/0: fc0c18a095a68277a0f90a115942683b
|
||||
rQXTmd/origin/1/0: 1b05beba6bdd908015fa8e798717f024
|
||||
rQXTmd/origin/2/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
rQXTmd/translation: 9387a4702b302f23f2f7c20b73a2b3dd
|
||||
VhMDMg/message: a552fc5c4189ebc3e2e6018edda7d18f
|
||||
VhMDMg/origin/0/0: 627b72cd19a7154ae1c28cbd7ca0c730
|
||||
@@ -550,9 +558,9 @@ checksums:
|
||||
VhMDMg/origin/2/0: 0627e0040ca4939c9a57349e98c51797
|
||||
VhMDMg/origin/3/0: 0627e0040ca4939c9a57349e98c51797
|
||||
VhMDMg/translation: a552fc5c4189ebc3e2e6018edda7d18f
|
||||
fzGyWs/translation: 25dbbca7a83f6af119179e7a56c953d4
|
||||
fzGyWs/message: 25dbbca7a83f6af119179e7a56c953d4
|
||||
fzGyWs/origin/0/0: 627b72cd19a7154ae1c28cbd7ca0c730
|
||||
fzGyWs/translation: 25dbbca7a83f6af119179e7a56c953d4
|
||||
yD3ZSw/message: 293d49fc3c75e9c425b64bd7126e6b46
|
||||
yD3ZSw/origin/0/0: 627b72cd19a7154ae1c28cbd7ca0c730
|
||||
yD3ZSw/translation: 293d49fc3c75e9c425b64bd7126e6b46
|
||||
@@ -699,6 +707,18 @@ checksums:
|
||||
F0YmUY/message: 9def232c9163d109f0e9093684cfe75f
|
||||
F0YmUY/origin/0/0: 1b05beba6bdd908015fa8e798717f024
|
||||
F0YmUY/translation: 9def232c9163d109f0e9093684cfe75f
|
||||
0utuU6/translation: 9d7e453b24dfaf217352aeadc262074d
|
||||
0utuU6/message: 9d7e453b24dfaf217352aeadc262074d
|
||||
0utuU6/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
fDQ7hA/translation: 1d24ecbad9f2f0081ab51c7366e40506
|
||||
fDQ7hA/message: 1d24ecbad9f2f0081ab51c7366e40506
|
||||
fDQ7hA/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
1bJKhj/translation: f61803ac8da8253e4ba35f92b3af1101
|
||||
1bJKhj/message: f61803ac8da8253e4ba35f92b3af1101
|
||||
1bJKhj/origin/0/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
uvH2xS/translation: ecc2617544a6d385d18678577fa112fd
|
||||
uvH2xS/message: ecc2617544a6d385d18678577fa112fd
|
||||
uvH2xS/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
9PaIxv/message: da95932e7a1465a5d21aa3b855a46dc2
|
||||
9PaIxv/origin/0/0: c0b59968b2b234167c488a05cc38710b
|
||||
9PaIxv/translation: da95932e7a1465a5d21aa3b855a46dc2
|
||||
@@ -838,7 +858,8 @@ checksums:
|
||||
tmpW%2Bw/origin/0/0: 0ad2af981579a62b084b3d41462c5546
|
||||
tmpW%2Bw/translation: e915cd160651ad4b61a67e8da0737c23
|
||||
nabda1/message: 18811ccf76fbb7a2772d648f7c5f0ea7
|
||||
nabda1/origin/0/0: 1b05beba6bdd908015fa8e798717f024
|
||||
nabda1/origin/0/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
nabda1/origin/1/0: 1b05beba6bdd908015fa8e798717f024
|
||||
nabda1/translation: 18811ccf76fbb7a2772d648f7c5f0ea7
|
||||
njJFtc/message: a3dae0c9ddbc73677aad9812e3dffd69
|
||||
njJFtc/origin/0/0: b2059fa4b8b99cc10440da26475b234c
|
||||
@@ -919,12 +940,15 @@ checksums:
|
||||
vUr5JT/origin/0/0: 4a1be3707d14c696a16f453d132fb248
|
||||
vUr5JT/translation: 6ae8282ab8b4a01b4ce87b119e13714b
|
||||
DPfwMq/message: ffd408fa29d5bc9039ef8ea1b9b699bb
|
||||
DPfwMq/origin/0/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/1/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/2/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/0/0: 81b173297e9c022d9ebde5f7018c95f9
|
||||
DPfwMq/origin/1/0: 120e67abaa784f3adb192e7519919a81
|
||||
DPfwMq/origin/2/0: fc68b5394b9b466d5d67df855060544d
|
||||
DPfwMq/origin/3/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/4/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/5/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/6/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/7/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/8/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/translation: ffd408fa29d5bc9039ef8ea1b9b699bb
|
||||
R3FsF4/message: 142b391cca3e05f2af9617092f7358c5
|
||||
R3FsF4/origin/0/0: 3a89a46ac678e34dc8eb687b07a4ad4b
|
||||
@@ -946,6 +970,16 @@ checksums:
|
||||
zxKs%2By/message: 0b6c8ad7aba0873b7e212d5f1949ca97
|
||||
zxKs%2By/origin/0/0: db1e755dc7d73e037f425111bf7c30a6
|
||||
zxKs%2By/translation: 0b6c8ad7aba0873b7e212d5f1949ca97
|
||||
euc6Ns/translation: 27756566785c2b8463e21582c4bb619b
|
||||
euc6Ns/message: 27756566785c2b8463e21582c4bb619b
|
||||
euc6Ns/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
BTDPLo/translation: f6a33afcf5ff1489551c94867e083dca
|
||||
BTDPLo/message: f6a33afcf5ff1489551c94867e083dca
|
||||
BTDPLo/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
BTDPLo/origin/1/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
06EQqT/translation: e9f56489fb395a890ae2557200c29a36
|
||||
06EQqT/message: e9f56489fb395a890ae2557200c29a36
|
||||
06EQqT/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
ePK91l/message: eee7f39ff90b18852afc1671f21fbaa9
|
||||
ePK91l/origin/0/0: b697a20a9536be4dd8ecd143461eafc7
|
||||
ePK91l/origin/1/0: dc1a240d2d0cccb102c67ca311d4e457
|
||||
@@ -992,6 +1026,9 @@ checksums:
|
||||
IqjpYe/message: 81d41cf573a7109c376d30d905beb596
|
||||
IqjpYe/origin/0/0: 5f6c4a0c9be5098099167871ae1564af
|
||||
IqjpYe/translation: 81d41cf573a7109c376d30d905beb596
|
||||
bFREhu/translation: b2cc9a311fc28d1594252f697c7f5342
|
||||
bFREhu/message: b2cc9a311fc28d1594252f697c7f5342
|
||||
bFREhu/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
0%2BewPp/message: 785fe23c0eef0a5b60b5b2a88151de31
|
||||
0%2BewPp/origin/0/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
0%2BewPp/origin/1/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
@@ -1170,9 +1207,9 @@ checksums:
|
||||
l31EoQ/message: b4c05c9174637f4da2a41ead0d65bc01
|
||||
l31EoQ/origin/0/0: 4980d8e27a628e7cd9a70b839d844083
|
||||
l31EoQ/translation: b4c05c9174637f4da2a41ead0d65bc01
|
||||
q3il6U/translation: 79a4fffd70bfbedb6174ea4c767f4ab9
|
||||
q3il6U/message: 79a4fffd70bfbedb6174ea4c767f4ab9
|
||||
q3il6U/origin/0/0: 627b72cd19a7154ae1c28cbd7ca0c730
|
||||
q3il6U/translation: 79a4fffd70bfbedb6174ea4c767f4ab9
|
||||
"%2B3TYXa/message": 9fa1c942feb40d6350322b71561df789
|
||||
"%2B3TYXa/origin/0/0": 4980d8e27a628e7cd9a70b839d844083
|
||||
"%2B3TYXa/translation": 9fa1c942feb40d6350322b71561df789
|
||||
@@ -1426,9 +1463,10 @@ checksums:
|
||||
h7MgpO/origin/0/0: feacdaf2791aaddec128f463ffa2e807
|
||||
h7MgpO/translation: ef00d7494b69def6841620bd6554d040
|
||||
h8DugX/message: 6f15627a90002323eac018274b6922d6
|
||||
h8DugX/origin/0/0: db1e755dc7d73e037f425111bf7c30a6
|
||||
h8DugX/origin/1/0: 984122104bcc6d8fa6d4c8a35e7751d2
|
||||
h8DugX/origin/2/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
h8DugX/origin/0/0: 120e67abaa784f3adb192e7519919a81
|
||||
h8DugX/origin/1/0: db1e755dc7d73e037f425111bf7c30a6
|
||||
h8DugX/origin/2/0: 984122104bcc6d8fa6d4c8a35e7751d2
|
||||
h8DugX/origin/3/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
h8DugX/translation: 6f15627a90002323eac018274b6922d6
|
||||
AZI%2Fwb/message: e1ceda0c6cc32fb04cb9423582ad8fe4
|
||||
AZI%2Fwb/origin/0/0: c0b59968b2b234167c488a05cc38710b
|
||||
@@ -1439,9 +1477,9 @@ checksums:
|
||||
vXIe7J/message: 277fd1a41cc237a437cd1d5e4a80463b
|
||||
vXIe7J/origin/0/0: 627b72cd19a7154ae1c28cbd7ca0c730
|
||||
vXIe7J/translation: 277fd1a41cc237a437cd1d5e4a80463b
|
||||
k7rCa%2F/translation: 890b7851af05f97ac53644e4154c13b4
|
||||
k7rCa%2F/message: 890b7851af05f97ac53644e4154c13b4
|
||||
k7rCa%2F/origin/0/0: 3de48fb04f7df9bdbe399911cdb3fe86
|
||||
k7rCa%2F/translation: 890b7851af05f97ac53644e4154c13b4
|
||||
8Is1ih/message: 624592ba2ecf647a3fe60d493c1ee71a
|
||||
8Is1ih/origin/0/0: ff4a160eca5f8da4f075fd1dd6061fa6
|
||||
8Is1ih/origin/1/0: fadd5a5b6963ec5f261072f7e8d2140b
|
||||
@@ -1473,12 +1511,14 @@ checksums:
|
||||
TCt%2F8K/translation: ffd8033392a01d7b33f13afa51bccb9a
|
||||
edWbV6/message: 4a4805700b31ec65bcb3ca962bccfd1b
|
||||
edWbV6/origin/0/0: 85d0a3237fa9329fba3f0278a2cd7284
|
||||
edWbV6/origin/1/0: 1b05beba6bdd908015fa8e798717f024
|
||||
edWbV6/origin/2/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
edWbV6/origin/3/0: faa8a2576972b4eb31565d8ea6098f43
|
||||
edWbV6/origin/1/0: fc0c18a095a68277a0f90a115942683b
|
||||
edWbV6/origin/2/0: 1b05beba6bdd908015fa8e798717f024
|
||||
edWbV6/origin/3/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
edWbV6/origin/4/0: faa8a2576972b4eb31565d8ea6098f43
|
||||
edWbV6/translation: 4a4805700b31ec65bcb3ca962bccfd1b
|
||||
2BBAbc/message: 94f13e7ef909a4de9db7abaa1f9f0b61
|
||||
2BBAbc/origin/0/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
2BBAbc/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
2BBAbc/origin/1/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
2BBAbc/translation: 94f13e7ef909a4de9db7abaa1f9f0b61
|
||||
zQX5Dj/message: e925e2e6ccaf0eb4064a888aaea8d3c2
|
||||
zQX5Dj/origin/0/0: 5e445d66b146e0ca392faa35098701c4
|
||||
@@ -1516,6 +1556,10 @@ checksums:
|
||||
DbZgsJ/message: 7a1c63b75cea690b3fda6c3f203e31ff
|
||||
DbZgsJ/origin/0/0: 0ad2af981579a62b084b3d41462c5546
|
||||
DbZgsJ/translation: 7a1c63b75cea690b3fda6c3f203e31ff
|
||||
hB02vO/translation: c13aa2a155ab4aff1c8fed6650a94e1a
|
||||
hB02vO/message: c13aa2a155ab4aff1c8fed6650a94e1a
|
||||
hB02vO/origin/0/0: fc68b5394b9b466d5d67df855060544d
|
||||
hB02vO/origin/1/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
IDvohQ/message: 35d0822f65971b97774b962561b02649
|
||||
IDvohQ/origin/0/0: 1d6b5f88cdad160a67842f6b65387448
|
||||
IDvohQ/translation: 35d0822f65971b97774b962561b02649
|
||||
@@ -1523,9 +1567,9 @@ checksums:
|
||||
jl3aEJ/placeholders/0/0: 06f23520ebabfffed4999b5bcf61c9cd
|
||||
jl3aEJ/origin/0/0: 1d6b5f88cdad160a67842f6b65387448
|
||||
jl3aEJ/translation: 4c38799ff25321ea25017bf4cd8e2e4f
|
||||
agPptk/translation: 43983f2178fb28aacaef6c5f41282c4a
|
||||
agPptk/message: 43983f2178fb28aacaef6c5f41282c4a
|
||||
agPptk/origin/0/0: 3de48fb04f7df9bdbe399911cdb3fe86
|
||||
agPptk/translation: 43983f2178fb28aacaef6c5f41282c4a
|
||||
W%2FElkg/message: 1f527cd6d1ed602930bcaa303f503b51
|
||||
W%2FElkg/origin/0/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
W%2FElkg/translation: 1f527cd6d1ed602930bcaa303f503b51
|
||||
@@ -1559,6 +1603,12 @@ checksums:
|
||||
vneRvS/message: e228107df80015377112d41f4f155cb3
|
||||
vneRvS/origin/0/0: 8628ddb6f4ded25c0f17e2cbda8207d3
|
||||
vneRvS/translation: e228107df80015377112d41f4f155cb3
|
||||
51UCsN/translation: ce14fde5e972c2b5276c25ddded817c4
|
||||
51UCsN/message: ce14fde5e972c2b5276c25ddded817c4
|
||||
51UCsN/origin/0/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
J4%2BOTA/translation: 6ad27e803483208b5313660b7b5791d3
|
||||
J4%2BOTA/message: 6ad27e803483208b5313660b7b5791d3
|
||||
J4%2BOTA/origin/0/0: fd0e66cef8d46e96bcf3143a12f9b6db
|
||||
BOqTi5/message: 11edf0427766c26db541d46379dc3c16
|
||||
BOqTi5/placeholders/0/0: 9db68a4b386b9fa1fd1d5797c32be100
|
||||
BOqTi5/placeholders/1/0: b837a9e8bd06b58be95699b3ee663f43
|
||||
@@ -1873,10 +1923,16 @@ checksums:
|
||||
PZCqeW/origin/1/0: 8628ddb6f4ded25c0f17e2cbda8207d3
|
||||
PZCqeW/translation: 325dea6dd0348a27a6818db2c1340c98
|
||||
fuwKpE/message: 346a18a54564881c888b95873d03df44
|
||||
fuwKpE/origin/0/0: 1b05beba6bdd908015fa8e798717f024
|
||||
fuwKpE/origin/1/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
fuwKpE/origin/2/0: faa8a2576972b4eb31565d8ea6098f43
|
||||
fuwKpE/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
fuwKpE/origin/1/0: fd0e66cef8d46e96bcf3143a12f9b6db
|
||||
fuwKpE/origin/2/0: fc0c18a095a68277a0f90a115942683b
|
||||
fuwKpE/origin/3/0: 1b05beba6bdd908015fa8e798717f024
|
||||
fuwKpE/origin/4/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
fuwKpE/origin/5/0: faa8a2576972b4eb31565d8ea6098f43
|
||||
fuwKpE/translation: 346a18a54564881c888b95873d03df44
|
||||
nYNMZZ/translation: d5a8a1b38eaf02d02cd6ac2211b42a92
|
||||
nYNMZZ/message: d5a8a1b38eaf02d02cd6ac2211b42a92
|
||||
nYNMZZ/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
aHCEmh/message: ce27f1aeacccc542a174c4b2bce022b0
|
||||
aHCEmh/origin/0/0: 98e2302d0d8d12ce65c4140b87450673
|
||||
aHCEmh/origin/1/0: 634473ef965836871efe40875a36b6ab
|
||||
@@ -2057,6 +2113,9 @@ checksums:
|
||||
K3iLeO/message: e3bf6f95419169f460fb6918a0afc512
|
||||
K3iLeO/origin/0/0: 5e2ed4920ffd2f535ece848ff44900bd
|
||||
K3iLeO/translation: e3bf6f95419169f460fb6918a0afc512
|
||||
zYRVNp/translation: 8b36455dd5b43e56d2ab06e522c3bacc
|
||||
zYRVNp/message: 8b36455dd5b43e56d2ab06e522c3bacc
|
||||
zYRVNp/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
wgNoIs/message: eedc7cdb02de467c15dc418a066a77f2
|
||||
wgNoIs/origin/0/0: 3c57a918258af4b44c187f58d203345f
|
||||
wgNoIs/origin/1/0: 3c57a918258af4b44c187f58d203345f
|
||||
@@ -2086,7 +2145,9 @@ checksums:
|
||||
v3YoMA/origin/0/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
v3YoMA/translation: 3fff865dc00435f82896fc302ea45630
|
||||
8AbRER/message: 3cb81c4829857556f5d117c9a23f7bcc
|
||||
8AbRER/origin/0/0: 2201bf679c663a732d7f1b3c3ce3897f
|
||||
8AbRER/origin/0/0: 81b173297e9c022d9ebde5f7018c95f9
|
||||
8AbRER/origin/1/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
8AbRER/origin/2/0: 2201bf679c663a732d7f1b3c3ce3897f
|
||||
8AbRER/translation: 3cb81c4829857556f5d117c9a23f7bcc
|
||||
Eit43O/message: b5d9a1edd988f455041aee9fa7fb3d63
|
||||
Eit43O/origin/0/0: 1d6b5f88cdad160a67842f6b65387448
|
||||
@@ -2151,9 +2212,9 @@ checksums:
|
||||
zEcnBA/message: 513c3b4d9f0fda60fe079d42dcbd3882
|
||||
zEcnBA/origin/0/0: c0b59968b2b234167c488a05cc38710b
|
||||
zEcnBA/translation: 513c3b4d9f0fda60fe079d42dcbd3882
|
||||
4Revpc/translation: 3adb3ae7891c5736776df840cff2360b
|
||||
4Revpc/message: 3adb3ae7891c5736776df840cff2360b
|
||||
4Revpc/origin/0/0: 3de48fb04f7df9bdbe399911cdb3fe86
|
||||
4Revpc/translation: 3adb3ae7891c5736776df840cff2360b
|
||||
"%2B%2BrVAm/message": 2227508afb0d38a027e323207e47d4a2
|
||||
"%2B%2BrVAm/origin/0/0": eb63312c63f6c8d2a5b6520c89012123
|
||||
"%2B%2BrVAm/translation": 2227508afb0d38a027e323207e47d4a2
|
||||
@@ -2250,6 +2311,9 @@ checksums:
|
||||
C6gv54/message: 1573c77059bc92df17e661befda8d9b7
|
||||
C6gv54/origin/0/0: 0ad2af981579a62b084b3d41462c5546
|
||||
C6gv54/translation: 1573c77059bc92df17e661befda8d9b7
|
||||
nKBUeF/translation: 07e3ce10820b8ab28643e38b2ed51f64
|
||||
nKBUeF/message: 07e3ce10820b8ab28643e38b2ed51f64
|
||||
nKBUeF/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
jlB2RY/message: 67a76bf346ab4b48563269e9d941a64a
|
||||
jlB2RY/origin/0/0: 0627e0040ca4939c9a57349e98c51797
|
||||
jlB2RY/translation: 67a76bf346ab4b48563269e9d941a64a
|
||||
@@ -2322,6 +2386,9 @@ checksums:
|
||||
MHrjPM/message: 344e64395eaff6822a57d18623853e1a
|
||||
MHrjPM/origin/0/0: a50c4fbde8da6e0a77c4e0aef0b44dea
|
||||
MHrjPM/translation: 344e64395eaff6822a57d18623853e1a
|
||||
wK4OTM/translation: b2a89bfd8e13d77f626e84840602a603
|
||||
wK4OTM/message: b2a89bfd8e13d77f626e84840602a603
|
||||
wK4OTM/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
Se%2FUIp/message: d60813ea824f373462471e092d136eed
|
||||
Se%2FUIp/origin/0/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
Se%2FUIp/origin/1/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
@@ -2364,9 +2431,10 @@ checksums:
|
||||
LeM5RY/origin/0/0: 0dccc39d0debbf3e089cb6d292de762b
|
||||
LeM5RY/translation: dfeac280858332082ad34d2623a59863
|
||||
W1ewR0/message: ddcac5d14d48358b043a9487b42eec16
|
||||
W1ewR0/origin/0/0: 1b05beba6bdd908015fa8e798717f024
|
||||
W1ewR0/origin/1/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
W1ewR0/origin/2/0: faa8a2576972b4eb31565d8ea6098f43
|
||||
W1ewR0/origin/0/0: fc0c18a095a68277a0f90a115942683b
|
||||
W1ewR0/origin/1/0: 1b05beba6bdd908015fa8e798717f024
|
||||
W1ewR0/origin/2/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
W1ewR0/origin/3/0: faa8a2576972b4eb31565d8ea6098f43
|
||||
W1ewR0/translation: ddcac5d14d48358b043a9487b42eec16
|
||||
zl1b97/message: 90112ea12ec6fa42097a6e022ae048a4
|
||||
zl1b97/origin/0/0: 984122104bcc6d8fa6d4c8a35e7751d2
|
||||
@@ -2399,6 +2467,12 @@ checksums:
|
||||
2QGEbi/message: 550198b2c87f06726a843c79c1026ed9
|
||||
2QGEbi/origin/0/0: 4b4f55c033add8db69178563832227c6
|
||||
2QGEbi/translation: 550198b2c87f06726a843c79c1026ed9
|
||||
Oo4E6p/translation: af985cf72c47df76e2f2139a427d9948
|
||||
Oo4E6p/message: af985cf72c47df76e2f2139a427d9948
|
||||
Oo4E6p/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
K7k9u3/translation: 1cf9d227042f6a65adba38cb7296830d
|
||||
K7k9u3/message: 1cf9d227042f6a65adba38cb7296830d
|
||||
K7k9u3/origin/0/0: fd0e66cef8d46e96bcf3143a12f9b6db
|
||||
8yFGGF/message: 39025a0c53818438829603d213baef06
|
||||
8yFGGF/origin/0/0: 68450df287fb46ea629e818732c1291a
|
||||
8yFGGF/translation: 39025a0c53818438829603d213baef06
|
||||
|
||||
@@ -28,6 +28,7 @@ const config = {
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
/** Enables hot reloading for local packages without a build step */
|
||||
transpilePackages: [
|
||||
"@kan/api",
|
||||
@@ -65,6 +66,8 @@ const config = {
|
||||
},
|
||||
},
|
||||
},
|
||||
serverExternalPackages: ["pino"],
|
||||
|
||||
experimental: {
|
||||
// instrumentationHook: true,
|
||||
swcPlugins: [["@lingui/swc-plugin", {}]],
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useTheme } from "next-themes";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { env } from "next-runtime-env";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
TbLayoutSidebarLeftCollapse,
|
||||
@@ -43,6 +45,7 @@ export default function Dashboard({
|
||||
const { resolvedTheme } = useTheme();
|
||||
const { openModal } = useModal();
|
||||
const { availableWorkspaces, hasLoaded } = useWorkspace();
|
||||
const router = useRouter();
|
||||
|
||||
const { data: session, isPending: sessionLoading } = authClient.useSession();
|
||||
const { data: user, isLoading: userLoading } = api.user.getUser.useQuery(
|
||||
@@ -98,9 +101,13 @@ export default function Dashboard({
|
||||
|
||||
useEffect(() => {
|
||||
if (hasLoaded && availableWorkspaces.length === 0) {
|
||||
openModal("NEW_WORKSPACE", undefined, undefined, false);
|
||||
if (env("NEXT_PUBLIC_KAN_ENV") === "cloud") {
|
||||
router.push(`/onboarding/select-plan?returnUrl=${encodeURIComponent(window.location.pathname)}`);
|
||||
} else {
|
||||
openModal("NEW_WORKSPACE", undefined, undefined, false);
|
||||
}
|
||||
}
|
||||
}, [hasLoaded, availableWorkspaces.length, openModal]);
|
||||
}, [hasLoaded, availableWorkspaces.length, openModal, router]);
|
||||
|
||||
const isDarkMode = resolvedTheme === "dark";
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
</button>
|
||||
)}
|
||||
{iconRight && type !== "password" && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 leading-[0]">
|
||||
{iconRight}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function LabelForm({
|
||||
labelPublicId: entityId,
|
||||
},
|
||||
{
|
||||
enabled: isEdit && !!entityId,
|
||||
enabled: !!isEdit && entityId.length >= 12,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ export function NewWorkspaceForm() {
|
||||
body: JSON.stringify({
|
||||
slug: slug || undefined,
|
||||
workspacePublicId: values.publicId,
|
||||
cancelUrl: "/settings/workspace?upgrade=pro",
|
||||
cancelUrl: window.location.pathname + window.location.search,
|
||||
successUrl: "/boards",
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -7,15 +7,17 @@ const Toggle = ({
|
||||
label,
|
||||
disabled,
|
||||
showLabel = true,
|
||||
labelPosition = "before",
|
||||
}: {
|
||||
isChecked: boolean;
|
||||
onChange: () => void;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
showLabel?: boolean;
|
||||
labelPosition?: "before" | "after";
|
||||
}) => (
|
||||
<div className="mr-4 flex items-center justify-end">
|
||||
{showLabel && (
|
||||
<div className="flex items-center">
|
||||
{showLabel && labelPosition === "before" && (
|
||||
<span className="mr-2 text-xs text-light-900 dark:text-dark-900">
|
||||
{label}
|
||||
</span>
|
||||
@@ -38,6 +40,11 @@ const Toggle = ({
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
{showLabel && labelPosition === "after" && (
|
||||
<span className="ml-2 text-xs text-light-900 dark:text-dark-900">
|
||||
{label}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Button, Menu, Transition } from "@headlessui/react";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { env } from "next-runtime-env";
|
||||
import { Fragment, useState } from "react";
|
||||
import { HiCheck, HiMagnifyingGlass } from "react-icons/hi2";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
@@ -18,6 +20,7 @@ export default function WorkspaceMenu({
|
||||
const { workspace, isLoading, availableWorkspaces, switchWorkspace } =
|
||||
useWorkspace();
|
||||
const { openModal } = useModal();
|
||||
const router = useRouter();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const { tooltipContent: commandPaletteShortcutTooltipContent } =
|
||||
@@ -147,7 +150,11 @@ export default function WorkspaceMenu({
|
||||
<div className="border-t-[1px] border-light-600 p-1 dark:border-dark-500">
|
||||
<Menu.Item>
|
||||
<button
|
||||
onClick={() => openModal("NEW_WORKSPACE")}
|
||||
onClick={() =>
|
||||
env("NEXT_PUBLIC_KAN_ENV") === "cloud"
|
||||
? router.push(`/onboarding/select-plan?returnUrl=${encodeURIComponent(window.location.pathname)}`)
|
||||
: openModal("NEW_WORKSPACE")
|
||||
}
|
||||
className="flex w-full items-center justify-between rounded-[5px] px-3 py-2 text-left text-xs text-neutral-900 hover:bg-light-200 dark:text-dark-1000 dark:hover:bg-dark-400"
|
||||
>
|
||||
{t`Create workspace`}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} nicht gefunden"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Aktivitätsprotokolle"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Label hinzufügen / bearbeiten",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Label hinzufügen"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Mitglied hinzufügen"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Karte"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Karte dupliziert",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1661,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Karten-Link kopieren"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Checklisten kopieren",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Labels kopieren",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Link zur Karte kopieren",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Mitglieder kopieren",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1757,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Neues Label erstellen"
|
||||
},
|
||||
@@ -1766,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Neue Liste erstellen"
|
||||
},
|
||||
@@ -1985,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Karte löschen"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2171,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2226,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Morgen fällig"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Duplizieren",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Karte duplizieren",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Wird dupliziert…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2338,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "E-Mail-Sichtbarkeit"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Ende der Liste",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2894,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Legen Sie los, indem Sie eine neue Liste erstellen"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3314,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Mitglied einladen"
|
||||
@@ -3386,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3497,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3507,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Liste"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3596,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Vorlage erstellen"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Mitglieder verwalten",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3695,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "Monatliche Abrechnung"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "In andere Liste verschieben",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "In Liste verschieben",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3806,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Neue Liste"
|
||||
},
|
||||
@@ -3932,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Keine Listen"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Es wurden noch keine Listen erstellt"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -4331,8 +4453,8 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
@@ -4387,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Bitte versuchen Sie es erneut."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Position in Liste (0 = erste, leer lassen für Ende)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4814,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Sehen Sie, was unsere Nutzer über Kan sagen"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Liste auswählen",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4884,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Fälligkeitsdatum festlegen"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -5184,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Vorlage"
|
||||
},
|
||||
@@ -5279,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Das Board wurde wiederhergestellt."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "Die Karte wurde dupliziert.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5461,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Titel"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Titel (optional)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5563,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5644,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Kommentar konnte nicht gelöscht werden"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Karte konnte nicht dupliziert werden",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Karte konnte nicht verschoben werden",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5702,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Karte konnte nicht aktualisiert werden"
|
||||
@@ -5747,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Liste konnte nicht aktualisiert werden"
|
||||
@@ -6256,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6467,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -123,7 +123,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
440
|
||||
556
|
||||
]
|
||||
],
|
||||
"translation": "{0} not found"
|
||||
@@ -400,6 +400,18 @@
|
||||
],
|
||||
"translation": "Activity logs"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Add / edit label",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
50
|
||||
]
|
||||
]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -483,11 +495,11 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/card/components/LabelSelector.tsx",
|
||||
113
|
||||
114
|
||||
],
|
||||
[
|
||||
"src/views/card/components/LabelSelector.tsx",
|
||||
118
|
||||
119
|
||||
]
|
||||
],
|
||||
"translation": "Add label"
|
||||
@@ -499,7 +511,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/card/components/MemberSelector.tsx",
|
||||
134
|
||||
135
|
||||
],
|
||||
[
|
||||
"src/views/members/components/InviteMemberForm.tsx",
|
||||
@@ -1409,7 +1421,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
413
|
||||
529
|
||||
],
|
||||
[
|
||||
"src/views/card/index.tsx",
|
||||
@@ -2051,6 +2063,10 @@
|
||||
"src/components/YouTubeEmbed/EditYouTubeModal.tsx",
|
||||
176
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
267
|
||||
],
|
||||
[
|
||||
"src/views/board/components/DeleteBoardConfirmation.tsx",
|
||||
44
|
||||
@@ -2118,6 +2134,18 @@
|
||||
],
|
||||
"translation": "Card"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Card duplicated",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
67
|
||||
]
|
||||
]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -2151,6 +2179,10 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
307
|
||||
],
|
||||
[
|
||||
"src/views/card/components/Dropdown.tsx",
|
||||
44
|
||||
@@ -2782,6 +2814,54 @@
|
||||
],
|
||||
"translation": "Copy card link"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Copy checklists",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
257
|
||||
]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Copy labels",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
221
|
||||
]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Copy link to card",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
62
|
||||
]
|
||||
]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Copy members",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
239
|
||||
]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -2945,7 +3025,7 @@
|
||||
],
|
||||
[
|
||||
"src/views/card/components/LabelSelector.tsx",
|
||||
100
|
||||
101
|
||||
]
|
||||
],
|
||||
"translation": "Create new label"
|
||||
@@ -2957,11 +3037,11 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
76
|
||||
91
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
554
|
||||
670
|
||||
]
|
||||
],
|
||||
"translation": "Create new list"
|
||||
@@ -3339,6 +3419,10 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
74
|
||||
],
|
||||
[
|
||||
"src/views/card/components/Dropdown.tsx",
|
||||
76
|
||||
@@ -3663,6 +3747,18 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDueDateModal.tsx",
|
||||
36
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextLabelsModal.tsx",
|
||||
48
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextMembersModal.tsx",
|
||||
68
|
||||
],
|
||||
[
|
||||
"src/views/boards/components/TemplateBoards.tsx",
|
||||
17
|
||||
@@ -3770,6 +3866,46 @@
|
||||
],
|
||||
"translation": "Due tomorrow"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Duplicate",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
279
|
||||
]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Duplicate card",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
111
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
68
|
||||
]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Duplicating…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
279
|
||||
]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -3954,6 +4090,18 @@
|
||||
],
|
||||
"translation": "Email visibility"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "End of list",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
200
|
||||
]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -4881,7 +5029,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
539
|
||||
655
|
||||
]
|
||||
],
|
||||
"translation": "Get started by creating a new list"
|
||||
@@ -5581,7 +5729,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/card/components/MemberSelector.tsx",
|
||||
114
|
||||
115
|
||||
],
|
||||
[
|
||||
"src/views/members/components/InviteMemberForm.tsx",
|
||||
@@ -5699,6 +5847,10 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextLabelsModal.tsx",
|
||||
31
|
||||
],
|
||||
[
|
||||
"src/views/board/components/Filters.tsx",
|
||||
155
|
||||
@@ -5887,6 +6039,10 @@
|
||||
"src/views/board/components/UpdateBoardSlugButton.tsx",
|
||||
80
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
305
|
||||
],
|
||||
[
|
||||
"src/views/card/components/Dropdown.tsx",
|
||||
42
|
||||
@@ -5907,6 +6063,10 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
117
|
||||
],
|
||||
[
|
||||
"src/views/card/index.tsx",
|
||||
124
|
||||
@@ -6058,6 +6218,22 @@
|
||||
],
|
||||
"translation": "Make template"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Manage members",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMembersModal.tsx",
|
||||
51
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
38
|
||||
]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -6230,6 +6406,30 @@
|
||||
],
|
||||
"translation": "monthly billing"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Move to another list",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
44
|
||||
]
|
||||
]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Move to list",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMoveListModal.tsx",
|
||||
70
|
||||
]
|
||||
]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -6428,7 +6628,7 @@
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
503
|
||||
619
|
||||
]
|
||||
],
|
||||
"translation": "New list"
|
||||
@@ -6636,7 +6836,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
535
|
||||
651
|
||||
]
|
||||
],
|
||||
"translation": "No lists"
|
||||
@@ -6648,7 +6848,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
540
|
||||
656
|
||||
]
|
||||
],
|
||||
"translation": "No lists have been created yet"
|
||||
@@ -7312,11 +7512,11 @@
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
194
|
||||
217
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
250
|
||||
273
|
||||
],
|
||||
[
|
||||
"src/views/boards/components/ImportBoardsForm.tsx",
|
||||
@@ -7486,6 +7686,18 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
76
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextMoveListModal.tsx",
|
||||
42
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
314
|
||||
],
|
||||
[
|
||||
"src/views/card/components/Dropdown.tsx",
|
||||
51
|
||||
@@ -7501,6 +7713,18 @@
|
||||
],
|
||||
"translation": "Please try again."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Position in list (0 = first, leave empty for end)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
193
|
||||
]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -8220,6 +8444,18 @@
|
||||
],
|
||||
"translation": "See what our users are saying about Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Select a list",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
131
|
||||
]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -8337,6 +8573,14 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDueDateModal.tsx",
|
||||
20
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
56
|
||||
],
|
||||
[
|
||||
"src/views/card/components/DueDateSelector.tsx",
|
||||
121
|
||||
@@ -8831,11 +9075,11 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
413
|
||||
529
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
449
|
||||
565
|
||||
]
|
||||
],
|
||||
"translation": "Template"
|
||||
@@ -8992,6 +9236,18 @@
|
||||
],
|
||||
"translation": "The board has been unarchived."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "The card has been duplicated.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
69
|
||||
]
|
||||
]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -9280,6 +9536,18 @@
|
||||
],
|
||||
"translation": "Title"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Title (optional)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
180
|
||||
]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -9449,6 +9717,10 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
312
|
||||
],
|
||||
[
|
||||
"src/views/card/components/Dropdown.tsx",
|
||||
49
|
||||
@@ -9588,6 +9860,30 @@
|
||||
],
|
||||
"translation": "Unable to delete comment"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Unable to duplicate card",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
75
|
||||
]
|
||||
]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Unable to move card",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMoveListModal.tsx",
|
||||
41
|
||||
]
|
||||
]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -9679,7 +9975,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
249
|
||||
272
|
||||
],
|
||||
[
|
||||
"src/views/card/index.tsx",
|
||||
@@ -9755,7 +10051,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
193
|
||||
216
|
||||
],
|
||||
[
|
||||
"src/views/card/components/ListSelector.tsx",
|
||||
@@ -10639,7 +10935,7 @@
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
413
|
||||
529
|
||||
],
|
||||
[
|
||||
"src/views/boards/index.tsx",
|
||||
@@ -10987,11 +11283,11 @@
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
487
|
||||
603
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
545
|
||||
661
|
||||
],
|
||||
[
|
||||
"src/views/boards/components/BoardsList.tsx",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} no encontrado"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Registros de actividad"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Añadir / editar etiqueta",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Añadir etiqueta"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Añadir miembro"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Tarjeta"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Tarjeta duplicada",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1661,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Copiar enlace de la tarjeta"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Copiar listas de verificación",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Copiar etiquetas",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Copiar enlace a la tarjeta",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Copiar miembros",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1757,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Crear nueva etiqueta"
|
||||
},
|
||||
@@ -1766,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Crear nueva lista"
|
||||
},
|
||||
@@ -1985,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Eliminar tarjeta"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2171,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2226,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Vence mañana"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Duplicar",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Duplicar tarjeta",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Duplicando…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2338,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "Visibilidad del email"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Fin de la lista",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2894,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Comienza creando una nueva lista"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3314,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Invitar miembro"
|
||||
@@ -3386,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3497,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3507,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Lista"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3596,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Crear plantilla"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Gestionar miembros",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3695,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "facturación mensual"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Mover a otra lista",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Mover a lista",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3806,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Nueva lista"
|
||||
},
|
||||
@@ -3932,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Sin listas"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Aún no se han creado listas"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -4331,8 +4453,8 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
@@ -4387,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Por favor, inténtalo de nuevo."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Posición en la lista (0 = primero, dejar vacío para el final)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4814,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Mira lo que nuestros usuarios dicen sobre Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Seleccionar una lista",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4884,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Establecer fecha de vencimiento"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -5184,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Plantilla"
|
||||
},
|
||||
@@ -5279,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "El tablero ha sido desarchivado."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "La tarjeta ha sido duplicada.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5461,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Título"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Título (opcional)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5563,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5644,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "No se pudo eliminar el comentario"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "No se puede duplicar la tarjeta",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "No se puede mover la tarjeta",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5702,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "No se pudo actualizar la tarjeta"
|
||||
@@ -5747,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "No se pudo actualizar la lista"
|
||||
@@ -6256,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6467,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} introuvable"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Journaux d'activité"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Ajouter / modifier l'étiquette",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Ajouter une étiquette"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Ajouter un membre"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Carte"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Carte dupliquée",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1661,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Copier le lien de la carte"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Copier les listes de contrôle",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Copier les étiquettes",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Copier le lien vers la carte",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Copier les membres",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1757,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Créer une nouvelle étiquette"
|
||||
},
|
||||
@@ -1766,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Créer une nouvelle liste"
|
||||
},
|
||||
@@ -1985,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Supprimer la carte"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2171,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2226,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Échéance demain"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Dupliquer",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Dupliquer la carte",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Duplication en cours…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2338,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "Visibilité de l'e-mail"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Fin de la liste",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2894,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Commencez en créant une nouvelle liste"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3314,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Inviter un membre"
|
||||
@@ -3386,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3497,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3507,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Liste"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3596,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Créer un modèle"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Gérer les membres",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3695,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "facturation mensuelle"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Déplacer vers une autre liste",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Déplacer vers la liste",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3806,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Nouvelle liste"
|
||||
},
|
||||
@@ -3932,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Aucune liste"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Aucune liste n'a encore été créée"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -4331,8 +4453,8 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
@@ -4387,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Veuillez réessayer."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Position dans la liste (0 = premier, laisser vide pour la fin)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4814,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Découvrez ce que nos utilisateurs disent de Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Sélectionner une liste",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4884,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Définir la date d'échéance"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -5184,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Modèle"
|
||||
},
|
||||
@@ -5279,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Le tableau a été désarchivé."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "La carte a été dupliquée.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5461,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Titre"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Titre (facultatif)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5563,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5644,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Impossible de supprimer le commentaire"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Impossible de dupliquer la carte",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Impossible de déplacer la carte",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5702,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Impossible de mettre à jour la carte"
|
||||
@@ -5747,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Impossible de mettre à jour la liste"
|
||||
@@ -6256,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6467,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} non trovato"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Registri attività"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Aggiungi / modifica etichetta",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Aggiungi etichetta"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Aggiungi membro"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Card"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Scheda duplicata",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1661,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Copia link card"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Copia checklist",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Copia etichette",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Copia link alla scheda",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Copia membri",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1757,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Crea nuova etichetta"
|
||||
},
|
||||
@@ -1766,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Crea nuova lista"
|
||||
},
|
||||
@@ -1985,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Elimina card"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2171,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2226,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Scadenza domani"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Duplica",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Duplica scheda",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Duplicazione in corso…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2338,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "Visibilità email"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Fine dell'elenco",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2894,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Inizia creando una nuova lista"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3314,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Invita membro"
|
||||
@@ -3386,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3497,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3507,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Lista"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3596,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Crea template"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Gestisci membri",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3695,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "fatturazione mensile"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Sposta in un altro elenco",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Sposta nell'elenco",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3806,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Nuova lista"
|
||||
},
|
||||
@@ -3932,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Nessuna lista"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Nessuna lista è stata ancora creata"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -4331,8 +4453,8 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
@@ -4387,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Riprova."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Posizione nell'elenco (0 = primo, lascia vuoto per la fine)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4814,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Scopri cosa dicono i nostri utenti su Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Seleziona un elenco",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4884,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Imposta scadenza"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -5184,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Template"
|
||||
},
|
||||
@@ -5279,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "La bacheca è stata ripristinata."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "La scheda è stata duplicata.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5461,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Titolo"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Titolo (facoltativo)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5563,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5644,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Impossibile eliminare il commento"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Impossibile duplicare la scheda",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Impossibile spostare la scheda",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5702,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Impossibile aggiornare la card"
|
||||
@@ -5747,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Impossibile aggiornare la lista"
|
||||
@@ -6256,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6467,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} niet gevonden"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Activiteitenlogboeken"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Label toevoegen / bewerken",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Label toevoegen"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Lid toevoegen"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Kaart"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Kaart gedupliceerd",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1661,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Kopieer kaartlink"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Checklists kopiëren",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Labels kopiëren",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Link naar kaart kopiëren",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Leden kopiëren",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1757,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Maak nieuw label"
|
||||
},
|
||||
@@ -1766,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Maak nieuwe lijst"
|
||||
},
|
||||
@@ -1985,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Kaart verwijderen"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2171,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2226,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Verloopt morgen"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Dupliceren",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Kaart dupliceren",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Dupliceren…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2338,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "E-mailzichtbaarheid"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Einde van lijst",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2894,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Ga aan de slag door een nieuwe lijst te maken"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3314,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Lid uitnodigen"
|
||||
@@ -3386,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3497,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3507,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Lijst"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3596,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Sjabloon maken"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Leden beheren",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3695,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "maandelijkse facturering"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Verplaats naar andere lijst",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Verplaats naar lijst",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3806,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Nieuwe lijst"
|
||||
},
|
||||
@@ -3932,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Geen lijsten"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Er zijn nog geen lijsten aangemaakt"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -4331,8 +4453,8 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
@@ -4387,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Probeer het opnieuw."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Positie in lijst (0 = eerste, leeg laten voor het einde)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4814,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Bekijk wat onze gebruikers over Kan zeggen"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Selecteer een lijst",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4884,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Vervaldatum instellen"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -5184,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Sjabloon"
|
||||
},
|
||||
@@ -5279,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Het board is gedearchiveerd."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "De kaart is gedupliceerd.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5461,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Titel"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Titel (optioneel)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5563,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5644,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Kan opmerking niet verwijderen"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Kan kaart niet dupliceren",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Kan kaart niet verplaatsen",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5702,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Kan kaart niet bijwerken"
|
||||
@@ -5747,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Kan lijst niet bijwerken"
|
||||
@@ -6256,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6467,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} nie znaleziono"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Dzienniki aktywności"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Dodaj / edytuj etykietę",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Dodaj etykietę"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Dodaj członka"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Karta"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Karta zduplikowana",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1661,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Kopiuj link do karty"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Kopiuj listy kontrolne",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Kopiuj etykiety",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Kopiuj link do karty",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Kopiuj członków",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1757,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Utwórz nową etykietę"
|
||||
},
|
||||
@@ -1766,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Utwórz nową listę"
|
||||
},
|
||||
@@ -1985,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Usuń kartę"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2171,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2226,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Termin jutro"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Duplikuj",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Duplikuj kartę",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Duplikowanie…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2338,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "Widoczność e-maila"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Koniec listy",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2894,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Rozpocznij, tworząc nową listę"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3314,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Zaproś członka"
|
||||
@@ -3386,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3497,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3507,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Lista"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3596,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Utwórz szablon"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Zarządzaj członkami",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3695,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "rozliczenie miesięczne"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Przenieś do innej listy",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Przenieś do listy",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3806,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Nowa lista"
|
||||
},
|
||||
@@ -3932,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Brak list"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Nie utworzono jeszcze żadnych list"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -4331,8 +4453,8 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
@@ -4387,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Spróbuj ponownie."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Pozycja na liście (0 = pierwsza, puste pole = koniec)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4814,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Zobacz, co nasi użytkownicy mówią o Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Wybierz listę",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4884,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Ustaw termin"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -5184,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Szablon"
|
||||
},
|
||||
@@ -5279,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Tablica została przywrócona z archiwum."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "Karta została zduplikowana.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5461,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Tytuł"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Tytuł (opcjonalnie)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5563,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5644,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Nie można usunąć komentarza"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Nie można zduplikować karty",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Nie można przenieść karty",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5702,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Nie można zaktualizować karty"
|
||||
@@ -5747,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Nie można zaktualizować listy"
|
||||
@@ -6256,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6467,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} não encontrado"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Registros de atividades"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Adicionar / editar etiqueta",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Adicionar etiqueta"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Adicionar membro"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Cartão"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Cartão duplicado",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1661,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Copiar link do cartão"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Copiar checklists",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Copiar etiquetas",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Copiar link do cartão",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Copiar membros",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1757,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Criar nova etiqueta"
|
||||
},
|
||||
@@ -1766,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Criar nova lista"
|
||||
},
|
||||
@@ -1985,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Excluir cartão"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2171,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2226,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Vence amanhã"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Duplicar",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Duplicar cartão",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Duplicando…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2338,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "Visibilidade do email"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Fim da lista",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2894,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Comece criando uma nova lista"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3314,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Convidar membro"
|
||||
@@ -3386,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3497,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3507,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Lista"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3596,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Criar modelo"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Gerenciar membros",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3695,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "cobrança mensal"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Mover para outra lista",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Mover para lista",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3806,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Nova lista"
|
||||
},
|
||||
@@ -3932,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Nenhuma lista"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Nenhuma lista foi criada ainda"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -4331,8 +4453,8 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
@@ -4387,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Por favor, tente novamente."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Posição na lista (0 = primeira, deixe vazio para o final)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4814,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Veja o que nossos usuários estão dizendo sobre o Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Selecione uma lista",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4884,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Definir data de vencimento"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -5184,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Modelo"
|
||||
},
|
||||
@@ -5279,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "O quadro foi desarquivado."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "O cartão foi duplicado.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5461,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Título"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Título (opcional)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5563,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5644,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Não foi possível excluir o comentário"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Não foi possível duplicar o cartão",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Não foi possível mover o cartão",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5702,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Não foi possível atualizar o cartão"
|
||||
@@ -5747,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Não foi possível atualizar a lista"
|
||||
@@ -6256,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6467,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} не найдено"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Журналы активности"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Добавить / изменить метку",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Добавить метку"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Добавить участника"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Карточка"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Карточка продублирована",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1661,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Скопировать ссылку на карточку"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Копировать чек-листы",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Копировать метки",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Копировать ссылку на карточку",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Копировать участников",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1757,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Создать новую метку"
|
||||
},
|
||||
@@ -1766,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Создать новый список"
|
||||
},
|
||||
@@ -1985,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Удалить карточку"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2171,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2226,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Срок — завтра"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Дублировать",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Дублировать карточку",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Дублирование…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2338,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "Видимость email"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Конец списка",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2894,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Начните с создания нового списка"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3314,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Пригласить участника"
|
||||
@@ -3386,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3497,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3507,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Список"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3596,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Создать шаблон"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Управление участниками",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3695,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "ежемесячная оплата"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Переместить в другой список",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Переместить в список",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3806,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Новый список"
|
||||
},
|
||||
@@ -3932,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Нет списков"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Списки ещё не созданы"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -4331,8 +4453,8 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
@@ -4387,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Пожалуйста, попробуйте ещё раз."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Позиция в списке (0 = первая, оставьте пустым для конца)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4814,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Посмотрите, что наши пользователи говорят о Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Выберите список",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4884,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Установить срок"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -5184,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Шаблон"
|
||||
},
|
||||
@@ -5279,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Доска была восстановлена из архива."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "Карточка была продублирована.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5461,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Заголовок"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Название (необязательно)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5563,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5644,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Не удалось удалить комментарий"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Невозможно дублировать карточку",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Невозможно переместить карточку",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5702,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Не удалось обновить карточку"
|
||||
@@ -5747,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Не удалось обновить список"
|
||||
@@ -6256,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6467,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,12 +1,13 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
|
||||
import { env } from "~/env";
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "GET") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
}
|
||||
@@ -32,7 +33,9 @@ export default withRateLimit(
|
||||
try {
|
||||
allowedHost = new URL(s3Endpoint).hostname.toLowerCase();
|
||||
} catch {
|
||||
return res.status(500).json({ message: "Storage endpoint misconfigured" });
|
||||
return res
|
||||
.status(500)
|
||||
.json({ message: "Storage endpoint misconfigured" });
|
||||
}
|
||||
|
||||
if (hostname !== allowedHost && !hostname.endsWith(`.${allowedHost}`)) {
|
||||
@@ -66,10 +69,7 @@ export default withRateLimit(
|
||||
const buffer = await upstream.arrayBuffer();
|
||||
return res.send(Buffer.from(buffer));
|
||||
} catch (error) {
|
||||
console.error("Error downloading attachment:", error);
|
||||
return res
|
||||
.status(500)
|
||||
.json({ message: "Failed to download attachment" });
|
||||
return res.status(500).json({ message: "Failed to download attachment" });
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "GET") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("https://formbricks.com/api/oss-friends");
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch from Formbricks");
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "GET") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
return res.status(200).json(data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching OSS friends:", error);
|
||||
return res.status(500).json({ message: "Failed to fetch OSS friends" });
|
||||
}
|
||||
},
|
||||
try {
|
||||
const response = await fetch("https://formbricks.com/api/oss-friends");
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch from Formbricks");
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
return res.status(200).json(data);
|
||||
} catch (error) {
|
||||
return res.status(500).json({ message: "Failed to fetch OSS friends" });
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -2,34 +2,34 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { env } from "next-runtime-env";
|
||||
|
||||
import { createNextApiContext } from "@kan/api/trpc";
|
||||
import { createStripeClient } from "@kan/stripe";
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
import { createStripeClient } from "@kan/stripe";
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const stripe = createStripeClient();
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const stripe = createStripeClient();
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
try {
|
||||
const { user } = await createNextApiContext(req);
|
||||
|
||||
if (!user?.stripeCustomerId) {
|
||||
return res.status(404).json({ error: "No billing account found" });
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
const session = await stripe.billingPortal.sessions.create({
|
||||
customer: user.stripeCustomerId,
|
||||
return_url: `${env("NEXT_PUBLIC_BASE_URL")}/settings`,
|
||||
});
|
||||
try {
|
||||
const { user } = await createNextApiContext(req);
|
||||
|
||||
return res.status(200).json({ url: session.url });
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
return res.status(500).json({ error: "Error creating portal session" });
|
||||
}
|
||||
},
|
||||
if (!user?.stripeCustomerId) {
|
||||
return res.status(404).json({ error: "No billing account found" });
|
||||
}
|
||||
|
||||
const session = await stripe.billingPortal.sessions.create({
|
||||
customer: user.stripeCustomerId,
|
||||
return_url: `${env("NEXT_PUBLIC_BASE_URL")}/settings`,
|
||||
});
|
||||
|
||||
return res.status(200).json({ url: session.url });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: "Error creating portal session" });
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -3,10 +3,13 @@ import { env } from "next-runtime-env";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNextApiContext } from "@kan/api/trpc";
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { assertPermission } from "@kan/api/utils/permissions";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
import * as subscriptionRepo from "@kan/db/repository/subscription.repo";
|
||||
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
|
||||
import { generateUID } from "@kan/shared/utils";
|
||||
import { createStripeClient } from "@kan/stripe";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
|
||||
const workspaceSlugSchema = z
|
||||
.string()
|
||||
@@ -17,103 +20,135 @@ const workspaceSlugSchema = z
|
||||
interface CheckoutSessionRequest {
|
||||
successUrl: string;
|
||||
cancelUrl: string;
|
||||
slug: string;
|
||||
workspacePublicId: string;
|
||||
stripeCustomerId: string;
|
||||
billing?: string;
|
||||
workspacePublicId?: string;
|
||||
slug?: string;
|
||||
workspaceName?: string;
|
||||
workspaceDescription?: string;
|
||||
workspaceSlug?: string;
|
||||
plan?: string;
|
||||
}
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const stripe = createStripeClient();
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const stripe = createStripeClient();
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
try {
|
||||
const { user, db } = await createNextApiContext(req);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
const body = req.body as CheckoutSessionRequest;
|
||||
const { successUrl, cancelUrl, slug, workspacePublicId } = body;
|
||||
try {
|
||||
const { user, db } = await createNextApiContext(req);
|
||||
|
||||
if (!successUrl || !cancelUrl || !workspacePublicId) {
|
||||
return res.status(400).json({ error: "Missing required fields" });
|
||||
}
|
||||
|
||||
if (slug) {
|
||||
const slugResult = workspaceSlugSchema.safeParse(slug);
|
||||
|
||||
if (!slugResult.success) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Invalid workspace slug" }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
);
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
}
|
||||
|
||||
const workspace = await workspaceRepo.getAllByUserId(db, user.id);
|
||||
|
||||
const isMemberOfWorkspace = workspace.some(
|
||||
({ workspace }) => workspace.publicId === body.workspacePublicId,
|
||||
);
|
||||
|
||||
if (!isMemberOfWorkspace) {
|
||||
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
||||
status: 403,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
const subscription = await subscriptionRepo.create(db, {
|
||||
plan: "pro",
|
||||
referenceId: workspacePublicId,
|
||||
userId: user.id,
|
||||
stripeCustomerId: user.stripeCustomerId ?? "",
|
||||
status: "incomplete",
|
||||
});
|
||||
|
||||
const subscriptionId = subscription?.id;
|
||||
|
||||
if (!subscriptionId) {
|
||||
return res.status(500).json({ error: "Error creating subscription" });
|
||||
}
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
mode: "subscription",
|
||||
payment_method_collection: "always",
|
||||
line_items: [
|
||||
{
|
||||
price: process.env.STRIPE_PRO_PLAN_MONTHLY_PRICE_ID,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
subscription_data: {
|
||||
trial_period_days: 14,
|
||||
},
|
||||
success_url: `${env("NEXT_PUBLIC_BASE_URL")}${successUrl}`,
|
||||
cancel_url: `${env("NEXT_PUBLIC_BASE_URL")}${cancelUrl}`,
|
||||
client_reference_id: workspacePublicId,
|
||||
customer: user.stripeCustomerId ?? undefined,
|
||||
metadata: {
|
||||
...(slug && { workspaceSlug: slug }),
|
||||
const body = req.body as CheckoutSessionRequest;
|
||||
const {
|
||||
successUrl,
|
||||
cancelUrl,
|
||||
billing,
|
||||
workspacePublicId,
|
||||
userId: user.id,
|
||||
subscriptionId,
|
||||
},
|
||||
});
|
||||
slug,
|
||||
workspaceName,
|
||||
workspaceDescription,
|
||||
workspaceSlug,
|
||||
} = body;
|
||||
|
||||
return res.status(200).json({ url: session.url });
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
return res.status(500).json({ error: "Error creating checkout session" });
|
||||
}
|
||||
},
|
||||
if (!successUrl || !cancelUrl) {
|
||||
return res.status(400).json({ error: "Missing required fields" });
|
||||
}
|
||||
|
||||
if (!workspacePublicId && !workspaceName) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Must provide workspacePublicId or workspaceName" });
|
||||
}
|
||||
|
||||
const resolvedSlug = slug ?? workspaceSlug;
|
||||
|
||||
if (resolvedSlug) {
|
||||
const slugResult = workspaceSlugSchema.safeParse(resolvedSlug);
|
||||
if (!slugResult.success) {
|
||||
return res.status(400).json({ error: "Invalid workspace slug" });
|
||||
}
|
||||
}
|
||||
|
||||
let resolvedWorkspacePublicId = workspacePublicId;
|
||||
let subscriptionId: number | undefined;
|
||||
|
||||
if (workspacePublicId) {
|
||||
// Existing workspace upgrade
|
||||
const workspace = await workspaceRepo.getByPublicId(
|
||||
db,
|
||||
workspacePublicId,
|
||||
);
|
||||
|
||||
if (!workspace) {
|
||||
return res.status(404).json({ error: "Workspace not found" });
|
||||
}
|
||||
|
||||
try {
|
||||
await assertPermission(db, user.id, workspace.id, "workspace:manage");
|
||||
} catch {
|
||||
return res.status(403).json({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
const subscription = await subscriptionRepo.create(db, {
|
||||
plan: "pro",
|
||||
referenceId: workspacePublicId,
|
||||
userId: user.id,
|
||||
stripeCustomerId: user.stripeCustomerId ?? "",
|
||||
status: "incomplete",
|
||||
});
|
||||
|
||||
subscriptionId = subscription?.id;
|
||||
|
||||
if (!subscriptionId) {
|
||||
return res.status(500).json({ error: "Error creating subscription" });
|
||||
}
|
||||
} else {
|
||||
resolvedWorkspacePublicId = generateUID();
|
||||
}
|
||||
|
||||
const isTeam = body.plan === "team";
|
||||
const annualPriceId = isTeam
|
||||
? (process.env.STRIPE_TEAM_PLAN_ANNUAL_PRICE_ID ??
|
||||
process.env.STRIPE_TEAM_PLAN_MONTHLY_PRICE_ID)
|
||||
: (process.env.STRIPE_PRO_PLAN_ANNUAL_PRICE_ID ??
|
||||
process.env.STRIPE_PRO_PLAN_MONTHLY_PRICE_ID);
|
||||
const monthlyPriceId = isTeam
|
||||
? process.env.STRIPE_TEAM_PLAN_MONTHLY_PRICE_ID
|
||||
: process.env.STRIPE_PRO_PLAN_MONTHLY_PRICE_ID;
|
||||
const priceId = billing === "annual" ? annualPriceId : monthlyPriceId;
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
mode: "subscription",
|
||||
payment_method_collection: "always",
|
||||
line_items: [{ price: priceId, quantity: 1 }],
|
||||
subscription_data: { trial_period_days: 14 },
|
||||
success_url: `${env("NEXT_PUBLIC_BASE_URL")}${successUrl}?workspacePublicId=${resolvedWorkspacePublicId}`,
|
||||
cancel_url: `${env("NEXT_PUBLIC_BASE_URL")}${cancelUrl}`,
|
||||
client_reference_id: resolvedWorkspacePublicId,
|
||||
customer: user.stripeCustomerId ?? undefined,
|
||||
metadata: {
|
||||
workspacePublicId: resolvedWorkspacePublicId!,
|
||||
userId: user.id,
|
||||
userEmail: user.email ?? "",
|
||||
...(resolvedSlug && { workspaceSlug: resolvedSlug }),
|
||||
...(workspaceName && { workspaceName }),
|
||||
...(workspaceDescription && { workspaceDescription }),
|
||||
...(subscriptionId && { subscriptionId: String(subscriptionId) }),
|
||||
plan: body.plan ?? "pro",
|
||||
isNewWorkspace: workspaceName ? "true" : "false",
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({ url: session.url });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: "Error creating checkout session" });
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import type { Readable } from "node:stream";
|
||||
|
||||
import { createNextApiContext } from "@kan/api/trpc";
|
||||
import * as subscriptionRepo from "@kan/db/repository/subscription.repo";
|
||||
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
|
||||
import { createLogger } from "@kan/logger";
|
||||
import { createStripeClient } from "@kan/stripe";
|
||||
|
||||
const log = createLogger("stripe-webhook");
|
||||
const log = createLogger("api");
|
||||
|
||||
async function buffer(readable: Readable) {
|
||||
const chunks = [];
|
||||
@@ -21,6 +23,9 @@ export default async function handler(
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
const stripe = createStripeClient();
|
||||
const start = Date.now();
|
||||
const requestId = randomUUID();
|
||||
const procedure = req.url?.split("?")[0] ?? "/api/stripe/webhook";
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
@@ -44,29 +49,85 @@ export default async function handler(
|
||||
|
||||
const { db } = await createNextApiContext(req);
|
||||
|
||||
log.info({ eventType: event.type, eventId: event.id }, "Stripe webhook received");
|
||||
|
||||
switch (event.type) {
|
||||
case "checkout.session.completed": {
|
||||
const checkoutSession = event.data.object;
|
||||
const meta = checkoutSession.metadata;
|
||||
|
||||
const metaData = checkoutSession.metadata;
|
||||
if (!meta?.workspacePublicId) break;
|
||||
|
||||
if (metaData?.workspacePublicId) {
|
||||
await workspaceRepo.update(db, metaData.workspacePublicId, {
|
||||
...(metaData.workspaceSlug && { slug: metaData.workspaceSlug }),
|
||||
plan: "pro",
|
||||
const plan = meta.plan === "team" ? "team" : ("pro" as const);
|
||||
|
||||
if (
|
||||
meta.isNewWorkspace === "true" &&
|
||||
meta.workspaceName &&
|
||||
meta.userId &&
|
||||
meta.userEmail
|
||||
) {
|
||||
const existing = await workspaceRepo.getByPublicId(db, meta.workspacePublicId);
|
||||
|
||||
if (!existing) {
|
||||
const slug = meta.workspaceSlug ?? meta.workspacePublicId;
|
||||
|
||||
await workspaceRepo.create(db, {
|
||||
publicId: meta.workspacePublicId,
|
||||
name: meta.workspaceName,
|
||||
slug,
|
||||
plan,
|
||||
createdBy: meta.userId,
|
||||
createdByEmail: meta.userEmail,
|
||||
...(meta.workspaceDescription && {
|
||||
description: meta.workspaceDescription,
|
||||
}),
|
||||
});
|
||||
|
||||
await subscriptionRepo.create(db, {
|
||||
plan,
|
||||
referenceId: meta.workspacePublicId,
|
||||
userId: meta.userId,
|
||||
stripeCustomerId: checkoutSession.customer as string,
|
||||
status: "active",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Existing workspace upgrade — update plan (and slug for pro)
|
||||
await workspaceRepo.update(db, meta.workspacePublicId, {
|
||||
plan,
|
||||
...(plan === "pro" && meta.workspaceSlug && { slug: meta.workspaceSlug }),
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
log.warn({ eventType: event.type }, "Unhandled Stripe event type");
|
||||
}
|
||||
|
||||
log.info(
|
||||
{
|
||||
requestId,
|
||||
procedure,
|
||||
transport: "rest",
|
||||
duration: Date.now() - start,
|
||||
status: 200,
|
||||
input: { eventType: event.type, eventId: event.id },
|
||||
},
|
||||
"API OK",
|
||||
);
|
||||
|
||||
return res.status(200).json({ received: true });
|
||||
} catch (err) {
|
||||
log.error({ err }, "Stripe webhook handler failed");
|
||||
log.error(
|
||||
{
|
||||
requestId,
|
||||
procedure,
|
||||
transport: "rest",
|
||||
duration: Date.now() - start,
|
||||
status: 400,
|
||||
err,
|
||||
},
|
||||
"API error",
|
||||
);
|
||||
return res.status(400).json({ message: "Webhook handler failed" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +1,58 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { addYears } from "date-fns";
|
||||
|
||||
import { createNextApiContext } from "@kan/api/trpc";
|
||||
import { integrations } from "@kan/db/schema";
|
||||
import { addYears } from "date-fns";
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
import { integrations } from "@kan/db/schema";
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
}
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
}
|
||||
|
||||
const { user } = await createNextApiContext(req);
|
||||
const { user } = await createNextApiContext(req);
|
||||
|
||||
if (!user)
|
||||
return res.status(401).json({ message: "User not authenticated" });
|
||||
if (!user)
|
||||
return res.status(401).json({ message: "User not authenticated" });
|
||||
|
||||
const apiKey = process.env.TRELLO_APP_API_KEY;
|
||||
const apiKey = process.env.TRELLO_APP_API_KEY;
|
||||
|
||||
if (!apiKey)
|
||||
return res.status(500).json({ message: "Trello API key not set in Environment Variables" });
|
||||
if (!apiKey)
|
||||
return res
|
||||
.status(500)
|
||||
.json({ message: "Trello API key not set in Environment Variables" });
|
||||
|
||||
const token = req.body.token;
|
||||
const token = req.body.token;
|
||||
|
||||
if (!token)
|
||||
return res.status(400).json({ message: "No token found" });
|
||||
if (!token) return res.status(400).json({ message: "No token found" });
|
||||
|
||||
try {
|
||||
const { db } = await createNextApiContext(req);
|
||||
try {
|
||||
const { db } = await createNextApiContext(req);
|
||||
|
||||
await db.insert(integrations).values({
|
||||
provider: "trello",
|
||||
userId: user.id,
|
||||
accessToken: token,
|
||||
expiresAt: addYears(new Date(), 1),
|
||||
}).onConflictDoUpdate({
|
||||
set: {
|
||||
accessToken: token,
|
||||
expiresAt: addYears(new Date(), 1),
|
||||
},
|
||||
target: [integrations.userId, integrations.provider],
|
||||
});
|
||||
await db
|
||||
.insert(integrations)
|
||||
.values({
|
||||
provider: "trello",
|
||||
userId: user.id,
|
||||
accessToken: token,
|
||||
expiresAt: addYears(new Date(), 1),
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
set: {
|
||||
accessToken: token,
|
||||
expiresAt: addYears(new Date(), 1),
|
||||
},
|
||||
target: [integrations.userId, integrations.provider],
|
||||
});
|
||||
|
||||
return res.status(200).json({ message: "Trello authentication successful" });
|
||||
} catch (err) {
|
||||
console.error("Trello authentication error:", err);
|
||||
return res.status(400).json({ message: "Trello authentication failed" });
|
||||
}
|
||||
},
|
||||
);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ message: "Trello authentication successful" });
|
||||
} catch (err) {
|
||||
return res.status(400).json({ message: "Trello authentication failed" });
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -3,9 +3,11 @@ import { Novu } from "@novu/api";
|
||||
import { jwtVerify } from "jose";
|
||||
import { z } from "zod";
|
||||
|
||||
import { env } from "~/env";
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
|
||||
import { env } from "~/env";
|
||||
|
||||
const requestSchema = z.object({
|
||||
token: z.string().min(1),
|
||||
});
|
||||
@@ -22,84 +24,85 @@ const textEncoder = new TextEncoder();
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse<ResponseData>) => {
|
||||
if (process.env.NEXT_PUBLIC_KAN_ENV !== "cloud") {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: "Unsubscribe endpoint is not available.",
|
||||
code: "UNAVAILABLE",
|
||||
});
|
||||
}
|
||||
withApiLogging(
|
||||
async (req: NextApiRequest, res: NextApiResponse<ResponseData>) => {
|
||||
if (process.env.NEXT_PUBLIC_KAN_ENV !== "cloud") {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: "Unsubscribe endpoint is not available.",
|
||||
code: "UNAVAILABLE",
|
||||
});
|
||||
}
|
||||
|
||||
if (req.method !== "POST") {
|
||||
res.setHeader("Allow", "POST");
|
||||
return res.status(405).json({
|
||||
success: false,
|
||||
error: "Method not allowed.",
|
||||
code: "METHOD_NOT_ALLOWED",
|
||||
});
|
||||
}
|
||||
if (req.method !== "POST") {
|
||||
res.setHeader("Allow", "POST");
|
||||
return res.status(405).json({
|
||||
success: false,
|
||||
error: "Method not allowed.",
|
||||
code: "METHOD_NOT_ALLOWED",
|
||||
});
|
||||
}
|
||||
|
||||
const parsedBody = requestSchema.safeParse(req.body);
|
||||
const parsedBody = requestSchema.safeParse(req.body);
|
||||
|
||||
if (!parsedBody.success) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: "Invalid request payload.",
|
||||
code: "BAD_REQUEST",
|
||||
});
|
||||
}
|
||||
if (!parsedBody.success) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: "Invalid request payload.",
|
||||
code: "BAD_REQUEST",
|
||||
});
|
||||
}
|
||||
|
||||
if (!env.EMAIL_UNSUBSCRIBE_SECRET || !env.NOVU_API_KEY) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: "Unsubscribe service is not configured.",
|
||||
code: "NOT_CONFIGURED",
|
||||
});
|
||||
}
|
||||
if (!env.EMAIL_UNSUBSCRIBE_SECRET || !env.NOVU_API_KEY) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: "Unsubscribe service is not configured.",
|
||||
code: "NOT_CONFIGURED",
|
||||
});
|
||||
}
|
||||
|
||||
let payload: z.infer<typeof tokenPayloadSchema>;
|
||||
let payload: z.infer<typeof tokenPayloadSchema>;
|
||||
|
||||
try {
|
||||
const verified = await jwtVerify(
|
||||
parsedBody.data.token,
|
||||
textEncoder.encode(env.EMAIL_UNSUBSCRIBE_SECRET),
|
||||
{
|
||||
// We intentionally do not use exp/iat claims –
|
||||
// tokens are long-lived and validated only by signature + payload.
|
||||
clockTolerance: "0s",
|
||||
},
|
||||
);
|
||||
payload = tokenPayloadSchema.parse(verified.payload);
|
||||
} catch {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: "Your unsubscribe link is invalid or has expired.",
|
||||
code: "INVALID_TOKEN",
|
||||
});
|
||||
}
|
||||
try {
|
||||
const verified = await jwtVerify(
|
||||
parsedBody.data.token,
|
||||
textEncoder.encode(env.EMAIL_UNSUBSCRIBE_SECRET),
|
||||
{
|
||||
// We intentionally do not use exp/iat claims –
|
||||
// tokens are long-lived and validated only by signature + payload.
|
||||
clockTolerance: "0s",
|
||||
},
|
||||
);
|
||||
payload = tokenPayloadSchema.parse(verified.payload);
|
||||
} catch {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: "Your unsubscribe link is invalid or has expired.",
|
||||
code: "INVALID_TOKEN",
|
||||
});
|
||||
}
|
||||
|
||||
const novu = new Novu({ secretKey: env.NOVU_API_KEY });
|
||||
const novu = new Novu({ secretKey: env.NOVU_API_KEY });
|
||||
|
||||
try {
|
||||
await novu.subscribers.preferences.update(
|
||||
{
|
||||
channels: {
|
||||
email: false,
|
||||
},
|
||||
},
|
||||
payload.subscriberId,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to update Novu preferences", error);
|
||||
return res.status(502).json({
|
||||
success: false,
|
||||
error:
|
||||
"We could not update your email preferences right now. Please try again later.",
|
||||
code: "NOVU_ERROR",
|
||||
});
|
||||
}
|
||||
try {
|
||||
await novu.subscribers.preferences.update(
|
||||
{
|
||||
channels: {
|
||||
email: false,
|
||||
},
|
||||
},
|
||||
payload.subscriberId,
|
||||
);
|
||||
} catch (error) {
|
||||
return res.status(502).json({
|
||||
success: false,
|
||||
error:
|
||||
"We could not update your email preferences right now. Please try again later.",
|
||||
code: "NOVU_ERROR",
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).json({ success: true });
|
||||
},
|
||||
return res.status(200).json({ success: true });
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { Upload } from "@aws-sdk/lib-storage";
|
||||
|
||||
import { createNextApiContext } from "@kan/api/trpc";
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { assertPermission } from "@kan/api/utils/permissions";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
import * as cardRepo from "@kan/db/repository/card.repo";
|
||||
@@ -22,7 +23,7 @@ export const config = {
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
@@ -36,7 +37,9 @@ export default withRateLimit(
|
||||
|
||||
const bucket = env.NEXT_PUBLIC_ATTACHMENTS_BUCKET_NAME;
|
||||
if (!bucket) {
|
||||
return res.status(500).json({ error: "Attachments bucket not configured" });
|
||||
return res
|
||||
.status(500)
|
||||
.json({ error: "Attachments bucket not configured" });
|
||||
}
|
||||
|
||||
const cardPublicId = req.query.cardPublicId;
|
||||
@@ -55,7 +58,9 @@ export default withRateLimit(
|
||||
}
|
||||
|
||||
if (!Number.isFinite(contentLength) || contentLength <= 0) {
|
||||
return res.status(400).json({ error: "Missing or invalid content length" });
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Missing or invalid content length" });
|
||||
}
|
||||
|
||||
if (contentLength > MAX_SIZE_BYTES) {
|
||||
@@ -129,8 +134,7 @@ export default withRateLimit(
|
||||
|
||||
return res.status(200).json({ attachment });
|
||||
} catch (error) {
|
||||
console.error("Attachment upload failed", error);
|
||||
return res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -2,13 +2,17 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { PutObjectCommand } from "@aws-sdk/client-s3";
|
||||
|
||||
import { createNextApiContext } from "@kan/api/trpc";
|
||||
import * as userRepo from "@kan/db/repository/user.repo";
|
||||
|
||||
import { env } from "~/env";
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
import * as userRepo from "@kan/db/repository/user.repo";
|
||||
import { createS3Client } from "@kan/shared/utils";
|
||||
|
||||
const MAX_SIZE_BYTES = parseInt(process.env.S3_AVATAR_UPLOAD_LIMIT || '2097152', 10); // Default 2MB
|
||||
import { env } from "~/env";
|
||||
|
||||
const MAX_SIZE_BYTES = parseInt(
|
||||
process.env.S3_AVATAR_UPLOAD_LIMIT || "2097152",
|
||||
10,
|
||||
); // Default 2MB
|
||||
const allowedContentTypes = ["image/jpeg", "image/png", "image/webp"];
|
||||
|
||||
export const config = {
|
||||
@@ -19,7 +23,7 @@ export const config = {
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
@@ -51,7 +55,9 @@ export default withRateLimit(
|
||||
}
|
||||
|
||||
if (!Number.isFinite(contentLength) || contentLength <= 0) {
|
||||
return res.status(400).json({ error: "Missing or invalid content length" });
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Missing or invalid content length" });
|
||||
}
|
||||
|
||||
if (contentLength > MAX_SIZE_BYTES) {
|
||||
@@ -93,9 +99,7 @@ export default withRateLimit(
|
||||
user: updatedUser,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Avatar upload failed", error);
|
||||
return res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
31
apps/web/src/pages/onboarding/select-plan.tsx
Normal file
31
apps/web/src/pages/onboarding/select-plan.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { env } from "next-runtime-env";
|
||||
|
||||
import { authClient } from "@kan/auth/client";
|
||||
|
||||
import { PageHead } from "~/components/PageHead";
|
||||
import SelectPlanView from "~/views/onboarding/select-plan";
|
||||
|
||||
export default function SelectPlanPage() {
|
||||
const router = useRouter();
|
||||
const { data: session, isPending } = authClient.useSession();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPending && !session?.user) {
|
||||
router.push("/login");
|
||||
}
|
||||
if (!isPending && env("NEXT_PUBLIC_KAN_ENV") !== "cloud") {
|
||||
router.push("/boards");
|
||||
}
|
||||
}, [session, isPending, router]);
|
||||
|
||||
if (isPending || !session?.user) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title="Select plan | kan.bn" />
|
||||
<SelectPlanView />
|
||||
</>
|
||||
);
|
||||
}
|
||||
28
apps/web/src/pages/onboarding/workspace.tsx
Normal file
28
apps/web/src/pages/onboarding/workspace.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { env } from "next-runtime-env";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { authClient } from "@kan/auth/client";
|
||||
|
||||
import { PageHead } from "~/components/PageHead";
|
||||
import WorkspaceDetailsView from "~/views/onboarding/workspace-details";
|
||||
|
||||
export default function WorkspaceDetailsPage() {
|
||||
const router = useRouter();
|
||||
const { data: session, isPending } = authClient.useSession();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPending && !session?.user) router.push("/login");
|
||||
if (!isPending && env("NEXT_PUBLIC_KAN_ENV") !== "cloud")
|
||||
router.push("/boards");
|
||||
}, [session, isPending, router]);
|
||||
|
||||
if (isPending || !session?.user) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title="Create workspace | kan.bn" />
|
||||
<WorkspaceDetailsView />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -17,7 +17,7 @@ interface Workspace {
|
||||
description: string | null | undefined;
|
||||
publicId: string;
|
||||
slug: string | undefined;
|
||||
plan: "free" | "pro" | "enterprise" | undefined;
|
||||
plan: "free" | "team" | "pro" | "enterprise" | undefined;
|
||||
role: "admin" | "member" | "guest";
|
||||
weekStartDay: 0 | 1 | 6;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ const initialWorkspace: Workspace = {
|
||||
description: null,
|
||||
publicId: "",
|
||||
slug: "",
|
||||
plan: "free",
|
||||
plan: "free" as const,
|
||||
role: "member",
|
||||
weekStartDay: 1,
|
||||
};
|
||||
@@ -50,7 +50,15 @@ export const WorkspaceProvider: React.FC<{ children: ReactNode }> = ({
|
||||
|
||||
const workspacePublicId = useSearchParams().get("workspacePublicId");
|
||||
|
||||
const { data, isLoading } = api.workspace.all.useQuery();
|
||||
const [pendingWorkspaceId, setPendingWorkspaceId] = useState<string | null>(
|
||||
workspacePublicId,
|
||||
);
|
||||
const pollAttemptsRef = React.useRef(0);
|
||||
const MAX_POLL_ATTEMPTS = 5;
|
||||
|
||||
const { data, isLoading } = api.workspace.all.useQuery(undefined, {
|
||||
refetchInterval: pendingWorkspaceId ? 2000 : false,
|
||||
});
|
||||
const utils = api.useUtils();
|
||||
|
||||
const switchWorkspace = (_workspace: Workspace) => {
|
||||
@@ -94,7 +102,16 @@ export const WorkspaceProvider: React.FC<{ children: ReactNode }> = ({
|
||||
({ workspace }) => workspace.publicId === storedWorkspaceId,
|
||||
);
|
||||
|
||||
if (!selectedWorkspace?.workspace) return;
|
||||
if (!selectedWorkspace?.workspace) {
|
||||
pollAttemptsRef.current += 1;
|
||||
if (pollAttemptsRef.current >= MAX_POLL_ATTEMPTS) {
|
||||
setPendingWorkspaceId(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
pollAttemptsRef.current = 0;
|
||||
setPendingWorkspaceId(null);
|
||||
|
||||
setWorkspace({
|
||||
publicId: selectedWorkspace.workspace.publicId,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { env } from "next-runtime-env";
|
||||
|
||||
export const formatToArray = (
|
||||
value: string | string[] | undefined,
|
||||
): string[] => {
|
||||
@@ -50,5 +52,21 @@ export const getAvatarUrl = (imageOrKey: string | null) => {
|
||||
return imageOrKey;
|
||||
}
|
||||
|
||||
// Construct URL from S3 key
|
||||
const useVirtualHosted = env("NEXT_PUBLIC_USE_VIRTUAL_HOSTED_URLS") === "true";
|
||||
const storageDomain = env("NEXT_PUBLIC_STORAGE_DOMAIN");
|
||||
const storageUrl = env("NEXT_PUBLIC_STORAGE_URL");
|
||||
const bucket = env("NEXT_PUBLIC_AVATAR_BUCKET_NAME");
|
||||
|
||||
if (useVirtualHosted && storageDomain && bucket) {
|
||||
// Virtual-hosted style: https://{bucket}.{domain}/{key}
|
||||
return `https://${bucket}.${storageDomain}/${imageOrKey}`;
|
||||
}
|
||||
|
||||
if (storageUrl && bucket) {
|
||||
// Path-style: {storageUrl}/{bucket}/{key}
|
||||
return `${storageUrl}/${bucket}/${imageOrKey}`;
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { t } from "@lingui/core/macro";
|
||||
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { api } from "~/utils/api";
|
||||
import { DueDateSelector } from "~/views/card/components/DueDateSelector";
|
||||
|
||||
export function CardContextDueDateModal() {
|
||||
const { entityId: cardPublicId, closeModal } = useModal();
|
||||
|
||||
const { data: card, isLoading } = api.card.byId.useQuery(
|
||||
{ cardPublicId: cardPublicId ?? "" },
|
||||
{ enabled: !!cardPublicId && cardPublicId.length >= 12 },
|
||||
);
|
||||
|
||||
if (!cardPublicId) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2 className="mb-4 text-lg font-semibold text-light-1000 dark:text-dark-1000">
|
||||
{t`Set due date`}
|
||||
</h2>
|
||||
{isLoading ? (
|
||||
<div className="h-10 w-full animate-pulse rounded bg-light-200 dark:bg-dark-300" />
|
||||
) : (
|
||||
<DueDateSelector
|
||||
cardPublicId={cardPublicId}
|
||||
dueDate={card?.dueDate ?? null}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-4 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="rounded-md border border-light-300 bg-light-50 px-3 py-1.5 text-sm font-medium text-light-1000 hover:bg-light-200 dark:border-dark-400 dark:bg-dark-200 dark:text-dark-1000 dark:hover:bg-dark-300"
|
||||
>
|
||||
{t`Done`}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
import {
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
ListboxOption,
|
||||
ListboxOptions,
|
||||
Transition,
|
||||
} from "@headlessui/react";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { useState } from "react";
|
||||
import { HiChevronDown } from "react-icons/hi2";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import Input from "~/components/Input";
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
interface CardContextDuplicateModalProps {
|
||||
boardPublicId?: string;
|
||||
isTemplate?: boolean;
|
||||
}
|
||||
|
||||
export function CardContextDuplicateModal({
|
||||
boardPublicId: boardPublicIdProp,
|
||||
isTemplate: isTemplateProp,
|
||||
}: CardContextDuplicateModalProps = {}) {
|
||||
const { entityId: cardPublicId, closeModal, getModalState } = useModal();
|
||||
const { showPopup } = usePopup();
|
||||
const utils = api.useUtils();
|
||||
|
||||
const modalState = getModalState("CARD_CONTEXT_DUPLICATE") as
|
||||
| { boardPublicId: string; isTemplate?: boolean }
|
||||
| undefined;
|
||||
const boardPublicId =
|
||||
boardPublicIdProp ?? modalState?.boardPublicId ?? "";
|
||||
const isTemplate = isTemplateProp ?? modalState?.isTemplate ?? false;
|
||||
|
||||
const [listPublicId, setListPublicId] = useState("");
|
||||
const [copyLabels, setCopyLabels] = useState(true);
|
||||
const [copyMembers, setCopyMembers] = useState(true);
|
||||
const [copyChecklists, setCopyChecklists] = useState(true);
|
||||
const [position, setPosition] = useState<string>("");
|
||||
const [title, setTitle] = useState("");
|
||||
|
||||
const { data: card, isLoading: isCardLoading } = api.card.byId.useQuery(
|
||||
{ cardPublicId: cardPublicId ?? "" },
|
||||
{ enabled: !!cardPublicId && cardPublicId.length >= 12 },
|
||||
);
|
||||
|
||||
const boardType = isTemplate ? "template" : "regular";
|
||||
const { data: board } = api.board.byId.useQuery(
|
||||
{ boardPublicId, type: boardType },
|
||||
{ enabled: !!boardPublicId },
|
||||
);
|
||||
const lists = board?.lists ?? [];
|
||||
const listOptions = lists.map((l) => ({ publicId: l.publicId, name: l.name }));
|
||||
const currentListPublicId = card?.list?.publicId;
|
||||
const hasLabels = (card?.labels?.length ?? 0) > 0;
|
||||
const hasMembers = (card?.members?.length ?? 0) > 0;
|
||||
const hasChecklists = (card?.checklists?.length ?? 0) > 0;
|
||||
const hasAnyCopyOption = hasLabels || hasMembers || hasChecklists;
|
||||
|
||||
const duplicateCard = api.card.duplicate.useMutation({
|
||||
onSuccess: () => {
|
||||
showPopup({
|
||||
header: t`Card duplicated`,
|
||||
icon: "success",
|
||||
message: t`The card has been duplicated.`,
|
||||
});
|
||||
closeModal();
|
||||
},
|
||||
onError: () => {
|
||||
showPopup({
|
||||
header: t`Unable to duplicate card`,
|
||||
message: t`Please try again.`,
|
||||
icon: "error",
|
||||
});
|
||||
},
|
||||
onSettled: async () => {
|
||||
await utils.board.byId.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!cardPublicId || !listPublicId) return;
|
||||
const indexNum =
|
||||
position === "" ? undefined : parseInt(position, 10);
|
||||
if (
|
||||
position !== "" &&
|
||||
(indexNum === undefined || isNaN(indexNum) || indexNum < 0)
|
||||
)
|
||||
return;
|
||||
duplicateCard.mutate({
|
||||
cardPublicId,
|
||||
listPublicId,
|
||||
copyLabels,
|
||||
copyMembers,
|
||||
copyChecklists,
|
||||
...(typeof indexNum === "number" && { index: indexNum }),
|
||||
title: title.trim() || undefined,
|
||||
});
|
||||
};
|
||||
|
||||
if (!cardPublicId) return null;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="p-4">
|
||||
<h2 className="mb-4 text-lg font-semibold text-light-1000 dark:text-dark-1000">
|
||||
{t`Duplicate card`}
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-light-900 dark:text-dark-900">
|
||||
{t`List`}
|
||||
</label>
|
||||
<Listbox value={listPublicId} onChange={setListPublicId}>
|
||||
<div className="relative">
|
||||
<ListboxButton
|
||||
className={twMerge(
|
||||
"relative w-full cursor-pointer rounded-md border border-light-300 bg-white py-2 pl-3 pr-10 text-left text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-light-400 focus:ring-offset-0 dark:border-dark-400 dark:bg-dark-200 dark:text-dark-1000 dark:focus:ring-dark-500",
|
||||
!listPublicId && "text-light-600 dark:text-dark-600",
|
||||
)}
|
||||
>
|
||||
<span className="block truncate">
|
||||
{listPublicId
|
||||
? listOptions.find((o) => o.publicId === listPublicId)
|
||||
?.name
|
||||
: t`Select a list`}
|
||||
</span>
|
||||
<span className="pointer-events-none absolute right-2 top-1/2 flex -translate-y-1/2 items-center">
|
||||
<HiChevronDown
|
||||
className="h-4 w-4 text-light-600 dark:text-dark-600"
|
||||
aria-hidden
|
||||
/>
|
||||
</span>
|
||||
</ListboxButton>
|
||||
<Transition
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<ListboxOptions className="absolute z-50 mt-1 w-full overflow-hidden rounded-md border border-light-200 bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-400 dark:bg-dark-200">
|
||||
<div className="max-h-60 overflow-y-auto py-1 pr-1 scrollbar scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-w-[8px] scrollbar-track-light-200 scrollbar-thumb-light-400 dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-600">
|
||||
{listOptions.map((option) => {
|
||||
const isCurrentList =
|
||||
option.publicId === currentListPublicId;
|
||||
return (
|
||||
<ListboxOption
|
||||
key={option.publicId}
|
||||
value={option.publicId}
|
||||
disabled={isCurrentList}
|
||||
className={({ focus }) =>
|
||||
twMerge(
|
||||
"relative select-none py-2 pl-3 pr-9 text-sm",
|
||||
isCurrentList
|
||||
? "cursor-default opacity-50"
|
||||
: "cursor-pointer",
|
||||
!isCurrentList && focus
|
||||
? "bg-light-200 text-light-1000 dark:bg-dark-400 dark:text-dark-1000"
|
||||
: "text-light-900 dark:text-dark-900",
|
||||
)
|
||||
}
|
||||
>
|
||||
{option.name}
|
||||
</ListboxOption>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ListboxOptions>
|
||||
</Transition>
|
||||
</div>
|
||||
</Listbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-light-900 dark:text-dark-900">
|
||||
{t`Title (optional)`}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder={card?.title ?? ""}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-light-900 dark:text-dark-900">
|
||||
{t`Position in list (0 = first, leave empty for end)`}
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
value={position}
|
||||
onChange={(e) => setPosition(e.target.value)}
|
||||
placeholder={t`End of list`}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hasAnyCopyOption && (
|
||||
<div className="flex flex-col gap-2">
|
||||
{hasLabels && (
|
||||
<div className="flex items-center gap-3">
|
||||
<label className="relative mt-[2px] inline-flex h-[16px] w-[16px] flex-shrink-0 cursor-pointer items-center justify-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={copyLabels}
|
||||
onChange={(e) => setCopyLabels(e.target.checked)}
|
||||
className={twMerge(
|
||||
"h-[16px] w-[16px] appearance-none rounded-md border border-light-500 bg-transparent outline-none ring-0 checked:bg-blue-600 focus:shadow-none focus:ring-0 focus:ring-offset-0 focus-visible:outline-none dark:border-dark-500 dark:hover:border-dark-500",
|
||||
"cursor-pointer",
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
<span className="text-sm text-light-900 dark:text-dark-900">
|
||||
{t`Copy labels`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{hasMembers && (
|
||||
<div className="flex items-center gap-3">
|
||||
<label className="relative mt-[2px] inline-flex h-[16px] w-[16px] flex-shrink-0 cursor-pointer items-center justify-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={copyMembers}
|
||||
onChange={(e) => setCopyMembers(e.target.checked)}
|
||||
className={twMerge(
|
||||
"h-[16px] w-[16px] appearance-none rounded-md border border-light-500 bg-transparent outline-none ring-0 checked:bg-blue-600 focus:shadow-none focus:ring-0 focus:ring-offset-0 focus-visible:outline-none dark:border-dark-500 dark:hover:border-dark-500",
|
||||
"cursor-pointer",
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
<span className="text-sm text-light-900 dark:text-dark-900">
|
||||
{t`Copy members`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{hasChecklists && (
|
||||
<div className="flex items-center gap-3">
|
||||
<label className="relative mt-[2px] inline-flex h-[16px] w-[16px] flex-shrink-0 cursor-pointer items-center justify-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={copyChecklists}
|
||||
onChange={(e) => setCopyChecklists(e.target.checked)}
|
||||
className={twMerge(
|
||||
"h-[16px] w-[16px] appearance-none rounded-md border border-light-500 bg-transparent outline-none ring-0 checked:bg-blue-600 focus:shadow-none focus:ring-0 focus:ring-offset-0 focus-visible:outline-none dark:border-dark-500 dark:hover:border-dark-500",
|
||||
"cursor-pointer",
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
<span className="text-sm text-light-900 dark:text-dark-900">
|
||||
{t`Copy checklists`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-end gap-2">
|
||||
<Button type="button" variant="secondary" onClick={closeModal}>
|
||||
{t`Cancel`}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
disabled={
|
||||
!listPublicId ||
|
||||
duplicateCard.isPending ||
|
||||
isCardLoading ||
|
||||
lists.length === 0
|
||||
}
|
||||
>
|
||||
{duplicateCard.isPending ? t`Duplicating…` : t`Duplicate`}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { t } from "@lingui/core/macro";
|
||||
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { api } from "~/utils/api";
|
||||
import LabelIcon from "~/components/LabelIcon";
|
||||
import LabelSelector from "~/views/card/components/LabelSelector";
|
||||
|
||||
export function CardContextLabelsModal() {
|
||||
const { entityId: cardPublicId, closeModal } = useModal();
|
||||
|
||||
const { data: card, isLoading } = api.card.byId.useQuery(
|
||||
{ cardPublicId: cardPublicId ?? "" },
|
||||
{ enabled: !!cardPublicId && cardPublicId.length >= 12 },
|
||||
);
|
||||
|
||||
const boardLabels = card?.list?.board?.labels ?? [];
|
||||
const selectedLabels = card?.labels ?? [];
|
||||
|
||||
const formattedLabels = boardLabels.map((label) => ({
|
||||
key: label.publicId,
|
||||
value: label.name,
|
||||
selected: selectedLabels.some((l) => l.publicId === label.publicId),
|
||||
leftIcon: <LabelIcon colourCode={label.colourCode} />,
|
||||
}));
|
||||
|
||||
if (!cardPublicId) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2 className="mb-4 text-lg font-semibold text-light-1000 dark:text-dark-1000">
|
||||
{t`Labels`}
|
||||
</h2>
|
||||
{isLoading ? (
|
||||
<div className="h-10 w-full animate-pulse rounded bg-light-200 dark:bg-dark-300" />
|
||||
) : (
|
||||
<LabelSelector
|
||||
cardPublicId={cardPublicId}
|
||||
labels={formattedLabels}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-4 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="rounded-md border border-light-300 bg-light-50 px-3 py-1.5 text-sm font-medium text-light-1000 hover:bg-light-200 dark:border-dark-400 dark:bg-dark-200 dark:text-dark-1000 dark:hover:bg-dark-300"
|
||||
>
|
||||
{t`Done`}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { t } from "@lingui/core/macro";
|
||||
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { api } from "~/utils/api";
|
||||
import { formatMemberDisplayName, getAvatarUrl } from "~/utils/helpers";
|
||||
import Avatar from "~/components/Avatar";
|
||||
import MemberSelector from "~/views/card/components/MemberSelector";
|
||||
|
||||
export function CardContextMembersModal() {
|
||||
const { entityId: cardPublicId, closeModal } = useModal();
|
||||
|
||||
const { data: card, isLoading } = api.card.byId.useQuery(
|
||||
{ cardPublicId: cardPublicId ?? "" },
|
||||
{ enabled: !!cardPublicId && cardPublicId.length >= 12 },
|
||||
);
|
||||
|
||||
const board = card?.list?.board;
|
||||
const workspaceMembers = board?.workspace?.members ?? [];
|
||||
const selectedMembers = card?.members ?? [];
|
||||
|
||||
const formattedMembers = workspaceMembers.map((member) => {
|
||||
const isSelected = selectedMembers.some(
|
||||
(m) => m.publicId === member.publicId,
|
||||
);
|
||||
return {
|
||||
key: member.publicId,
|
||||
value: formatMemberDisplayName(
|
||||
member.user?.name ?? null,
|
||||
member.user?.email ?? member.email,
|
||||
),
|
||||
imageUrl: member.user?.image ? getAvatarUrl(member.user.image) : undefined,
|
||||
selected: isSelected,
|
||||
leftIcon: (
|
||||
<Avatar
|
||||
size="xs"
|
||||
name={member.user?.name ?? ""}
|
||||
imageUrl={
|
||||
member.user?.image ? getAvatarUrl(member.user.image) : undefined
|
||||
}
|
||||
email={member.user?.email ?? member.email}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
if (!cardPublicId) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2 className="mb-4 text-lg font-semibold text-light-1000 dark:text-dark-1000">
|
||||
{t`Manage members`}
|
||||
</h2>
|
||||
{isLoading ? (
|
||||
<div className="h-10 w-full animate-pulse rounded bg-light-200 dark:bg-dark-300" />
|
||||
) : (
|
||||
<MemberSelector
|
||||
cardPublicId={cardPublicId}
|
||||
members={formattedMembers}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-4 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="rounded-md border border-light-300 bg-light-50 px-3 py-1.5 text-sm font-medium text-light-1000 hover:bg-light-200 dark:border-dark-400 dark:bg-dark-200 dark:text-dark-1000 dark:hover:bg-dark-300"
|
||||
>
|
||||
{t`Done`}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
130
apps/web/src/views/board/components/CardContextMenu.tsx
Normal file
130
apps/web/src/views/board/components/CardContextMenu.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { useEffect, useRef } from "react";
|
||||
import {
|
||||
HiLink,
|
||||
HiOutlineCalendar,
|
||||
HiOutlineDocumentDuplicate,
|
||||
HiOutlineTag,
|
||||
HiOutlineTrash,
|
||||
HiOutlineUserGroup,
|
||||
HiOutlineArrowRightCircle,
|
||||
} from "react-icons/hi2";
|
||||
|
||||
export type CardContextMenuAction =
|
||||
| "members"
|
||||
| "move"
|
||||
| "labels"
|
||||
| "dueDate"
|
||||
| "copyLink"
|
||||
| "duplicate"
|
||||
| "delete";
|
||||
|
||||
interface CardContextMenuProps {
|
||||
x: number;
|
||||
y: number;
|
||||
onClose: () => void;
|
||||
onAction: (action: CardContextMenuAction) => void;
|
||||
canEdit: boolean;
|
||||
}
|
||||
|
||||
const MENU_ITEMS: {
|
||||
action: CardContextMenuAction;
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
requiresEdit: boolean;
|
||||
}[] = [
|
||||
{
|
||||
action: "members",
|
||||
label: t`Manage members`,
|
||||
icon: <HiOutlineUserGroup className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: true,
|
||||
},
|
||||
{
|
||||
action: "move",
|
||||
label: t`Move to another list`,
|
||||
icon: <HiOutlineArrowRightCircle className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: true,
|
||||
},
|
||||
{
|
||||
action: "labels",
|
||||
label: t`Add / edit label`,
|
||||
icon: <HiOutlineTag className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: true,
|
||||
},
|
||||
{
|
||||
action: "dueDate",
|
||||
label: t`Set due date`,
|
||||
icon: <HiOutlineCalendar className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: true,
|
||||
},
|
||||
{
|
||||
action: "copyLink",
|
||||
label: t`Copy link to card`,
|
||||
icon: <HiLink className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: false,
|
||||
},
|
||||
{
|
||||
action: "duplicate",
|
||||
label: t`Duplicate card`,
|
||||
icon: <HiOutlineDocumentDuplicate className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: true,
|
||||
},
|
||||
{
|
||||
action: "delete",
|
||||
label: t`Delete card`,
|
||||
icon: <HiOutlineTrash className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: true,
|
||||
},
|
||||
];
|
||||
|
||||
export function CardContextMenu({
|
||||
x,
|
||||
y,
|
||||
onClose,
|
||||
onAction,
|
||||
canEdit,
|
||||
}: CardContextMenuProps) {
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
document.addEventListener("keydown", handleEscape);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
document.removeEventListener("keydown", handleEscape);
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
const items = MENU_ITEMS.filter((item) => !item.requiresEdit || canEdit);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="fixed z-[200] min-w-[200px] rounded-md border border-light-200 bg-white py-1 shadow-lg dark:border-dark-400 dark:bg-dark-200"
|
||||
style={{ left: x, top: y }}
|
||||
>
|
||||
{items.map(({ action, label, icon }) => (
|
||||
<button
|
||||
key={action}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onAction(action);
|
||||
onClose();
|
||||
}}
|
||||
className="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-neutral-900 hover:bg-light-200 dark:text-dark-1000 dark:hover:bg-dark-400"
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
101
apps/web/src/views/board/components/CardContextMoveListModal.tsx
Normal file
101
apps/web/src/views/board/components/CardContextMoveListModal.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { t } from "@lingui/core/macro";
|
||||
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
import { invalidateCard } from "~/utils/cardInvalidation";
|
||||
|
||||
export function CardContextMoveListModal() {
|
||||
const { entityId: cardPublicId, closeModal } = useModal();
|
||||
const { showPopup } = usePopup();
|
||||
const utils = api.useUtils();
|
||||
|
||||
const { data: card, isLoading } = api.card.byId.useQuery(
|
||||
{ cardPublicId: cardPublicId ?? "" },
|
||||
{ enabled: !!cardPublicId && cardPublicId.length >= 12 },
|
||||
);
|
||||
|
||||
const updateCardList = api.card.update.useMutation({
|
||||
onMutate: async (vars) => {
|
||||
if (!cardPublicId) return undefined;
|
||||
await utils.card.byId.cancel();
|
||||
const previous = utils.card.byId.getData({ cardPublicId });
|
||||
utils.card.byId.setData({ cardPublicId }, (old) => {
|
||||
if (!old) return old;
|
||||
const list = old.list.board?.lists?.find(
|
||||
(l) => l.publicId === vars.listPublicId,
|
||||
);
|
||||
if (!list) return old;
|
||||
return {
|
||||
...old,
|
||||
list: { ...old.list, publicId: list.publicId, name: list.name },
|
||||
};
|
||||
});
|
||||
return { previous };
|
||||
},
|
||||
onError: (_err, _vars, ctx) => {
|
||||
if (cardPublicId && ctx?.previous) {
|
||||
utils.card.byId.setData({ cardPublicId }, ctx.previous);
|
||||
}
|
||||
showPopup({
|
||||
header: t`Unable to move card`,
|
||||
message: t`Please try again.`,
|
||||
icon: "error",
|
||||
});
|
||||
},
|
||||
onSettled: async () => {
|
||||
if (cardPublicId) {
|
||||
await invalidateCard(utils, cardPublicId);
|
||||
await utils.board.byId.invalidate();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const lists = card?.list?.board?.lists ?? [];
|
||||
const currentListPublicId = card?.list?.publicId;
|
||||
|
||||
const handleSelectList = (listPublicId: string) => {
|
||||
if (listPublicId === currentListPublicId || !cardPublicId) return;
|
||||
updateCardList.mutate(
|
||||
{ cardPublicId, listPublicId, index: 0 },
|
||||
{ onSuccess: closeModal },
|
||||
);
|
||||
};
|
||||
|
||||
if (!cardPublicId) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2 className="mb-4 text-lg font-semibold text-light-1000 dark:text-dark-1000">
|
||||
{t`Move to list`}
|
||||
</h2>
|
||||
{isLoading ? (
|
||||
<div className="space-y-2">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="h-10 w-full animate-pulse rounded bg-light-200 dark:bg-dark-300"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-h-[60vh] overflow-y-auto pr-1 scrollbar scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-w-[8px] scrollbar-track-light-200 scrollbar-thumb-light-400 dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-600">
|
||||
<ul className="space-y-1">
|
||||
{lists.map((list) => (
|
||||
<li key={list.publicId}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSelectList(list.publicId)}
|
||||
disabled={list.publicId === currentListPublicId}
|
||||
className="w-full rounded-md px-3 py-2 text-left text-sm hover:bg-light-200 disabled:opacity-50 dark:hover:bg-dark-400"
|
||||
>
|
||||
{list.name}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
|
||||
import type { UpdateBoardInput } from "@kan/api/types";
|
||||
|
||||
import type { CardContextMenuAction } from "./components/CardContextMenu";
|
||||
import Button from "~/components/Button";
|
||||
import { DeleteLabelConfirmation } from "~/components/DeleteLabelConfirmation";
|
||||
import { LabelForm } from "~/components/LabelForm";
|
||||
@@ -26,16 +27,23 @@ import { StrictModeDroppable as Droppable } from "~/components/StrictModeDroppab
|
||||
import { Tooltip } from "~/components/Tooltip";
|
||||
import { EditYouTubeModal } from "~/components/YouTubeEmbed/EditYouTubeModal";
|
||||
import { useDragToScroll } from "~/hooks/useDragToScroll";
|
||||
import { useScrollRestore } from "~/hooks/useScrollRestore";
|
||||
import { usePermissions } from "~/hooks/usePermissions";
|
||||
import { useScrollRestore } from "~/hooks/useScrollRestore";
|
||||
import { useKeyboardShortcut } from "~/providers/keyboard-shortcuts";
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { useWorkspace } from "~/providers/workspace";
|
||||
import { api } from "~/utils/api";
|
||||
import { formatToArray } from "~/utils/helpers";
|
||||
import { DeleteCardConfirmation } from "~/views/card/components/DeleteCardConfirmation";
|
||||
import BoardDropdown from "./components/BoardDropdown";
|
||||
import Card from "./components/Card";
|
||||
import { CardContextDueDateModal } from "./components/CardContextDueDateModal";
|
||||
import { CardContextDuplicateModal } from "./components/CardContextDuplicateModal";
|
||||
import { CardContextLabelsModal } from "./components/CardContextLabelsModal";
|
||||
import { CardContextMembersModal } from "./components/CardContextMembersModal";
|
||||
import { CardContextMenu } from "./components/CardContextMenu";
|
||||
import { CardContextMoveListModal } from "./components/CardContextMoveListModal";
|
||||
import { DeleteBoardConfirmation } from "./components/DeleteBoardConfirmation";
|
||||
import { DeleteListConfirmation } from "./components/DeleteListConfirmation";
|
||||
import Filters from "./components/Filters";
|
||||
@@ -55,11 +63,18 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
const utils = api.useUtils();
|
||||
const { showPopup } = usePopup();
|
||||
const { workspace } = useWorkspace();
|
||||
const { openModal, modalContentType, entityId, isOpen } = useModal();
|
||||
const { openModal, modalContentType, entityId, isOpen, setModalState } =
|
||||
useModal();
|
||||
const [selectedPublicListId, setSelectedPublicListId] =
|
||||
useState<PublicListId>("");
|
||||
const [isInitialLoading, setIsInitialLoading] = useState(true);
|
||||
|
||||
const [contextMenu, setContextMenu] = useState<{
|
||||
x: number;
|
||||
y: number;
|
||||
cardPublicId: string;
|
||||
} | null>(null);
|
||||
|
||||
const { ref: scrollRef, onMouseDown } = useDragToScroll({
|
||||
enabled: true,
|
||||
direction: "horizontal",
|
||||
@@ -134,7 +149,10 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
// Redirect to 404 if board doesn't exist
|
||||
useEffect(() => {
|
||||
if (router.isReady && boardId && !isQueryLoading) {
|
||||
if (error?.data?.code === "NOT_FOUND" || (!boardData && !isQueryLoading)) {
|
||||
if (
|
||||
error?.data?.code === "NOT_FOUND" ||
|
||||
(!boardData && !isQueryLoading)
|
||||
) {
|
||||
router.replace("/404");
|
||||
}
|
||||
}
|
||||
@@ -152,7 +170,12 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
|
||||
const isLoading = isInitialLoading || isQueryLoading;
|
||||
|
||||
useScrollRestore(boardId, scrollRef, router, !isLoading && (boardData?.lists.length ?? 0) > 0);
|
||||
useScrollRestore(
|
||||
boardId,
|
||||
scrollRef,
|
||||
router,
|
||||
!isLoading && (boardData?.lists.length ?? 0) > 0,
|
||||
);
|
||||
|
||||
const updateListMutation = api.list.update.useMutation({
|
||||
onMutate: async (args) => {
|
||||
@@ -267,6 +290,56 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
setSelectedPublicListId(publicBoardId);
|
||||
};
|
||||
|
||||
const handleCardContextMenuAction = (action: CardContextMenuAction) => {
|
||||
const cardPublicId = contextMenu?.cardPublicId;
|
||||
if (!cardPublicId) return;
|
||||
setContextMenu(null);
|
||||
if (action === "copyLink") {
|
||||
const path = isTemplate
|
||||
? `/templates/${boardId}/cards/${cardPublicId}`
|
||||
: `/cards/${cardPublicId}`;
|
||||
const url = `${typeof window !== "undefined" ? window.location.origin : ""}${path}`;
|
||||
void navigator.clipboard.writeText(url).then(
|
||||
() => {
|
||||
showPopup({
|
||||
header: t`Link copied`,
|
||||
icon: "success",
|
||||
message: t`Card URL copied to clipboard`,
|
||||
});
|
||||
},
|
||||
() => {
|
||||
showPopup({
|
||||
header: t`Unable to copy link`,
|
||||
icon: "error",
|
||||
message: t`Please try again.`,
|
||||
});
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (action === "duplicate") {
|
||||
setModalState("CARD_CONTEXT_DUPLICATE", {
|
||||
boardPublicId: boardId ?? "",
|
||||
isTemplate: !!isTemplate,
|
||||
});
|
||||
openModal("CARD_CONTEXT_DUPLICATE", cardPublicId);
|
||||
return;
|
||||
}
|
||||
if (action === "delete") {
|
||||
openModal("DELETE_CARD", cardPublicId);
|
||||
return;
|
||||
}
|
||||
const modalType =
|
||||
action === "members"
|
||||
? "CARD_CONTEXT_MEMBERS"
|
||||
: action === "move"
|
||||
? "CARD_CONTEXT_MOVE_LIST"
|
||||
: action === "labels"
|
||||
? "CARD_CONTEXT_LABELS"
|
||||
: "CARD_CONTEXT_DUE_DATE";
|
||||
openModal(modalType, cardPublicId);
|
||||
};
|
||||
|
||||
const onDragEnd = ({
|
||||
source: _source,
|
||||
destination,
|
||||
@@ -403,6 +476,49 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
>
|
||||
<EditYouTubeModal />
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
modalSize="sm"
|
||||
isVisible={isOpen && modalContentType === "CARD_CONTEXT_MEMBERS"}
|
||||
>
|
||||
<CardContextMembersModal />
|
||||
</Modal>
|
||||
<Modal
|
||||
modalSize="sm"
|
||||
isVisible={isOpen && modalContentType === "CARD_CONTEXT_MOVE_LIST"}
|
||||
>
|
||||
<CardContextMoveListModal />
|
||||
</Modal>
|
||||
<Modal
|
||||
modalSize="sm"
|
||||
isVisible={isOpen && modalContentType === "CARD_CONTEXT_LABELS"}
|
||||
>
|
||||
<CardContextLabelsModal />
|
||||
</Modal>
|
||||
<Modal
|
||||
modalSize="sm"
|
||||
isVisible={isOpen && modalContentType === "CARD_CONTEXT_DUE_DATE"}
|
||||
>
|
||||
<CardContextDueDateModal />
|
||||
</Modal>
|
||||
<Modal
|
||||
modalSize="md"
|
||||
isVisible={isOpen && modalContentType === "CARD_CONTEXT_DUPLICATE"}
|
||||
>
|
||||
<CardContextDuplicateModal
|
||||
boardPublicId={boardId ?? ""}
|
||||
isTemplate={!!isTemplate}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
modalSize="sm"
|
||||
isVisible={isOpen && modalContentType === "DELETE_CARD"}
|
||||
>
|
||||
<DeleteCardConfirmation
|
||||
cardPublicId={entityId}
|
||||
boardPublicId={boardId ?? ""}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -605,18 +721,33 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
)
|
||||
e.preventDefault();
|
||||
}}
|
||||
onContextMenu={(e) => {
|
||||
if (
|
||||
card.publicId.startsWith(
|
||||
"PLACEHOLDER",
|
||||
)
|
||||
)
|
||||
return;
|
||||
e.preventDefault();
|
||||
setContextMenu({
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
cardPublicId: card.publicId,
|
||||
});
|
||||
}}
|
||||
key={card.publicId}
|
||||
href={
|
||||
isTemplate
|
||||
? `/templates/${boardId}/cards/${card.publicId}`
|
||||
: `/cards/${card.publicId}`
|
||||
}
|
||||
className={`mb-2 flex !cursor-pointer flex-col ${card.publicId.startsWith(
|
||||
"PLACEHOLDER",
|
||||
)
|
||||
? "pointer-events-none"
|
||||
: ""
|
||||
}`}
|
||||
className={`mb-2 flex !cursor-pointer flex-col ${
|
||||
card.publicId.startsWith(
|
||||
"PLACEHOLDER",
|
||||
)
|
||||
? "pointer-events-none"
|
||||
: ""
|
||||
}`}
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
@@ -653,6 +784,15 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
{contextMenu && (
|
||||
<CardContextMenu
|
||||
x={contextMenu.x}
|
||||
y={contextMenu.y}
|
||||
onClose={() => setContextMenu(null)}
|
||||
onAction={handleCardContextMenuAction}
|
||||
canEdit={!!canEditCard}
|
||||
/>
|
||||
)}
|
||||
{renderModalContent()}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -78,6 +78,7 @@ export default function LabelSelector({
|
||||
},
|
||||
onSettled: async () => {
|
||||
await invalidateCard(utils, cardPublicId);
|
||||
await utils.board.byId.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ export default function ListSelector({
|
||||
},
|
||||
onSettled: async () => {
|
||||
await invalidateCard(utils, cardPublicId);
|
||||
await utils.board.byId.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ export default function MemberSelector({
|
||||
},
|
||||
onSettled: async () => {
|
||||
await invalidateCard(utils, cardPublicId);
|
||||
await utils.board.byId.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
289
apps/web/src/views/onboarding/select-plan/index.tsx
Normal file
289
apps/web/src/views/onboarding/select-plan/index.tsx
Normal file
@@ -0,0 +1,289 @@
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { Radio, RadioGroup } from "@headlessui/react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
import { HiUser } from "react-icons/hi2";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { authClient } from "@kan/auth/client";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
type PlanId = "solo" | "team" | "pro";
|
||||
type Billing = "monthly" | "annual";
|
||||
|
||||
// Each user orbits via a zero-size pivot div at center (144,144) that rotates.
|
||||
// The icon is offset from the pivot by `radius` pixels along the x-axis.
|
||||
// A counter-rotation on the icon keeps it upright.
|
||||
const ORBIT_USERS: Record<
|
||||
PlanId,
|
||||
{ id: string; angle: number; radius: number; duration: number }[]
|
||||
> = {
|
||||
solo: [],
|
||||
team: [
|
||||
{ id: "t1", angle: 45, radius: 104, duration: 50 },
|
||||
{ id: "t2", angle: 170, radius: 104, duration: 50 },
|
||||
{ id: "t3", angle: 290, radius: 104, duration: 50 },
|
||||
],
|
||||
pro: [
|
||||
{ id: "t1", angle: 45, radius: 104, duration: 50 },
|
||||
{ id: "t2", angle: 170, radius: 104, duration: 50 },
|
||||
{ id: "t3", angle: 290, radius: 104, duration: 50 },
|
||||
{ id: "p1", angle: 10, radius: 144, duration: 70 },
|
||||
{ id: "p2", angle: 120, radius: 144, duration: 70 },
|
||||
{ id: "p4", angle: 240, radius: 144, duration: 70 },
|
||||
{ id: "p3", angle: 230, radius: 64, duration: 30 },
|
||||
],
|
||||
};
|
||||
|
||||
export default function SelectPlanView() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [selected, setSelected] = useState<PlanId>(
|
||||
(searchParams.get("plan") as PlanId | null) ?? "solo",
|
||||
);
|
||||
const [billing, setBilling] = useState<Billing>(
|
||||
(searchParams.get("billing") as Billing | null) ?? "annual",
|
||||
);
|
||||
const returnUrl = searchParams.get("returnUrl") ?? "/boards";
|
||||
const { data: workspaces } = api.workspace.all.useQuery();
|
||||
const { data: session } = authClient.useSession();
|
||||
const { data: user } = api.user.getUser.useQuery(undefined, {
|
||||
enabled: !!session?.user,
|
||||
});
|
||||
|
||||
const userImage = user?.image ?? session?.user.image ?? null;
|
||||
|
||||
const hasExistingWorkspace = !!workspaces?.length;
|
||||
|
||||
const FREQUENCIES: { value: Billing; label: string }[] = [
|
||||
{ value: "monthly", label: t`Monthly` },
|
||||
{ value: "annual", label: t`Annual` },
|
||||
];
|
||||
|
||||
const PLANS: {
|
||||
id: PlanId;
|
||||
name: string;
|
||||
monthly: string;
|
||||
annual: string;
|
||||
description: string;
|
||||
trial?: boolean;
|
||||
}[] = [
|
||||
{
|
||||
id: "solo",
|
||||
name: t`Solo`,
|
||||
monthly: t`Free`,
|
||||
annual: t`Free`,
|
||||
description: t`Good for individuals starting out who just need the essentials.`,
|
||||
},
|
||||
{
|
||||
id: "team",
|
||||
name: t`Team`,
|
||||
monthly: "$10/user/mo",
|
||||
annual: "$8/user/mo",
|
||||
description: t`Best for small teams who want to collaborate and move faster together.`,
|
||||
trial: true,
|
||||
},
|
||||
{
|
||||
id: "pro",
|
||||
name: t`Pro`,
|
||||
monthly: "$29/mo",
|
||||
annual: "$23/mo",
|
||||
description: t`Unlimited members and a custom workspace username for teams ready to scale.`,
|
||||
trial: true,
|
||||
},
|
||||
];
|
||||
|
||||
const handleSelectPlan = (plan: PlanId) => {
|
||||
setSelected(plan);
|
||||
router.replace(`/onboarding/select-plan?plan=${plan}&billing=${billing}&returnUrl=${encodeURIComponent(returnUrl)}`);
|
||||
};
|
||||
|
||||
const handleSetBilling = (b: Billing) => {
|
||||
setBilling(b);
|
||||
router.replace(`/onboarding/select-plan?plan=${selected}&billing=${b}&returnUrl=${encodeURIComponent(returnUrl)}`);
|
||||
};
|
||||
|
||||
const handleContinue = () =>
|
||||
router.push(`/onboarding/workspace?plan=${selected}&billing=${billing}&returnUrl=${encodeURIComponent(returnUrl)}`);
|
||||
|
||||
const handleCancel = () => router.push(returnUrl);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center gap-4 bg-light-100 px-4 py-8 dark:bg-dark-50 md:px-6">
|
||||
<div className="w-full max-w-3xl overflow-hidden rounded-xl border border-light-400 bg-light-200 shadow-xl dark:border-dark-400 dark:bg-dark-100">
|
||||
<div className="flex flex-col md:h-[520px] md:flex-row">
|
||||
{/* Left panel */}
|
||||
<div className="flex flex-col p-6 md:w-[55%] md:p-8">
|
||||
<div className="flex-1">
|
||||
<h2 className="text-xl font-bold text-light-1000 dark:text-dark-1000">
|
||||
{t`Choose a plan`}
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-light-800 dark:text-dark-800">
|
||||
{t`Pick a plan to get started. All paid plans include a 14-day free trial.`}
|
||||
</p>
|
||||
|
||||
{/* Billing toggle */}
|
||||
<div className="mt-4 flex justify-end">
|
||||
<RadioGroup
|
||||
value={billing}
|
||||
onChange={handleSetBilling}
|
||||
className="grid grid-cols-2 gap-x-1 rounded-full p-1 text-center text-xs font-semibold ring-1 ring-inset ring-light-600 dark:ring-dark-600"
|
||||
>
|
||||
{FREQUENCIES.map((f) => (
|
||||
<Radio
|
||||
key={f.value}
|
||||
value={f.value}
|
||||
className={twMerge(
|
||||
"cursor-pointer rounded-full px-2.5 py-0.5 text-xs transition-colors",
|
||||
billing === f.value
|
||||
? "bg-dark-50 text-white dark:bg-light-50 dark:text-dark-50"
|
||||
: "text-light-900 hover:bg-light-200 dark:text-dark-900 dark:hover:bg-dark-200",
|
||||
)}
|
||||
>
|
||||
{f.label}
|
||||
</Radio>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 space-y-3">
|
||||
{PLANS.map((plan) => {
|
||||
const badge =
|
||||
billing === "annual" ? plan.annual : plan.monthly;
|
||||
return (
|
||||
<button
|
||||
key={plan.id}
|
||||
type="button"
|
||||
onClick={() => handleSelectPlan(plan.id)}
|
||||
className={`relative w-full rounded-lg border px-4 py-3 text-left transition-colors ${
|
||||
selected === plan.id
|
||||
? "border-light-700 bg-light-300 dark:border-dark-600 dark:bg-dark-200"
|
||||
: "border-light-500 bg-light-200 hover:border-light-600 dark:border-dark-500 dark:bg-dark-100 dark:hover:border-dark-600"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1 pr-8">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-semibold text-light-1000 dark:text-dark-1000">
|
||||
{plan.name}
|
||||
</span>
|
||||
<span className="rounded-full bg-neutral-700 px-2 py-px text-[11px] font-medium text-neutral-200">
|
||||
{badge}
|
||||
</span>
|
||||
{plan.trial && billing === "annual" && (
|
||||
<span className="rounded-full bg-emerald-500/10 px-2 py-px text-[11px] font-medium text-emerald-600 ring-1 ring-inset ring-emerald-500/20 dark:text-emerald-400">
|
||||
-20%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-light-800 dark:text-dark-800">
|
||||
{plan.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-0.5 flex-shrink-0">
|
||||
<div
|
||||
className={`flex h-4 w-4 items-center justify-center rounded-full border-2 ${
|
||||
selected === plan.id
|
||||
? "border-light-900 bg-light-900 dark:border-dark-900 dark:bg-dark-900"
|
||||
: "border-light-700 dark:border-dark-700"
|
||||
}`}
|
||||
>
|
||||
{selected === plan.id && (
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-light-100 dark:bg-dark-100" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-end gap-2 md:mt-8">
|
||||
{hasExistingWorkspace && (
|
||||
<Button variant="ghost" onClick={handleCancel}>
|
||||
{t`Cancel`}
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={handleContinue}>{t`Continue`}</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right panel */}
|
||||
<div className="hidden items-center justify-center bg-light-200 dark:bg-dark-200 md:flex md:w-[45%]">
|
||||
<div className="relative h-72 w-72">
|
||||
{/* Orbit rings */}
|
||||
<div className="absolute inset-0 m-auto h-72 w-72 rounded-full border border-light-400 dark:border-dark-400" />
|
||||
<div className="absolute inset-0 m-auto h-52 w-52 rounded-full border border-light-400 dark:border-dark-400" />
|
||||
<div className="absolute inset-0 m-auto h-32 w-32 rounded-full border border-light-400 dark:border-dark-400" />
|
||||
|
||||
{/* Centre person */}
|
||||
<div className="absolute inset-0 m-auto flex h-16 w-16 items-center justify-center overflow-hidden rounded-full bg-light-300 dark:bg-dark-300">
|
||||
{userImage ? (
|
||||
<img
|
||||
src={userImage}
|
||||
alt={t`Your avatar`}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<HiUser className="h-7 w-7 text-light-700 dark:text-dark-700" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Orbiting users — zero-size pivot at center rotates; icon offset by radius stays on the ring */}
|
||||
<AnimatePresence>
|
||||
{ORBIT_USERS[selected].map((u) => (
|
||||
<motion.div
|
||||
key={u.id}
|
||||
style={{ position: "absolute", left: 144, top: 144 }}
|
||||
initial={{ opacity: 0, rotate: u.angle }}
|
||||
animate={{ opacity: 1, rotate: u.angle + 360 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{
|
||||
opacity: { duration: 0.3 },
|
||||
rotate: {
|
||||
duration: u.duration,
|
||||
repeat: Infinity,
|
||||
ease: "linear",
|
||||
repeatType: "loop",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Counter-rotate icon so it stays upright */}
|
||||
<motion.div
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: u.radius - 12,
|
||||
top: -12,
|
||||
}}
|
||||
initial={{ rotate: -u.angle }}
|
||||
animate={{ rotate: -(u.angle + 360) }}
|
||||
transition={{
|
||||
duration: u.duration,
|
||||
repeat: Infinity,
|
||||
ease: "linear",
|
||||
repeatType: "loop",
|
||||
}}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-full bg-light-400 dark:bg-dark-400"
|
||||
>
|
||||
<HiUser className="h-3.5 w-3.5 text-light-800 dark:text-dark-800" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!hasExistingWorkspace && (
|
||||
<Button variant="ghost" onClick={() => authClient.signOut()}>
|
||||
{t`Sign out`}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
367
apps/web/src/views/onboarding/workspace-details/index.tsx
Normal file
367
apps/web/src/views/onboarding/workspace-details/index.tsx
Normal file
@@ -0,0 +1,367 @@
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { motion } from "framer-motion";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
HiArrowLeft,
|
||||
HiArrowPath,
|
||||
HiArrowRight,
|
||||
HiCheck,
|
||||
HiEllipsisVertical,
|
||||
HiInformationCircle,
|
||||
HiLockClosed,
|
||||
} from "react-icons/hi2";
|
||||
|
||||
import { authClient } from "@kan/auth/client";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import Input from "~/components/Input";
|
||||
import LoadingSpinner from "~/components/LoadingSpinner";
|
||||
import Toggle from "~/components/Toggle";
|
||||
import { Tooltip } from "~/components/Tooltip";
|
||||
import { useDebounce } from "~/hooks/useDebounce";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
function slugify(value: string) {
|
||||
return value
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9\s-]/g, "")
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/-+/g, "-")
|
||||
.slice(0, 60);
|
||||
}
|
||||
|
||||
export default function WorkspaceNameView() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const plan = searchParams.get("plan") ?? "solo";
|
||||
const billing = searchParams.get("billing") ?? "annual";
|
||||
const returnUrl = searchParams.get("returnUrl") ?? "/boards";
|
||||
const { showPopup } = usePopup();
|
||||
const [isProToggle, setIsProToggle] = useState(plan === "pro");
|
||||
|
||||
const [name, setName] = useState("");
|
||||
const [slug, setSlug] = useState("");
|
||||
const [slugManuallyEdited, setSlugManuallyEdited] = useState(false);
|
||||
const [description, setDescription] = useState("");
|
||||
|
||||
const handleNameChange = (value: string) => {
|
||||
setName(value);
|
||||
if (isProToggle && !slugManuallyEdited) {
|
||||
setSlug(slugify(value));
|
||||
}
|
||||
};
|
||||
|
||||
const [debouncedSlug] = useDebounce(slug, 500);
|
||||
const isTyping = slug !== debouncedSlug;
|
||||
|
||||
const slugAvailability = api.workspace.checkSlugAvailability.useQuery(
|
||||
{ workspaceSlug: debouncedSlug },
|
||||
{ enabled: isProToggle && debouncedSlug.length >= 3 && !isTyping },
|
||||
);
|
||||
|
||||
const isSlugAvailable =
|
||||
slugAvailability.data?.isAvailable && !slugAvailability.data?.isReserved;
|
||||
const isSlugTaken =
|
||||
slugAvailability.data?.isAvailable === false &&
|
||||
!slugAvailability.data?.isReserved;
|
||||
const isSlugReserved = slugAvailability.data?.isReserved === true;
|
||||
|
||||
const slugError = isSlugTaken
|
||||
? t`This URL has already been taken`
|
||||
: isSlugReserved
|
||||
? t`This URL is reserved`
|
||||
: undefined;
|
||||
|
||||
const { data: session } = authClient.useSession();
|
||||
const { data: user } = api.user.getUser.useQuery(undefined, {
|
||||
enabled: !!session?.user,
|
||||
});
|
||||
const { data: workspaces } = api.workspace.all.useQuery();
|
||||
const hasExistingWorkspace = !!workspaces?.length;
|
||||
|
||||
const utils = api.useUtils();
|
||||
|
||||
const previewSlug = isProToggle
|
||||
? slugify(slug) || slugify(name) || "your-workspace"
|
||||
: "your-workspace";
|
||||
|
||||
const createWorkspace = api.workspace.create.useMutation({
|
||||
onSuccess: async (workspace) => {
|
||||
if (!workspace.publicId) return;
|
||||
localStorage.setItem("workspacePublicId", workspace.publicId);
|
||||
void utils.workspace.all.invalidate();
|
||||
router.push("/boards");
|
||||
},
|
||||
onError: () => {
|
||||
showPopup({
|
||||
header: t`Unable to create workspace`,
|
||||
message: t`Please try again later, or contact customer support.`,
|
||||
icon: "error",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [isRedirectingToCheckout, setIsRedirectingToCheckout] = useState(false);
|
||||
|
||||
const handleContinue = async () => {
|
||||
if (!name.trim()) return;
|
||||
|
||||
if (plan === "solo") {
|
||||
createWorkspace.mutate({
|
||||
name: name.trim(),
|
||||
...(description.trim() && { description: description.trim() }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// team/pro: redirect to Stripe — workspace created on checkout_success
|
||||
setIsRedirectingToCheckout(true);
|
||||
try {
|
||||
const response = await fetch("/api/stripe/create_checkout_session", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
workspaceName: name.trim(),
|
||||
...(description.trim() && {
|
||||
workspaceDescription: description.trim(),
|
||||
}),
|
||||
...(plan === "pro" && slug ? { workspaceSlug: slug } : {}),
|
||||
cancelUrl: window.location.pathname + window.location.search,
|
||||
successUrl: "/boards",
|
||||
billing,
|
||||
plan,
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
const url = (data as { url: string }).url;
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
setIsRedirectingToCheckout(false);
|
||||
showPopup({
|
||||
header: t`Unable to start checkout`,
|
||||
message: t`Please try again later, or contact customer support.`,
|
||||
icon: "error",
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.getElementById("workspace-name-input")?.focus();
|
||||
}, []);
|
||||
|
||||
const displayName = user?.name ?? session?.user.name ?? "";
|
||||
|
||||
const BOARDS = [t`Roadmap`, t`Engineering`, t`Marketing`];
|
||||
const [visibleCount, setVisibleCount] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
const timers = BOARDS.slice(1).map((_, i) =>
|
||||
setTimeout(() => setVisibleCount(i + 2), (i + 1) * 2000),
|
||||
);
|
||||
return () => timers.forEach(clearTimeout);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center gap-4 bg-light-100 px-4 py-8 dark:bg-dark-50 md:px-6">
|
||||
<div className="w-full max-w-3xl overflow-hidden rounded-xl border border-light-400 bg-light-200 shadow-xl dark:border-dark-400 dark:bg-dark-100">
|
||||
<div className="flex flex-col md:h-[520px] md:flex-row">
|
||||
{/* Left panel */}
|
||||
<div className="flex flex-col p-6 md:w-[55%] md:p-8">
|
||||
<div className="flex-1">
|
||||
<h2 className="text-xl font-bold text-light-1000 dark:text-dark-1000">
|
||||
{t`Set up your workspace`}
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-light-800 dark:text-dark-800">
|
||||
{t`You can always change these later in settings.`}
|
||||
</p>
|
||||
|
||||
<div className="mt-6 space-y-3">
|
||||
<Input
|
||||
id="workspace-name-input"
|
||||
placeholder={t`Workspace name`}
|
||||
value={name}
|
||||
onChange={(e) => handleNameChange(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleContinue()}
|
||||
maxLength={64}
|
||||
/>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Input
|
||||
placeholder={t`your-workspace`}
|
||||
value={isProToggle ? slug : t`your-workspace`}
|
||||
onChange={(e) => {
|
||||
setSlugManuallyEdited(true);
|
||||
setSlug(
|
||||
e.target.value
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, "")
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/-+/g, "-")
|
||||
.slice(0, 60),
|
||||
);
|
||||
}}
|
||||
disabled={!isProToggle}
|
||||
prefix="kan.bn/"
|
||||
className={
|
||||
!isProToggle ? "cursor-not-allowed opacity-50" : ""
|
||||
}
|
||||
errorMessage={slugError}
|
||||
iconRight={
|
||||
!isProToggle ? (
|
||||
<Tooltip
|
||||
content={
|
||||
<span className="text-xs">{t`Custom usernames require upgrading to a Pro plan`}</span>
|
||||
}
|
||||
placement="top"
|
||||
delay={0}
|
||||
>
|
||||
<HiInformationCircle className="h-4 w-4 leading-[0] text-dark-700 dark:text-dark-700" />
|
||||
</Tooltip>
|
||||
) : isProToggle && slug.length >= 3 ? (
|
||||
isTyping || slugAvailability.isPending ? (
|
||||
<LoadingSpinner />
|
||||
) : isSlugAvailable ? (
|
||||
<HiCheck className="h-4 w-4 text-white" />
|
||||
) : null
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{plan !== "pro" && (
|
||||
<div className="pb-2">
|
||||
<Toggle
|
||||
isChecked={isProToggle}
|
||||
onChange={() => setIsProToggle((v) => !v)}
|
||||
label={t`Upgrade to Pro ($29/month)`}
|
||||
labelPosition="after"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder={t`Workspace description`}
|
||||
maxLength={280}
|
||||
rows={3}
|
||||
className="block w-full resize-none rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 text-sm shadow-sm ring-1 ring-inset ring-light-600 placeholder:text-dark-800 focus:ring-2 focus:ring-inset focus:ring-light-700 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:leading-6"
|
||||
/>
|
||||
<p className="mt-1 text-right text-[10px] text-light-700 dark:text-dark-700">
|
||||
{description.length}/280
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="ml-auto flex gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() =>
|
||||
router.replace(
|
||||
`/onboarding/select-plan?plan=${plan}&billing=${billing}&returnUrl=${encodeURIComponent(returnUrl)}`,
|
||||
)
|
||||
}
|
||||
>
|
||||
{t`Back`}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => void handleContinue()}
|
||||
disabled={
|
||||
!name.trim() ||
|
||||
createWorkspace.isPending ||
|
||||
isRedirectingToCheckout ||
|
||||
(isProToggle &&
|
||||
slug.length >= 3 &&
|
||||
(isTyping || slugAvailability.isPending || !!slugError))
|
||||
}
|
||||
isLoading={
|
||||
createWorkspace.isPending || isRedirectingToCheckout
|
||||
}
|
||||
>
|
||||
{t`Continue`}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right panel — browser mockup */}
|
||||
<div className="hidden flex-col bg-light-300 dark:bg-dark-200 md:flex md:w-[45%]">
|
||||
{/* Browser chrome */}
|
||||
<div className="flex items-center gap-2 border-b border-light-400 px-3 py-2.5 dark:border-dark-400">
|
||||
<div className="flex items-center gap-1.5 text-light-700 dark:text-dark-700">
|
||||
<HiArrowLeft className="h-3.5 w-3.5" />
|
||||
<HiArrowRight className="h-3.5 w-3.5" />
|
||||
<HiArrowPath className="h-3.5 w-3.5" />
|
||||
</div>
|
||||
<div className="flex flex-1 items-center gap-1.5 rounded-md bg-light-200 px-2.5 py-1 dark:bg-dark-300">
|
||||
<HiLockClosed className="h-3 w-3 flex-shrink-0 text-light-700 dark:text-dark-700" />
|
||||
<span className="truncate text-xs text-light-900 dark:text-dark-900">
|
||||
kan.bn/
|
||||
<span className="text-light-1000 dark:text-dark-1000">
|
||||
{previewSlug}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<HiEllipsisVertical className="h-4 w-4 flex-shrink-0 text-light-700 dark:text-dark-700" />
|
||||
</div>
|
||||
|
||||
{/* Browser content */}
|
||||
<div className="flex flex-1 flex-col items-center overflow-hidden px-14 py-8">
|
||||
{/* Workspace name + description */}
|
||||
<p className="text-center text-xs font-bold text-light-1000 dark:text-dark-1000">
|
||||
{name || t`Your workspace`}
|
||||
</p>
|
||||
<p className="mt-0.5 line-clamp-2 break-all text-center text-[10px] text-light-800 dark:text-dark-800">
|
||||
{description || t`Your workspace description`}
|
||||
</p>
|
||||
|
||||
{/* Boards area */}
|
||||
<div className="mt-3 flex w-full flex-1 flex-col rounded-lg bg-light-200 p-2 dark:bg-dark-300">
|
||||
<div className="flex flex-1 flex-col gap-1.5">
|
||||
{BOARDS.map((board, i) => (
|
||||
<motion.div
|
||||
key={board}
|
||||
initial={{ opacity: 0, y: -12 }}
|
||||
animate={
|
||||
i < visibleCount
|
||||
? { opacity: 1, y: 0 }
|
||||
: { opacity: 0, y: -12 }
|
||||
}
|
||||
transition={{
|
||||
opacity: { duration: 0.2 },
|
||||
y: { duration: 0.25, ease: "easeOut" },
|
||||
}}
|
||||
className="flex w-full flex-1 items-center justify-center rounded border border-dashed border-light-400 bg-light-50 px-3 text-[10px] font-medium text-light-900 dark:border-dark-400 dark:bg-dark-100 dark:text-dark-900"
|
||||
>
|
||||
{board}
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<p className="mt-2 text-[10px] font-semibold text-light-900 dark:text-dark-900">
|
||||
kan.bn
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!hasExistingWorkspace && (
|
||||
<Button variant="ghost" onClick={() => authClient.signOut()}>
|
||||
{t`Sign out`}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -110,18 +110,17 @@ export default function Avatar({
|
||||
const cropYpx = (crop.y / 100) * image.naturalHeight;
|
||||
const cropWpx = (crop.width / 100) * image.naturalWidth;
|
||||
const cropHpx = (crop.height / 100) * image.naturalHeight;
|
||||
canvas.width = Math.max(1, Math.floor(cropWpx));
|
||||
canvas.height = Math.max(1, Math.floor(cropHpx));
|
||||
|
||||
// Cap output at 512x512 — avatars display at 64x64, so higher res is wasteful
|
||||
// and causes "File too large" errors on HiDPI screens
|
||||
const maxSize = 512;
|
||||
const scale = Math.min(maxSize / cropWpx, maxSize / cropHpx, 1);
|
||||
canvas.width = Math.max(1, Math.floor(cropWpx * scale));
|
||||
canvas.height = Math.max(1, Math.floor(cropHpx * scale));
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) throw new Error("Canvas not supported");
|
||||
|
||||
// For better quality on HiDPI screens
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
canvas.width = canvas.width * pixelRatio;
|
||||
canvas.height = canvas.height * pixelRatio;
|
||||
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
||||
ctx.imageSmoothingQuality = "high";
|
||||
|
||||
ctx.drawImage(
|
||||
image,
|
||||
cropXpx,
|
||||
@@ -130,8 +129,8 @@ export default function Avatar({
|
||||
cropHpx,
|
||||
0,
|
||||
0,
|
||||
canvas.width / pixelRatio,
|
||||
canvas.height / pixelRatio,
|
||||
canvas.width,
|
||||
canvas.height,
|
||||
);
|
||||
|
||||
const mime = selectedFile?.type ?? "image/jpeg";
|
||||
@@ -139,6 +138,7 @@ export default function Avatar({
|
||||
canvas.toBlob(
|
||||
(b) => (b ? resolve(b) : reject(new Error("toBlob failed"))),
|
||||
mime,
|
||||
0.85,
|
||||
);
|
||||
});
|
||||
return blob;
|
||||
|
||||
@@ -39,6 +39,8 @@ services:
|
||||
|
||||
# Logging (optional - debug, info, warn, error; defaults to debug in dev, info in prod)
|
||||
- LOG_LEVEL=${LOG_LEVEL}
|
||||
- AXIOM_TOKEN=${AXIOM_TOKEN}
|
||||
- AXIOM_DATASET=${AXIOM_DATASET}
|
||||
|
||||
# Stripe
|
||||
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
"types": "./dist/utils/rateLimit.d.ts",
|
||||
"default": "./src/utils/rateLimit.ts"
|
||||
},
|
||||
"./utils/apiLogging": {
|
||||
"types": "./dist/utils/apiLogging.d.ts",
|
||||
"default": "./src/utils/apiLogging.ts"
|
||||
},
|
||||
"./utils/permissions": {
|
||||
"types": "./dist/utils/permissions.d.ts",
|
||||
"default": "./src/utils/permissions.ts"
|
||||
|
||||
@@ -227,7 +227,7 @@ export const boardRouter = createTRPCRouter({
|
||||
boardSlug: z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(24)
|
||||
.max(60)
|
||||
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/),
|
||||
members: z.array(z.string().min(12)).optional(),
|
||||
labels: z.array(z.string().min(12)).optional(),
|
||||
@@ -657,7 +657,7 @@ export const boardRouter = createTRPCRouter({
|
||||
boardSlug: z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(24)
|
||||
.max(60)
|
||||
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/),
|
||||
boardPublicId: z.string().min(12),
|
||||
}),
|
||||
|
||||
@@ -4,6 +4,7 @@ import { z } from "zod";
|
||||
import * as cardRepo from "@kan/db/repository/card.repo";
|
||||
import * as cardActivityRepo from "@kan/db/repository/cardActivity.repo";
|
||||
import * as cardCommentRepo from "@kan/db/repository/cardComment.repo";
|
||||
import * as checklistRepo from "@kan/db/repository/checklist.repo";
|
||||
import * as labelRepo from "@kan/db/repository/label.repo";
|
||||
import * as listRepo from "@kan/db/repository/list.repo";
|
||||
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
|
||||
@@ -1184,4 +1185,176 @@ export const cardRouter = createTRPCRouter({
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
duplicate: protectedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
summary: "Duplicate a card",
|
||||
method: "POST",
|
||||
path: "/cards/{cardPublicId}/duplicate",
|
||||
description: "Duplicates a card to a target list with optional options",
|
||||
tags: ["Cards"],
|
||||
protect: true,
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
cardPublicId: z.string().min(12),
|
||||
listPublicId: z.string().min(12),
|
||||
index: z.number().int().min(0).optional(),
|
||||
title: z.string().min(1).max(2000).optional(),
|
||||
copyLabels: z.boolean(),
|
||||
copyMembers: z.boolean(),
|
||||
copyChecklists: z.boolean(),
|
||||
}),
|
||||
)
|
||||
.output(
|
||||
z.object({
|
||||
publicId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const userId = ctx.user?.id;
|
||||
|
||||
if (!userId)
|
||||
throw new TRPCError({
|
||||
message: `User not authenticated`,
|
||||
code: "UNAUTHORIZED",
|
||||
});
|
||||
|
||||
const sourceCardMeta = await cardRepo.getWorkspaceAndCardIdByCardPublicId(
|
||||
ctx.db,
|
||||
input.cardPublicId,
|
||||
);
|
||||
|
||||
if (!sourceCardMeta)
|
||||
throw new TRPCError({
|
||||
message: `Card with public ID ${input.cardPublicId} not found`,
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
|
||||
await assertPermission(
|
||||
ctx.db,
|
||||
userId,
|
||||
sourceCardMeta.workspaceId,
|
||||
"card:create",
|
||||
);
|
||||
|
||||
const targetList = await listRepo.getWorkspaceAndListIdByListPublicId(
|
||||
ctx.db,
|
||||
input.listPublicId,
|
||||
);
|
||||
|
||||
if (!targetList)
|
||||
throw new TRPCError({
|
||||
message: `List with public ID ${input.listPublicId} not found`,
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
|
||||
if (targetList.workspaceId !== sourceCardMeta.workspaceId)
|
||||
throw new TRPCError({
|
||||
message: `Target list must be in the same workspace`,
|
||||
code: "BAD_REQUEST",
|
||||
});
|
||||
|
||||
const sourceCard = await cardRepo.getWithListAndMembersByPublicId(
|
||||
ctx.db,
|
||||
input.cardPublicId,
|
||||
);
|
||||
|
||||
if (!sourceCard)
|
||||
throw new TRPCError({
|
||||
message: `Card with public ID ${input.cardPublicId} not found`,
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
|
||||
const newCard = await cardRepo.create(ctx.db, {
|
||||
title: input.title ?? sourceCard.title,
|
||||
description: sourceCard.description ?? "",
|
||||
createdBy: userId,
|
||||
listId: targetList.id,
|
||||
position: "end",
|
||||
dueDate: sourceCard.dueDate ?? null,
|
||||
});
|
||||
|
||||
if (input.index !== undefined && input.index >= 0) {
|
||||
await cardRepo.reorder(ctx.db, {
|
||||
cardId: newCard.id,
|
||||
newIndex: input.index,
|
||||
newListId: targetList.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (input.copyLabels && sourceCard.labels?.length) {
|
||||
const labelPublicIds = sourceCard.labels.map((l) => l.publicId);
|
||||
const labels = await labelRepo.getAllByPublicIds(ctx.db, labelPublicIds);
|
||||
if (labels.length) {
|
||||
const labelsInsert = labels.map((label) => ({
|
||||
cardId: newCard.id,
|
||||
labelId: label.id,
|
||||
}));
|
||||
await cardRepo.bulkCreateCardLabelRelationships(ctx.db, labelsInsert);
|
||||
const cardActivitesInsert = labels.map((cardLabel) => ({
|
||||
type: "card.updated.label.added" as const,
|
||||
cardId: newCard.id,
|
||||
labelId: cardLabel.id,
|
||||
createdBy: userId,
|
||||
}));
|
||||
await cardActivityRepo.bulkCreate(ctx.db, cardActivitesInsert);
|
||||
}
|
||||
}
|
||||
|
||||
if (input.copyMembers && sourceCard.members?.length) {
|
||||
const memberPublicIds = sourceCard.members.map((m) => m.publicId);
|
||||
const members = await workspaceRepo.getAllMembersByPublicIds(
|
||||
ctx.db,
|
||||
memberPublicIds,
|
||||
);
|
||||
if (members.length) {
|
||||
const membersInsert = members.map((member) => ({
|
||||
cardId: newCard.id,
|
||||
workspaceMemberId: member.id,
|
||||
}));
|
||||
await cardRepo.bulkCreateCardWorkspaceMemberRelationships(
|
||||
ctx.db,
|
||||
membersInsert,
|
||||
);
|
||||
const cardActivitesInsert = members.map((member) => ({
|
||||
type: "card.updated.member.added" as const,
|
||||
cardId: newCard.id,
|
||||
workspaceMemberId: member.id,
|
||||
createdBy: userId,
|
||||
}));
|
||||
await cardActivityRepo.bulkCreate(ctx.db, cardActivitesInsert);
|
||||
}
|
||||
}
|
||||
|
||||
if (input.copyChecklists && sourceCard.checklists?.length) {
|
||||
for (const checklist of sourceCard.checklists) {
|
||||
const newChecklist = await checklistRepo.create(ctx.db, {
|
||||
cardId: newCard.id,
|
||||
name: checklist.name,
|
||||
createdBy: userId,
|
||||
});
|
||||
if (!newChecklist?.id) continue;
|
||||
if (checklist.items?.length) {
|
||||
for (const item of checklist.items) {
|
||||
await checklistRepo.createItem(ctx.db, {
|
||||
checklistId: newChecklist.id,
|
||||
title: item.title,
|
||||
createdBy: userId,
|
||||
completed: item.completed ?? false,
|
||||
});
|
||||
}
|
||||
}
|
||||
await cardActivityRepo.create(ctx.db, {
|
||||
type: "card.updated.checklist.added",
|
||||
cardId: newCard.id,
|
||||
toTitle: newChecklist.name,
|
||||
createdBy: userId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { publicId: newCard.publicId };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -202,6 +202,7 @@ export const workspaceRouter = createTRPCRouter({
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string().min(1).max(64),
|
||||
description: z.string().max(280).optional(),
|
||||
slug: z
|
||||
.string()
|
||||
.min(3)
|
||||
@@ -260,6 +261,7 @@ export const workspaceRouter = createTRPCRouter({
|
||||
slug: workspaceSlug,
|
||||
createdBy: userId,
|
||||
createdByEmail: userEmail,
|
||||
...(input.description && { description: input.description }),
|
||||
});
|
||||
|
||||
if (!result.publicId)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
|
||||
import type { NextApiRequest } from "next";
|
||||
import type { OpenApiMeta } from "trpc-to-openapi";
|
||||
@@ -11,7 +12,28 @@ import { initAuth } from "@kan/auth/server";
|
||||
import { createDrizzleClient } from "@kan/db/client";
|
||||
import { createLogger } from "@kan/logger";
|
||||
|
||||
const log = createLogger("trpc");
|
||||
const log = createLogger("api");
|
||||
|
||||
const TRPC_STATUS_MAP: Partial<Record<TRPCError["code"], number>> = {
|
||||
PARSE_ERROR: 400,
|
||||
BAD_REQUEST: 400,
|
||||
UNAUTHORIZED: 401,
|
||||
FORBIDDEN: 403,
|
||||
NOT_FOUND: 404,
|
||||
METHOD_NOT_SUPPORTED: 405,
|
||||
TIMEOUT: 408,
|
||||
CONFLICT: 409,
|
||||
PRECONDITION_FAILED: 412,
|
||||
PAYLOAD_TOO_LARGE: 413,
|
||||
UNPROCESSABLE_CONTENT: 422,
|
||||
TOO_MANY_REQUESTS: 429,
|
||||
CLIENT_CLOSED_REQUEST: 499,
|
||||
INTERNAL_SERVER_ERROR: 500,
|
||||
NOT_IMPLEMENTED: 501,
|
||||
BAD_GATEWAY: 502,
|
||||
SERVICE_UNAVAILABLE: 503,
|
||||
GATEWAY_TIMEOUT: 504,
|
||||
};
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
@@ -50,6 +72,7 @@ interface CreateContextOptions {
|
||||
db: dbClient;
|
||||
auth: ReturnType<typeof createAuthWithHeaders>;
|
||||
headers: Headers;
|
||||
transport?: "trpc" | "rest";
|
||||
}
|
||||
|
||||
export const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
||||
@@ -58,6 +81,8 @@ export const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
||||
db: opts.db,
|
||||
auth: opts.auth,
|
||||
headers: opts.headers,
|
||||
transport: opts.transport ?? "trpc",
|
||||
requestId: randomUUID(),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -69,7 +94,13 @@ export const createTRPCContext = async ({ req }: CreateNextContextOptions) => {
|
||||
|
||||
const session = await auth.api.getSession();
|
||||
|
||||
return createInnerTRPCContext({ db, user: session?.user, auth, headers });
|
||||
return createInnerTRPCContext({
|
||||
db,
|
||||
user: session?.user,
|
||||
auth,
|
||||
headers,
|
||||
transport: "trpc",
|
||||
});
|
||||
};
|
||||
|
||||
export const createNextApiContext = async (req: NextApiRequest) => {
|
||||
@@ -80,7 +111,13 @@ export const createNextApiContext = async (req: NextApiRequest) => {
|
||||
|
||||
const session = await auth.api.getSession();
|
||||
|
||||
return createInnerTRPCContext({ db, user: session?.user, auth, headers });
|
||||
return createInnerTRPCContext({
|
||||
db,
|
||||
user: session?.user,
|
||||
auth,
|
||||
headers,
|
||||
transport: "trpc",
|
||||
});
|
||||
};
|
||||
|
||||
export const createRESTContext = async ({ req }: CreateNextContextOptions) => {
|
||||
@@ -93,11 +130,16 @@ export const createRESTContext = async ({ req }: CreateNextContextOptions) => {
|
||||
try {
|
||||
session = await auth.api.getSession();
|
||||
} catch (error) {
|
||||
log.error({ err: error }, "Error getting session");
|
||||
throw error;
|
||||
log.warn({ err: error }, "Failed to get session, treating as unauthenticated");
|
||||
}
|
||||
|
||||
return createInnerTRPCContext({ db, user: session?.user, auth, headers });
|
||||
return createInnerTRPCContext({
|
||||
db,
|
||||
user: session?.user,
|
||||
auth,
|
||||
headers,
|
||||
transport: "rest",
|
||||
});
|
||||
};
|
||||
|
||||
const t = initTRPC
|
||||
@@ -121,17 +163,39 @@ export const createTRPCRouter = t.router;
|
||||
|
||||
export const createCallerFactory = t.createCallerFactory;
|
||||
|
||||
const loggingMiddleware = t.middleware(async ({ path, type, next, ctx }) => {
|
||||
const loggingMiddleware = t.middleware(async ({ path, type, next, ctx, getRawInput }) => {
|
||||
const start = Date.now();
|
||||
const result = await next();
|
||||
const [result, input] = await Promise.all([next(), getRawInput().catch(() => undefined)]);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
const meta = { procedure: path, type, duration, userId: (ctx as { user?: { id: string } }).user?.id };
|
||||
const { user, transport, requestId } = ctx as {
|
||||
user?: { id: string; email: string };
|
||||
transport?: string;
|
||||
requestId?: string;
|
||||
};
|
||||
const isCloud = process.env.NEXT_PUBLIC_KAN_ENV === "cloud";
|
||||
const meta = {
|
||||
requestId,
|
||||
procedure: path,
|
||||
type,
|
||||
transport,
|
||||
duration,
|
||||
userId: user?.id,
|
||||
...(isCloud && { email: user?.email }),
|
||||
input,
|
||||
};
|
||||
|
||||
const label = transport === "rest" ? "REST" : "tRPC";
|
||||
|
||||
if (result.ok) {
|
||||
log.info(meta, "tRPC OK");
|
||||
log.info({ ...meta, status: 200 }, `${label} OK`);
|
||||
} else {
|
||||
log.error({ ...meta, err: result.error }, "tRPC error");
|
||||
const status = TRPC_STATUS_MAP[result.error.code] ?? 500;
|
||||
const errorCode = result.error.code;
|
||||
log.error(
|
||||
{ ...meta, status, errorCode, err: result.error },
|
||||
`${label} error`,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
64
packages/api/src/utils/apiLogging.ts
Normal file
64
packages/api/src/utils/apiLogging.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { createLogger } from "@kan/logger";
|
||||
|
||||
import { createNextApiContext } from "../trpc";
|
||||
|
||||
const log = createLogger("api");
|
||||
|
||||
const isCloud = process.env.NEXT_PUBLIC_KAN_ENV === "cloud";
|
||||
|
||||
export function withApiLogging(
|
||||
handler: (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) => Promise<unknown> | unknown,
|
||||
) {
|
||||
return async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const start = Date.now();
|
||||
const requestId = randomUUID();
|
||||
const route = req.url?.split("?")[0] ?? "unknown";
|
||||
const input = {
|
||||
...(req.query && Object.keys(req.query).length > 0 && { query: req.query }),
|
||||
...(req.body && typeof req.body === "object" && Object.keys(req.body).length > 0 && { body: req.body }),
|
||||
};
|
||||
|
||||
let statusCode = 200;
|
||||
const originalStatus = res.status.bind(res);
|
||||
res.status = (code: number) => {
|
||||
statusCode = code;
|
||||
return originalStatus(code);
|
||||
};
|
||||
|
||||
let userId: string | undefined;
|
||||
let email: string | undefined;
|
||||
try {
|
||||
const ctx = await createNextApiContext(req);
|
||||
userId = ctx.user?.id;
|
||||
email = ctx.user?.email ?? undefined;
|
||||
} catch {
|
||||
// unauthenticated or auth unavailable
|
||||
}
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
const duration = Date.now() - start;
|
||||
const meta = {
|
||||
requestId,
|
||||
procedure: route,
|
||||
transport: "rest",
|
||||
duration,
|
||||
userId,
|
||||
...(isCloud && email && { email }),
|
||||
...(Object.keys(input).length > 0 && { input }),
|
||||
status: statusCode,
|
||||
};
|
||||
|
||||
if (statusCode < 400) {
|
||||
log.info(meta, "API OK");
|
||||
} else {
|
||||
log.error(meta, "API error");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TYPE "public"."workspace_plan" ADD VALUE 'team' BEFORE 'pro';
|
||||
3869
packages/db/migrations/meta/20260402225628_snapshot.json
Normal file
3869
packages/db/migrations/meta/20260402225628_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -218,6 +218,13 @@
|
||||
"when": 1773212242728,
|
||||
"tag": "20260311065722_AddWeekStartDay",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 31,
|
||||
"version": "7",
|
||||
"when": 1775170588247,
|
||||
"tag": "20260402225628_AddTeamWorkspacePlan",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -64,6 +64,8 @@ export const create = async (
|
||||
slug: string;
|
||||
createdBy: string;
|
||||
createdByEmail: string;
|
||||
description?: string;
|
||||
plan?: "free" | "team" | "pro" | "enterprise";
|
||||
},
|
||||
) => {
|
||||
const [workspace] = await db
|
||||
@@ -73,6 +75,8 @@ export const create = async (
|
||||
name: workspaceInput.name,
|
||||
slug: workspaceInput.slug,
|
||||
createdBy: workspaceInput.createdBy,
|
||||
...(workspaceInput.description && { description: workspaceInput.description }),
|
||||
...(workspaceInput.plan && { plan: workspaceInput.plan }),
|
||||
})
|
||||
.returning({
|
||||
id: workspaces.id,
|
||||
@@ -124,7 +128,7 @@ export const update = async (
|
||||
workspaceInput: {
|
||||
name?: string;
|
||||
slug?: string;
|
||||
plan?: "free" | "pro" | "enterprise";
|
||||
plan?: "free" | "team" | "pro" | "enterprise";
|
||||
description?: string;
|
||||
showEmailsToMembers?: boolean;
|
||||
weekStartDay?: number;
|
||||
|
||||
@@ -34,7 +34,7 @@ export const slugTypes = ["reserved", "premium"] as const;
|
||||
export type SlugType = (typeof slugTypes)[number];
|
||||
export const slugTypeEnum = pgEnum("slug_type", slugTypes);
|
||||
|
||||
export const workspacePlans = ["free", "pro", "enterprise"] as const;
|
||||
export const workspacePlans = ["free", "team", "pro", "enterprise"] as const;
|
||||
export type WorkspacePlan = (typeof workspacePlans)[number];
|
||||
export const workspacePlanEnum = pgEnum("workspace_plan", workspacePlans);
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
||||
},
|
||||
"dependencies": {
|
||||
"@axiomhq/js": "^1.4.0",
|
||||
"pino": "^9.14.0",
|
||||
"pino-pretty": "^13.1.3"
|
||||
},
|
||||
|
||||
@@ -1,20 +1,43 @@
|
||||
import { Axiom } from "@axiomhq/js";
|
||||
import pino from "pino";
|
||||
|
||||
const isDev = process.env.NODE_ENV !== "production";
|
||||
const isCloud = process.env.NEXT_PUBLIC_KAN_ENV === "cloud";
|
||||
const level = process.env.LOG_LEVEL || (isDev ? "debug" : "info");
|
||||
|
||||
export const logger = pino({
|
||||
level,
|
||||
...(isDev && {
|
||||
transport: {
|
||||
target: "pino-pretty",
|
||||
options: {
|
||||
colorize: true,
|
||||
ignore: "pid,hostname",
|
||||
translateTime: "HH:MM:ss",
|
||||
},
|
||||
const axiomToken = process.env.AXIOM_TOKEN;
|
||||
const axiomDataset = process.env.AXIOM_DATASET;
|
||||
const useAxiom = isCloud && !!axiomToken && !!axiomDataset;
|
||||
|
||||
function createAxiomStream(token: string, dataset: string): pino.DestinationStream {
|
||||
const client = new Axiom({ token });
|
||||
return {
|
||||
write(msg: string) {
|
||||
try {
|
||||
client.ingest(dataset, [JSON.parse(msg) as Record<string, unknown>]);
|
||||
} catch {
|
||||
// ignore malformed log lines
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export const logger = useAxiom
|
||||
? pino(
|
||||
{ level },
|
||||
pino.multistream([
|
||||
{ stream: process.stdout, level },
|
||||
{ stream: createAxiomStream(axiomToken, axiomDataset), level },
|
||||
]),
|
||||
)
|
||||
: pino({
|
||||
level,
|
||||
...(isDev && {
|
||||
transport: {
|
||||
target: "pino-pretty",
|
||||
options: { colorize: true, ignore: "pid,hostname", translateTime: "HH:MM:ss" },
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
export const createLogger = (module: string) => logger.child({ module });
|
||||
|
||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@@ -504,6 +504,9 @@ importers:
|
||||
|
||||
packages/logger:
|
||||
dependencies:
|
||||
'@axiomhq/js':
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
pino:
|
||||
specifier: ^9.14.0
|
||||
version: 9.14.0
|
||||
@@ -891,6 +894,10 @@ packages:
|
||||
resolution: {integrity: sha512-kLO7k7cGJ6KaHiExSJWojZurF7SnGMDHXRuQunFnEoD0n1yB6Lqy/S/zHiQ7oJnBhPr9q0TW9qFkrsZb1Uc54w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@axiomhq/js@1.4.0':
|
||||
resolution: {integrity: sha512-wC5x1ud/QJMstrjpicATkyY8+ZVWEl4WlXMtA5EZf7hkj0+b191yv4yynLxLEfr/MveXora9m6CWdJq4DsbcAg==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@babel/code-frame@7.27.1':
|
||||
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -5628,6 +5635,9 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fetch-retry@6.0.0:
|
||||
resolution: {integrity: sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag==}
|
||||
|
||||
fflate@0.4.8:
|
||||
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
|
||||
|
||||
@@ -9384,6 +9394,10 @@ snapshots:
|
||||
'@smithy/types': 4.3.2
|
||||
tslib: 2.8.1
|
||||
|
||||
'@axiomhq/js@1.4.0':
|
||||
dependencies:
|
||||
fetch-retry: 6.0.0
|
||||
|
||||
'@babel/code-frame@7.27.1':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
@@ -14403,6 +14417,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
fetch-retry@6.0.0: {}
|
||||
|
||||
fflate@0.4.8: {}
|
||||
|
||||
figures@3.2.0:
|
||||
|
||||
@@ -130,7 +130,9 @@
|
||||
"NOVU_API_KEY",
|
||||
"EMAIL_UNSUBSCRIBE_SECRET",
|
||||
"REDIS_URL",
|
||||
"LOG_LEVEL"
|
||||
"LOG_LEVEL",
|
||||
"AXIOM_TOKEN",
|
||||
"AXIOM_DATASET"
|
||||
],
|
||||
"globalPassThroughEnv": [
|
||||
"NODE_ENV",
|
||||
|
||||
Reference in New Issue
Block a user