Compare commits
1288 Commits
feature/IO
...
rrScratch2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
910d388e05 | ||
|
|
9faad53b99 | ||
|
|
3b07055d5a | ||
|
|
ec29a22984 | ||
|
|
2b1836d450 | ||
|
|
ae7d150a6c | ||
|
|
b2184a2d11 | ||
|
|
9b1c8fa72b | ||
|
|
6d6b64ebc3 | ||
|
|
c3bc29fa9b | ||
|
|
c954695d3c | ||
|
|
338d8e2136 | ||
|
|
6674206b4f | ||
|
|
c46ad521d1 | ||
|
|
34f45379a6 | ||
|
|
66e5bec4d8 | ||
|
|
0d3161ef84 | ||
|
|
1cd11bdc18 | ||
|
|
9cce2696e2 | ||
|
|
e20ef4374c | ||
|
|
43dc760c95 | ||
|
|
cfd5aaff87 | ||
|
|
6162e7f18d | ||
|
|
cdee754042 | ||
|
|
a8fb16122a | ||
|
|
a88be98d45 | ||
|
|
e44bc07ffe | ||
|
|
60e8aadd8c | ||
|
|
daf9f197eb | ||
|
|
5848daef72 | ||
|
|
bc77bec610 | ||
|
|
c655badae2 | ||
|
|
7af70f7512 | ||
|
|
a8dcc542cc | ||
|
|
e4c87dd06d | ||
|
|
16899007d8 | ||
|
|
9cb1b25b1d | ||
|
|
4c250f6189 | ||
|
|
9c2c0b665d | ||
|
|
09ea6dff2b | ||
|
|
577c3bec04 | ||
|
|
90f653c0b7 | ||
|
|
556cd993b9 | ||
|
|
e3b4620d0c | ||
|
|
cbfda822c6 | ||
|
|
52811f5f45 | ||
|
|
508d32d2d9 | ||
|
|
cccc307862 | ||
|
|
29049cf1b0 | ||
|
|
b517f3966d | ||
|
|
0772139a60 | ||
|
|
70028c8be6 | ||
|
|
3dc22bfdab | ||
|
|
91f419f4b3 | ||
|
|
f3ee421030 | ||
|
|
a5f8fbacc1 | ||
|
|
8bb58df32e | ||
|
|
1e3b3b853e | ||
|
|
02eb212758 | ||
|
|
6b41d6f2a2 | ||
|
|
d45d557a81 | ||
|
|
c0157454e1 | ||
|
|
00e6d31a88 | ||
|
|
e5ed11287d | ||
|
|
fa250f10a2 | ||
|
|
b8fed77f43 | ||
|
|
3f5614d77e | ||
|
|
5a8a5bf7ab | ||
|
|
9ce022b5e8 | ||
|
|
b0b73f1af8 | ||
|
|
a788beaa19 | ||
|
|
6c5c4bd333 | ||
|
|
43c1eef70c | ||
|
|
b8d97d9821 | ||
|
|
6843441b17 | ||
|
|
d6b295855d | ||
|
|
e36baaa682 | ||
|
|
409e04ed0e | ||
|
|
95c7872b34 | ||
|
|
91bf5c8d0f | ||
|
|
3660fb1b1b | ||
|
|
35f00df77e | ||
|
|
7573286163 | ||
|
|
34ab42c0ad | ||
|
|
a3c3f60d2a | ||
|
|
286c49deb1 | ||
|
|
a16c680d04 | ||
|
|
9341806b0f | ||
|
|
da28fe8592 | ||
|
|
f2faa5b686 | ||
|
|
bedca60744 | ||
|
|
5344a2031d | ||
|
|
8147bc76fd | ||
|
|
c60dfa4319 | ||
|
|
aa692d4d05 | ||
|
|
9ef1022311 | ||
|
|
5bbda89fb9 | ||
|
|
ccf3d7df5b | ||
|
|
3c0e62ffac | ||
|
|
eeb685802e | ||
|
|
ea14606538 | ||
|
|
3d24d44274 | ||
|
|
a9a0415501 | ||
|
|
fbaf47b89b | ||
|
|
65e26ed5c9 | ||
|
|
a73617fd3c | ||
|
|
32a0e89467 | ||
|
|
e06f0f9918 | ||
|
|
907f291f90 | ||
|
|
abce19530f | ||
|
|
1fd9b68320 | ||
|
|
cff1afe605 | ||
|
|
b337309f94 | ||
|
|
319f3220ed | ||
|
|
77f041b0f1 | ||
|
|
68be8670b4 | ||
|
|
745c429f08 | ||
|
|
cc9e4740de | ||
|
|
a4da874a1a | ||
|
|
2f4c0e329a | ||
|
|
06ebcbaa07 | ||
|
|
42427c4569 | ||
|
|
c6d083ce02 | ||
|
|
d5f921ed35 | ||
|
|
043471fdbc | ||
|
|
54850e8ee2 | ||
|
|
199ddc7d9e | ||
|
|
e514cf8d6a | ||
|
|
6671db1724 | ||
|
|
5a9381ebdb | ||
|
|
6bab792b5e | ||
|
|
13a44b9a59 | ||
|
|
e3337bacea | ||
|
|
8e6c809fc6 | ||
|
|
41afedd02c | ||
|
|
64207ef76c | ||
|
|
29df829120 | ||
|
|
11e0c3e507 | ||
|
|
6c1c7c9c2c | ||
|
|
d2b6054e60 | ||
|
|
7004ed9880 | ||
|
|
36dfed80fb | ||
|
|
de02b34a63 | ||
|
|
e3df22160b | ||
|
|
2ffc4b81f4 | ||
|
|
ec30e73b3e | ||
|
|
0ccfe6f3aa | ||
|
|
34d70f6a18 | ||
|
|
fc1bf213c7 | ||
|
|
4dc72986d0 | ||
|
|
f19b9cb8e1 | ||
|
|
c149d457e7 | ||
|
|
88c00cf34f | ||
|
|
c9ade68dc9 | ||
|
|
0d247a38d2 | ||
|
|
595ffa02ba | ||
|
|
d573335eb0 | ||
|
|
bdd5056c9a | ||
|
|
4f6db827e7 | ||
|
|
d16c8d5bd5 | ||
|
|
92b05a290e | ||
|
|
6421fc8002 | ||
|
|
0d9a7dda53 | ||
|
|
601eed6db8 | ||
|
|
9b708d5e8e | ||
|
|
6b4bc27205 | ||
|
|
e50bbc3bcc | ||
|
|
15792cb0ef | ||
|
|
856a24d496 | ||
|
|
74d6bcc004 | ||
|
|
2af95de353 | ||
|
|
7ffb2c1aad | ||
|
|
cf3f94bf98 | ||
|
|
4899297539 | ||
|
|
0bd8e862e4 | ||
|
|
4afafef98f | ||
|
|
48a3220c49 | ||
|
|
31529fad80 | ||
|
|
7bc105ea46 | ||
|
|
ac4fcf1694 | ||
|
|
678a87f55d | ||
|
|
9e977d9a58 | ||
|
|
43f822e6ad | ||
|
|
7239698a21 | ||
|
|
46be1fa889 | ||
|
|
40a8036472 | ||
|
|
22970ac149 | ||
|
|
0eb0e335fe | ||
|
|
63eeab888f | ||
|
|
367ced88d8 | ||
|
|
71b08855b8 | ||
|
|
01664c52ec | ||
|
|
67b1a7f9f4 | ||
|
|
7f67b01c87 | ||
|
|
e66d52784f | ||
|
|
a75969097e | ||
|
|
5e308f13d3 | ||
|
|
24f017bfd2 | ||
|
|
93e137c84e | ||
|
|
42027f0858 | ||
|
|
3b663d7954 | ||
|
|
e737af2d41 | ||
|
|
10d3b4a485 | ||
|
|
99b79126c3 | ||
|
|
d0eeb7d55d | ||
|
|
36d4a02be0 | ||
|
|
d95c5ce8f9 | ||
|
|
b995e1f35d | ||
|
|
3d112ed2cd | ||
|
|
e978a5a561 | ||
|
|
31367f278e | ||
|
|
a384fd5072 | ||
|
|
490c66a9cb | ||
|
|
d38dab0738 | ||
|
|
37624d385f | ||
|
|
d565934288 | ||
|
|
5d03574d65 | ||
|
|
34f7c115b4 | ||
|
|
3ac74df504 | ||
|
|
9ff3311579 | ||
|
|
d5f13f750f | ||
|
|
b9ddac36a9 | ||
|
|
c982dde1f5 | ||
|
|
cd1e8b0b15 | ||
|
|
3d910aa246 | ||
|
|
f7799ffd03 | ||
|
|
57baa3d9fd | ||
|
|
7f61f652f7 | ||
|
|
c1e1d5e82c | ||
|
|
5ae2e33596 | ||
|
|
e11260e8fc | ||
|
|
282a22ff51 | ||
|
|
dfd88308e0 | ||
|
|
33579c3e6a | ||
|
|
0b9b3c027f | ||
|
|
154f9cdfe6 | ||
|
|
5b8c7d922c | ||
|
|
d20347d5dc | ||
|
|
68c4a1efd7 | ||
|
|
3e6d6fdbd1 | ||
|
|
c6cd6d0f4e | ||
|
|
f796dd0f89 | ||
|
|
302fd58a56 | ||
|
|
31cfdf9ea3 | ||
|
|
f93800ded4 | ||
|
|
05385fca6d | ||
|
|
1355d79fa4 | ||
|
|
8805538706 | ||
|
|
9e35b0f123 | ||
|
|
517c30787d | ||
|
|
252758747b | ||
|
|
8b39b7c7be | ||
|
|
ada07bad62 | ||
|
|
166a33af4e | ||
|
|
038aa82087 | ||
|
|
99f425eac4 | ||
|
|
b2c504c69d | ||
|
|
ac6856b136 | ||
|
|
521955089f | ||
|
|
4afff893c0 | ||
|
|
cc934fe333 | ||
|
|
ddd3b3d056 | ||
|
|
8ded028197 | ||
|
|
2660466db1 | ||
|
|
c8771275ce | ||
|
|
fd229d5d09 | ||
|
|
7c8260685e | ||
|
|
2fec9fd16e | ||
|
|
0f1348496c | ||
|
|
c42a0139fc | ||
|
|
02974e6e4b | ||
|
|
de26979e44 | ||
|
|
1bb66a5378 | ||
|
|
fe67efe47c | ||
|
|
69a35772e5 | ||
|
|
38932f4bf9 | ||
|
|
3fcb36a28e | ||
|
|
fe78f5c7ff | ||
|
|
683846c3b0 | ||
|
|
2cc0b247b6 | ||
|
|
661678eb1c | ||
|
|
31579354d4 | ||
|
|
0e7531dc54 | ||
|
|
268b57c38a | ||
|
|
2b8b8b8073 | ||
|
|
808eeb91e9 | ||
|
|
23dd8fc9de | ||
|
|
f499859078 | ||
|
|
84d9e3251a | ||
|
|
bd7db4dd02 | ||
|
|
e9804b736b | ||
|
|
a14874f116 | ||
|
|
ac9fac458c | ||
|
|
8f9db15852 | ||
|
|
1728982b2b | ||
|
|
8aa747edc5 | ||
|
|
01fc0cbc08 | ||
|
|
61b3d3c18c | ||
|
|
e6f08d3b1c | ||
|
|
d5cf0f8371 | ||
|
|
52e230fc54 | ||
|
|
4011237c22 | ||
|
|
c24bfbf655 | ||
|
|
08fe8c3c70 | ||
|
|
771a239773 | ||
|
|
82195a0584 | ||
|
|
838c24b3f1 | ||
|
|
13cb68b0af | ||
|
|
7c84b08707 | ||
|
|
3165957e95 | ||
|
|
d9f59fcad4 | ||
|
|
edaeb5d77a | ||
|
|
544494ce0d | ||
|
|
5365d95d6f | ||
|
|
5cfefd5afd | ||
|
|
bbccdb0650 | ||
|
|
f3535c01af | ||
|
|
7f8c82b300 | ||
|
|
9ab3d8c81a | ||
|
|
83c0696a3e | ||
|
|
609ac2bd33 | ||
|
|
0883274320 | ||
|
|
fa33b88632 | ||
|
|
bec32c1d70 | ||
|
|
eb18130e51 | ||
|
|
0fbf63dec8 | ||
|
|
f817902d5c | ||
|
|
7a383aaec9 | ||
|
|
e5c0ace6cb | ||
|
|
814447373a | ||
|
|
d766a468c3 | ||
|
|
0ede2d0649 | ||
|
|
54089c2ab3 | ||
|
|
73e3d71cf1 | ||
|
|
f071a5cc9e | ||
|
|
67002b8443 | ||
|
|
3f83c6afa7 | ||
|
|
b7f57e91aa | ||
|
|
b8465c0cc7 | ||
|
|
6bae0b8406 | ||
|
|
2e25324ae9 | ||
|
|
f525ec6fb8 | ||
|
|
8296d914c5 | ||
|
|
3d691568ff | ||
|
|
553c154e46 | ||
|
|
35cbb921d2 | ||
|
|
6b926401d0 | ||
|
|
5b400dce4f | ||
|
|
914946c264 | ||
|
|
2939b5795b | ||
|
|
531f968ce6 | ||
|
|
24cc3fa6a4 | ||
|
|
a6f17e7db5 | ||
|
|
29c904feea | ||
|
|
0743048e75 | ||
|
|
e2f1758378 | ||
|
|
b6dc7a4d92 | ||
|
|
130745d7e7 | ||
|
|
a722ab9758 | ||
|
|
41c9c0be49 | ||
|
|
52f62126e1 | ||
|
|
cfdfb8110b | ||
|
|
eb04a8b6c5 | ||
|
|
141b05f558 | ||
|
|
e9ea36fdad | ||
|
|
e990781ff7 | ||
|
|
e24237e010 | ||
|
|
f00f5f2e4a | ||
|
|
f38b550e75 | ||
|
|
fabe1508ac | ||
|
|
5f3c880d0b | ||
|
|
86f0c02c82 | ||
|
|
2004eb840f | ||
|
|
2f0190e190 | ||
|
|
e5a48531a0 | ||
|
|
5552c73721 | ||
|
|
9860447e42 | ||
|
|
364813193f | ||
|
|
2c8f3a173e | ||
|
|
1577921334 | ||
|
|
a934249c02 | ||
|
|
6486de3c61 | ||
|
|
ed6a8b210d | ||
|
|
96e38d509f | ||
|
|
42e072e8ff | ||
|
|
6942d6e4f9 | ||
|
|
43d5a77d60 | ||
|
|
a74ce063ec | ||
|
|
7ab070e2bc | ||
|
|
47a974e3cb | ||
|
|
9c45b49ab9 | ||
|
|
d690790dfe | ||
|
|
eac81cdffb | ||
|
|
70fa638c37 | ||
|
|
77c3e6f7e7 | ||
|
|
7f07da4360 | ||
|
|
c47144df72 | ||
|
|
77cacdec91 | ||
|
|
33fb60ca1a | ||
|
|
f6d6b548be | ||
|
|
c0a215d20d | ||
|
|
8c9ef375be | ||
|
|
5342377ca9 | ||
|
|
8295cb111a | ||
|
|
cd4754069b | ||
|
|
951d214d49 | ||
|
|
c586d0283b | ||
|
|
203cc1ebdf | ||
|
|
db09f33e5c | ||
|
|
1941034dcb | ||
|
|
6bd2828176 | ||
|
|
6f19c1dd3f | ||
|
|
b428a1078c | ||
|
|
cc232eac93 | ||
|
|
5e90504e56 | ||
|
|
73e103f2df | ||
|
|
b09b8b4f34 | ||
|
|
9dd34c9f6c | ||
|
|
cf60f7cd03 | ||
|
|
637e95c351 | ||
|
|
7af7f3c4e7 | ||
|
|
0cadf007b5 | ||
|
|
1394176218 | ||
|
|
284d25eeb9 | ||
|
|
60258a0f5d | ||
|
|
7873405a30 | ||
|
|
c38d7d9aea | ||
|
|
7639655911 | ||
|
|
4fb1871044 | ||
|
|
e5dd1edf13 | ||
|
|
542c95c395 | ||
|
|
a3122a59b1 | ||
|
|
81d642fcd3 | ||
|
|
960a2ccd30 | ||
|
|
2ab6093bd8 | ||
|
|
7ed7b6117f | ||
|
|
7158676562 | ||
|
|
67a8c13bad | ||
|
|
766b4b950a | ||
|
|
88ba8ab929 | ||
|
|
0d570d0323 | ||
|
|
898b97151f | ||
|
|
de6bb3d634 | ||
|
|
2b40793c77 | ||
|
|
c4649b2fc6 | ||
|
|
4d475e25fa | ||
|
|
00d2d3012d | ||
|
|
4e5aba59d7 | ||
|
|
09f96f0b68 | ||
|
|
866f5f72eb | ||
|
|
f0c166907b | ||
|
|
c06b4e8af5 | ||
|
|
bebe3ef633 | ||
|
|
50d2e912ed | ||
|
|
a7e21b0505 | ||
|
|
3b481afa9e | ||
|
|
75de177b7b | ||
|
|
ec6c0279de | ||
|
|
c9572d2db5 | ||
|
|
93e9e20f6f | ||
|
|
4e8ea736c5 | ||
|
|
8f00dbfc17 | ||
|
|
55d242d40d | ||
|
|
4f99ae40d3 | ||
|
|
05c049c9af | ||
|
|
d94b573ae6 | ||
|
|
790ab0447f | ||
|
|
84795b2048 | ||
|
|
567002236d | ||
|
|
0ed41de956 | ||
|
|
8a2dfae487 | ||
|
|
3737fe457f | ||
|
|
c5978d4c21 | ||
|
|
bb4e8eb5bd | ||
|
|
27a07e8d5d | ||
|
|
e52fe93e14 | ||
|
|
e2618eee83 | ||
|
|
dd20871707 | ||
|
|
66c51a4be5 | ||
|
|
d5afcaeaab | ||
|
|
d0370d3e60 | ||
|
|
c332ec11b7 | ||
|
|
cf31290f05 | ||
|
|
203dc28720 | ||
|
|
dbbab910b6 | ||
|
|
abf01b4966 | ||
|
|
952feeb685 | ||
|
|
bb2b67cece | ||
|
|
a965f9edf5 | ||
|
|
f02ca05eba | ||
|
|
a182ea0869 | ||
|
|
7bc2d41a68 | ||
|
|
e4fb8b61b0 | ||
|
|
1cc33a67e6 | ||
|
|
5277e90946 | ||
|
|
0a16a0fcbc | ||
|
|
15ea4e6afa | ||
|
|
295f1d1cb3 | ||
|
|
5b3b6a409c | ||
|
|
d92bab113e | ||
|
|
93c6e2b601 | ||
|
|
19a90571f6 | ||
|
|
37ceddf54d | ||
|
|
736e9cedfa | ||
|
|
c433103e1b | ||
|
|
2892fdbb58 | ||
|
|
c45f38e47b | ||
|
|
6f4d21cac9 | ||
|
|
6b5ad3dafa | ||
|
|
54a58c9fbc | ||
|
|
1934ae0758 | ||
|
|
0ac9bbd97c | ||
|
|
dfa457e3c6 | ||
|
|
e8dec042bd | ||
|
|
d65e7deacc | ||
|
|
953e70efef | ||
|
|
a6bae390e5 | ||
|
|
139ee46dc0 | ||
|
|
cf9d8d649d | ||
|
|
a25051c4c2 | ||
|
|
7a1e7bd4f9 | ||
|
|
254fb4f77f | ||
|
|
53a559b126 | ||
|
|
d5c3152631 | ||
|
|
66c425bf96 | ||
|
|
ffad0dfbf7 | ||
|
|
17285fc029 | ||
|
|
401e3cff73 | ||
|
|
865680e019 | ||
|
|
9f97ca0336 | ||
|
|
5df38f8612 | ||
|
|
63c5719420 | ||
|
|
d6c80f1420 | ||
|
|
fade927c9e | ||
|
|
9f472ce1d0 | ||
|
|
47a56e32b9 | ||
|
|
f13f79acb6 | ||
|
|
bfa9fddb9e | ||
|
|
28abd9707e | ||
|
|
5f621e1ae0 | ||
|
|
624414799e | ||
|
|
72091e9eae | ||
|
|
9cfacdd025 | ||
|
|
d5c63b798a | ||
|
|
655e516246 | ||
|
|
7b12f0a3b9 | ||
|
|
e0b937474d | ||
|
|
5c4267f3ef | ||
|
|
4dcfb382a9 | ||
|
|
cf181dfd0a | ||
|
|
1127864ba9 | ||
|
|
79e379b61a | ||
|
|
e79e512291 | ||
|
|
f0064abfbe | ||
|
|
4a30a5bc64 | ||
|
|
32bdea559e | ||
|
|
d4215b7aee | ||
|
|
2494399993 | ||
|
|
34f62a8858 | ||
|
|
9e5689b06f | ||
|
|
931fa4b82b | ||
|
|
5d69d37db2 | ||
|
|
9ab2fdc868 | ||
|
|
fbd6766dcd | ||
|
|
9ace531edb | ||
|
|
2e3944099b | ||
|
|
9b53bd9b40 | ||
|
|
443ed717cb | ||
|
|
9845c1cea5 | ||
|
|
2061a49e0e | ||
|
|
f8a3d0f854 | ||
|
|
91cc12873e | ||
|
|
c71026f22a | ||
|
|
bd2720f534 | ||
|
|
8bc6bea4b2 | ||
|
|
23901c0cc1 | ||
|
|
b99a212d75 | ||
|
|
0891c7d4b3 | ||
|
|
a4963922da | ||
|
|
3ae41b7016 | ||
|
|
9c59fd4c00 | ||
|
|
a9f959cced | ||
|
|
414897bba0 | ||
|
|
94b154a4ac | ||
|
|
5fa58e5013 | ||
|
|
6496f6c414 | ||
|
|
3a0a3c9fb9 | ||
|
|
7467a31d76 | ||
|
|
894f6bf6d2 | ||
|
|
744dfa8163 | ||
|
|
2293119518 | ||
|
|
bd529a0dfa | ||
|
|
646c42b8c7 | ||
|
|
8de92403ee | ||
|
|
57ad89747f | ||
|
|
3ae8f38adb | ||
|
|
dc5ed1a39c | ||
|
|
f5ea8719ef | ||
|
|
aa6e6b8980 | ||
|
|
1dc80c068b | ||
|
|
bd0c4ceae2 | ||
|
|
30b58c6ea5 | ||
|
|
a55e9224f8 | ||
|
|
0c80abb3ca | ||
|
|
7137e611cd | ||
|
|
6f9d291d36 | ||
|
|
f2a2653eae | ||
|
|
73c25ab91f | ||
|
|
780449bac6 | ||
|
|
2509a1ecf3 | ||
|
|
16075f7ddd | ||
|
|
27d28e7ffc | ||
|
|
66b87e5c45 | ||
|
|
c1e1dff7d2 | ||
|
|
0e1ec83fcd | ||
|
|
01185b3073 | ||
|
|
49b0990c7b | ||
|
|
f76eb7abf5 | ||
|
|
25ea2a80a3 | ||
|
|
7d930045ef | ||
|
|
633d5668f0 | ||
|
|
cbb6c43ec3 | ||
|
|
09e1887609 | ||
|
|
4b83330db9 | ||
|
|
b0283f827e | ||
|
|
3c71902047 | ||
|
|
79bf30b299 | ||
|
|
dc3e9b7226 | ||
|
|
00cc47553b | ||
|
|
2505edede7 | ||
|
|
4b75504d9e | ||
|
|
8af6c8dd24 | ||
|
|
4c6344a8d7 | ||
|
|
92369fceba | ||
|
|
3c360130a3 | ||
|
|
13e4143eeb | ||
|
|
68c7b184d2 | ||
|
|
9b85d15ff1 | ||
|
|
e7cf49a2ec | ||
|
|
04b29b6970 | ||
|
|
f5bc79cba7 | ||
|
|
2ae18681cb | ||
|
|
fda763476a | ||
|
|
999cbd80f4 | ||
|
|
ad2a5fe95b | ||
|
|
d835021069 | ||
|
|
c4b303aee1 | ||
|
|
e2c5a4cba4 | ||
|
|
fd04125ed1 | ||
|
|
a0566e76ab | ||
|
|
87e8b2ce27 | ||
|
|
d52426f5f5 | ||
|
|
5e24404e82 | ||
|
|
64a280b111 | ||
|
|
cf393e8f9e | ||
|
|
909a21023a | ||
|
|
0402156b4d | ||
|
|
94bdc6c43f | ||
|
|
9466d36e69 | ||
|
|
412efb06e5 | ||
|
|
da7e637183 | ||
|
|
2e95fa25af | ||
|
|
f6c63bbd74 | ||
|
|
0a654082c2 | ||
|
|
2c20b731d2 | ||
|
|
8a22897cdd | ||
|
|
677da61b52 | ||
|
|
6513434bd7 | ||
|
|
fe2600029f | ||
|
|
c5b4efedfb | ||
|
|
310321d0ab | ||
|
|
7e884c42ea | ||
|
|
e279bf41a4 | ||
|
|
4a060ab51c | ||
|
|
62c1c77a18 | ||
|
|
db19ecb28c | ||
|
|
51748ce28d | ||
|
|
4bbfd8a9da | ||
|
|
d4d2db2cac | ||
|
|
23483144e1 | ||
|
|
67d5dcb062 | ||
|
|
901a49e571 | ||
|
|
49ae107fde | ||
|
|
0135281bcd | ||
|
|
99cf95daf0 | ||
|
|
8c1758ae49 | ||
|
|
2d764921ff | ||
|
|
858a11f8b4 | ||
|
|
4859239f55 | ||
|
|
5c64d7185e | ||
|
|
152479bc08 | ||
|
|
2c508cf1a1 | ||
|
|
16a91c772a | ||
|
|
5c47088b11 | ||
|
|
8e5dc4fa71 | ||
|
|
39c3729f6d | ||
|
|
e3d854e02b | ||
|
|
618acf2acf | ||
|
|
2cf2b70293 | ||
|
|
0541afceb8 | ||
|
|
28ed3f9936 | ||
|
|
6afa50332b | ||
|
|
8c8c68867d | ||
|
|
8ee52598e8 | ||
|
|
c822028174 | ||
|
|
36b82c6195 | ||
|
|
079dffce4d | ||
|
|
831802f5af | ||
|
|
7bd5190bf2 | ||
|
|
83860152a9 | ||
|
|
1e10493615 | ||
|
|
9d81c68a4d | ||
|
|
985d066978 | ||
|
|
6ad9e27d1d | ||
|
|
19ebdda5b3 | ||
|
|
4602dd1183 | ||
|
|
6005eaee6a | ||
|
|
6d59e3994f | ||
|
|
f770b2f1b1 | ||
|
|
b014744940 | ||
|
|
714c90c25e | ||
|
|
9a3a971da6 | ||
|
|
96cba0aaab | ||
|
|
c069600cfd | ||
|
|
186cbf2c97 | ||
|
|
392988ae11 | ||
|
|
2e33b79eb9 | ||
|
|
d4f718c44c | ||
|
|
fa99ef7b37 | ||
|
|
c4aff1b516 | ||
|
|
61276bb2d1 | ||
|
|
8b89e2eb9d | ||
|
|
9ab41308e7 | ||
|
|
f76052ec9b | ||
|
|
b8841e3ded | ||
|
|
a49b3f6496 | ||
|
|
163eaac110 | ||
|
|
3e17ec3cf8 | ||
|
|
76c0c7c41e | ||
|
|
025b986f60 | ||
|
|
6e6addd62f | ||
|
|
266c3acf34 | ||
|
|
c4631f50e5 | ||
|
|
ca18291425 | ||
|
|
110fad2abc | ||
|
|
b7456cecd4 | ||
|
|
84db1fe81b | ||
|
|
b539111be8 | ||
|
|
8a8bc5a6ed | ||
|
|
020db91105 | ||
|
|
1dd28af752 | ||
|
|
5ba192eee0 | ||
|
|
8109a12898 | ||
|
|
2deb7fd520 | ||
|
|
f6cd136679 | ||
|
|
e50cb86296 | ||
|
|
a5a01c44fa | ||
|
|
947e0705e4 | ||
|
|
aa8a6a837d | ||
|
|
5db440fc9c | ||
|
|
c299b9376a | ||
|
|
e5d530ea3e | ||
|
|
6da9850946 | ||
|
|
f62609f60c | ||
|
|
b2d8c66e5b | ||
|
|
3c4ed3ba0c | ||
|
|
2e7f827c3f | ||
|
|
dc82b39dc8 | ||
|
|
a9814c1eb1 | ||
|
|
bdb741caf8 | ||
|
|
f50b198c21 | ||
|
|
3495326de3 | ||
|
|
b5973085e7 | ||
|
|
19ce1c66ad | ||
|
|
3fe0e3a33c | ||
|
|
8687214420 | ||
|
|
d61b89a1e5 | ||
|
|
468b42abd2 | ||
|
|
fc03e5f983 | ||
|
|
c4742e38ea | ||
|
|
99e1adbe13 | ||
|
|
eb5c797a43 | ||
|
|
0595c5545e | ||
|
|
12c87ed689 | ||
|
|
55944257aa | ||
|
|
03241778fa | ||
|
|
555b81fb14 | ||
|
|
a56b720e09 | ||
|
|
b89eede164 | ||
|
|
c21cc8d6b9 | ||
|
|
d02a6bc197 | ||
|
|
360c1ce82d | ||
|
|
a7ef02976c | ||
|
|
6a9e36ea4d | ||
|
|
37d4c0a40f | ||
|
|
5ebca3ff06 | ||
|
|
1969a92226 | ||
|
|
8840ffc9ba | ||
|
|
19e42ef397 | ||
|
|
e2b4b408ed | ||
|
|
c7eb026986 | ||
|
|
b0dcd3618e | ||
|
|
5f23f135f2 | ||
|
|
159ee7364d | ||
|
|
aa6ad109c9 | ||
|
|
f2a896d568 | ||
|
|
546ebba0bd | ||
|
|
0e75f54d6e | ||
|
|
30f34a17ea | ||
|
|
6035d94404 | ||
|
|
0b7a23d555 | ||
|
|
91fe1f4af9 | ||
|
|
f09cb7b247 | ||
|
|
35a7222f5e | ||
|
|
d444821cf7 | ||
|
|
b5cb520944 | ||
|
|
6814a3bc33 | ||
|
|
19c2b19abc | ||
|
|
22b011139d | ||
|
|
567171c722 | ||
|
|
03acb78ab2 | ||
|
|
5b30daefe5 | ||
|
|
e015d3574a | ||
|
|
60140902d4 | ||
|
|
84f41b2c11 | ||
|
|
e8b9fcbc6e | ||
|
|
5adf591670 | ||
|
|
f55764e859 | ||
|
|
282fa787a9 | ||
|
|
037efff81c | ||
|
|
e26eb17d09 | ||
|
|
fbea9fde27 | ||
|
|
ce7cf6bdbe | ||
|
|
2c47e5d852 | ||
|
|
a6f809b20a | ||
|
|
2bcad68351 | ||
|
|
6b1b393804 | ||
|
|
c5181d1c5d | ||
|
|
e33ff2a45d | ||
|
|
9eb77964db | ||
|
|
0a68d2791d | ||
|
|
11928d9a7e | ||
|
|
c169bb5d5d | ||
|
|
3cc4f1c63e | ||
|
|
5237b1d535 | ||
|
|
cd56c50cf9 | ||
|
|
a18ce18d72 | ||
|
|
3691d32aaa | ||
|
|
5f66488410 | ||
|
|
d1be7f6e09 | ||
|
|
44f02f28a6 | ||
|
|
6d33622b4e | ||
|
|
f8b8e23ef4 | ||
|
|
db09d09428 | ||
|
|
451820a67c | ||
|
|
ba0ce5027e | ||
|
|
f777d26cc1 | ||
|
|
1463037878 | ||
|
|
7ddec0bb0f | ||
|
|
51c2d3351a | ||
|
|
8323fa6696 | ||
|
|
27a3932c08 | ||
|
|
add88659a4 | ||
|
|
320ad065d0 | ||
|
|
a9bc51949a | ||
|
|
39d1397221 | ||
|
|
b44b71072f | ||
|
|
f3e2a83bab | ||
|
|
0ef030bb89 | ||
|
|
3e9e6baf32 | ||
|
|
c03d45b3fc | ||
|
|
0a9b583c4b | ||
|
|
54ac0c84a7 | ||
|
|
4d59798d8d | ||
|
|
f95dab544d | ||
|
|
41e43dda96 | ||
|
|
cec60db78c | ||
|
|
7e741e4af9 | ||
|
|
24d47ae1c5 | ||
|
|
f556d59ad7 | ||
|
|
09c4662436 | ||
|
|
9bf6ba9cf0 | ||
|
|
7843ca9b1a | ||
|
|
c78b9866a3 | ||
|
|
c8701aba63 | ||
|
|
09c1a8ae35 | ||
|
|
0ef2814de3 | ||
|
|
8e105f0b36 | ||
|
|
ba4da3e35c | ||
|
|
1b8be56c15 | ||
|
|
f6e65f82e5 | ||
|
|
8b7bb099f3 | ||
|
|
2b26db78eb | ||
|
|
663d91b648 | ||
|
|
2a7686ec75 | ||
|
|
549cb56cdf | ||
|
|
146bb6c5c0 | ||
|
|
67b6da7c31 | ||
|
|
c2d96922c8 | ||
|
|
624894621b | ||
|
|
3fba215266 | ||
|
|
bbf291e8f3 | ||
|
|
70b4ec7948 | ||
|
|
341fc09c22 | ||
|
|
a3ec364034 | ||
|
|
fb30529808 | ||
|
|
e1728b275b | ||
|
|
46999145fc | ||
|
|
9d1f810af2 | ||
|
|
10d55df461 | ||
|
|
b9693aae95 | ||
|
|
02f5f1985c | ||
|
|
37edceee84 | ||
|
|
76a855990d | ||
|
|
517eca0900 | ||
|
|
ba9b248b1f | ||
|
|
0cd1e3ae98 | ||
|
|
ecac8197a9 | ||
|
|
c595a00a45 | ||
|
|
ed7aaac620 | ||
|
|
b88795078c | ||
|
|
1fd63012b0 | ||
|
|
3c02553d08 | ||
|
|
f485951a4c | ||
|
|
1b5ae29078 | ||
|
|
aaf966e721 | ||
|
|
c9cc9d2df3 | ||
|
|
b5611c8470 | ||
|
|
e36bb65e4c | ||
|
|
3b21c603f6 | ||
|
|
c568970fd8 | ||
|
|
4589e7fa05 | ||
|
|
922aaaf4b2 | ||
|
|
f3b9c6399f | ||
|
|
8bb86b9caa | ||
|
|
c0670e09e0 | ||
|
|
14046b96db | ||
|
|
65ae105c33 | ||
|
|
cf376d413f | ||
|
|
96557115b8 | ||
|
|
85f1d5cae2 | ||
|
|
b9eb622207 | ||
|
|
7e2a214a50 | ||
|
|
cf084fa168 | ||
|
|
4c6d28f612 | ||
|
|
38119f7f1f | ||
|
|
96af289640 | ||
|
|
e7c4797fef | ||
|
|
869fe78d8e | ||
|
|
4a9b0cae69 | ||
|
|
de3f1972a6 | ||
|
|
88c35e8c48 | ||
|
|
f8df351de6 | ||
|
|
02a9274f98 | ||
|
|
2c0eab9366 | ||
|
|
b831d8ca8a | ||
|
|
87a57e057d | ||
|
|
69da6bccf7 | ||
|
|
8623172aa1 | ||
|
|
b8c096f4ff | ||
|
|
f2e399f0df | ||
|
|
9a1f0e1e42 | ||
|
|
93ad23b615 | ||
|
|
0675f84386 | ||
|
|
6994e44bd3 | ||
|
|
0d6d8e9d7c | ||
|
|
0a918535bb | ||
|
|
f7c01d5b35 | ||
|
|
e3d7ebd7d8 | ||
|
|
4863b16b5f | ||
|
|
acea8d2fee | ||
|
|
5f0b63a192 | ||
|
|
a27f5e2153 | ||
|
|
1d0b4386d1 | ||
|
|
a36db7cee7 | ||
|
|
7a5ac739ab | ||
|
|
e2297be0af | ||
|
|
3ffea50072 | ||
|
|
a3c0e25407 | ||
|
|
73c4983342 | ||
|
|
166e1e4030 | ||
|
|
34af7d3880 | ||
|
|
a6c863f67d | ||
|
|
5fa7377121 | ||
|
|
f21ba8e087 | ||
|
|
4432721c27 | ||
|
|
169b5265c3 | ||
|
|
d56d1f369c | ||
|
|
360a1954f4 | ||
|
|
65ad4d9426 | ||
|
|
72ee621303 | ||
|
|
478e5fb569 | ||
|
|
6b047418cc | ||
|
|
87db292e5d | ||
|
|
9ef8440e64 | ||
|
|
8ae3b28cb6 | ||
|
|
87a55028e1 | ||
|
|
8045c228d6 | ||
|
|
6ecc67184d | ||
|
|
18924b4f08 | ||
|
|
b97bc0df8e | ||
|
|
0d80854196 | ||
|
|
029fb58f48 | ||
|
|
85929b0bb1 | ||
|
|
dc234e4d72 | ||
|
|
c524f5f0e0 | ||
|
|
cf86430aa9 | ||
|
|
212fc4a7cc | ||
|
|
8de7db60e6 | ||
|
|
d6df5af1a4 | ||
|
|
8d36ad3589 | ||
|
|
9061821347 | ||
|
|
aa6fc78aa0 | ||
|
|
2fbac78eec | ||
|
|
77e4d72a54 | ||
|
|
1fad3968bb | ||
|
|
1d84dd1a83 | ||
|
|
4734971d48 | ||
|
|
9a5a2c7497 | ||
|
|
a492909ad7 | ||
|
|
14a885b443 | ||
|
|
d5bd9d9b59 | ||
|
|
fc1055c644 | ||
|
|
774f1fea68 | ||
|
|
6e6cabbd63 | ||
|
|
480838b1dc | ||
|
|
57930005b2 | ||
|
|
24798390b5 | ||
|
|
e7bbb96dc3 | ||
|
|
ffadd31a5f | ||
|
|
235527140c | ||
|
|
a992dead04 | ||
|
|
af6139dcaf | ||
|
|
ef22ba3d2c | ||
|
|
11ff8e91c7 | ||
|
|
f039cd8d0d | ||
|
|
f120116e52 | ||
|
|
71dd138f2f | ||
|
|
36f4cc8cb8 | ||
|
|
d2944ff902 | ||
|
|
494e691230 | ||
|
|
46af401e9b | ||
|
|
3cbcbb92eb | ||
|
|
02e6c6007c | ||
|
|
2cee5f1944 | ||
|
|
4cc7366290 | ||
|
|
1c1f0a16e2 | ||
|
|
ef695776cd | ||
|
|
53580fbc78 | ||
|
|
21335d4e8c | ||
|
|
fd9d660a61 | ||
|
|
8b98206e63 | ||
|
|
9b545d6c8c | ||
|
|
fbe674a2e5 | ||
|
|
2a65cb5025 | ||
|
|
0b5bd4f718 | ||
|
|
14cffd3ad4 | ||
|
|
b4a3960eac | ||
|
|
358503f9ef | ||
|
|
25a9e6cea1 | ||
|
|
7511b42bd4 | ||
|
|
9567cd88b1 | ||
|
|
e40e0bbb8f | ||
|
|
8fdd07827e | ||
|
|
059067bc61 | ||
|
|
f8ae6dc5af | ||
|
|
26f94c4d5b | ||
|
|
ac2bb42124 | ||
|
|
b149f70b6f | ||
|
|
ec8a413ed1 | ||
|
|
76ec755d07 | ||
|
|
07faa5eec2 | ||
|
|
0810798d30 | ||
|
|
aa55f4840b | ||
|
|
7bbbf5934a | ||
|
|
fd7850b551 | ||
|
|
2b76f8a12d | ||
|
|
aa073cfd68 | ||
|
|
2810428d19 | ||
|
|
03863ce838 | ||
|
|
1b22697429 | ||
|
|
4fc3fbdcc0 | ||
|
|
163978930f | ||
|
|
c75e27e018 | ||
|
|
555bedbb6c | ||
|
|
a57abec81b | ||
|
|
b9df4c2587 | ||
|
|
15686bdab8 | ||
|
|
175e2097fa | ||
|
|
359c4c75a1 | ||
|
|
86aa5bf5e7 | ||
|
|
35b92570e5 | ||
|
|
b5c03b8cf0 | ||
|
|
3c45519457 | ||
|
|
dc60b8d18e | ||
|
|
83da64f96b | ||
|
|
ea75ac49aa | ||
|
|
1f8d027f97 | ||
|
|
f3c6c7f004 | ||
|
|
65fb73ae82 | ||
|
|
2f8ba20a5b | ||
|
|
617e39eb17 | ||
|
|
b525f920e0 | ||
|
|
f4a3b75a86 | ||
|
|
c0ffda27cf | ||
|
|
f51fa08961 | ||
|
|
ba63e8054f | ||
|
|
91fe6745fe | ||
|
|
32813032e6 | ||
|
|
b9073fe3f5 | ||
|
|
787366b231 | ||
|
|
a5904f55aa | ||
|
|
f6acc1107c | ||
|
|
9b871149ac | ||
|
|
9a71779cfe | ||
|
|
5bd6f0453d | ||
|
|
f6328d10f7 | ||
|
|
2c95b49ae1 | ||
|
|
ace0039429 | ||
|
|
f13a70a22f | ||
|
|
fa29bd609f | ||
|
|
3766c3d938 | ||
|
|
01b18a4a02 | ||
|
|
38681158c1 | ||
|
|
25b289b65d | ||
|
|
17c4e2fd0e | ||
|
|
eb51085055 | ||
|
|
abd530b8b2 | ||
|
|
e4d437018d | ||
|
|
0767e290f4 | ||
|
|
b86309e74b | ||
|
|
7e2bd128e8 | ||
|
|
7f547c90c2 | ||
|
|
fa39e2b97e | ||
|
|
c5d00f7641 | ||
|
|
08b7f0e59c | ||
|
|
f0af12bc2c | ||
|
|
ace9ec792d | ||
|
|
66671385d0 | ||
|
|
015f4cc5bd | ||
|
|
4f1c0b9996 | ||
|
|
b395839b37 | ||
|
|
0f067fc503 | ||
|
|
a5cf81bd28 | ||
|
|
e892e4cab1 | ||
|
|
5adb54f5cb | ||
|
|
9bde06e110 | ||
|
|
ef4bb75ce7 | ||
|
|
459af4f537 | ||
|
|
30449ca113 | ||
|
|
f860931eab | ||
|
|
0405d19f98 | ||
|
|
0bf9f932b7 | ||
|
|
2c5310403b | ||
|
|
a077cf0820 | ||
|
|
c1abe98b89 | ||
|
|
0f32e6ffc7 | ||
|
|
eca7ff4a42 | ||
|
|
7d6b95d344 | ||
|
|
9e44ee2a26 | ||
|
|
5d0500582e | ||
|
|
f53fcc345e | ||
|
|
1b7cb7c852 | ||
|
|
c82cfb3ec2 | ||
|
|
cc5fea9410 | ||
|
|
29f7144e72 | ||
|
|
1384616d66 | ||
|
|
366f7b9c4a | ||
|
|
67e904e121 | ||
|
|
e2ef4f1caf | ||
|
|
83ea51157d | ||
|
|
9f207f0946 | ||
|
|
2a81517104 | ||
|
|
00005c881e | ||
|
|
c1ea8e8a3d | ||
|
|
adb15a4748 | ||
|
|
c214ed1dfb | ||
|
|
c02c36c548 | ||
|
|
a15f86cc4e | ||
|
|
8a88a241d6 | ||
|
|
df13f257db | ||
|
|
b32a2d4d86 | ||
|
|
5cfadf7929 | ||
|
|
4a46870327 | ||
|
|
4684bada1e | ||
|
|
163354f4b4 | ||
|
|
3d225c9f92 | ||
|
|
f3b2edea1c | ||
|
|
01e103fd0e | ||
|
|
1fc21e49a0 | ||
|
|
19d608e2b0 | ||
|
|
7c92484ae0 | ||
|
|
4b184d1d42 | ||
|
|
3f75041ad9 | ||
|
|
09973ecb3b | ||
|
|
8c541dad05 | ||
|
|
921cca86c1 | ||
|
|
841312ebcd | ||
|
|
5ed00eaffe | ||
|
|
994ea8bb20 | ||
|
|
580641bae6 | ||
|
|
024b4fe21b | ||
|
|
40aca91c76 | ||
|
|
72305f91d8 | ||
|
|
abe4f4fb3d | ||
|
|
142617bc3d | ||
|
|
2ee582bfa2 | ||
|
|
35a3726cf0 | ||
|
|
54820fe3c8 | ||
|
|
b1ffbe0e12 | ||
|
|
ba2d03176f | ||
|
|
95a592fb9a | ||
|
|
b069b6bc4c | ||
|
|
fbb473941c | ||
|
|
6d343e9b7f | ||
|
|
c27b1d802f | ||
|
|
f11d9dd804 | ||
|
|
996f5b3c71 | ||
|
|
9bb7f647a7 | ||
|
|
760f2ac7f9 | ||
|
|
4d2d9500ff | ||
|
|
67cada5d8e | ||
|
|
872e36a61a | ||
|
|
779f608506 | ||
|
|
d9f562faa4 | ||
|
|
14e362ec3f | ||
|
|
c213e13624 | ||
|
|
dae7642a8c | ||
|
|
c751f0cba4 | ||
|
|
47fe1959b1 | ||
|
|
e128c108f8 | ||
|
|
b8b76cb96c | ||
|
|
fa958cbbfe | ||
|
|
e8ee2a9416 | ||
|
|
4bf68b637f | ||
|
|
2146672916 | ||
|
|
b40c433865 | ||
|
|
f96460f332 | ||
|
|
04c70876d0 | ||
|
|
bc6a94eede | ||
|
|
f288b0ee22 | ||
|
|
e54692928b | ||
|
|
0fd8bcb1b1 | ||
|
|
07b18836f5 | ||
|
|
ff08d19d79 | ||
|
|
55ed499ab5 | ||
|
|
bd6f300c8d | ||
|
|
ac2fbaf6f7 | ||
|
|
f409acc7fd | ||
|
|
06dcb20b2b | ||
|
|
f4fed0db9d | ||
|
|
8430f500ef | ||
|
|
68584243f4 | ||
|
|
c8f5c3ed9e | ||
|
|
994d7e17aa | ||
|
|
fd1dd6dddd | ||
|
|
1e9b82ba1e | ||
|
|
353bc3bc05 | ||
|
|
312795618e | ||
|
|
35b5645d6f | ||
|
|
a49d845f50 | ||
|
|
9d44540ca8 | ||
|
|
df5c96345c | ||
|
|
cc95d3bd44 | ||
|
|
648c47cde2 | ||
|
|
17cf6e7696 | ||
|
|
7326ffbae6 | ||
|
|
b2f73c4fba | ||
|
|
6628d43e12 | ||
|
|
a064b8e07e | ||
|
|
2c7c187c45 | ||
|
|
9efaa55235 | ||
|
|
da41668b3f | ||
|
|
df008abec9 | ||
|
|
3a5a78d60a | ||
|
|
6dd2871c07 | ||
|
|
29c99f2dd9 | ||
|
|
3033e84f45 | ||
|
|
ef36ab9da0 | ||
|
|
a917f6bcdf | ||
|
|
18966476e4 | ||
|
|
c5d6457146 | ||
|
|
f3831e934f | ||
|
|
db9e86e4c8 | ||
|
|
85c446bc57 | ||
|
|
5cf6f47bdc | ||
|
|
1db4cbeeb8 | ||
|
|
c88bf4065e |
@@ -9,13 +9,13 @@ orbs:
|
|||||||
jobs:
|
jobs:
|
||||||
imex-api-deploy:
|
imex-api-deploy:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- eb/setup
|
- eb/setup
|
||||||
- run:
|
- run:
|
||||||
command: |
|
command: |
|
||||||
eb init imex-online-production-api -r ca-central-1 -p "Node.js 18 running on 64bit Amazon Linux 2"
|
eb init imex-online-production-api -r ca-central-1 -p "Node.js 22 running on 64bit Amazon Linux 2023"
|
||||||
eb status --verbose
|
eb status --verbose
|
||||||
eb deploy
|
eb deploy
|
||||||
eb status
|
eb status
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
|
|
||||||
imex-hasura-migrate:
|
imex-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
parameters:
|
parameters:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
pipeline_number: << pipeline.number >>
|
pipeline_number: << pipeline.number >>
|
||||||
imex-app-build:
|
imex-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
resource_class: large
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
steps:
|
steps:
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
|
|
||||||
imex-app-beta-build:
|
imex-app-beta-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
resource_class: large
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: npm i
|
command: npm i
|
||||||
|
|
||||||
- run: npm run build:production:imex
|
- run: NODE_OPTIONS=--max-old-space-size=8192 npm run build:production:imex
|
||||||
|
|
||||||
- aws-cli/setup:
|
- aws-cli/setup:
|
||||||
aws_access_key_id: AWS_ACCESS_KEY_ID
|
aws_access_key_id: AWS_ACCESS_KEY_ID
|
||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
- eb/setup
|
- eb/setup
|
||||||
- run:
|
- run:
|
||||||
command: |
|
command: |
|
||||||
eb init romeonline-productionapi -r us-east-2 -p "Node.js 18 running on 64bit Amazon Linux 2"
|
eb init romeonline-productionapi -r us-east-2 -p "Node.js 22 running on 64bit Amazon Linux 2023"
|
||||||
eb status --verbose
|
eb status --verbose
|
||||||
eb deploy
|
eb deploy
|
||||||
eb status
|
eb status
|
||||||
@@ -126,7 +126,7 @@ jobs:
|
|||||||
pipeline_number: << pipeline.number >>
|
pipeline_number: << pipeline.number >>
|
||||||
rome-hasura-migrate:
|
rome-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
parameters:
|
parameters:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
@@ -150,8 +150,8 @@ jobs:
|
|||||||
pipeline_number: << pipeline.number >>
|
pipeline_number: << pipeline.number >>
|
||||||
rome-app-build:
|
rome-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -161,7 +161,7 @@ jobs:
|
|||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: npm i
|
command: npm i
|
||||||
|
|
||||||
- run: npm run build:production:rome
|
- run: NODE_OPTIONS=--max-old-space-size=8192 npm run build:production:rome
|
||||||
|
|
||||||
- aws-cli/setup:
|
- aws-cli/setup:
|
||||||
aws_access_key_id: AWS_ACCESS_KEY_ID
|
aws_access_key_id: AWS_ACCESS_KEY_ID
|
||||||
@@ -181,7 +181,7 @@ jobs:
|
|||||||
|
|
||||||
test-rome-hasura-migrate:
|
test-rome-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
parameters:
|
parameters:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
@@ -208,8 +208,8 @@ jobs:
|
|||||||
|
|
||||||
test-rome-app-build:
|
test-rome-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -219,7 +219,7 @@ jobs:
|
|||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: npm i
|
command: npm i
|
||||||
|
|
||||||
- run: npm run build:test:rome
|
- run: NODE_OPTIONS=--max-old-space-size=8192 npm run build:test:rome
|
||||||
|
|
||||||
- aws-cli/setup:
|
- aws-cli/setup:
|
||||||
aws_access_key_id: AWS_ACCESS_KEY_ID
|
aws_access_key_id: AWS_ACCESS_KEY_ID
|
||||||
@@ -239,7 +239,7 @@ jobs:
|
|||||||
|
|
||||||
test-hasura-migrate:
|
test-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
parameters:
|
parameters:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
@@ -266,7 +266,7 @@ jobs:
|
|||||||
|
|
||||||
imex-test-app-build:
|
imex-test-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
resource_class: large
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
@@ -277,7 +277,7 @@ jobs:
|
|||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: npm i
|
command: npm i
|
||||||
|
|
||||||
- run: npm run build:test:imex
|
- run: NODE_OPTIONS=--max-old-space-size=8192 npm run build:test:imex
|
||||||
|
|
||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
@@ -286,7 +286,7 @@ jobs:
|
|||||||
|
|
||||||
imex-test-app-beta-build:
|
imex-test-app-beta-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
resource_class: large
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
@@ -298,7 +298,7 @@ jobs:
|
|||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: npm i
|
command: npm i
|
||||||
|
|
||||||
- run: npm run build:test:imex
|
- run: NODE_OPTIONS=--max-old-space-size=8192 npm run build:test:imex
|
||||||
|
|
||||||
- aws-cli/setup:
|
- aws-cli/setup:
|
||||||
aws_access_key_id: AWS_ACCESS_KEY_ID
|
aws_access_key_id: AWS_ACCESS_KEY_ID
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ node_modules
|
|||||||
# Files to exclude
|
# Files to exclude
|
||||||
.ebignore
|
.ebignore
|
||||||
.editorconfig
|
.editorconfig
|
||||||
.eslintrc.json
|
|
||||||
.gitignore
|
.gitignore
|
||||||
.prettierrc.js
|
.prettierrc.js
|
||||||
Dockerfile
|
Dockerfile
|
||||||
@@ -19,6 +18,6 @@ README.MD
|
|||||||
bodyshop_translations.babel
|
bodyshop_translations.babel
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
ecosystem.config.js
|
ecosystem.config.js
|
||||||
|
eslint.config.mjs
|
||||||
# Optional: Exclude logs and temporary files
|
# Optional: Exclude logs and temporary files
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"es6": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": "eslint:recommended",
|
|
||||||
"globals": {
|
|
||||||
"Atomics": "readonly",
|
|
||||||
"SharedArrayBuffer": "readonly"
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2018,
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"no-console": "off"
|
|
||||||
},
|
|
||||||
"settings": {}
|
|
||||||
}
|
|
||||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -121,3 +121,14 @@ logs/oAuthClient-log.log
|
|||||||
/*.env.*
|
/*.env.*
|
||||||
.idea/*
|
.idea/*
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
# Vitest
|
||||||
|
vitest-report*/
|
||||||
|
vitest-coverage/
|
||||||
|
*.vitest.log
|
||||||
|
test-output.txt
|
||||||
|
server/job/test/fixtures
|
||||||
|
|
||||||
|
.github
|
||||||
|
_reference/ragmate/.ragmate.env
|
||||||
|
docker_data
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM amazonlinux:2023
|
|||||||
|
|
||||||
# Install Git and Node.js (Amazon Linux 2023 uses the DNF package manager)
|
# Install Git and Node.js (Amazon Linux 2023 uses the DNF package manager)
|
||||||
RUN dnf install -y git \
|
RUN dnf install -y git \
|
||||||
&& curl -sL https://rpm.nodesource.com/setup_20.x | bash - \
|
&& curl -sL https://rpm.nodesource.com/setup_22.x | bash - \
|
||||||
&& dnf install -y nodejs \
|
&& dnf install -y nodejs \
|
||||||
&& dnf clean all
|
&& dnf clean all
|
||||||
|
|
||||||
@@ -56,4 +56,5 @@ COPY . .
|
|||||||
EXPOSE 4000 9229
|
EXPOSE 4000 9229
|
||||||
|
|
||||||
# Start the application
|
# Start the application
|
||||||
CMD ["nodemon", "--legacy-watch", "--inspect=0.0.0.0:9229", "server.js"]
|
RUN echo "Starting the application..."
|
||||||
|
CMD ["nodemon", "--ignore", "./server/job/test/fixtures", "--legacy-watch", "--inspect=0.0.0.0:9229", "server.js"]
|
||||||
|
|||||||
346
Fortellis Notes.md
Normal file
346
Fortellis Notes.md
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
Fortellis Notes
|
||||||
|
|
||||||
|
Subscription ID
|
||||||
|
|
||||||
|
- Appears to give us a list of all dealerships we have access to, and `apiDmsInfo` contains the integrations that are enabled for that dealership.
|
||||||
|
- Will likely need to filter based on the DMS ID or something?
|
||||||
|
- Should store the whole subscription object. Contains department information needed in subsequent calls.
|
||||||
|
|
||||||
|
Department ID
|
||||||
|
|
||||||
|
- May have multiple departments. Appears that financial stuff goes to Accounting, History will go to Service.
|
||||||
|
- TODO: How do we handle the multiple departments that may come up.
|
||||||
|
|
||||||
|
###Internal Questions
|
||||||
|
|
||||||
|
* Overview of the redis storing mechanism to cache this data.
|
||||||
|
*
|
||||||
|
|
||||||
|
# GL Wip Posting
|
||||||
|
|
||||||
|
## Org Helper Return Data
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"acctgLgnID": "DEVWB-A",
|
||||||
|
"applCode": "V",
|
||||||
|
"coID": "77",
|
||||||
|
"companyName": "TEST SYS C187092 DEVWB",
|
||||||
|
"lgnDesc": "DEV WRITE BACK VMS",
|
||||||
|
"logon": "DEVWB-V"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"acctgLgnID": "DEVWB-A",
|
||||||
|
"applCode": "F",
|
||||||
|
"coID": "77",
|
||||||
|
"companyName": "TEST SYS C187092 DEVWB",
|
||||||
|
"lgnDesc": "DEV WRITE BACK F&I SALES",
|
||||||
|
"logon": "DEVWB-FI"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"acctgLgnID": "DEVWB-A",
|
||||||
|
"applCode": "CS",
|
||||||
|
"coID": "77",
|
||||||
|
"companyName": "TEST SYS C187092 DEVWB",
|
||||||
|
"lgnDesc": "DEV WRITE BACK SERVICE",
|
||||||
|
"logon": "DEVWB-S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"acctgLgnID": "DEVWB-A",
|
||||||
|
"applCode": "A",
|
||||||
|
"coID": "77",
|
||||||
|
"companyName": "TEST SYS C187092 DEVWB",
|
||||||
|
"lgnDesc": "DEV WRITE BACK ACCTG",
|
||||||
|
"logon": "DEVWB-A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"acctgLgnID": "DEVWB-A",
|
||||||
|
"applCode": "SL",
|
||||||
|
"coID": "77",
|
||||||
|
"companyName": "TEST SYS C187092 DEVWB",
|
||||||
|
"lgnDesc": "DEV WRTIE BACK SLS MGMT",
|
||||||
|
"logon": "DEVWB-SL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"acctgLgnID": "DEVWB-A",
|
||||||
|
"applCode": "O",
|
||||||
|
"coID": "77",
|
||||||
|
"companyName": "TEST SYS C187092 DEVWB",
|
||||||
|
"lgnDesc": "DEV WRITE BACK PARTS",
|
||||||
|
"logon": "DEVWB-I"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Journal Helper Return Data
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "32",
|
||||||
|
"jrnlName": "PARTS SALES",
|
||||||
|
"jrnlType": "S",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "4",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "92",
|
||||||
|
"jrnlName": "YTD ADJUSTMENTS",
|
||||||
|
"jrnlType": "Y",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "3",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "12",
|
||||||
|
"jrnlName": "FLEET SALES",
|
||||||
|
"jrnlType": "S",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "9",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "57",
|
||||||
|
"jrnlName": "CASH RECEIPTS (OPEN-ITEM)",
|
||||||
|
"jrnlType": "R",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "1",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "93",
|
||||||
|
"jrnlName": "SET UP HISTORY",
|
||||||
|
"jrnlType": "H",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "10",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "88",
|
||||||
|
"jrnlName": "F/S STATISCAL DATA",
|
||||||
|
"jrnlType": "F",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "10",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "58",
|
||||||
|
"jrnlName": "WARRANTY CREDITS",
|
||||||
|
"jrnlType": "G",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "3",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "FC",
|
||||||
|
"jrnlName": "FINANCE CHARGE",
|
||||||
|
"jrnlType": "A",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "12",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "94",
|
||||||
|
"jrnlName": "SET UP SCHEDULES",
|
||||||
|
"jrnlType": "C",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "3",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "95",
|
||||||
|
"jrnlName": "SET UP GENERAL LEDGER",
|
||||||
|
"jrnlType": "B",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "3",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "20",
|
||||||
|
"jrnlName": "USED VEHICLE SALES",
|
||||||
|
"jrnlType": "S",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "9",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "60",
|
||||||
|
"jrnlName": "CASH DISBURSEMENTS",
|
||||||
|
"jrnlType": "G",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "2",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "30",
|
||||||
|
"jrnlName": "SERVICE SALES",
|
||||||
|
"jrnlType": "S",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "7",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "40",
|
||||||
|
"jrnlName": "PAYROLL",
|
||||||
|
"jrnlType": "G",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "11",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "15",
|
||||||
|
"jrnlName": "DEALER TRADES",
|
||||||
|
"jrnlType": "S",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "9",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "70",
|
||||||
|
"jrnlName": "NEW VEHICLE PURCHASES",
|
||||||
|
"jrnlType": "G",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "8",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "25",
|
||||||
|
"jrnlName": "USED WHOLESALE",
|
||||||
|
"jrnlType": "S",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "9",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "75",
|
||||||
|
"jrnlName": "GENERAL PURCHASES",
|
||||||
|
"jrnlType": "G",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "5",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "10",
|
||||||
|
"jrnlName": "NEW VEHICLE SALES",
|
||||||
|
"jrnlType": "S",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "9",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "80",
|
||||||
|
"jrnlName": "GENERAL JOURNAL",
|
||||||
|
"jrnlType": "G",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "3",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "11",
|
||||||
|
"jrnlName": "WORK IN PROGRESS",
|
||||||
|
"jrnlType": "G",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "10",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "56",
|
||||||
|
"jrnlName": "CASH RECEIPTS (BALANCE FWD)",
|
||||||
|
"jrnlType": "G",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "1",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "81",
|
||||||
|
"jrnlName": "STANDARD ENTRIES",
|
||||||
|
"jrnlType": "G",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "6",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "51",
|
||||||
|
"jrnlName": "CASH RECEIPTS JOURNAL - EFT",
|
||||||
|
"jrnlType": "G",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "10",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "61",
|
||||||
|
"jrnlName": "CASH DISBURSMENTS -EFT",
|
||||||
|
"jrnlType": "G",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "10",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"companyNo": "77",
|
||||||
|
"jrnlID": "71",
|
||||||
|
"jrnlName": "USED VEHICLE PURCHASES",
|
||||||
|
"jrnlType": "G",
|
||||||
|
"intercoFlag": "0",
|
||||||
|
"defaultDocType": "8",
|
||||||
|
"errCode": "",
|
||||||
|
"errMsg": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Feedback
|
||||||
|
|
||||||
|
- Receiving bad request errors, with no details. API errors page doesn't indicate what's wrong for certain types of error codes.
|
||||||
|
- API Error page works on a several minute delay.
|
||||||
@@ -1,116 +1,96 @@
|
|||||||
// index.js
|
// index.js
|
||||||
|
|
||||||
import express from 'express';
|
import express from "express";
|
||||||
import fetch from 'node-fetch';
|
import fetch from "node-fetch";
|
||||||
import {simpleParser} from 'mailparser';
|
import { simpleParser } from "mailparser";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = 3334;
|
const PORT = 3334;
|
||||||
|
|
||||||
app.get('/', async (req, res) => {
|
app.get("/", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:4566/_aws/ses');
|
const response = await fetch("http://localhost:4566/_aws/ses");
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Network response was not ok');
|
throw new Error("Network response was not ok");
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
const messagesHtml = await parseMessages(data.messages);
|
|
||||||
res.send(renderHtml(messagesHtml));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching messages:', error);
|
|
||||||
res.status(500).send('Error fetching messages');
|
|
||||||
}
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
const messagesHtml = await parseMessages(data.messages);
|
||||||
|
res.send(renderHtml(messagesHtml));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching messages:", error);
|
||||||
|
res.status(500).send("Error fetching messages");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function parseMessages(messages) {
|
async function parseMessages(messages) {
|
||||||
const parsedMessages = await Promise.all(
|
const parsedMessages = await Promise.all(
|
||||||
messages.map(async (message, index) => {
|
messages.map(async (message, index) => {
|
||||||
try {
|
try {
|
||||||
const parsed = await simpleParser(message.RawData);
|
const parsed = await simpleParser(message.RawData);
|
||||||
return `
|
return `
|
||||||
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: lightgray">
|
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: lightgray">
|
||||||
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: white">
|
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: white">
|
||||||
<div class="mb-2">
|
<div class="mb-2"><span class="font-bold text-lg">Message ${index + 1}</span></div>
|
||||||
<span class="font-bold text-lg">Message ${index + 1}</span>
|
<div class="mb-2"><span class="font-semibold">From:</span> ${message.Source}</div>
|
||||||
</div>
|
<div class="mb-2"><span class="font-semibold">To:</span> ${parsed.to.text || "No To Address"}</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2"><span class="font-semibold">Subject:</span> ${parsed.subject || "No Subject"}</div>
|
||||||
<span class="font-semibold">From:</span> ${message.Source}
|
<div class="mb-2"><span class="font-semibold">Region:</span> ${message.Region}</div>
|
||||||
</div>
|
<div class="mb-2"><span class="font-semibold">Timestamp:</span> ${message.Timestamp}</div>
|
||||||
<div class="mb-2">
|
</div>
|
||||||
<span class="font-semibold">Region:</span> ${message.Region}
|
<div class="prose">${parsed.html || parsed.textAsHtml || "No HTML content available"}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
`;
|
||||||
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
|
} catch (error) {
|
||||||
</div>
|
console.error("Error parsing email:", error);
|
||||||
</div>
|
return `
|
||||||
<div class="prose">
|
<div class="bg-white shadow-md rounded-lg p-4 mb-6">
|
||||||
${parsed.html || parsed.textAsHtml || 'No HTML content available'}
|
<div class="mb-2"><span class="font-bold text-lg">Message ${index + 1}</span></div>
|
||||||
</div>
|
<div class="mb-2"><span class="font-semibold">From:</span> ${message.Source}</div>
|
||||||
</div>
|
<div class="mb-2"><span class="font-semibold">Region:</span> ${message.Region}</div>
|
||||||
`;
|
<div class="mb-2"><span class="font-semibold">Timestamp:</span> ${message.Timestamp}</div>
|
||||||
} catch (error) {
|
<div class="text-red-500">Error parsing email content</div>
|
||||||
console.error('Error parsing email:', error);
|
</div>
|
||||||
return `
|
`;
|
||||||
<div class="bg-white shadow-md rounded-lg p-4 mb-6">
|
}
|
||||||
<div class="mb-2">
|
})
|
||||||
<span class="font-bold text-lg">Message ${index + 1}</span>
|
);
|
||||||
</div>
|
return parsedMessages.join("");
|
||||||
<div class="mb-2">
|
|
||||||
<span class="font-semibold">From:</span> ${message.Source}
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<span class="font-semibold">Region:</span> ${message.Region}
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
|
|
||||||
</div>
|
|
||||||
<div class="text-red-500">
|
|
||||||
Error parsing email content
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return parsedMessages.join('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderHtml(messagesHtml) {
|
function renderHtml(messagesHtml) {
|
||||||
return `
|
return `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Email Messages Viewer</title>
|
<title>Email Messages Viewer</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background-color: #f3f4f6;
|
background-color: #f3f4f6;
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 50px auto;
|
margin: 50px auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
.prose {
|
.prose {
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container bg-white shadow-lg rounded-lg p-6">
|
<div class="container bg-white shadow-lg rounded-lg p-6">
|
||||||
<h1 class="text-2xl font-bold text-center mb-6">Email Messages Viewer</h1>
|
<h1 class="text-2xl font-bold text-center mb-6">Email Messages Viewer</h1>
|
||||||
<div id="messages-container">
|
<div id="messages-container">${messagesHtml}</div>
|
||||||
${messagesHtml}
|
</div>
|
||||||
</div>
|
</body>
|
||||||
</div>
|
</html>
|
||||||
</body>
|
`;
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Server is running on http://localhost:${PORT}`);
|
console.log(`Server is running on http://localhost:${PORT}`);
|
||||||
});
|
});
|
||||||
764
_reference/localEmailViewer/package-lock.json
generated
764
_reference/localEmailViewer/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,8 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.21.1",
|
"express": "^5.1.0",
|
||||||
"mailparser": "^3.7.1",
|
"mailparser": "^3.7.4",
|
||||||
"node-fetch": "^3.3.2"
|
"node-fetch": "^3.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# PATCH /integrations/parts-management/job/:id/status
|
||||||
|
|
||||||
|
Update (patch) the status of a job created under parts management. This endpoint is only available
|
||||||
|
for jobs whose parent bodyshop has an `external_shop_id` (i.e., is provisioned for parts
|
||||||
|
management).
|
||||||
|
|
||||||
|
## Endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
PATCH /integrations/parts-management/job/:id/status
|
||||||
|
```
|
||||||
|
|
||||||
|
- `:id` is the UUID of the job to update.
|
||||||
|
|
||||||
|
## Request Headers
|
||||||
|
|
||||||
|
- `Authorization`: (if required by your integration middleware)
|
||||||
|
- `Content-Type: application/json`
|
||||||
|
|
||||||
|
## Request Body
|
||||||
|
|
||||||
|
Send a JSON object with the following field:
|
||||||
|
|
||||||
|
- `status` (string, required): The new status for the job.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
PATCH /integrations/parts-management/job/123e4567-e89b-12d3-a456-426614174000/status
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"status": "IN_PROGRESS"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Success Response
|
||||||
|
|
||||||
|
- **200 OK**
|
||||||
|
- Returns the updated job object with the new status.
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": "123e4567-e89b-12d3-a456-426614174000",
|
||||||
|
"status": "IN_PROGRESS",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Responses
|
||||||
|
|
||||||
|
- **400 Bad Request**: Missing status field, or parent bodyshop does not have an `external_shop_id`.
|
||||||
|
- **404 Not Found**: No job found with the given ID.
|
||||||
|
- **500 Internal Server Error**: Unexpected error.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Only jobs whose parent bodyshop has an `external_shop_id` can be patched via this route.
|
||||||
|
- Fields other than `status` will be ignored if included in the request body.
|
||||||
|
- The route is protected by the same middleware as other parts management endpoints.
|
||||||
|
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# PATCH /integrations/parts-management/provision/:id
|
||||||
|
|
||||||
|
Update (patch) select fields for a parts management bodyshop. Only available for shops that have an
|
||||||
|
`external_shop_id` (i.e., are provisioned for parts management).
|
||||||
|
|
||||||
|
## Endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
PATCH /integrations/parts-management/provision/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
- `:id` is the UUID of the bodyshop to update.
|
||||||
|
|
||||||
|
## Request Headers
|
||||||
|
|
||||||
|
- `Authorization`: (if required by your integration middleware)
|
||||||
|
- `Content-Type: application/json`
|
||||||
|
|
||||||
|
## Request Body
|
||||||
|
|
||||||
|
Send a JSON object with one or more of the following fields to update:
|
||||||
|
|
||||||
|
- `shopname` (string)
|
||||||
|
- `address1` (string)
|
||||||
|
- `address2` (string, optional)
|
||||||
|
- `city` (string)
|
||||||
|
- `state` (string)
|
||||||
|
- `zip_post` (string)
|
||||||
|
- `country` (string)
|
||||||
|
- `email` (string, shop's email, not user email)
|
||||||
|
- `timezone` (string)
|
||||||
|
- `phone` (string)
|
||||||
|
- `logo_img_path` (object, e.g. `{ src, width, height, headerMargin }`)
|
||||||
|
|
||||||
|
Any fields not included in the request body will remain unchanged.
|
||||||
|
|
||||||
|
## Example Request
|
||||||
|
|
||||||
|
```
|
||||||
|
PATCH /integrations/parts-management/provision/123e4567-e89b-12d3-a456-426614174000
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"shopname": "New Shop Name",
|
||||||
|
"address1": "123 Main St",
|
||||||
|
"city": "Springfield",
|
||||||
|
"state": "IL",
|
||||||
|
"zip_post": "62704",
|
||||||
|
"country": "USA",
|
||||||
|
"email": "shop@example.com",
|
||||||
|
"timezone": "America/Chicago",
|
||||||
|
"phone": "555-123-4567",
|
||||||
|
"logo_img_path": {
|
||||||
|
"src": "https://example.com/logo.png",
|
||||||
|
"width": "200",
|
||||||
|
"height": "100",
|
||||||
|
"headerMargin": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Success Response
|
||||||
|
|
||||||
|
- **200 OK**
|
||||||
|
- Returns the updated shop object with the patched fields.
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": "123e4567-e89b-12d3-a456-426614174000",
|
||||||
|
"shopname": "New Shop Name",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Responses
|
||||||
|
|
||||||
|
- **400 Bad Request**: No valid fields provided, or shop does not have an `external_shop_id`.
|
||||||
|
- **404 Not Found**: No shop found with the given ID.
|
||||||
|
- **500 Internal Server Error**: Unexpected error.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Only shops with an `external_shop_id` can be patched via this route.
|
||||||
|
- Fields not listed above will be ignored if included in the request body.
|
||||||
|
- The route is protected by the same middleware as other parts management endpoints.
|
||||||
|
|
||||||
166
_reference/partsManagement/swagger.yaml
Normal file
166
_reference/partsManagement/swagger.yaml
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: Parts Management Provisioning API
|
||||||
|
description: API endpoint to provision a new shop and user in the Parts Management system.
|
||||||
|
version: 1.0.0
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/parts-management/provision:
|
||||||
|
post:
|
||||||
|
summary: Provision a new parts management shop and user
|
||||||
|
operationId: partsManagementProvisioning
|
||||||
|
tags:
|
||||||
|
- Parts Management
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- external_shop_id
|
||||||
|
- shopname
|
||||||
|
- address1
|
||||||
|
- city
|
||||||
|
- state
|
||||||
|
- zip_post
|
||||||
|
- country
|
||||||
|
- email
|
||||||
|
- phone
|
||||||
|
- userEmail
|
||||||
|
properties:
|
||||||
|
external_shop_id:
|
||||||
|
type: string
|
||||||
|
description: External shop ID (must be unique)
|
||||||
|
shopname:
|
||||||
|
type: string
|
||||||
|
address1:
|
||||||
|
type: string
|
||||||
|
address2:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
city:
|
||||||
|
type: string
|
||||||
|
state:
|
||||||
|
type: string
|
||||||
|
zip_post:
|
||||||
|
type: string
|
||||||
|
country:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
phone:
|
||||||
|
type: string
|
||||||
|
userEmail:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
userPassword:
|
||||||
|
type: string
|
||||||
|
description: Optional password for the new user. If provided, the password is set directly, and no password reset link is sent. Must be at least 6 characters.
|
||||||
|
nullable: true
|
||||||
|
logoUrl:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
nullable: true
|
||||||
|
timezone:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
vendors:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
street1:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
street2:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
city:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
state:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
zip:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
country:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
nullable: true
|
||||||
|
discount:
|
||||||
|
type: number
|
||||||
|
nullable: true
|
||||||
|
due_date:
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
|
nullable: true
|
||||||
|
cost_center:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
favorite:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
phone:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
active:
|
||||||
|
type: boolean
|
||||||
|
nullable: true
|
||||||
|
dmsid:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Shop and user successfully created
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
shop:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
shopname:
|
||||||
|
type: string
|
||||||
|
user:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
resetLink:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
nullable: true
|
||||||
|
description: Password reset link for the user. Only included if userPassword is not provided in the request.
|
||||||
|
'400':
|
||||||
|
description: Bad request (missing or invalid fields)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
10
_reference/ragmate/local-rag-compose.yml
Normal file
10
_reference/ragmate/local-rag-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
services:
|
||||||
|
ragmate:
|
||||||
|
image: ghcr.io/ragmate/ragmate:latest
|
||||||
|
ports:
|
||||||
|
- "11434:11434"
|
||||||
|
env_file:
|
||||||
|
- .ragmate.env
|
||||||
|
volumes:
|
||||||
|
- .:/project
|
||||||
|
- ./docker_data/ragmate:/apps/cache
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,12 @@ VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
|||||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
VITE_APP_AXIOS_BASE_API_URL=/api/
|
VITE_APP_AXIOS_BASE_API_URL=/api/
|
||||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
VITE_APP_REPORTS_SERVER_URL=https://reports.test.imex.online
|
||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_INSTANCE=IMEX
|
VITE_APP_INSTANCE=IMEX
|
||||||
|
TEST_USERNAME="test@imex.dev"
|
||||||
|
TEST_PASSWORD="test123"
|
||||||
|
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||||
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
|
VITE_APP_AMP_KEY=6228a598e57cd66875cfd41604f1f891
|
||||||
@@ -10,7 +10,13 @@ VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
|||||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
||||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
VITE_APP_AXIOS_BASE_API_URL=/api/
|
VITE_APP_AXIOS_BASE_API_URL=/api/
|
||||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
VITE_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
|
||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_COUNTRY=USA
|
VITE_APP_COUNTRY=USA
|
||||||
VITE_APP_INSTANCE=ROME
|
VITE_APP_INSTANCE=ROME
|
||||||
|
TEST_USERNAME="test@imex.dev"
|
||||||
|
TEST_PASSWORD="test123"
|
||||||
|
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||||
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
|
VITE_APP_AMP_KEY=46b1193a867d4e3131ae4c3a64a3fc78
|
||||||
@@ -13,3 +13,7 @@ VITE_APP_AXIOS_BASE_API_URL=https://api.imex.online/
|
|||||||
VITE_APP_REPORTS_SERVER_URL=https://reports.imex.online
|
VITE_APP_REPORTS_SERVER_URL=https://reports.imex.online
|
||||||
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||||
VITE_APP_INSTANCE=IMEX
|
VITE_APP_INSTANCE=IMEX
|
||||||
|
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||||
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
|
VITE_APP_AMP_KEY=6228a598e57cd66875cfd41604f1f891
|
||||||
@@ -13,3 +13,7 @@ VITE_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
|
|||||||
VITE_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
|
VITE_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
|
||||||
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||||
VITE_APP_INSTANCE=ROME
|
VITE_APP_INSTANCE=ROME
|
||||||
|
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||||
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
|
VITE_APP_AMP_KEY=46b1193a867d4e3131ae4c3a64a3fc78
|
||||||
@@ -9,7 +9,11 @@ VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
|||||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
|
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
|
||||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
VITE_APP_AXIOS_BASE_API_URL=https://api.test.imex.online/
|
VITE_APP_AXIOS_BASE_API_URL=https://api.test.imex.online/
|
||||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
VITE_APP_REPORTS_SERVER_URL=https://reports.test.imex.online
|
||||||
VITE_APP_IS_TEST=true
|
VITE_APP_IS_TEST=true
|
||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_INSTANCE=IMEX
|
VITE_APP_INSTANCE=IMEX
|
||||||
|
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||||
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
|
VITE_APP_AMP_KEY=6228a598e57cd66875cfd41604f1f891
|
||||||
@@ -13,3 +13,7 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
|
|||||||
VITE_APP_IS_TEST=true
|
VITE_APP_IS_TEST=true
|
||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_INSTANCE=ROME
|
VITE_APP_INSTANCE=ROME
|
||||||
|
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||||
|
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
|
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||||
|
VITE_APP_AMP_KEY=46b1193a867d4e3131ae4c3a64a3fc78
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"react-app"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"no-useless-rename": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
client/.gitignore
vendored
11
client/.gitignore
vendored
@@ -1,3 +1,14 @@
|
|||||||
|
# Vitest
|
||||||
|
vitest-report*/
|
||||||
|
vitest-coverage/
|
||||||
|
*.vitest.log
|
||||||
|
test-output.txt
|
||||||
|
|
||||||
|
# Playwright
|
||||||
|
playwright-report/
|
||||||
|
test-results/
|
||||||
|
playwright/.cache/
|
||||||
|
*.playwright.log
|
||||||
|
|
||||||
# Sentry Config File
|
# Sentry Config File
|
||||||
.sentryclirc
|
.sentryclirc
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
const { defineConfig } = require("cypress");
|
|
||||||
|
|
||||||
module.exports = defineConfig({
|
|
||||||
experimentalStudio: true,
|
|
||||||
env: {
|
|
||||||
FIREBASE_USERNAME: "cypress@imex.test",
|
|
||||||
FIREBASE_PASSWORD: "cypress"
|
|
||||||
},
|
|
||||||
e2e: {
|
|
||||||
// We've imported your old cypress plugins here.
|
|
||||||
// You may want to clean this up later by importing these.
|
|
||||||
setupNodeEvents(on, config) {
|
|
||||||
return require("./cypress/plugins/index.js")(on, config);
|
|
||||||
},
|
|
||||||
baseUrl: "https://localhost:3000"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/// <reference types="Cypress" />
|
|
||||||
const { FIREBASE_USERNAME, FIREBASE_PASSWORcD } = Cypress.env();
|
|
||||||
describe("Renders the General Page", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("/");
|
|
||||||
});
|
|
||||||
it("Renders Correctly", () => {});
|
|
||||||
it("Has the Slogan", () => {
|
|
||||||
cy.findByText("A whole x22new kind of shop management system.").should("exist");
|
|
||||||
/* ==== Generated with Cypress Studio ==== */
|
|
||||||
cy.get(".ant-menu-item-active > .ant-menu-title-content > .header0-item-block").click();
|
|
||||||
cy.get("#email").clear();
|
|
||||||
cy.get("#email").type("patrick@imex.dev");
|
|
||||||
cy.get("#password").clear();
|
|
||||||
cy.get("#password").type("patrick123{enter}");
|
|
||||||
cy.get(".ant-form > .ant-btn").click();
|
|
||||||
/* ==== End Cypress Studio ==== */
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
// Welcome to Cypress!
|
|
||||||
//
|
|
||||||
// This spec file contains a variety of sample tests
|
|
||||||
// for a todo list app that are designed to demonstrate
|
|
||||||
// the power of writing tests in Cypress.
|
|
||||||
//
|
|
||||||
// To learn more about how Cypress works and
|
|
||||||
// what makes it such an awesome testing tool,
|
|
||||||
// please read our getting started guide:
|
|
||||||
// https://on.cypress.io/introduction-to-cypress
|
|
||||||
|
|
||||||
describe("example to-do app", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
// Cypress starts out with a blank slate for each test
|
|
||||||
// so we must tell it to visit our website with the `cy.visit()` command.
|
|
||||||
// Since we want to visit the same URL at the start of all our tests,
|
|
||||||
// we include it in our beforeEach function so that it runs before each test
|
|
||||||
cy.visit("https://example.cypress.io/todo");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays two todo items by default", () => {
|
|
||||||
// We use the `cy.get()` command to get all elements that match the selector.
|
|
||||||
// Then, we use `should` to assert that there are two matched items,
|
|
||||||
// which are the two default items.
|
|
||||||
cy.get(".todo-list li").should("have.length", 2);
|
|
||||||
|
|
||||||
// We can go even further and check that the default todos each contain
|
|
||||||
// the correct text. We use the `first` and `last` functions
|
|
||||||
// to get just the first and last matched elements individually,
|
|
||||||
// and then perform an assertion with `should`.
|
|
||||||
cy.get(".todo-list li").first().should("have.text", "Pay electric bill");
|
|
||||||
cy.get(".todo-list li").last().should("have.text", "Walk the dog");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can add new todo items", () => {
|
|
||||||
// We'll store our item text in a variable so we can reuse it
|
|
||||||
const newItem = "Feed the cat";
|
|
||||||
|
|
||||||
// Let's get the input element and use the `type` command to
|
|
||||||
// input our new list item. After typing the content of our item,
|
|
||||||
// we need to type the enter key as well in order to submit the input.
|
|
||||||
// This input has a data-test attribute so we'll use that to select the
|
|
||||||
// element in accordance with best practices:
|
|
||||||
// https://on.cypress.io/selecting-elements
|
|
||||||
cy.get("[data-test=new-todo]").type(`${newItem}{enter}`);
|
|
||||||
|
|
||||||
// Now that we've typed our new item, let's check that it actually was added to the list.
|
|
||||||
// Since it's the newest item, it should exist as the last element in the list.
|
|
||||||
// In addition, with the two default items, we should have a total of 3 elements in the list.
|
|
||||||
// Since assertions yield the element that was asserted on,
|
|
||||||
// we can chain both of these assertions together into a single statement.
|
|
||||||
cy.get(".todo-list li").should("have.length", 3).last().should("have.text", newItem);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can check off an item as completed", () => {
|
|
||||||
// In addition to using the `get` command to get an element by selector,
|
|
||||||
// we can also use the `contains` command to get an element by its contents.
|
|
||||||
// However, this will yield the <label>, which is lowest-level element that contains the text.
|
|
||||||
// In order to check the item, we'll find the <input> element for this <label>
|
|
||||||
// by traversing up the dom to the parent element. From there, we can `find`
|
|
||||||
// the child checkbox <input> element and use the `check` command to check it.
|
|
||||||
cy.contains("Pay electric bill").parent().find("input[type=checkbox]").check();
|
|
||||||
|
|
||||||
// Now that we've checked the button, we can go ahead and make sure
|
|
||||||
// that the list element is now marked as completed.
|
|
||||||
// Again we'll use `contains` to find the <label> element and then use the `parents` command
|
|
||||||
// to traverse multiple levels up the dom until we find the corresponding <li> element.
|
|
||||||
// Once we get that element, we can assert that it has the completed class.
|
|
||||||
cy.contains("Pay electric bill").parents("li").should("have.class", "completed");
|
|
||||||
});
|
|
||||||
|
|
||||||
context("with a checked task", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
// We'll take the command we used above to check off an element
|
|
||||||
// Since we want to perform multiple tests that start with checking
|
|
||||||
// one element, we put it in the beforeEach hook
|
|
||||||
// so that it runs at the start of every test.
|
|
||||||
cy.contains("Pay electric bill").parent().find("input[type=checkbox]").check();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can filter for uncompleted tasks", () => {
|
|
||||||
// We'll click on the "active" button in order to
|
|
||||||
// display only incomplete items
|
|
||||||
cy.contains("Active").click();
|
|
||||||
|
|
||||||
// After filtering, we can assert that there is only the one
|
|
||||||
// incomplete item in the list.
|
|
||||||
cy.get(".todo-list li").should("have.length", 1).first().should("have.text", "Walk the dog");
|
|
||||||
|
|
||||||
// For good measure, let's also assert that the task we checked off
|
|
||||||
// does not exist on the page.
|
|
||||||
cy.contains("Pay electric bill").should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can filter for completed tasks", () => {
|
|
||||||
// We can perform similar steps as the test above to ensure
|
|
||||||
// that only completed tasks are shown
|
|
||||||
cy.contains("Completed").click();
|
|
||||||
|
|
||||||
cy.get(".todo-list li").should("have.length", 1).first().should("have.text", "Pay electric bill");
|
|
||||||
|
|
||||||
cy.contains("Walk the dog").should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can delete all completed tasks", () => {
|
|
||||||
// First, let's click the "Clear completed" button
|
|
||||||
// `contains` is actually serving two purposes here.
|
|
||||||
// First, it's ensuring that the button exists within the dom.
|
|
||||||
// This button only appears when at least one task is checked
|
|
||||||
// so this command is implicitly verifying that it does exist.
|
|
||||||
// Second, it selects the button so we can click it.
|
|
||||||
cy.contains("Clear completed").click();
|
|
||||||
|
|
||||||
// Then we can make sure that there is only one element
|
|
||||||
// in the list and our element does not exist
|
|
||||||
cy.get(".todo-list li").should("have.length", 1).should("not.have.text", "Pay electric bill");
|
|
||||||
|
|
||||||
// Finally, make sure that the clear button no longer exists.
|
|
||||||
cy.contains("Clear completed").should("not.exist");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Actions", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/actions");
|
|
||||||
});
|
|
||||||
|
|
||||||
// https://on.cypress.io/interacting-with-elements
|
|
||||||
|
|
||||||
it(".type() - type into a DOM element", () => {
|
|
||||||
// https://on.cypress.io/type
|
|
||||||
cy.get(".action-email")
|
|
||||||
.type("fake@email.com")
|
|
||||||
.should("have.value", "fake@email.com")
|
|
||||||
|
|
||||||
// .type() with special character sequences
|
|
||||||
.type("{leftarrow}{rightarrow}{uparrow}{downarrow}")
|
|
||||||
.type("{del}{selectall}{backspace}")
|
|
||||||
|
|
||||||
// .type() with key modifiers
|
|
||||||
.type("{alt}{option}") //these are equivalent
|
|
||||||
.type("{ctrl}{control}") //these are equivalent
|
|
||||||
.type("{meta}{command}{cmd}") //these are equivalent
|
|
||||||
.type("{shift}")
|
|
||||||
|
|
||||||
// Delay each keypress by 0.1 sec
|
|
||||||
.type("slow.typing@email.com", { delay: 100 })
|
|
||||||
.should("have.value", "slow.typing@email.com");
|
|
||||||
|
|
||||||
cy.get(".action-disabled")
|
|
||||||
// Ignore error checking prior to type
|
|
||||||
// like whether the input is visible or disabled
|
|
||||||
.type("disabled error checking", { force: true })
|
|
||||||
.should("have.value", "disabled error checking");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".focus() - focus on a DOM element", () => {
|
|
||||||
// https://on.cypress.io/focus
|
|
||||||
cy.get(".action-focus").focus().should("have.class", "focus").prev().should("have.attr", "style", "color: orange;");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".blur() - blur off a DOM element", () => {
|
|
||||||
// https://on.cypress.io/blur
|
|
||||||
cy.get(".action-blur")
|
|
||||||
.type("About to blur")
|
|
||||||
.blur()
|
|
||||||
.should("have.class", "error")
|
|
||||||
.prev()
|
|
||||||
.should("have.attr", "style", "color: red;");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".clear() - clears an input or textarea element", () => {
|
|
||||||
// https://on.cypress.io/clear
|
|
||||||
cy.get(".action-clear")
|
|
||||||
.type("Clear this text")
|
|
||||||
.should("have.value", "Clear this text")
|
|
||||||
.clear()
|
|
||||||
.should("have.value", "");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".submit() - submit a form", () => {
|
|
||||||
// https://on.cypress.io/submit
|
|
||||||
cy.get(".action-form").find('[type="text"]').type("HALFOFF");
|
|
||||||
|
|
||||||
cy.get(".action-form").submit().next().should("contain", "Your form has been submitted!");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".click() - click on a DOM element", () => {
|
|
||||||
// https://on.cypress.io/click
|
|
||||||
cy.get(".action-btn").click();
|
|
||||||
|
|
||||||
// You can click on 9 specific positions of an element:
|
|
||||||
// -----------------------------------
|
|
||||||
// | topLeft top topRight |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | left center right |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | bottomLeft bottom bottomRight |
|
|
||||||
// -----------------------------------
|
|
||||||
|
|
||||||
// clicking in the center of the element is the default
|
|
||||||
cy.get("#action-canvas").click();
|
|
||||||
|
|
||||||
cy.get("#action-canvas").click("topLeft");
|
|
||||||
cy.get("#action-canvas").click("top");
|
|
||||||
cy.get("#action-canvas").click("topRight");
|
|
||||||
cy.get("#action-canvas").click("left");
|
|
||||||
cy.get("#action-canvas").click("right");
|
|
||||||
cy.get("#action-canvas").click("bottomLeft");
|
|
||||||
cy.get("#action-canvas").click("bottom");
|
|
||||||
cy.get("#action-canvas").click("bottomRight");
|
|
||||||
|
|
||||||
// .click() accepts an x and y coordinate
|
|
||||||
// that controls where the click occurs :)
|
|
||||||
|
|
||||||
cy.get("#action-canvas")
|
|
||||||
.click(80, 75) // click 80px on x coord and 75px on y coord
|
|
||||||
.click(170, 75)
|
|
||||||
.click(80, 165)
|
|
||||||
.click(100, 185)
|
|
||||||
.click(125, 190)
|
|
||||||
.click(150, 185)
|
|
||||||
.click(170, 165);
|
|
||||||
|
|
||||||
// click multiple elements by passing multiple: true
|
|
||||||
cy.get(".action-labels>.label").click({ multiple: true });
|
|
||||||
|
|
||||||
// Ignore error checking prior to clicking
|
|
||||||
cy.get(".action-opacity>.btn").click({ force: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".dblclick() - double click on a DOM element", () => {
|
|
||||||
// https://on.cypress.io/dblclick
|
|
||||||
|
|
||||||
// Our app has a listener on 'dblclick' event in our 'scripts.js'
|
|
||||||
// that hides the div and shows an input on double click
|
|
||||||
cy.get(".action-div").dblclick().should("not.be.visible");
|
|
||||||
cy.get(".action-input-hidden").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".rightclick() - right click on a DOM element", () => {
|
|
||||||
// https://on.cypress.io/rightclick
|
|
||||||
|
|
||||||
// Our app has a listener on 'contextmenu' event in our 'scripts.js'
|
|
||||||
// that hides the div and shows an input on right click
|
|
||||||
cy.get(".rightclick-action-div").rightclick().should("not.be.visible");
|
|
||||||
cy.get(".rightclick-action-input-hidden").should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".check() - check a checkbox or radio element", () => {
|
|
||||||
// https://on.cypress.io/check
|
|
||||||
|
|
||||||
// By default, .check() will check all
|
|
||||||
// matching checkbox or radio elements in succession, one after another
|
|
||||||
cy.get('.action-checkboxes [type="checkbox"]').not("[disabled]").check().should("be.checked");
|
|
||||||
|
|
||||||
cy.get('.action-radios [type="radio"]').not("[disabled]").check().should("be.checked");
|
|
||||||
|
|
||||||
// .check() accepts a value argument
|
|
||||||
cy.get('.action-radios [type="radio"]').check("radio1").should("be.checked");
|
|
||||||
|
|
||||||
// .check() accepts an array of values
|
|
||||||
cy.get('.action-multiple-checkboxes [type="checkbox"]').check(["checkbox1", "checkbox2"]).should("be.checked");
|
|
||||||
|
|
||||||
// Ignore error checking prior to checking
|
|
||||||
cy.get(".action-checkboxes [disabled]").check({ force: true }).should("be.checked");
|
|
||||||
|
|
||||||
cy.get('.action-radios [type="radio"]').check("radio3", { force: true }).should("be.checked");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".uncheck() - uncheck a checkbox element", () => {
|
|
||||||
// https://on.cypress.io/uncheck
|
|
||||||
|
|
||||||
// By default, .uncheck() will uncheck all matching
|
|
||||||
// checkbox elements in succession, one after another
|
|
||||||
cy.get('.action-check [type="checkbox"]').not("[disabled]").uncheck().should("not.be.checked");
|
|
||||||
|
|
||||||
// .uncheck() accepts a value argument
|
|
||||||
cy.get('.action-check [type="checkbox"]').check("checkbox1").uncheck("checkbox1").should("not.be.checked");
|
|
||||||
|
|
||||||
// .uncheck() accepts an array of values
|
|
||||||
cy.get('.action-check [type="checkbox"]')
|
|
||||||
.check(["checkbox1", "checkbox3"])
|
|
||||||
.uncheck(["checkbox1", "checkbox3"])
|
|
||||||
.should("not.be.checked");
|
|
||||||
|
|
||||||
// Ignore error checking prior to unchecking
|
|
||||||
cy.get(".action-check [disabled]").uncheck({ force: true }).should("not.be.checked");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".select() - select an option in a <select> element", () => {
|
|
||||||
// https://on.cypress.io/select
|
|
||||||
|
|
||||||
// at first, no option should be selected
|
|
||||||
cy.get(".action-select").should("have.value", "--Select a fruit--");
|
|
||||||
|
|
||||||
// Select option(s) with matching text content
|
|
||||||
cy.get(".action-select").select("apples");
|
|
||||||
// confirm the apples were selected
|
|
||||||
// note that each value starts with "fr-" in our HTML
|
|
||||||
cy.get(".action-select").should("have.value", "fr-apples");
|
|
||||||
|
|
||||||
cy.get(".action-select-multiple")
|
|
||||||
.select(["apples", "oranges", "bananas"])
|
|
||||||
// when getting multiple values, invoke "val" method first
|
|
||||||
.invoke("val")
|
|
||||||
.should("deep.equal", ["fr-apples", "fr-oranges", "fr-bananas"]);
|
|
||||||
|
|
||||||
// Select option(s) with matching value
|
|
||||||
cy.get(".action-select")
|
|
||||||
.select("fr-bananas")
|
|
||||||
// can attach an assertion right away to the element
|
|
||||||
.should("have.value", "fr-bananas");
|
|
||||||
|
|
||||||
cy.get(".action-select-multiple")
|
|
||||||
.select(["fr-apples", "fr-oranges", "fr-bananas"])
|
|
||||||
.invoke("val")
|
|
||||||
.should("deep.equal", ["fr-apples", "fr-oranges", "fr-bananas"]);
|
|
||||||
|
|
||||||
// assert the selected values include oranges
|
|
||||||
cy.get(".action-select-multiple").invoke("val").should("include", "fr-oranges");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".scrollIntoView() - scroll an element into view", () => {
|
|
||||||
// https://on.cypress.io/scrollintoview
|
|
||||||
|
|
||||||
// normally all of these buttons are hidden,
|
|
||||||
// because they're not within
|
|
||||||
// the viewable area of their parent
|
|
||||||
// (we need to scroll to see them)
|
|
||||||
cy.get("#scroll-horizontal button").should("not.be.visible");
|
|
||||||
|
|
||||||
// scroll the button into view, as if the user had scrolled
|
|
||||||
cy.get("#scroll-horizontal button").scrollIntoView().should("be.visible");
|
|
||||||
|
|
||||||
cy.get("#scroll-vertical button").should("not.be.visible");
|
|
||||||
|
|
||||||
// Cypress handles the scroll direction needed
|
|
||||||
cy.get("#scroll-vertical button").scrollIntoView().should("be.visible");
|
|
||||||
|
|
||||||
cy.get("#scroll-both button").should("not.be.visible");
|
|
||||||
|
|
||||||
// Cypress knows to scroll to the right and down
|
|
||||||
cy.get("#scroll-both button").scrollIntoView().should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".trigger() - trigger an event on a DOM element", () => {
|
|
||||||
// https://on.cypress.io/trigger
|
|
||||||
|
|
||||||
// To interact with a range input (slider)
|
|
||||||
// we need to set its value & trigger the
|
|
||||||
// event to signal it changed
|
|
||||||
|
|
||||||
// Here, we invoke jQuery's val() method to set
|
|
||||||
// the value and trigger the 'change' event
|
|
||||||
cy.get(".trigger-input-range")
|
|
||||||
.invoke("val", 25)
|
|
||||||
.trigger("change")
|
|
||||||
.get("input[type=range]")
|
|
||||||
.siblings("p")
|
|
||||||
.should("have.text", "25");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.scrollTo() - scroll the window or element to a position", () => {
|
|
||||||
// https://on.cypress.io/scrollto
|
|
||||||
|
|
||||||
// You can scroll to 9 specific positions of an element:
|
|
||||||
// -----------------------------------
|
|
||||||
// | topLeft top topRight |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | left center right |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | bottomLeft bottom bottomRight |
|
|
||||||
// -----------------------------------
|
|
||||||
|
|
||||||
// if you chain .scrollTo() off of cy, we will
|
|
||||||
// scroll the entire window
|
|
||||||
cy.scrollTo("bottom");
|
|
||||||
|
|
||||||
cy.get("#scrollable-horizontal").scrollTo("right");
|
|
||||||
|
|
||||||
// or you can scroll to a specific coordinate:
|
|
||||||
// (x axis, y axis) in pixels
|
|
||||||
cy.get("#scrollable-vertical").scrollTo(250, 250);
|
|
||||||
|
|
||||||
// or you can scroll to a specific percentage
|
|
||||||
// of the (width, height) of the element
|
|
||||||
cy.get("#scrollable-both").scrollTo("75%", "25%");
|
|
||||||
|
|
||||||
// control the easing of the scroll (default is 'swing')
|
|
||||||
cy.get("#scrollable-vertical").scrollTo("center", { easing: "linear" });
|
|
||||||
|
|
||||||
// control the duration of the scroll (in ms)
|
|
||||||
cy.get("#scrollable-both").scrollTo("center", { duration: 2000 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Aliasing", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/aliasing");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".as() - alias a DOM element for later use", () => {
|
|
||||||
// https://on.cypress.io/as
|
|
||||||
|
|
||||||
// Alias a DOM element for use later
|
|
||||||
// We don't have to traverse to the element
|
|
||||||
// later in our code, we reference it with @
|
|
||||||
|
|
||||||
cy.get(".as-table").find("tbody>tr").first().find("td").first().find("button").as("firstBtn");
|
|
||||||
|
|
||||||
// when we reference the alias, we place an
|
|
||||||
// @ in front of its name
|
|
||||||
cy.get("@firstBtn").click();
|
|
||||||
|
|
||||||
cy.get("@firstBtn").should("have.class", "btn-success").and("contain", "Changed");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".as() - alias a route for later use", () => {
|
|
||||||
// Alias the route to wait for its response
|
|
||||||
cy.intercept("GET", "**/comments/*").as("getComment");
|
|
||||||
|
|
||||||
// we have code that gets a comment when
|
|
||||||
// the button is clicked in scripts.js
|
|
||||||
cy.get(".network-btn").click();
|
|
||||||
|
|
||||||
// https://on.cypress.io/wait
|
|
||||||
cy.wait("@getComment").its("response.statusCode").should("eq", 200);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Assertions", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/assertions");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Implicit Assertions", () => {
|
|
||||||
it(".should() - make an assertion about the current subject", () => {
|
|
||||||
// https://on.cypress.io/should
|
|
||||||
cy.get(".assertion-table")
|
|
||||||
.find("tbody tr:last")
|
|
||||||
.should("have.class", "success")
|
|
||||||
.find("td")
|
|
||||||
.first()
|
|
||||||
// checking the text of the <td> element in various ways
|
|
||||||
.should("have.text", "Column content")
|
|
||||||
.should("contain", "Column content")
|
|
||||||
.should("have.html", "Column content")
|
|
||||||
// chai-jquery uses "is()" to check if element matches selector
|
|
||||||
.should("match", "td")
|
|
||||||
// to match text content against a regular expression
|
|
||||||
// first need to invoke jQuery method text()
|
|
||||||
// and then match using regular expression
|
|
||||||
.invoke("text")
|
|
||||||
.should("match", /column content/i);
|
|
||||||
|
|
||||||
// a better way to check element's text content against a regular expression
|
|
||||||
// is to use "cy.contains"
|
|
||||||
// https://on.cypress.io/contains
|
|
||||||
cy.get(".assertion-table")
|
|
||||||
.find("tbody tr:last")
|
|
||||||
// finds first <td> element with text content matching regular expression
|
|
||||||
.contains("td", /column content/i)
|
|
||||||
.should("be.visible");
|
|
||||||
|
|
||||||
// for more information about asserting element's text
|
|
||||||
// see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-element’s-text-contents
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".and() - chain multiple assertions together", () => {
|
|
||||||
// https://on.cypress.io/and
|
|
||||||
cy.get(".assertions-link").should("have.class", "active").and("have.attr", "href").and("include", "cypress.io");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Explicit Assertions", () => {
|
|
||||||
// https://on.cypress.io/assertions
|
|
||||||
it("expect - make an assertion about a specified subject", () => {
|
|
||||||
// We can use Chai's BDD style assertions
|
|
||||||
expect(true).to.be.true;
|
|
||||||
const o = { foo: "bar" };
|
|
||||||
|
|
||||||
expect(o).to.equal(o);
|
|
||||||
expect(o).to.deep.equal({ foo: "bar" });
|
|
||||||
// matching text using regular expression
|
|
||||||
expect("FooBar").to.match(/bar$/i);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("pass your own callback function to should()", () => {
|
|
||||||
// Pass a function to should that can have any number
|
|
||||||
// of explicit assertions within it.
|
|
||||||
// The ".should(cb)" function will be retried
|
|
||||||
// automatically until it passes all your explicit assertions or times out.
|
|
||||||
cy.get(".assertions-p")
|
|
||||||
.find("p")
|
|
||||||
.should(($p) => {
|
|
||||||
// https://on.cypress.io/$
|
|
||||||
// return an array of texts from all of the p's
|
|
||||||
// @ts-ignore TS6133 unused variable
|
|
||||||
const texts = $p.map((i, el) => Cypress.$(el).text());
|
|
||||||
|
|
||||||
// jquery map returns jquery object
|
|
||||||
// and .get() convert this to simple array
|
|
||||||
const paragraphs = texts.get();
|
|
||||||
|
|
||||||
// array should have length of 3
|
|
||||||
expect(paragraphs, "has 3 paragraphs").to.have.length(3);
|
|
||||||
|
|
||||||
// use second argument to expect(...) to provide clear
|
|
||||||
// message with each assertion
|
|
||||||
expect(paragraphs, "has expected text in each paragraph").to.deep.eq([
|
|
||||||
"Some text from first p",
|
|
||||||
"More text from second p",
|
|
||||||
"And even more text from third p"
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("finds element by class name regex", () => {
|
|
||||||
cy.get(".docs-header")
|
|
||||||
.find("div")
|
|
||||||
// .should(cb) callback function will be retried
|
|
||||||
.should(($div) => {
|
|
||||||
expect($div).to.have.length(1);
|
|
||||||
|
|
||||||
const className = $div[0].className;
|
|
||||||
|
|
||||||
expect(className).to.match(/heading-/);
|
|
||||||
})
|
|
||||||
// .then(cb) callback is not retried,
|
|
||||||
// it either passes or fails
|
|
||||||
.then(($div) => {
|
|
||||||
expect($div, "text content").to.have.text("Introduction");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can throw any error", () => {
|
|
||||||
cy.get(".docs-header")
|
|
||||||
.find("div")
|
|
||||||
.should(($div) => {
|
|
||||||
if ($div.length !== 1) {
|
|
||||||
// you can throw your own errors
|
|
||||||
throw new Error("Did not find 1 element");
|
|
||||||
}
|
|
||||||
|
|
||||||
const className = $div[0].className;
|
|
||||||
|
|
||||||
if (!className.match(/heading-/)) {
|
|
||||||
throw new Error(`Could not find class "heading-" in ${className}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("matches unknown text between two elements", () => {
|
|
||||||
/**
|
|
||||||
* Text from the first element.
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
let text;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalizes passed text,
|
|
||||||
* useful before comparing text with spaces and different capitalization.
|
|
||||||
* @param {string} s Text to normalize
|
|
||||||
*/
|
|
||||||
const normalizeText = (s) => s.replace(/\s/g, "").toLowerCase();
|
|
||||||
|
|
||||||
cy.get(".two-elements")
|
|
||||||
.find(".first")
|
|
||||||
.then(($first) => {
|
|
||||||
// save text from the first element
|
|
||||||
text = normalizeText($first.text());
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get(".two-elements")
|
|
||||||
.find(".second")
|
|
||||||
.should(($div) => {
|
|
||||||
// we can massage text before comparing
|
|
||||||
const secondText = normalizeText($div.text());
|
|
||||||
|
|
||||||
expect(secondText, "second text").to.equal(text);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("assert - assert shape of an object", () => {
|
|
||||||
const person = {
|
|
||||||
name: "Joe",
|
|
||||||
age: 20
|
|
||||||
};
|
|
||||||
|
|
||||||
assert.isObject(person, "value is object");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("retries the should callback until assertions pass", () => {
|
|
||||||
cy.get("#random-number").should(($div) => {
|
|
||||||
const n = parseFloat($div.text());
|
|
||||||
|
|
||||||
expect(n).to.be.gte(1).and.be.lte(10);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Connectors", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/connectors");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".each() - iterate over an array of elements", () => {
|
|
||||||
// https://on.cypress.io/each
|
|
||||||
cy.get(".connectors-each-ul>li").each(($el, index, $list) => {
|
|
||||||
console.log($el, index, $list);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".its() - get properties on the current subject", () => {
|
|
||||||
// https://on.cypress.io/its
|
|
||||||
cy.get(".connectors-its-ul>li")
|
|
||||||
// calls the 'length' property yielding that value
|
|
||||||
.its("length")
|
|
||||||
.should("be.gt", 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".invoke() - invoke a function on the current subject", () => {
|
|
||||||
// our div is hidden in our script.js
|
|
||||||
// $('.connectors-div').hide()
|
|
||||||
|
|
||||||
// https://on.cypress.io/invoke
|
|
||||||
cy.get(".connectors-div")
|
|
||||||
.should("be.hidden")
|
|
||||||
// call the jquery method 'show' on the 'div.container'
|
|
||||||
.invoke("show")
|
|
||||||
.should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".spread() - spread an array as individual args to callback function", () => {
|
|
||||||
// https://on.cypress.io/spread
|
|
||||||
const arr = ["foo", "bar", "baz"];
|
|
||||||
|
|
||||||
cy.wrap(arr).spread((foo, bar, baz) => {
|
|
||||||
expect(foo).to.eq("foo");
|
|
||||||
expect(bar).to.eq("bar");
|
|
||||||
expect(baz).to.eq("baz");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe(".then()", () => {
|
|
||||||
it("invokes a callback function with the current subject", () => {
|
|
||||||
// https://on.cypress.io/then
|
|
||||||
cy.get(".connectors-list > li").then(($lis) => {
|
|
||||||
expect($lis, "3 items").to.have.length(3);
|
|
||||||
expect($lis.eq(0), "first item").to.contain("Walk the dog");
|
|
||||||
expect($lis.eq(1), "second item").to.contain("Feed the cat");
|
|
||||||
expect($lis.eq(2), "third item").to.contain("Write JavaScript");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("yields the returned value to the next command", () => {
|
|
||||||
cy.wrap(1)
|
|
||||||
.then((num) => {
|
|
||||||
expect(num).to.equal(1);
|
|
||||||
|
|
||||||
return 2;
|
|
||||||
})
|
|
||||||
.then((num) => {
|
|
||||||
expect(num).to.equal(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("yields the original subject without return", () => {
|
|
||||||
cy.wrap(1)
|
|
||||||
.then((num) => {
|
|
||||||
expect(num).to.equal(1);
|
|
||||||
// note that nothing is returned from this callback
|
|
||||||
})
|
|
||||||
.then((num) => {
|
|
||||||
// this callback receives the original unchanged value 1
|
|
||||||
expect(num).to.equal(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("yields the value yielded by the last Cypress command inside", () => {
|
|
||||||
cy.wrap(1)
|
|
||||||
.then((num) => {
|
|
||||||
expect(num).to.equal(1);
|
|
||||||
// note how we run a Cypress command
|
|
||||||
// the result yielded by this Cypress command
|
|
||||||
// will be passed to the second ".then"
|
|
||||||
cy.wrap(2);
|
|
||||||
})
|
|
||||||
.then((num) => {
|
|
||||||
// this callback receives the value yielded by "cy.wrap(2)"
|
|
||||||
expect(num).to.equal(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Cookies", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
Cypress.Cookies.debug(true);
|
|
||||||
|
|
||||||
cy.visit("https://example.cypress.io/commands/cookies");
|
|
||||||
|
|
||||||
// clear cookies again after visiting to remove
|
|
||||||
// any 3rd party cookies picked up such as cloudflare
|
|
||||||
cy.clearCookies();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.getCookie() - get a browser cookie", () => {
|
|
||||||
// https://on.cypress.io/getcookie
|
|
||||||
cy.get("#getCookie .set-a-cookie").click();
|
|
||||||
|
|
||||||
// cy.getCookie() yields a cookie object
|
|
||||||
cy.getCookie("token").should("have.property", "value", "123ABC");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.getCookies() - get browser cookies", () => {
|
|
||||||
// https://on.cypress.io/getcookies
|
|
||||||
cy.getCookies().should("be.empty");
|
|
||||||
|
|
||||||
cy.get("#getCookies .set-a-cookie").click();
|
|
||||||
|
|
||||||
// cy.getCookies() yields an array of cookies
|
|
||||||
cy.getCookies()
|
|
||||||
.should("have.length", 1)
|
|
||||||
.should((cookies) => {
|
|
||||||
// each cookie has these properties
|
|
||||||
expect(cookies[0]).to.have.property("name", "token");
|
|
||||||
expect(cookies[0]).to.have.property("value", "123ABC");
|
|
||||||
expect(cookies[0]).to.have.property("httpOnly", false);
|
|
||||||
expect(cookies[0]).to.have.property("secure", false);
|
|
||||||
expect(cookies[0]).to.have.property("domain");
|
|
||||||
expect(cookies[0]).to.have.property("path");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.setCookie() - set a browser cookie", () => {
|
|
||||||
// https://on.cypress.io/setcookie
|
|
||||||
cy.getCookies().should("be.empty");
|
|
||||||
|
|
||||||
cy.setCookie("foo", "bar");
|
|
||||||
|
|
||||||
// cy.getCookie() yields a cookie object
|
|
||||||
cy.getCookie("foo").should("have.property", "value", "bar");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.clearCookie() - clear a browser cookie", () => {
|
|
||||||
// https://on.cypress.io/clearcookie
|
|
||||||
cy.getCookie("token").should("be.null");
|
|
||||||
|
|
||||||
cy.get("#clearCookie .set-a-cookie").click();
|
|
||||||
|
|
||||||
cy.getCookie("token").should("have.property", "value", "123ABC");
|
|
||||||
|
|
||||||
// cy.clearCookies() yields null
|
|
||||||
cy.clearCookie("token").should("be.null");
|
|
||||||
|
|
||||||
cy.getCookie("token").should("be.null");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.clearCookies() - clear browser cookies", () => {
|
|
||||||
// https://on.cypress.io/clearcookies
|
|
||||||
cy.getCookies().should("be.empty");
|
|
||||||
|
|
||||||
cy.get("#clearCookies .set-a-cookie").click();
|
|
||||||
|
|
||||||
cy.getCookies().should("have.length", 1);
|
|
||||||
|
|
||||||
// cy.clearCookies() yields null
|
|
||||||
cy.clearCookies();
|
|
||||||
|
|
||||||
cy.getCookies().should("be.empty");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Cypress.Commands", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/cypress-api");
|
|
||||||
});
|
|
||||||
|
|
||||||
// https://on.cypress.io/custom-commands
|
|
||||||
|
|
||||||
it(".add() - create a custom command", () => {
|
|
||||||
Cypress.Commands.add(
|
|
||||||
"console",
|
|
||||||
{
|
|
||||||
prevSubject: true
|
|
||||||
},
|
|
||||||
(subject, method) => {
|
|
||||||
// the previous subject is automatically received
|
|
||||||
// and the commands arguments are shifted
|
|
||||||
|
|
||||||
// allow us to change the console method used
|
|
||||||
method = method || "log";
|
|
||||||
|
|
||||||
// log the subject to the console
|
|
||||||
// @ts-ignore TS7017
|
|
||||||
console[method]("The subject is", subject);
|
|
||||||
|
|
||||||
// whatever we return becomes the new subject
|
|
||||||
// we don't want to change the subject so
|
|
||||||
// we return whatever was passed in
|
|
||||||
return subject;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// @ts-ignore TS2339
|
|
||||||
cy.get("button")
|
|
||||||
.console("info")
|
|
||||||
.then(($button) => {
|
|
||||||
// subject is still $button
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context("Cypress.Cookies", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/cypress-api");
|
|
||||||
});
|
|
||||||
|
|
||||||
// https://on.cypress.io/cookies
|
|
||||||
it(".debug() - enable or disable debugging", () => {
|
|
||||||
Cypress.Cookies.debug(true);
|
|
||||||
|
|
||||||
// Cypress will now log in the console when
|
|
||||||
// cookies are set or cleared
|
|
||||||
cy.setCookie("fakeCookie", "123ABC");
|
|
||||||
cy.clearCookie("fakeCookie");
|
|
||||||
cy.setCookie("fakeCookie", "123ABC");
|
|
||||||
cy.clearCookie("fakeCookie");
|
|
||||||
cy.setCookie("fakeCookie", "123ABC");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".preserveOnce() - preserve cookies by key", () => {
|
|
||||||
// normally cookies are reset after each test
|
|
||||||
cy.getCookie("fakeCookie").should("not.be.ok");
|
|
||||||
|
|
||||||
// preserving a cookie will not clear it when
|
|
||||||
// the next test starts
|
|
||||||
cy.setCookie("lastCookie", "789XYZ");
|
|
||||||
Cypress.Cookies.preserveOnce("lastCookie");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".defaults() - set defaults for all cookies", () => {
|
|
||||||
// now any cookie with the name 'session_id' will
|
|
||||||
// not be cleared before each new test runs
|
|
||||||
Cypress.Cookies.defaults({
|
|
||||||
preserve: "session_id"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context("Cypress.arch", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/cypress-api");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Get CPU architecture name of underlying OS", () => {
|
|
||||||
// https://on.cypress.io/arch
|
|
||||||
expect(Cypress.arch).to.exist;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context("Cypress.config()", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/cypress-api");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Get and set configuration options", () => {
|
|
||||||
// https://on.cypress.io/config
|
|
||||||
let myConfig = Cypress.config();
|
|
||||||
|
|
||||||
expect(myConfig).to.have.property("animationDistanceThreshold", 5);
|
|
||||||
expect(myConfig).to.have.property("baseUrl", null);
|
|
||||||
expect(myConfig).to.have.property("defaultCommandTimeout", 4000);
|
|
||||||
expect(myConfig).to.have.property("requestTimeout", 5000);
|
|
||||||
expect(myConfig).to.have.property("responseTimeout", 30000);
|
|
||||||
expect(myConfig).to.have.property("viewportHeight", 660);
|
|
||||||
expect(myConfig).to.have.property("viewportWidth", 1000);
|
|
||||||
expect(myConfig).to.have.property("pageLoadTimeout", 60000);
|
|
||||||
expect(myConfig).to.have.property("waitForAnimations", true);
|
|
||||||
|
|
||||||
expect(Cypress.config("pageLoadTimeout")).to.eq(60000);
|
|
||||||
|
|
||||||
// this will change the config for the rest of your tests!
|
|
||||||
Cypress.config("pageLoadTimeout", 20000);
|
|
||||||
|
|
||||||
expect(Cypress.config("pageLoadTimeout")).to.eq(20000);
|
|
||||||
|
|
||||||
Cypress.config("pageLoadTimeout", 60000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context("Cypress.dom", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/cypress-api");
|
|
||||||
});
|
|
||||||
|
|
||||||
// https://on.cypress.io/dom
|
|
||||||
it(".isHidden() - determine if a DOM element is hidden", () => {
|
|
||||||
let hiddenP = Cypress.$(".dom-p p.hidden").get(0);
|
|
||||||
let visibleP = Cypress.$(".dom-p p.visible").get(0);
|
|
||||||
|
|
||||||
// our first paragraph has css class 'hidden'
|
|
||||||
expect(Cypress.dom.isHidden(hiddenP)).to.be.true;
|
|
||||||
expect(Cypress.dom.isHidden(visibleP)).to.be.false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context("Cypress.env()", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/cypress-api");
|
|
||||||
});
|
|
||||||
|
|
||||||
// We can set environment variables for highly dynamic values
|
|
||||||
|
|
||||||
// https://on.cypress.io/environment-variables
|
|
||||||
it("Get environment variables", () => {
|
|
||||||
// https://on.cypress.io/env
|
|
||||||
// set multiple environment variables
|
|
||||||
Cypress.env({
|
|
||||||
host: "veronica.dev.local",
|
|
||||||
api_server: "http://localhost:8888/v1/"
|
|
||||||
});
|
|
||||||
|
|
||||||
// get environment variable
|
|
||||||
expect(Cypress.env("host")).to.eq("veronica.dev.local");
|
|
||||||
|
|
||||||
// set environment variable
|
|
||||||
Cypress.env("api_server", "http://localhost:8888/v2/");
|
|
||||||
expect(Cypress.env("api_server")).to.eq("http://localhost:8888/v2/");
|
|
||||||
|
|
||||||
// get all environment variable
|
|
||||||
expect(Cypress.env()).to.have.property("host", "veronica.dev.local");
|
|
||||||
expect(Cypress.env()).to.have.property("api_server", "http://localhost:8888/v2/");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context("Cypress.log", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/cypress-api");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Control what is printed to the Command Log", () => {
|
|
||||||
// https://on.cypress.io/cypress-log
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context("Cypress.platform", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/cypress-api");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Get underlying OS name", () => {
|
|
||||||
// https://on.cypress.io/platform
|
|
||||||
expect(Cypress.platform).to.be.exist;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context("Cypress.version", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/cypress-api");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Get current version of Cypress being run", () => {
|
|
||||||
// https://on.cypress.io/version
|
|
||||||
expect(Cypress.version).to.be.exist;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context("Cypress.spec", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/cypress-api");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Get current spec information", () => {
|
|
||||||
// https://on.cypress.io/spec
|
|
||||||
// wrap the object so we can inspect it easily by clicking in the command log
|
|
||||||
cy.wrap(Cypress.spec).should("include.keys", ["name", "relative", "absolute"]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
/// JSON fixture file can be loaded directly using
|
|
||||||
// the built-in JavaScript bundler
|
|
||||||
// @ts-ignore
|
|
||||||
const requiredExample = require("../../fixtures/example");
|
|
||||||
|
|
||||||
context("Files", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/files");
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// load example.json fixture file and store
|
|
||||||
// in the test context object
|
|
||||||
cy.fixture("example.json").as("example");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.fixture() - load a fixture", () => {
|
|
||||||
// https://on.cypress.io/fixture
|
|
||||||
|
|
||||||
// Instead of writing a response inline you can
|
|
||||||
// use a fixture file's content.
|
|
||||||
|
|
||||||
// when application makes an Ajax request matching "GET **/comments/*"
|
|
||||||
// Cypress will intercept it and reply with the object in `example.json` fixture
|
|
||||||
cy.intercept("GET", "**/comments/*", { fixture: "example.json" }).as("getComment");
|
|
||||||
|
|
||||||
// we have code that gets a comment when
|
|
||||||
// the button is clicked in scripts.js
|
|
||||||
cy.get(".fixture-btn").click();
|
|
||||||
|
|
||||||
cy.wait("@getComment")
|
|
||||||
.its("response.body")
|
|
||||||
.should("have.property", "name")
|
|
||||||
.and("include", "Using fixtures to represent data");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.fixture() or require - load a fixture", function () {
|
|
||||||
// we are inside the "function () { ... }"
|
|
||||||
// callback and can use test context object "this"
|
|
||||||
// "this.example" was loaded in "beforeEach" function callback
|
|
||||||
expect(this.example, "fixture in the test context").to.deep.equal(requiredExample);
|
|
||||||
|
|
||||||
// or use "cy.wrap" and "should('deep.equal', ...)" assertion
|
|
||||||
cy.wrap(this.example).should("deep.equal", requiredExample);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.readFile() - read file contents", () => {
|
|
||||||
// https://on.cypress.io/readfile
|
|
||||||
|
|
||||||
// You can read a file and yield its contents
|
|
||||||
// The filePath is relative to your project's root.
|
|
||||||
cy.readFile("cypress.json").then((json) => {
|
|
||||||
expect(json).to.be.an("object");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.writeFile() - write to a file", () => {
|
|
||||||
// https://on.cypress.io/writefile
|
|
||||||
|
|
||||||
// You can write to a file
|
|
||||||
|
|
||||||
// Use a response from a request to automatically
|
|
||||||
// generate a fixture file for use later
|
|
||||||
cy.request("https://jsonplaceholder.cypress.io/users").then((response) => {
|
|
||||||
cy.writeFile("cypress/fixtures/users.json", response.body);
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.fixture("users").should((users) => {
|
|
||||||
expect(users[0].name).to.exist;
|
|
||||||
});
|
|
||||||
|
|
||||||
// JavaScript arrays and objects are stringified
|
|
||||||
// and formatted into text.
|
|
||||||
cy.writeFile("cypress/fixtures/profile.json", {
|
|
||||||
id: 8739,
|
|
||||||
name: "Jane",
|
|
||||||
email: "jane@example.com"
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.fixture("profile").should((profile) => {
|
|
||||||
expect(profile.name).to.eq("Jane");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Local Storage", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/local-storage");
|
|
||||||
});
|
|
||||||
// Although local storage is automatically cleared
|
|
||||||
// in between tests to maintain a clean state
|
|
||||||
// sometimes we need to clear the local storage manually
|
|
||||||
|
|
||||||
it("cy.clearLocalStorage() - clear all data in local storage", () => {
|
|
||||||
// https://on.cypress.io/clearlocalstorage
|
|
||||||
cy.get(".ls-btn")
|
|
||||||
.click()
|
|
||||||
.should(() => {
|
|
||||||
expect(localStorage.getItem("prop1")).to.eq("red");
|
|
||||||
expect(localStorage.getItem("prop2")).to.eq("blue");
|
|
||||||
expect(localStorage.getItem("prop3")).to.eq("magenta");
|
|
||||||
});
|
|
||||||
|
|
||||||
// clearLocalStorage() yields the localStorage object
|
|
||||||
cy.clearLocalStorage().should((ls) => {
|
|
||||||
expect(ls.getItem("prop1")).to.be.null;
|
|
||||||
expect(ls.getItem("prop2")).to.be.null;
|
|
||||||
expect(ls.getItem("prop3")).to.be.null;
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get(".ls-btn")
|
|
||||||
.click()
|
|
||||||
.should(() => {
|
|
||||||
expect(localStorage.getItem("prop1")).to.eq("red");
|
|
||||||
expect(localStorage.getItem("prop2")).to.eq("blue");
|
|
||||||
expect(localStorage.getItem("prop3")).to.eq("magenta");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear key matching string in Local Storage
|
|
||||||
cy.clearLocalStorage("prop1").should((ls) => {
|
|
||||||
expect(ls.getItem("prop1")).to.be.null;
|
|
||||||
expect(ls.getItem("prop2")).to.eq("blue");
|
|
||||||
expect(ls.getItem("prop3")).to.eq("magenta");
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get(".ls-btn")
|
|
||||||
.click()
|
|
||||||
.should(() => {
|
|
||||||
expect(localStorage.getItem("prop1")).to.eq("red");
|
|
||||||
expect(localStorage.getItem("prop2")).to.eq("blue");
|
|
||||||
expect(localStorage.getItem("prop3")).to.eq("magenta");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear keys matching regex in Local Storage
|
|
||||||
cy.clearLocalStorage(/prop1|2/).should((ls) => {
|
|
||||||
expect(ls.getItem("prop1")).to.be.null;
|
|
||||||
expect(ls.getItem("prop2")).to.be.null;
|
|
||||||
expect(ls.getItem("prop3")).to.eq("magenta");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Location", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/location");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.hash() - get the current URL hash", () => {
|
|
||||||
// https://on.cypress.io/hash
|
|
||||||
cy.hash().should("be.empty");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.location() - get window.location", () => {
|
|
||||||
// https://on.cypress.io/location
|
|
||||||
cy.location().should((location) => {
|
|
||||||
expect(location.hash).to.be.empty;
|
|
||||||
expect(location.href).to.eq("https://example.cypress.io/commands/location");
|
|
||||||
expect(location.host).to.eq("example.cypress.io");
|
|
||||||
expect(location.hostname).to.eq("example.cypress.io");
|
|
||||||
expect(location.origin).to.eq("https://example.cypress.io");
|
|
||||||
expect(location.pathname).to.eq("/commands/location");
|
|
||||||
expect(location.port).to.eq("");
|
|
||||||
expect(location.protocol).to.eq("https:");
|
|
||||||
expect(location.search).to.be.empty;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.url() - get the current URL", () => {
|
|
||||||
// https://on.cypress.io/url
|
|
||||||
cy.url().should("eq", "https://example.cypress.io/commands/location");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Misc", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/misc");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".end() - end the command chain", () => {
|
|
||||||
// https://on.cypress.io/end
|
|
||||||
|
|
||||||
// cy.end is useful when you want to end a chain of commands
|
|
||||||
// and force Cypress to re-query from the root element
|
|
||||||
cy.get(".misc-table").within(() => {
|
|
||||||
// ends the current chain and yields null
|
|
||||||
cy.contains("Cheryl").click().end();
|
|
||||||
|
|
||||||
// queries the entire table again
|
|
||||||
cy.contains("Charles").click();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.exec() - execute a system command", () => {
|
|
||||||
// execute a system command.
|
|
||||||
// so you can take actions necessary for
|
|
||||||
// your test outside the scope of Cypress.
|
|
||||||
// https://on.cypress.io/exec
|
|
||||||
|
|
||||||
// we can use Cypress.platform string to
|
|
||||||
// select appropriate command
|
|
||||||
// https://on.cypress/io/platform
|
|
||||||
cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`);
|
|
||||||
|
|
||||||
// on CircleCI Windows build machines we have a failure to run bash shell
|
|
||||||
// https://github.com/cypress-io/cypress/issues/5169
|
|
||||||
// so skip some of the tests by passing flag "--env circle=true"
|
|
||||||
const isCircleOnWindows = Cypress.platform === "win32" && Cypress.env("circle");
|
|
||||||
|
|
||||||
if (isCircleOnWindows) {
|
|
||||||
cy.log("Skipping test on CircleCI");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// cy.exec problem on Shippable CI
|
|
||||||
// https://github.com/cypress-io/cypress/issues/6718
|
|
||||||
const isShippable = Cypress.platform === "linux" && Cypress.env("shippable");
|
|
||||||
|
|
||||||
if (isShippable) {
|
|
||||||
cy.log("Skipping test on ShippableCI");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cy.exec("echo Jane Lane").its("stdout").should("contain", "Jane Lane");
|
|
||||||
|
|
||||||
if (Cypress.platform === "win32") {
|
|
||||||
cy.exec("print cypress.json").its("stderr").should("be.empty");
|
|
||||||
} else {
|
|
||||||
cy.exec("cat cypress.json").its("stderr").should("be.empty");
|
|
||||||
|
|
||||||
cy.exec("pwd").its("code").should("eq", 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.focused() - get the DOM element that has focus", () => {
|
|
||||||
// https://on.cypress.io/focused
|
|
||||||
cy.get(".misc-form").find("#name").click();
|
|
||||||
cy.focused().should("have.id", "name");
|
|
||||||
|
|
||||||
cy.get(".misc-form").find("#description").click();
|
|
||||||
cy.focused().should("have.id", "description");
|
|
||||||
});
|
|
||||||
|
|
||||||
context("Cypress.Screenshot", function () {
|
|
||||||
it("cy.screenshot() - take a screenshot", () => {
|
|
||||||
// https://on.cypress.io/screenshot
|
|
||||||
cy.screenshot("my-image");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Cypress.Screenshot.defaults() - change default config of screenshots", function () {
|
|
||||||
Cypress.Screenshot.defaults({
|
|
||||||
blackout: [".foo"],
|
|
||||||
capture: "viewport",
|
|
||||||
clip: { x: 0, y: 0, width: 200, height: 200 },
|
|
||||||
scale: false,
|
|
||||||
disableTimersAndAnimations: true,
|
|
||||||
screenshotOnRunFailure: true,
|
|
||||||
onBeforeScreenshot() {},
|
|
||||||
onAfterScreenshot() {}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.wrap() - wrap an object", () => {
|
|
||||||
// https://on.cypress.io/wrap
|
|
||||||
cy.wrap({ foo: "bar" }).should("have.property", "foo").and("include", "bar");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Navigation", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io");
|
|
||||||
cy.get(".navbar-nav").contains("Commands").click();
|
|
||||||
cy.get(".dropdown-menu").contains("Navigation").click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.go() - go back or forward in the browser's history", () => {
|
|
||||||
// https://on.cypress.io/go
|
|
||||||
|
|
||||||
cy.location("pathname").should("include", "navigation");
|
|
||||||
|
|
||||||
cy.go("back");
|
|
||||||
cy.location("pathname").should("not.include", "navigation");
|
|
||||||
|
|
||||||
cy.go("forward");
|
|
||||||
cy.location("pathname").should("include", "navigation");
|
|
||||||
|
|
||||||
// clicking back
|
|
||||||
cy.go(-1);
|
|
||||||
cy.location("pathname").should("not.include", "navigation");
|
|
||||||
|
|
||||||
// clicking forward
|
|
||||||
cy.go(1);
|
|
||||||
cy.location("pathname").should("include", "navigation");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.reload() - reload the page", () => {
|
|
||||||
// https://on.cypress.io/reload
|
|
||||||
cy.reload();
|
|
||||||
|
|
||||||
// reload the page without using the cache
|
|
||||||
cy.reload(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.visit() - visit a remote url", () => {
|
|
||||||
// https://on.cypress.io/visit
|
|
||||||
|
|
||||||
// Visit any sub-domain of your current domain
|
|
||||||
|
|
||||||
// Pass options to the visit
|
|
||||||
cy.visit("https://example.cypress.io/commands/navigation", {
|
|
||||||
timeout: 50000, // increase total time for the visit to resolve
|
|
||||||
onBeforeLoad(contentWindow) {
|
|
||||||
// contentWindow is the remote page's window object
|
|
||||||
expect(typeof contentWindow === "object").to.be.true;
|
|
||||||
},
|
|
||||||
onLoad(contentWindow) {
|
|
||||||
// contentWindow is the remote page's window object
|
|
||||||
expect(typeof contentWindow === "object").to.be.true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Network Requests", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/network-requests");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Manage HTTP requests in your app
|
|
||||||
|
|
||||||
it("cy.request() - make an XHR request", () => {
|
|
||||||
// https://on.cypress.io/request
|
|
||||||
cy.request("https://jsonplaceholder.cypress.io/comments").should((response) => {
|
|
||||||
expect(response.status).to.eq(200);
|
|
||||||
// the server sometimes gets an extra comment posted from another machine
|
|
||||||
// which gets returned as 1 extra object
|
|
||||||
expect(response.body).to.have.property("length").and.be.oneOf([500, 501]);
|
|
||||||
expect(response).to.have.property("headers");
|
|
||||||
expect(response).to.have.property("duration");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.request() - verify response using BDD syntax", () => {
|
|
||||||
cy.request("https://jsonplaceholder.cypress.io/comments").then((response) => {
|
|
||||||
// https://on.cypress.io/assertions
|
|
||||||
expect(response).property("status").to.equal(200);
|
|
||||||
expect(response).property("body").to.have.property("length").and.be.oneOf([500, 501]);
|
|
||||||
expect(response).to.include.keys("headers", "duration");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.request() with query parameters", () => {
|
|
||||||
// will execute request
|
|
||||||
// https://jsonplaceholder.cypress.io/comments?postId=1&id=3
|
|
||||||
cy.request({
|
|
||||||
url: "https://jsonplaceholder.cypress.io/comments",
|
|
||||||
qs: {
|
|
||||||
postId: 1,
|
|
||||||
id: 3
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.its("body")
|
|
||||||
.should("be.an", "array")
|
|
||||||
.and("have.length", 1)
|
|
||||||
.its("0") // yields first element of the array
|
|
||||||
.should("contain", {
|
|
||||||
postId: 1,
|
|
||||||
id: 3
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.request() - pass result to the second request", () => {
|
|
||||||
// first, let's find out the userId of the first user we have
|
|
||||||
cy.request("https://jsonplaceholder.cypress.io/users?_limit=1")
|
|
||||||
.its("body") // yields the response object
|
|
||||||
.its("0") // yields the first element of the returned list
|
|
||||||
// the above two commands its('body').its('0')
|
|
||||||
// can be written as its('body.0')
|
|
||||||
// if you do not care about TypeScript checks
|
|
||||||
.then((user) => {
|
|
||||||
expect(user).property("id").to.be.a("number");
|
|
||||||
// make a new post on behalf of the user
|
|
||||||
cy.request("POST", "https://jsonplaceholder.cypress.io/posts", {
|
|
||||||
userId: user.id,
|
|
||||||
title: "Cypress Test Runner",
|
|
||||||
body: "Fast, easy and reliable testing for anything that runs in a browser."
|
|
||||||
});
|
|
||||||
})
|
|
||||||
// note that the value here is the returned value of the 2nd request
|
|
||||||
// which is the new post object
|
|
||||||
.then((response) => {
|
|
||||||
expect(response).property("status").to.equal(201); // new entity created
|
|
||||||
expect(response).property("body").to.contain({
|
|
||||||
title: "Cypress Test Runner"
|
|
||||||
});
|
|
||||||
|
|
||||||
// we don't know the exact post id - only that it will be > 100
|
|
||||||
// since JSONPlaceholder has built-in 100 posts
|
|
||||||
expect(response.body).property("id").to.be.a("number").and.to.be.gt(100);
|
|
||||||
|
|
||||||
// we don't know the user id here - since it was in above closure
|
|
||||||
// so in this test just confirm that the property is there
|
|
||||||
expect(response.body).property("userId").to.be.a("number");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.request() - save response in the shared test context", () => {
|
|
||||||
// https://on.cypress.io/variables-and-aliases
|
|
||||||
cy.request("https://jsonplaceholder.cypress.io/users?_limit=1")
|
|
||||||
.its("body")
|
|
||||||
.its("0") // yields the first element of the returned list
|
|
||||||
.as("user") // saves the object in the test context
|
|
||||||
.then(function () {
|
|
||||||
// NOTE 👀
|
|
||||||
// By the time this callback runs the "as('user')" command
|
|
||||||
// has saved the user object in the test context.
|
|
||||||
// To access the test context we need to use
|
|
||||||
// the "function () { ... }" callback form,
|
|
||||||
// otherwise "this" points at a wrong or undefined object!
|
|
||||||
cy.request("POST", "https://jsonplaceholder.cypress.io/posts", {
|
|
||||||
userId: this.user.id,
|
|
||||||
title: "Cypress Test Runner",
|
|
||||||
body: "Fast, easy and reliable testing for anything that runs in a browser."
|
|
||||||
})
|
|
||||||
.its("body")
|
|
||||||
.as("post"); // save the new post from the response
|
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
// When this callback runs, both "cy.request" API commands have finished
|
|
||||||
// and the test context has "user" and "post" objects set.
|
|
||||||
// Let's verify them.
|
|
||||||
expect(this.post, "post has the right user id").property("userId").to.equal(this.user.id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.intercept() - route responses to matching requests", () => {
|
|
||||||
// https://on.cypress.io/intercept
|
|
||||||
|
|
||||||
let message = "whoa, this comment does not exist";
|
|
||||||
|
|
||||||
// Listen to GET to comments/1
|
|
||||||
cy.intercept("GET", "**/comments/*").as("getComment");
|
|
||||||
|
|
||||||
// we have code that gets a comment when
|
|
||||||
// the button is clicked in scripts.js
|
|
||||||
cy.get(".network-btn").click();
|
|
||||||
|
|
||||||
// https://on.cypress.io/wait
|
|
||||||
cy.wait("@getComment").its("response.statusCode").should("be.oneOf", [200, 304]);
|
|
||||||
|
|
||||||
// Listen to POST to comments
|
|
||||||
cy.intercept("POST", "**/comments").as("postComment");
|
|
||||||
|
|
||||||
// we have code that posts a comment when
|
|
||||||
// the button is clicked in scripts.js
|
|
||||||
cy.get(".network-post").click();
|
|
||||||
cy.wait("@postComment").should(({ request, response }) => {
|
|
||||||
expect(request.body).to.include("email");
|
|
||||||
expect(request.headers).to.have.property("content-type");
|
|
||||||
expect(response && response.body).to.have.property("name", "Using POST in cy.intercept()");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Stub a response to PUT comments/ ****
|
|
||||||
cy.intercept(
|
|
||||||
{
|
|
||||||
method: "PUT",
|
|
||||||
url: "**/comments/*"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
statusCode: 404,
|
|
||||||
body: { error: message },
|
|
||||||
headers: { "access-control-allow-origin": "*" },
|
|
||||||
delayMs: 500
|
|
||||||
}
|
|
||||||
).as("putComment");
|
|
||||||
|
|
||||||
// we have code that puts a comment when
|
|
||||||
// the button is clicked in scripts.js
|
|
||||||
cy.get(".network-put").click();
|
|
||||||
|
|
||||||
cy.wait("@putComment");
|
|
||||||
|
|
||||||
// our 404 statusCode logic in scripts.js executed
|
|
||||||
cy.get(".network-put-comment").should("contain", message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Querying", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/querying");
|
|
||||||
});
|
|
||||||
|
|
||||||
// The most commonly used query is 'cy.get()', you can
|
|
||||||
// think of this like the '$' in jQuery
|
|
||||||
|
|
||||||
it("cy.get() - query DOM elements", () => {
|
|
||||||
// https://on.cypress.io/get
|
|
||||||
|
|
||||||
cy.get("#query-btn").should("contain", "Button");
|
|
||||||
|
|
||||||
cy.get(".query-btn").should("contain", "Button");
|
|
||||||
|
|
||||||
cy.get("#querying .well>button:first").should("contain", "Button");
|
|
||||||
// ↲
|
|
||||||
// Use CSS selectors just like jQuery
|
|
||||||
|
|
||||||
cy.get('[data-test-id="test-example"]').should("have.class", "example");
|
|
||||||
|
|
||||||
// 'cy.get()' yields jQuery object, you can get its attribute
|
|
||||||
// by invoking `.attr()` method
|
|
||||||
cy.get('[data-test-id="test-example"]').invoke("attr", "data-test-id").should("equal", "test-example");
|
|
||||||
|
|
||||||
// or you can get element's CSS property
|
|
||||||
cy.get('[data-test-id="test-example"]').invoke("css", "position").should("equal", "static");
|
|
||||||
|
|
||||||
// or use assertions directly during 'cy.get()'
|
|
||||||
// https://on.cypress.io/assertions
|
|
||||||
cy.get('[data-test-id="test-example"]')
|
|
||||||
.should("have.attr", "data-test-id", "test-example")
|
|
||||||
.and("have.css", "position", "static");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.contains() - query DOM elements with matching content", () => {
|
|
||||||
// https://on.cypress.io/contains
|
|
||||||
cy.get(".query-list").contains("bananas").should("have.class", "third");
|
|
||||||
|
|
||||||
// we can pass a regexp to `.contains()`
|
|
||||||
cy.get(".query-list").contains(/^b\w+/).should("have.class", "third");
|
|
||||||
|
|
||||||
cy.get(".query-list").contains("apples").should("have.class", "first");
|
|
||||||
|
|
||||||
// passing a selector to contains will
|
|
||||||
// yield the selector containing the text
|
|
||||||
cy.get("#querying").contains("ul", "oranges").should("have.class", "query-list");
|
|
||||||
|
|
||||||
cy.get(".query-button").contains("Save Form").should("have.class", "btn");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".within() - query DOM elements within a specific element", () => {
|
|
||||||
// https://on.cypress.io/within
|
|
||||||
cy.get(".query-form").within(() => {
|
|
||||||
cy.get("input:first").should("have.attr", "placeholder", "Email");
|
|
||||||
cy.get("input:last").should("have.attr", "placeholder", "Password");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.root() - query the root DOM element", () => {
|
|
||||||
// https://on.cypress.io/root
|
|
||||||
|
|
||||||
// By default, root is the document
|
|
||||||
cy.root().should("match", "html");
|
|
||||||
|
|
||||||
cy.get(".query-ul").within(() => {
|
|
||||||
// In this within, the root is now the ul DOM element
|
|
||||||
cy.root().should("have.class", "query-ul");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("best practices - selecting elements", () => {
|
|
||||||
// https://on.cypress.io/best-practices#Selecting-Elements
|
|
||||||
cy.get("[data-cy=best-practices-selecting-elements]").within(() => {
|
|
||||||
// Worst - too generic, no context
|
|
||||||
cy.get("button").click();
|
|
||||||
|
|
||||||
// Bad. Coupled to styling. Highly subject to change.
|
|
||||||
cy.get(".btn.btn-large").click();
|
|
||||||
|
|
||||||
// Average. Coupled to the `name` attribute which has HTML semantics.
|
|
||||||
cy.get("[name=submission]").click();
|
|
||||||
|
|
||||||
// Better. But still coupled to styling or JS event listeners.
|
|
||||||
cy.get("#main").click();
|
|
||||||
|
|
||||||
// Slightly better. Uses an ID but also ensures the element
|
|
||||||
// has an ARIA role attribute
|
|
||||||
cy.get("#main[role=button]").click();
|
|
||||||
|
|
||||||
// Much better. But still coupled to text content that may change.
|
|
||||||
cy.contains("Submit").click();
|
|
||||||
|
|
||||||
// Best. Insulated from all changes.
|
|
||||||
cy.get("[data-cy=submit]").click();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
// remove no check once Cypress.sinon is typed
|
|
||||||
// https://github.com/cypress-io/cypress/issues/6720
|
|
||||||
|
|
||||||
context("Spies, Stubs, and Clock", () => {
|
|
||||||
it("cy.spy() - wrap a method in a spy", () => {
|
|
||||||
// https://on.cypress.io/spy
|
|
||||||
cy.visit("https://example.cypress.io/commands/spies-stubs-clocks");
|
|
||||||
|
|
||||||
const obj = {
|
|
||||||
foo() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const spy = cy.spy(obj, "foo").as("anyArgs");
|
|
||||||
|
|
||||||
obj.foo();
|
|
||||||
|
|
||||||
expect(spy).to.be.called;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.spy() retries until assertions pass", () => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/spies-stubs-clocks");
|
|
||||||
|
|
||||||
const obj = {
|
|
||||||
/**
|
|
||||||
* Prints the argument passed
|
|
||||||
* @param x {any}
|
|
||||||
*/
|
|
||||||
foo(x) {
|
|
||||||
console.log("obj.foo called with", x);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
cy.spy(obj, "foo").as("foo");
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
obj.foo("first");
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
obj.foo("second");
|
|
||||||
}, 2500);
|
|
||||||
|
|
||||||
cy.get("@foo").should("have.been.calledTwice");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.stub() - create a stub and/or replace a function with stub", () => {
|
|
||||||
// https://on.cypress.io/stub
|
|
||||||
cy.visit("https://example.cypress.io/commands/spies-stubs-clocks");
|
|
||||||
|
|
||||||
const obj = {
|
|
||||||
/**
|
|
||||||
* prints both arguments to the console
|
|
||||||
* @param a {string}
|
|
||||||
* @param b {string}
|
|
||||||
*/
|
|
||||||
foo(a, b) {
|
|
||||||
console.log("a", a, "b", b);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const stub = cy.stub(obj, "foo").as("foo");
|
|
||||||
|
|
||||||
obj.foo("foo", "bar");
|
|
||||||
|
|
||||||
expect(stub).to.be.called;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.clock() - control time in the browser", () => {
|
|
||||||
// https://on.cypress.io/clock
|
|
||||||
|
|
||||||
// create the date in UTC so its always the same
|
|
||||||
// no matter what local timezone the browser is running in
|
|
||||||
const now = new Date(Date.UTC(2017, 2, 14)).getTime();
|
|
||||||
|
|
||||||
cy.clock(now);
|
|
||||||
cy.visit("https://example.cypress.io/commands/spies-stubs-clocks");
|
|
||||||
cy.get("#clock-div").click().should("have.text", "1489449600");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.tick() - move time in the browser", () => {
|
|
||||||
// https://on.cypress.io/tick
|
|
||||||
|
|
||||||
// create the date in UTC so its always the same
|
|
||||||
// no matter what local timezone the browser is running in
|
|
||||||
const now = new Date(Date.UTC(2017, 2, 14)).getTime();
|
|
||||||
|
|
||||||
cy.clock(now);
|
|
||||||
cy.visit("https://example.cypress.io/commands/spies-stubs-clocks");
|
|
||||||
cy.get("#tick-div").click().should("have.text", "1489449600");
|
|
||||||
|
|
||||||
cy.tick(10000); // 10 seconds passed
|
|
||||||
cy.get("#tick-div").click().should("have.text", "1489449610");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.stub() matches depending on arguments", () => {
|
|
||||||
// see all possible matchers at
|
|
||||||
// https://sinonjs.org/releases/latest/matchers/
|
|
||||||
const greeter = {
|
|
||||||
/**
|
|
||||||
* Greets a person
|
|
||||||
* @param {string} name
|
|
||||||
*/
|
|
||||||
greet(name) {
|
|
||||||
return `Hello, ${name}!`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
cy.stub(greeter, "greet")
|
|
||||||
.callThrough() // if you want non-matched calls to call the real method
|
|
||||||
.withArgs(Cypress.sinon.match.string)
|
|
||||||
.returns("Hi")
|
|
||||||
.withArgs(Cypress.sinon.match.number)
|
|
||||||
.throws(new Error("Invalid name"));
|
|
||||||
|
|
||||||
expect(greeter.greet("World")).to.equal("Hi");
|
|
||||||
// @ts-ignore
|
|
||||||
expect(() => greeter.greet(42)).to.throw("Invalid name");
|
|
||||||
expect(greeter.greet).to.have.been.calledTwice;
|
|
||||||
|
|
||||||
// non-matched calls goes the actual method
|
|
||||||
// @ts-ignore
|
|
||||||
expect(greeter.greet()).to.equal("Hello, undefined!");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("matches call arguments using Sinon matchers", () => {
|
|
||||||
// see all possible matchers at
|
|
||||||
// https://sinonjs.org/releases/latest/matchers/
|
|
||||||
const calculator = {
|
|
||||||
/**
|
|
||||||
* returns the sum of two arguments
|
|
||||||
* @param a {number}
|
|
||||||
* @param b {number}
|
|
||||||
*/
|
|
||||||
add(a, b) {
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const spy = cy.spy(calculator, "add").as("add");
|
|
||||||
|
|
||||||
expect(calculator.add(2, 3)).to.equal(5);
|
|
||||||
|
|
||||||
// if we want to assert the exact values used during the call
|
|
||||||
expect(spy).to.be.calledWith(2, 3);
|
|
||||||
|
|
||||||
// let's confirm "add" method was called with two numbers
|
|
||||||
expect(spy).to.be.calledWith(Cypress.sinon.match.number, Cypress.sinon.match.number);
|
|
||||||
|
|
||||||
// alternatively, provide the value to match
|
|
||||||
expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3));
|
|
||||||
|
|
||||||
// match any value
|
|
||||||
expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3);
|
|
||||||
|
|
||||||
// match any value from a list
|
|
||||||
expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the given number is event
|
|
||||||
* @param {number} x
|
|
||||||
*/
|
|
||||||
const isEven = (x) => x % 2 === 0;
|
|
||||||
|
|
||||||
// expect the value to pass a custom predicate function
|
|
||||||
// the second argument to "sinon.match(predicate, message)" is
|
|
||||||
// shown if the predicate does not pass and assertion fails
|
|
||||||
expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, "isEven"), 3);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a function that checks if a given number is larger than the limit
|
|
||||||
* @param {number} limit
|
|
||||||
* @returns {(x: number) => boolean}
|
|
||||||
*/
|
|
||||||
const isGreaterThan = (limit) => (x) => x > limit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a function that checks if a given number is less than the limit
|
|
||||||
* @param {number} limit
|
|
||||||
* @returns {(x: number) => boolean}
|
|
||||||
*/
|
|
||||||
const isLessThan = (limit) => (x) => x < limit;
|
|
||||||
|
|
||||||
// you can combine several matchers using "and", "or"
|
|
||||||
expect(spy).to.be.calledWith(
|
|
||||||
Cypress.sinon.match.number,
|
|
||||||
Cypress.sinon.match(isGreaterThan(2), "> 2").and(Cypress.sinon.match(isLessThan(4), "< 4"))
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(spy).to.be.calledWith(
|
|
||||||
Cypress.sinon.match.number,
|
|
||||||
Cypress.sinon.match(isGreaterThan(200), "> 200").or(Cypress.sinon.match(3))
|
|
||||||
);
|
|
||||||
|
|
||||||
// matchers can be used from BDD assertions
|
|
||||||
cy.get("@add").should("have.been.calledWith", Cypress.sinon.match.number, Cypress.sinon.match(3));
|
|
||||||
|
|
||||||
// you can alias matchers for shorter test code
|
|
||||||
const { match: M } = Cypress.sinon;
|
|
||||||
|
|
||||||
cy.get("@add").should("have.been.calledWith", M.number, M(3));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Traversal", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/traversal");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".children() - get child DOM elements", () => {
|
|
||||||
// https://on.cypress.io/children
|
|
||||||
cy.get(".traversal-breadcrumb").children(".active").should("contain", "Data");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".closest() - get closest ancestor DOM element", () => {
|
|
||||||
// https://on.cypress.io/closest
|
|
||||||
cy.get(".traversal-badge").closest("ul").should("have.class", "list-group");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".eq() - get a DOM element at a specific index", () => {
|
|
||||||
// https://on.cypress.io/eq
|
|
||||||
cy.get(".traversal-list>li").eq(1).should("contain", "siamese");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".filter() - get DOM elements that match the selector", () => {
|
|
||||||
// https://on.cypress.io/filter
|
|
||||||
cy.get(".traversal-nav>li").filter(".active").should("contain", "About");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".find() - get descendant DOM elements of the selector", () => {
|
|
||||||
// https://on.cypress.io/find
|
|
||||||
cy.get(".traversal-pagination").find("li").find("a").should("have.length", 7);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".first() - get first DOM element", () => {
|
|
||||||
// https://on.cypress.io/first
|
|
||||||
cy.get(".traversal-table td").first().should("contain", "1");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".last() - get last DOM element", () => {
|
|
||||||
// https://on.cypress.io/last
|
|
||||||
cy.get(".traversal-buttons .btn").last().should("contain", "Submit");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".next() - get next sibling DOM element", () => {
|
|
||||||
// https://on.cypress.io/next
|
|
||||||
cy.get(".traversal-ul").contains("apples").next().should("contain", "oranges");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".nextAll() - get all next sibling DOM elements", () => {
|
|
||||||
// https://on.cypress.io/nextall
|
|
||||||
cy.get(".traversal-next-all").contains("oranges").nextAll().should("have.length", 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".nextUntil() - get next sibling DOM elements until next el", () => {
|
|
||||||
// https://on.cypress.io/nextuntil
|
|
||||||
cy.get("#veggies").nextUntil("#nuts").should("have.length", 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".not() - remove DOM elements from set of DOM elements", () => {
|
|
||||||
// https://on.cypress.io/not
|
|
||||||
cy.get(".traversal-disabled .btn").not("[disabled]").should("not.contain", "Disabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".parent() - get parent DOM element from DOM elements", () => {
|
|
||||||
// https://on.cypress.io/parent
|
|
||||||
cy.get(".traversal-mark").parent().should("contain", "Morbi leo risus");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".parents() - get parent DOM elements from DOM elements", () => {
|
|
||||||
// https://on.cypress.io/parents
|
|
||||||
cy.get(".traversal-cite").parents().should("match", "blockquote");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".parentsUntil() - get parent DOM elements from DOM elements until el", () => {
|
|
||||||
// https://on.cypress.io/parentsuntil
|
|
||||||
cy.get(".clothes-nav").find(".active").parentsUntil(".clothes-nav").should("have.length", 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".prev() - get previous sibling DOM element", () => {
|
|
||||||
// https://on.cypress.io/prev
|
|
||||||
cy.get(".birds").find(".active").prev().should("contain", "Lorikeets");
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".prevAll() - get all previous sibling DOM elements", () => {
|
|
||||||
// https://on.cypress.io/prevall
|
|
||||||
cy.get(".fruits-list").find(".third").prevAll().should("have.length", 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".prevUntil() - get all previous sibling DOM elements until el", () => {
|
|
||||||
// https://on.cypress.io/prevuntil
|
|
||||||
cy.get(".foods-list").find("#nuts").prevUntil("#veggies").should("have.length", 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(".siblings() - get all sibling DOM elements", () => {
|
|
||||||
// https://on.cypress.io/siblings
|
|
||||||
cy.get(".traversal-pills .active").siblings().should("have.length", 2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Utilities", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/utilities");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Cypress._ - call a lodash method", () => {
|
|
||||||
// https://on.cypress.io/_
|
|
||||||
cy.request("https://jsonplaceholder.cypress.io/users").then((response) => {
|
|
||||||
let ids = Cypress._.chain(response.body).map("id").take(3).value();
|
|
||||||
|
|
||||||
expect(ids).to.deep.eq([1, 2, 3]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Cypress.$ - call a jQuery method", () => {
|
|
||||||
// https://on.cypress.io/$
|
|
||||||
let $li = Cypress.$(".utility-jquery li:first");
|
|
||||||
|
|
||||||
cy.wrap($li).should("not.have.class", "active").click().should("have.class", "active");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Cypress.Blob - blob utilities and base64 string conversion", () => {
|
|
||||||
// https://on.cypress.io/blob
|
|
||||||
cy.get(".utility-blob").then(($div) => {
|
|
||||||
// https://github.com/nolanlawson/blob-util#imgSrcToDataURL
|
|
||||||
// get the dataUrl string for the javascript-logo
|
|
||||||
return Cypress.Blob.imgSrcToDataURL(
|
|
||||||
"https://example.cypress.io/assets/img/javascript-logo.png",
|
|
||||||
undefined,
|
|
||||||
"anonymous"
|
|
||||||
).then((dataUrl) => {
|
|
||||||
// create an <img> element and set its src to the dataUrl
|
|
||||||
let img = Cypress.$("<img />", { src: dataUrl });
|
|
||||||
|
|
||||||
// need to explicitly return cy here since we are initially returning
|
|
||||||
// the Cypress.Blob.imgSrcToDataURL promise to our test
|
|
||||||
// append the image
|
|
||||||
$div.append(img);
|
|
||||||
|
|
||||||
cy.get(".utility-blob img").click().should("have.attr", "src", dataUrl);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Cypress.minimatch - test out glob patterns against strings", () => {
|
|
||||||
// https://on.cypress.io/minimatch
|
|
||||||
let matching = Cypress.minimatch("/users/1/comments", "/users/*/comments", {
|
|
||||||
matchBase: true
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(matching, "matching wildcard").to.be.true;
|
|
||||||
|
|
||||||
matching = Cypress.minimatch("/users/1/comments/2", "/users/*/comments", {
|
|
||||||
matchBase: true
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(matching, "comments").to.be.false;
|
|
||||||
|
|
||||||
// ** matches against all downstream path segments
|
|
||||||
matching = Cypress.minimatch("/foo/bar/baz/123/quux?a=b&c=2", "/foo/**", {
|
|
||||||
matchBase: true
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(matching, "comments").to.be.true;
|
|
||||||
|
|
||||||
// whereas * matches only the next path segment
|
|
||||||
|
|
||||||
matching = Cypress.minimatch("/foo/bar/baz/123/quux?a=b&c=2", "/foo/*", {
|
|
||||||
matchBase: false
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(matching, "comments").to.be.false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Cypress.Promise - instantiate a bluebird promise", () => {
|
|
||||||
// https://on.cypress.io/promise
|
|
||||||
let waited = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Bluebird<string>
|
|
||||||
*/
|
|
||||||
function waitOneSecond() {
|
|
||||||
// return a promise that resolves after 1 second
|
|
||||||
// @ts-ignore TS2351 (new Cypress.Promise)
|
|
||||||
return new Cypress.Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
// set waited to true
|
|
||||||
waited = true;
|
|
||||||
|
|
||||||
// resolve with 'foo' string
|
|
||||||
resolve("foo");
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cy.then(() => {
|
|
||||||
// return a promise to cy.then() that
|
|
||||||
// is awaited until it resolves
|
|
||||||
// @ts-ignore TS7006
|
|
||||||
return waitOneSecond().then((str) => {
|
|
||||||
expect(str).to.eq("foo");
|
|
||||||
expect(waited).to.be.true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Viewport", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/viewport");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.viewport() - set the viewport size and dimension", () => {
|
|
||||||
// https://on.cypress.io/viewport
|
|
||||||
|
|
||||||
cy.get("#navbar").should("be.visible");
|
|
||||||
cy.viewport(320, 480);
|
|
||||||
|
|
||||||
// the navbar should have collapse since our screen is smaller
|
|
||||||
cy.get("#navbar").should("not.be.visible");
|
|
||||||
cy.get(".navbar-toggle").should("be.visible").click();
|
|
||||||
cy.get(".nav").find("a").should("be.visible");
|
|
||||||
|
|
||||||
// lets see what our app looks like on a super large screen
|
|
||||||
cy.viewport(2999, 2999);
|
|
||||||
|
|
||||||
// cy.viewport() accepts a set of preset sizes
|
|
||||||
// to easily set the screen to a device's width and height
|
|
||||||
|
|
||||||
// We added a cy.wait() between each viewport change so you can see
|
|
||||||
// the change otherwise it is a little too fast to see :)
|
|
||||||
|
|
||||||
cy.viewport("macbook-15");
|
|
||||||
cy.wait(200);
|
|
||||||
cy.viewport("macbook-13");
|
|
||||||
cy.wait(200);
|
|
||||||
cy.viewport("macbook-11");
|
|
||||||
cy.wait(200);
|
|
||||||
cy.viewport("ipad-2");
|
|
||||||
cy.wait(200);
|
|
||||||
cy.viewport("ipad-mini");
|
|
||||||
cy.wait(200);
|
|
||||||
cy.viewport("iphone-6+");
|
|
||||||
cy.wait(200);
|
|
||||||
cy.viewport("iphone-6");
|
|
||||||
cy.wait(200);
|
|
||||||
cy.viewport("iphone-5");
|
|
||||||
cy.wait(200);
|
|
||||||
cy.viewport("iphone-4");
|
|
||||||
cy.wait(200);
|
|
||||||
cy.viewport("iphone-3");
|
|
||||||
cy.wait(200);
|
|
||||||
|
|
||||||
// cy.viewport() accepts an orientation for all presets
|
|
||||||
// the default orientation is 'portrait'
|
|
||||||
cy.viewport("ipad-2", "portrait");
|
|
||||||
cy.wait(200);
|
|
||||||
cy.viewport("iphone-4", "landscape");
|
|
||||||
cy.wait(200);
|
|
||||||
|
|
||||||
// The viewport will be reset back to the default dimensions
|
|
||||||
// in between tests (the default can be set in cypress.json)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Waiting", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/waiting");
|
|
||||||
});
|
|
||||||
// BE CAREFUL of adding unnecessary wait times.
|
|
||||||
// https://on.cypress.io/best-practices#Unnecessary-Waiting
|
|
||||||
|
|
||||||
// https://on.cypress.io/wait
|
|
||||||
it("cy.wait() - wait for a specific amount of time", () => {
|
|
||||||
cy.get(".wait-input1").type("Wait 1000ms after typing");
|
|
||||||
cy.wait(1000);
|
|
||||||
cy.get(".wait-input2").type("Wait 1000ms after typing");
|
|
||||||
cy.wait(1000);
|
|
||||||
cy.get(".wait-input3").type("Wait 1000ms after typing");
|
|
||||||
cy.wait(1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.wait() - wait for a specific route", () => {
|
|
||||||
// Listen to GET to comments/1
|
|
||||||
cy.intercept("GET", "**/comments/*").as("getComment");
|
|
||||||
|
|
||||||
// we have code that gets a comment when
|
|
||||||
// the button is clicked in scripts.js
|
|
||||||
cy.get(".network-btn").click();
|
|
||||||
|
|
||||||
// wait for GET comments/1
|
|
||||||
cy.wait("@getComment").its("response.statusCode").should("be.oneOf", [200, 304]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context("Window", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("https://example.cypress.io/commands/window");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.window() - get the global window object", () => {
|
|
||||||
// https://on.cypress.io/window
|
|
||||||
cy.window().should("have.property", "top");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.document() - get the document object", () => {
|
|
||||||
// https://on.cypress.io/document
|
|
||||||
cy.document().should("have.property", "charset").and("eq", "UTF-8");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cy.title() - get the title", () => {
|
|
||||||
// https://on.cypress.io/title
|
|
||||||
cy.title().should("include", "Kitchen Sink");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Using fixtures to represent data",
|
|
||||||
"email": "hello@cypress.io",
|
|
||||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"id": 8739,
|
|
||||||
"name": "Jane",
|
|
||||||
"email": "jane@example.com"
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[]
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
// ***********************************************************
|
|
||||||
// This example plugins/index.jsx can be used to load plugins
|
|
||||||
//
|
|
||||||
// You can change the location of this file or turn off loading
|
|
||||||
// the plugins file with the 'pluginsFile' configuration option.
|
|
||||||
//
|
|
||||||
// You can read more here:
|
|
||||||
// https://on.cypress.io/plugins-guide
|
|
||||||
// ***********************************************************
|
|
||||||
|
|
||||||
// This function is called when a project is opened or re-opened (e.g. due to
|
|
||||||
// the project's config changing)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {Cypress.PluginConfig}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
module.exports = (on, config) => {
|
|
||||||
// `on` is used to hook into various events Cypress emits
|
|
||||||
// `config` is the resolved Cypress config
|
|
||||||
};
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// ***********************************************
|
|
||||||
// This example commands.js shows you how to
|
|
||||||
// create various custom commands and overwrite
|
|
||||||
// existing commands.
|
|
||||||
//
|
|
||||||
// For more comprehensive examples of custom
|
|
||||||
// commands please read more here:
|
|
||||||
// https://on.cypress.io/custom-commands
|
|
||||||
// ***********************************************
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This is a parent command --
|
|
||||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This is a child command --
|
|
||||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This is a dual command --
|
|
||||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This will overwrite an existing command --
|
|
||||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
|
||||||
|
|
||||||
import "@testing-library/cypress/add-commands";
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// ***********************************************************
|
|
||||||
// This example support/index.jsx is processed and
|
|
||||||
// loaded automatically before your test files.
|
|
||||||
//
|
|
||||||
// This is a great place to put global configuration and
|
|
||||||
// behavior that modifies Cypress.
|
|
||||||
//
|
|
||||||
// You can change the location of this file or turn off
|
|
||||||
// automatically serving support files with the
|
|
||||||
// 'supportFile' configuration option.
|
|
||||||
//
|
|
||||||
// You can read more here:
|
|
||||||
// https://on.cypress.io/configuration
|
|
||||||
// ***********************************************************
|
|
||||||
|
|
||||||
// Import commands.js using ES2015 syntax:
|
|
||||||
import "./commands";
|
|
||||||
|
|
||||||
// Alternatively you can use CommonJS syntax:
|
|
||||||
// require('./commands')
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"allowJs": true,
|
|
||||||
"baseUrl": "../node_modules",
|
|
||||||
"types": ["cypress"]
|
|
||||||
},
|
|
||||||
"include": ["**/*.*"]
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,9 @@ import globals from "globals";
|
|||||||
import pluginJs from "@eslint/js";
|
import pluginJs from "@eslint/js";
|
||||||
import pluginReact from "eslint-plugin-react";
|
import pluginReact from "eslint-plugin-react";
|
||||||
|
|
||||||
/** @type {import('eslint').Linter.Config[]} */
|
/** @type {import("eslint").Linter.Config[]} */
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
|
{ ignores: ["node_modules/**", "dist/**", "build/**", "dev-dist/**"] },
|
||||||
{
|
{
|
||||||
files: ["**/*.{js,mjs,cjs,jsx}"]
|
files: ["**/*.{js,mjs,cjs,jsx}"]
|
||||||
},
|
},
|
||||||
@@ -12,9 +12,13 @@ export default [
|
|||||||
pluginJs.configs.recommended,
|
pluginJs.configs.recommended,
|
||||||
{
|
{
|
||||||
...pluginReact.configs.flat.recommended,
|
...pluginReact.configs.flat.recommended,
|
||||||
|
settings: {
|
||||||
|
react: { version: "detect" }
|
||||||
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...pluginReact.configs.flat.recommended.rules,
|
...pluginReact.configs.flat.recommended.rules,
|
||||||
"react/prop-types": 0
|
"react/prop-types": 0,
|
||||||
|
"react/no-children-prop": 0 // Disable react/no-children-prop rule
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pluginReact.configs.flat["jsx-runtime"]
|
pluginReact.configs.flat["jsx-runtime"]
|
||||||
|
|||||||
@@ -74,50 +74,8 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<% } %>
|
<% } %>
|
||||||
<script>
|
<script>!function(w,d,i,s){function l(){if(!d.getElementById(i)){var f=d.getElementsByTagName(s)[0],e=d.createElement(s);e.type="text/javascript",e.async=!0,e.src="https://canny.io/sdk.js",f.parentNode.insertBefore(e,f)}}if("function"!=typeof w.Canny){var c=function(){c.q.push(arguments)};c.q=[],w.Canny=c,"complete"===d.readyState?l():w.attachEvent?w.attachEvent("onload",l):w.addEventListener("load",l,!1)}}(window,document,"canny-jssdk","script");</script>
|
||||||
!(function () {
|
|
||||||
"use strict";
|
|
||||||
var e = [
|
|
||||||
"debug",
|
|
||||||
"destroy",
|
|
||||||
"do",
|
|
||||||
"help",
|
|
||||||
"identify",
|
|
||||||
"is",
|
|
||||||
"off",
|
|
||||||
"on",
|
|
||||||
"ready",
|
|
||||||
"render",
|
|
||||||
"reset",
|
|
||||||
"safe",
|
|
||||||
"set"
|
|
||||||
];
|
|
||||||
if (window.noticeable) console.warn("Noticeable SDK code snippet loaded more than once");
|
|
||||||
else {
|
|
||||||
var n = (window.noticeable = window.noticeable || []);
|
|
||||||
|
|
||||||
function t(e) {
|
|
||||||
return function () {
|
|
||||||
var t = Array.prototype.slice.call(arguments);
|
|
||||||
return t.unshift(e), n.push(t), n;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
!(function () {
|
|
||||||
for (var o = 0; o < e.length; o++) {
|
|
||||||
var r = e[o];
|
|
||||||
n[r] = t(r);
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
(function () {
|
|
||||||
var e = document.createElement("script");
|
|
||||||
(e.async = !0), (e.src = "https://sdk.noticeable.io/l.js");
|
|
||||||
var n = document.head;
|
|
||||||
n.insertBefore(e, n.firstChild);
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
14736
client/package-lock.json
generated
14736
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,83 +2,90 @@
|
|||||||
"name": "bodyshop",
|
"name": "bodyshop",
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18.2"
|
"node": ">=22.0.0"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:4000",
|
"proxy": "http://localhost:4000",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/pro-layout": "^7.19.12",
|
"@amplitude/analytics-browser": "^2.30.1",
|
||||||
"@apollo/client": "^3.11.8",
|
"@ant-design/pro-layout": "^7.22.6",
|
||||||
"@emotion/is-prop-valid": "^1.3.1",
|
"@apollo/client": "^3.13.9",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.5.0",
|
"@emotion/is-prop-valid": "^1.4.0",
|
||||||
|
"@fingerprintjs/fingerprintjs": "^4.6.1",
|
||||||
|
"@firebase/analytics": "^0.10.19",
|
||||||
|
"@firebase/app": "^0.14.6",
|
||||||
|
"@firebase/auth": "^1.11.1",
|
||||||
|
"@firebase/firestore": "^4.9.2",
|
||||||
|
"@firebase/messaging": "^0.12.22",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.2.7",
|
"@reduxjs/toolkit": "^2.10.1",
|
||||||
"@sentry/cli": "^2.36.2",
|
"@sentry/cli": "^2.58.2",
|
||||||
"@sentry/react": "^7.114.0",
|
"@sentry/react": "^9.43.0",
|
||||||
"@splitsoftware/splitio-react": "^1.13.0",
|
"@sentry/vite-plugin": "^4.6.0",
|
||||||
"@tanem/react-nprogress": "^5.0.51",
|
"@splitsoftware/splitio-react": "^2.6.0",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@tanem/react-nprogress": "^5.0.56",
|
||||||
"antd": "^5.20.1",
|
"antd": "^5.28.1",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
"apollo-link-sentry": "^3.3.0",
|
"apollo-link-sentry": "^4.4.0",
|
||||||
"autosize": "^6.0.1",
|
"autosize": "^6.0.1",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.13.2",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"css-box-model": "^1.2.1",
|
"css-box-model": "^1.2.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.19",
|
||||||
"dayjs-business-days2": "^1.2.2",
|
"dayjs-business-days2": "^1.3.1",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^17.2.3",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"firebase": "^10.13.2",
|
"graphql": "^16.12.0",
|
||||||
"graphql": "^16.9.0",
|
"i18next": "^25.6.2",
|
||||||
"i18next": "^23.15.1",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
|
||||||
"immutability-helper": "^3.1.1",
|
"immutability-helper": "^3.1.1",
|
||||||
"libphonenumber-js": "^1.11.9",
|
"libphonenumber-js": "^1.12.26",
|
||||||
"logrocket": "^8.1.2",
|
"lightningcss": "^1.30.2",
|
||||||
"markerjs2": "^2.32.2",
|
"logrocket": "^9.0.2",
|
||||||
|
"markerjs2": "^2.32.7",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
"normalize-url": "^8.0.1",
|
"normalize-url": "^8.1.0",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
|
"phone": "^3.1.67",
|
||||||
|
"posthog-js": "^1.294.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^9.1.0",
|
"query-string": "^9.3.1",
|
||||||
"raf-schd": "^4.0.3",
|
"raf-schd": "^4.0.3",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-big-calendar": "^1.14.1",
|
"react-big-calendar": "^1.19.4",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-cookie": "^7.2.0",
|
"react-cookie": "^8.0.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-drag-listview": "^2.0.0",
|
"react-drag-listview": "^2.0.0",
|
||||||
"react-grid-gallery": "^1.0.1",
|
"react-grid-gallery": "^1.0.1",
|
||||||
"react-grid-layout": "1.3.4",
|
"react-grid-layout": "1.3.4",
|
||||||
"react-i18next": "^14.1.3",
|
"react-i18next": "^15.7.3",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-image-lightbox": "^5.1.4",
|
"react-image-lightbox": "^5.1.4",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^10.1.0",
|
||||||
"react-number-format": "^5.4.2",
|
"react-number-format": "^5.4.3",
|
||||||
"react-popopo": "^2.1.9",
|
"react-popopo": "^2.1.9",
|
||||||
"react-product-fruits": "^2.2.61",
|
"react-product-fruits": "^2.2.62",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.2.0",
|
||||||
"react-resizable": "^3.0.5",
|
"react-resizable": "^3.0.5",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-router-dom": "^6.30.0",
|
||||||
"react-sticky": "^6.0.3",
|
"react-sticky": "^6.0.3",
|
||||||
"react-virtuoso": "^4.10.4",
|
"react-virtuoso": "^4.14.1",
|
||||||
"recharts": "^2.12.7",
|
"recharts": "^2.15.2",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
"redux-actions": "^3.0.3",
|
"redux-actions": "^3.0.3",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-saga": "^1.3.0",
|
"redux-saga": "^1.4.2",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
"reselect": "^5.1.1",
|
"reselect": "^5.1.1",
|
||||||
"sass": "^1.79.3",
|
"sass": "^1.94.0",
|
||||||
"socket.io-client": "^4.8.0",
|
"socket.io-client": "^4.8.1",
|
||||||
"styled-components": "^6.1.13",
|
"styled-components": "^6.1.19",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
"use-memo-one": "^1.1.3",
|
"use-memo-one": "^1.1.3",
|
||||||
"userpilot": "^1.3.6",
|
|
||||||
"vite-plugin-ejs": "^1.7.0",
|
"vite-plugin-ejs": "^1.7.0",
|
||||||
"web-vitals": "^3.5.2"
|
"web-vitals": "^3.5.2"
|
||||||
},
|
},
|
||||||
@@ -95,11 +102,17 @@
|
|||||||
"build:test:rome": "env-cmd -f .env.test.rome npm run build",
|
"build:test:rome": "env-cmd -f .env.test.rome npm run build",
|
||||||
"build:production:imex": "env-cmd -f .env.production.imex npm run build",
|
"build:production:imex": "env-cmd -f .env.production.imex npm run build",
|
||||||
"build:production:rome": "env-cmd -f .env.production.rome npm run build",
|
"build:production:rome": "env-cmd -f .env.production.rome npm run build",
|
||||||
"test": "cypress open",
|
|
||||||
"eject": "react-scripts eject",
|
|
||||||
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
|
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
|
||||||
"eulaize": "node src/utils/eulaize.js",
|
"eulaize": "node src/utils/eulaize.js",
|
||||||
"sentry:sourcemaps:imex": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build"
|
"test:unit": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"test:e2e:imex": "playwright test --config playwright.config.js",
|
||||||
|
"test:e2e:rome": "playwright test --config playwright.rome.config.js",
|
||||||
|
"test:e2e:imex:headed": "playwright test --config playwright.config.js --headed",
|
||||||
|
"test:e2e:rome:headed": "playwright test --config playwright.rome.config.js --headed",
|
||||||
|
"test:e2e:report": "playwright show-report",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"lint:fix": "eslint . --fix"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
@@ -120,36 +133,39 @@
|
|||||||
"@rollup/rollup-linux-x64-gnu": "4.6.1"
|
"@rollup/rollup-linux-x64-gnu": "4.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/icons": "^5.5.1",
|
"@ant-design/icons": "^6.1.0",
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@babel/preset-react": "^7.24.7",
|
"@babel/preset-react": "^7.28.5",
|
||||||
"@dotenvx/dotenvx": "^1.14.1",
|
"@dotenvx/dotenvx": "^1.51.1",
|
||||||
"@emotion/babel-plugin": "^11.12.0",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.14.0",
|
||||||
"@eslint/js": "^9.15.0",
|
"@eslint/js": "^9.39.1",
|
||||||
"@sentry/webpack-plugin": "^2.22.4",
|
"@playwright/test": "^1.56.1",
|
||||||
"@testing-library/cypress": "^10.0.2",
|
"@sentry/webpack-plugin": "^4.6.0",
|
||||||
"browserslist": "^4.23.3",
|
"@testing-library/dom": "^10.4.1",
|
||||||
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
|
"@testing-library/react": "^16.3.0",
|
||||||
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
|
"browserslist": "^4.28.0",
|
||||||
"browserslist-to-esbuild": "^2.1.1",
|
"browserslist-to-esbuild": "^2.1.1",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.6.2",
|
||||||
"cross-env": "^7.0.3",
|
"eslint": "^9.39.1",
|
||||||
"cypress": "^13.14.2",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint": "^8.57.1",
|
"globals": "^15.15.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"jsdom": "^26.0.0",
|
||||||
"eslint-plugin-cypress": "^2.15.1",
|
"memfs": "^4.51.0",
|
||||||
"eslint-plugin-react": "^7.37.2",
|
|
||||||
"globals": "^15.12.0",
|
|
||||||
"memfs": "^4.12.0",
|
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
"react-error-overlay": "6.0.11",
|
"playwright": "^1.56.1",
|
||||||
|
"react-error-overlay": "^6.1.0",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"source-map-explorer": "^2.5.3",
|
"source-map-explorer": "^2.5.3",
|
||||||
"vite": "^5.4.7",
|
"vite": "^7.2.2",
|
||||||
"vite-plugin-babel": "^1.2.0",
|
"vite-plugin-babel": "^1.3.2",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-node-polyfills": "^0.22.0",
|
"vite-plugin-node-polyfills": "^0.24.0",
|
||||||
"vite-plugin-pwa": "^0.20.5",
|
"vite-plugin-pwa": "^1.1.0",
|
||||||
"vite-plugin-style-import": "^2.0.0",
|
"vite-plugin-style-import": "^2.0.0",
|
||||||
"workbox-window": "^7.1.0"
|
"vitest": "^3.2.4",
|
||||||
|
"workbox-window": "^7.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
client/playwright.config.js
Normal file
26
client/playwright.config.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { defineConfig } from "@playwright/test";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
dotenv.config({
|
||||||
|
path: "./.env.development.imex",
|
||||||
|
prefix: "TEST_"
|
||||||
|
});
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./tests/e2e",
|
||||||
|
testMatch: "*.e2e.js",
|
||||||
|
timeout: 60 * 1000,
|
||||||
|
reporter: [["list"], ["html"]],
|
||||||
|
use: {
|
||||||
|
baseURL: "https://localhost:3000",
|
||||||
|
browser: "chromium",
|
||||||
|
ignoreHTTPSErrors: true
|
||||||
|
},
|
||||||
|
webServer: {
|
||||||
|
command: "npm run start:imex",
|
||||||
|
ignoreHTTPSErrors: true,
|
||||||
|
url: "https://localhost:3000/health", // Health check endpoint will tell us when the server is ready
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
reuseExistingServer: !process.env.CI // Reuse server locally, not in CI
|
||||||
|
}
|
||||||
|
});
|
||||||
26
client/playwright.rome.config.js
Normal file
26
client/playwright.rome.config.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { defineConfig } from "@playwright/test";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
dotenv.config({
|
||||||
|
path: "./.env.development.rome",
|
||||||
|
prefix: "TEST_"
|
||||||
|
});
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./tests/e2e",
|
||||||
|
testMatch: "*.e2e.js",
|
||||||
|
timeout: 60 * 1000,
|
||||||
|
reporter: [["list"], ["html"]],
|
||||||
|
use: {
|
||||||
|
baseURL: "https://localhost:3000",
|
||||||
|
browser: "chromium",
|
||||||
|
ignoreHTTPSErrors: true
|
||||||
|
},
|
||||||
|
webServer: {
|
||||||
|
command: "npm run start:rome",
|
||||||
|
ignoreHTTPSErrors: true,
|
||||||
|
url: "https://localhost:3000/health", // Health check endpoint will tell us when the server is ready
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
reuseExistingServer: !process.env.CI // Reuse server locally, not in CI
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
// Scripts for firebase and firebase messaging
|
// Scripts for firebase and firebase messaging
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js");
|
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js");
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging-compat.js");
|
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging-compat.js");
|
||||||
|
|
||||||
// Initialize the Firebase app in the service worker by passing the generated config
|
// Initialize the Firebase app in the service worker by passing the generated config
|
||||||
@@ -42,13 +44,16 @@ switch (this.location.hostname) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
firebase.initializeApp(firebaseConfig);
|
firebase.initializeApp(firebaseConfig);
|
||||||
|
|
||||||
// Retrieve firebase messaging
|
// Retrieve firebase messaging
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
const messaging = firebase.messaging();
|
const messaging = firebase.messaging();
|
||||||
|
|
||||||
messaging.onBackgroundMessage(function (payload) {
|
messaging.onBackgroundMessage(function (payload) {
|
||||||
// Customize notification here
|
// Customize notification here
|
||||||
console.log("[firebase-messaging-sw.js] Received background message ", payload);
|
console.log("[firebase-messaging-sw.js] Received background message ", payload);
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
self.registration.showNotification(notificationTitle, notificationOptions);
|
self.registration.showNotification(notificationTitle, notificationOptions);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,54 +1,130 @@
|
|||||||
import { ApolloProvider } from "@apollo/client";
|
import { ApolloProvider } from "@apollo/client";
|
||||||
import { SplitFactoryProvider, SplitSdk } from "@splitsoftware/splitio-react";
|
import * as Sentry from "@sentry/react";
|
||||||
|
import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
|
||||||
import { ConfigProvider } from "antd";
|
import { ConfigProvider } from "antd";
|
||||||
import enLocale from "antd/es/locale/en_US";
|
import enLocale from "antd/es/locale/en_US";
|
||||||
import React from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
|
import { CookiesProvider } from "react-cookie";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect, useSelector } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||||
|
import { setDarkMode } from "../redux/application/application.actions";
|
||||||
|
import { selectDarkMode } from "../redux/application/application.selectors";
|
||||||
|
import { selectCurrentUser } from "../redux/user/user.selectors.js";
|
||||||
|
import { signOutStart } from "../redux/user/user.actions";
|
||||||
import client from "../utils/GraphQLClient";
|
import client from "../utils/GraphQLClient";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import * as Sentry from "@sentry/react";
|
import getTheme from "./themeProvider";
|
||||||
|
|
||||||
import themeProvider from "./themeProvider";
|
|
||||||
import { Userpilot } from "userpilot";
|
|
||||||
|
|
||||||
// Initialize Userpilot
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
Userpilot.initialize("NX-69145f08");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Base Split configuration
|
||||||
const config = {
|
const config = {
|
||||||
core: {
|
core: {
|
||||||
authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
|
authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
|
||||||
key: "anon"
|
key: "anon"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const factory = SplitSdk(config);
|
|
||||||
|
|
||||||
function AppContainer() {
|
function SplitClientProvider({ children }) {
|
||||||
|
const imexshopid = useSelector((state) => state.user.imexshopid);
|
||||||
|
const splitClient = useSplitClient({ key: imexshopid || "anon" });
|
||||||
|
useEffect(() => {
|
||||||
|
if (splitClient && imexshopid) {
|
||||||
|
console.log(`Split client initialized with key: ${imexshopid}, isReady: ${splitClient.isReady}`);
|
||||||
|
}
|
||||||
|
}, [splitClient, imexshopid]);
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
currentUser: selectCurrentUser
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
setDarkMode: (isDarkMode) => dispatch(setDarkMode(isDarkMode)),
|
||||||
|
signOutStart: () => dispatch(signOutStart())
|
||||||
|
});
|
||||||
|
|
||||||
|
function AppContainer({ currentUser, setDarkMode, signOutStart }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const isDarkMode = useSelector(selectDarkMode);
|
||||||
|
const theme = useMemo(() => getTheme(isDarkMode), [isDarkMode]);
|
||||||
|
|
||||||
|
// Global seamless logout listener with redirect to /signin
|
||||||
|
useEffect(() => {
|
||||||
|
const handleSeamlessLogout = (event) => {
|
||||||
|
if (event.data?.type !== "seamlessLogoutRequest") return;
|
||||||
|
|
||||||
|
const requestOrigin = event.origin;
|
||||||
|
|
||||||
|
if (currentUser?.authorized !== true) {
|
||||||
|
window.parent.postMessage(
|
||||||
|
{ type: "seamlessLogoutResponse", status: "already_logged_out" },
|
||||||
|
requestOrigin || "*"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
signOutStart();
|
||||||
|
window.parent.postMessage({ type: "seamlessLogoutResponse", status: "logged_out" }, requestOrigin || "*");
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("message", handleSeamlessLogout);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("message", handleSeamlessLogout);
|
||||||
|
};
|
||||||
|
}, [signOutStart, currentUser]);
|
||||||
|
|
||||||
|
// Update data-theme attribute
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.setAttribute("data-theme", isDarkMode ? "dark" : "light");
|
||||||
|
return () => document.documentElement.removeAttribute("data-theme");
|
||||||
|
}, [isDarkMode]);
|
||||||
|
|
||||||
|
// Sync darkMode with localStorage
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentUser?.uid) {
|
||||||
|
const savedMode = localStorage.getItem(`dark-mode-${currentUser.uid}`);
|
||||||
|
if (savedMode !== null) {
|
||||||
|
setDarkMode(JSON.parse(savedMode));
|
||||||
|
} else {
|
||||||
|
setDarkMode(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setDarkMode(false);
|
||||||
|
}
|
||||||
|
}, [currentUser?.uid, setDarkMode]);
|
||||||
|
|
||||||
|
// Persist darkMode
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentUser?.uid) {
|
||||||
|
localStorage.setItem(`dark-mode-${currentUser.uid}`, JSON.stringify(isDarkMode));
|
||||||
|
}
|
||||||
|
}, [isDarkMode, currentUser?.uid]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ApolloProvider client={client}>
|
<CookiesProvider>
|
||||||
<ConfigProvider
|
<ApolloProvider client={client}>
|
||||||
//componentSize="small"
|
<ConfigProvider
|
||||||
input={{ autoComplete: "new-password" }}
|
input={{ autoComplete: "new-password" }}
|
||||||
locale={enLocale}
|
locale={enLocale}
|
||||||
theme={themeProvider}
|
theme={theme}
|
||||||
form={{
|
form={{
|
||||||
validateMessages: {
|
validateMessages: {
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
required: t("general.validation.required", { label: "${label}" })
|
||||||
required: t("general.validation.required", { label: "${label}" })
|
}
|
||||||
}
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<GlobalLoadingBar />
|
||||||
<GlobalLoadingBar />
|
<SplitFactoryProvider config={config}>
|
||||||
<SplitFactoryProvider factory={factory}>
|
<SplitClientProvider>
|
||||||
<App />
|
<App />
|
||||||
</SplitFactoryProvider>
|
</SplitClientProvider>
|
||||||
</ConfigProvider>
|
</SplitFactoryProvider>
|
||||||
</ApolloProvider>
|
</ConfigProvider>
|
||||||
|
</ApolloProvider>
|
||||||
|
</CookiesProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Sentry.withProfiler(AppContainer);
|
export default Sentry.withProfiler(connect(mapStateToProps, mapDispatchToProps)(AppContainer));
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
import { useSplitClient } from "@splitsoftware/splitio-react";
|
import { useSplitClient } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Result } from "antd";
|
import { Button, Result } from "antd";
|
||||||
import LogRocket from "logrocket";
|
import LogRocket from "logrocket";
|
||||||
import React, { lazy, Suspense, useEffect, useState } from "react";
|
import { lazy, Suspense, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Route, Routes } from "react-router-dom";
|
import { Route, Routes, useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
||||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component"; // Component Imports
|
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
||||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||||
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
||||||
import LandingPage from "../pages/landing/landing.page";
|
import LandingPage from "../pages/landing/landing.page";
|
||||||
import TechPageContainer from "../pages/tech/tech.page.container";
|
import TechPageContainer from "../pages/tech/tech.page.container";
|
||||||
import { setOnline } from "../redux/application/application.actions";
|
import SimplifiedPartsPageContainer from "../pages/simplified-parts/simplified-parts.page.container.jsx";
|
||||||
import { selectOnline } from "../redux/application/application.selectors";
|
import { setIsPartsEntry, setOnline } from "../redux/application/application.actions";
|
||||||
|
import { selectIsPartsEntry, selectOnline } from "../redux/application/application.selectors";
|
||||||
import { checkUserSession } from "../redux/user/user.actions";
|
import { checkUserSession } from "../redux/user/user.actions";
|
||||||
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
|
||||||
import PrivateRoute from "../components/PrivateRoute";
|
import PrivateRoute from "../components/PrivateRoute";
|
||||||
@@ -21,42 +22,56 @@ import "./App.styles.scss";
|
|||||||
import Eula from "../components/eula/eula.component";
|
import Eula from "../components/eula/eula.component";
|
||||||
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||||
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
||||||
import { SocketProvider } from "../contexts/SocketIO/socketContext.jsx";
|
|
||||||
import { NotificationProvider } from "../contexts/Notifications/notificationContext.jsx";
|
import { NotificationProvider } from "../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import SocketProvider from "../contexts/SocketIO/socketProvider.jsx";
|
||||||
|
import SoundWrapper from "./SoundWrapper.jsx";
|
||||||
|
|
||||||
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
||||||
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||||
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
||||||
const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
|
const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
|
||||||
const MobilePaymentContainer = lazy(() => import("../pages/mobile-payment/mobile-payment.container"));
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
online: selectOnline,
|
online: selectOnline,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentEula: selectCurrentEula
|
currentEula: selectCurrentEula,
|
||||||
|
isPartsEntry: selectIsPartsEntry
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
checkUserSession: () => dispatch(checkUserSession()),
|
checkUserSession: () => dispatch(checkUserSession()),
|
||||||
setOnline: (isOnline) => dispatch(setOnline(isOnline))
|
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
||||||
|
setIsPartsEntry: (isParts) => dispatch(setIsPartsEntry(isParts))
|
||||||
});
|
});
|
||||||
|
|
||||||
export function App({ bodyshop, checkUserSession, currentUser, online, setOnline, currentEula }) {
|
export function App({
|
||||||
|
bodyshop,
|
||||||
|
checkUserSession,
|
||||||
|
currentUser,
|
||||||
|
online,
|
||||||
|
setOnline,
|
||||||
|
setIsPartsEntry,
|
||||||
|
currentEula,
|
||||||
|
isPartsEntry
|
||||||
|
}) {
|
||||||
const client = useSplitClient().client;
|
const client = useSplitClient().client;
|
||||||
const [listenersAdded, setListenersAdded] = useState(false);
|
const [listenersAdded, setListenersAdded] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!navigator.onLine) {
|
if (!navigator.onLine) {
|
||||||
setOnline(false);
|
setOnline(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkUserSession();
|
checkUserSession();
|
||||||
}, [checkUserSession, setOnline]);
|
}, [checkUserSession, setOnline]);
|
||||||
|
|
||||||
//const b = Grid.useBreakpoint();
|
useEffect(() => {
|
||||||
// console.log("Breakpoints:", b);
|
const pathname = window.location.pathname;
|
||||||
|
const isParts = pathname === "/parts" || pathname.startsWith("/parts/");
|
||||||
|
setIsPartsEntry(isParts);
|
||||||
|
}, [setIsPartsEntry]);
|
||||||
|
|
||||||
// Associate event listeners, memoize to prevent multiple listeners being added
|
// Associate event listeners, memoize to prevent multiple listeners being added
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -141,89 +156,93 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
|||||||
>
|
>
|
||||||
<ProductFruitsWrapper
|
<ProductFruitsWrapper
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
workspaceCode={InstanceRenderMgr({
|
bodyshop={bodyshop}
|
||||||
imex: null,
|
workspaceCode={bodyshop?.tours_enabled ? "9BkbEseqNqxw8jUH" : ""}
|
||||||
rome: "9BkbEseqNqxw8jUH"
|
isPartsEntry={isPartsEntry}
|
||||||
})}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NotificationProvider>
|
<NotificationProvider>
|
||||||
<Routes>
|
<SoundWrapper bodyshop={bodyshop}>
|
||||||
<Route
|
<Routes>
|
||||||
path="*"
|
<Route
|
||||||
element={
|
path="*"
|
||||||
<ErrorBoundary>
|
element={
|
||||||
<LandingPage />
|
<ErrorBoundary>
|
||||||
</ErrorBoundary>
|
<LandingPage />
|
||||||
}
|
</ErrorBoundary>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/signin"
|
<Route
|
||||||
element={
|
path="/signin"
|
||||||
<ErrorBoundary>
|
element={
|
||||||
<SignInPage />
|
<ErrorBoundary>
|
||||||
</ErrorBoundary>
|
<SignInPage />
|
||||||
}
|
</ErrorBoundary>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/resetpassword"
|
<Route
|
||||||
element={
|
path="/resetpassword"
|
||||||
<ErrorBoundary>
|
element={
|
||||||
<ResetPassword />
|
<ErrorBoundary>
|
||||||
</ErrorBoundary>
|
<ResetPassword />
|
||||||
}
|
</ErrorBoundary>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/csi/:surveyId"
|
<Route
|
||||||
element={
|
path="/csi/:surveyId"
|
||||||
<ErrorBoundary>
|
element={
|
||||||
<CsiPage />
|
<ErrorBoundary>
|
||||||
</ErrorBoundary>
|
<CsiPage />
|
||||||
}
|
</ErrorBoundary>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/disclaimer"
|
<Route
|
||||||
element={
|
path="/disclaimer"
|
||||||
<ErrorBoundary>
|
element={
|
||||||
<DisclaimerPage />
|
<ErrorBoundary>
|
||||||
</ErrorBoundary>
|
<DisclaimerPage />
|
||||||
}
|
</ErrorBoundary>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/mp/:paymentIs"
|
<Route
|
||||||
element={
|
path="/manage/*"
|
||||||
<ErrorBoundary>
|
element={
|
||||||
<MobilePaymentContainer />
|
<ErrorBoundary>
|
||||||
</ErrorBoundary>
|
<SocketProvider bodyshop={bodyshop} navigate={navigate} currentUser={currentUser}>
|
||||||
}
|
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||||
/>
|
</SocketProvider>
|
||||||
<Route
|
</ErrorBoundary>
|
||||||
path="/manage/*"
|
}
|
||||||
element={
|
>
|
||||||
<ErrorBoundary>
|
<Route path="*" element={<ManagePage />} />
|
||||||
<SocketProvider bodyshop={bodyshop}>
|
</Route>
|
||||||
|
<Route
|
||||||
|
path="/tech/*"
|
||||||
|
element={
|
||||||
|
<ErrorBoundary>
|
||||||
|
<SocketProvider bodyshop={bodyshop} navigate={navigate} currentUser={currentUser}>
|
||||||
|
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||||
|
</SocketProvider>
|
||||||
|
</ErrorBoundary>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route path="*" element={<TechPageContainer />} />
|
||||||
|
</Route>
|
||||||
|
<Route
|
||||||
|
path="/parts/*"
|
||||||
|
element={
|
||||||
|
<ErrorBoundary>
|
||||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||||
</SocketProvider>
|
</ErrorBoundary>
|
||||||
</ErrorBoundary>
|
}
|
||||||
}
|
>
|
||||||
>
|
<Route path="*" element={<SimplifiedPartsPageContainer />} />
|
||||||
<Route path="*" element={<ManagePage />} />
|
</Route>
|
||||||
</Route>
|
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized} />}>
|
||||||
<Route
|
<Route path="*" element={<DocumentEditorContainer />} />
|
||||||
path="/tech/*"
|
</Route>
|
||||||
element={
|
</Routes>
|
||||||
<ErrorBoundary>
|
</SoundWrapper>
|
||||||
<SocketProvider bodyshop={bodyshop}>
|
|
||||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
|
||||||
</SocketProvider>
|
|
||||||
</ErrorBoundary>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Route path="*" element={<TechPageContainer />} />
|
|
||||||
</Route>
|
|
||||||
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized} />}>
|
|
||||||
<Route path="*" element={<DocumentEditorContainer />} />
|
|
||||||
</Route>
|
|
||||||
</Routes>
|
|
||||||
</NotificationProvider>
|
</NotificationProvider>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,225 @@
|
|||||||
//Global Styles.
|
@use "react-big-calendar/lib/sass/styles" as rbc;
|
||||||
@import "react-big-calendar/lib/sass/styles";
|
|
||||||
|
:root {
|
||||||
|
--table-stripe-bg: #f4f4f4; /* Light mode table stripe */
|
||||||
|
--menu-divider-color: #74695c; /* Light mode menu divider */
|
||||||
|
--menu-submenu-text: rgba(255, 255, 255, 0.65); /* Light mode submenu text */
|
||||||
|
--kanban-column-bg: #ddd; /* Light mode kanban column */
|
||||||
|
--alert-color: blue; /* Light mode alert */
|
||||||
|
--completion-soon-color: rgba(255, 140, 0, 0.8); /* Light mode completion soon */
|
||||||
|
--completion-past-color: rgba(255, 0, 0, 0.8); /* Light mode completion past */
|
||||||
|
--job-line-manual-color: tomato; /* Light mode job line manual */
|
||||||
|
--muted-button-color: lightgray; /* Light mode muted button */
|
||||||
|
--muted-button-hover-color: darkgrey; /* Light mode muted button hover */
|
||||||
|
--table-border-color: #ddd; /* Light mode table border */
|
||||||
|
--table-hover-bg: #f5f5f5; /* Light mode table hover */
|
||||||
|
--popover-bg: #fff; /* Light mode popover background */
|
||||||
|
--error-text: red; /* Light mode error message */
|
||||||
|
--no-jobs-text: #888; /* Light mode no jobs message */
|
||||||
|
--message-yours-bg: #eee; /* Light mode yours message background */
|
||||||
|
--message-mine-bg-start: #00d0ea; /* Light mode mine message gradient start */
|
||||||
|
--message-mine-bg-end: #0085d1; /* Light mode mine message gradient end */
|
||||||
|
--message-mine-text: white; /* Light mode mine message text */
|
||||||
|
--message-mine-tail-bg: white; /* Light mode mine/yours message tail */
|
||||||
|
--system-message-bg: #f5f5f5; /* Light mode system message background */
|
||||||
|
--system-message-text: #555; /* Light mode system message text */
|
||||||
|
--system-label-text: #888; /* Light mode system label/date text */
|
||||||
|
--message-icon-color: whitesmoke; /* Light mode message icon */
|
||||||
|
--eula-card-bg: lightgray; /* Light mode eula card background */
|
||||||
|
--notification-bg: #fff; /* Light mode notification background */
|
||||||
|
--notification-text: rgba(0, 0, 0, 0.85); /* Light mode notification text */
|
||||||
|
--notification-border: #d9d9d9; /* Light mode notification border */
|
||||||
|
--notification-header-bg: #fafafa; /* Light mode notification header background */
|
||||||
|
--notification-header-border: #f0f0f0; /* Light mode notification header border */
|
||||||
|
--notification-header-text: rgba(0, 0, 0, 0.85); /* Light mode notification header text */
|
||||||
|
--notification-toggle-icon: #1677ff; /* Light mode notification toggle icon */
|
||||||
|
--notification-switch-bg: #1677ff; /* Light mode notification switch background */
|
||||||
|
--notification-btn-link: #1677ff; /* Light mode notification link button */
|
||||||
|
--notification-btn-link-hover: #69b1ff; /* Light mode notification link button hover */
|
||||||
|
--notification-btn-link-disabled: rgba(0, 0, 0, 0.25); /* Light mode notification link button disabled */
|
||||||
|
--notification-btn-link-active: #0958d9; /* Light mode notification link button active */
|
||||||
|
--notification-read-bg: #fff; /* Light mode notification read background */
|
||||||
|
--notification-read-text: rgba(0, 0, 0, 0.65); /* Light mode notification read text */
|
||||||
|
--notification-unread-bg: #f5f5f5; /* Light mode notification unread background */
|
||||||
|
--notification-unread-text: rgba(0, 0, 0, 0.85); /* Light mode notification unread text */
|
||||||
|
--notification-item-hover-bg: #fafafa; /* Light mode notification item hover background */
|
||||||
|
--notification-ro-number: #1677ff; /* Light mode notification RO number */
|
||||||
|
--notification-relative-time: rgba(0, 0, 0, 0.45); /* Light mode notification relative time */
|
||||||
|
--alert-bg: #fff1f0; /* Light mode alert background */
|
||||||
|
--alert-text: rgba(0, 0, 0, 0.85); /* Light mode alert text */
|
||||||
|
--alert-border: #ffa39e; /* Light mode alert border */
|
||||||
|
--alert-message: #ff4d4f; /* Light mode alert message */
|
||||||
|
--share-badge-bg: #cccccc; /* Light mode share badge background */
|
||||||
|
--column-header-bg: #d0d0d0; /* Light mode column header background */
|
||||||
|
--footer-bg: #d0d0d0; /* Light mode footer background */
|
||||||
|
--tech-icon-color: orangered; /* Light mode tech icon color */
|
||||||
|
--clone-border-color: #1890ff; /* Light mode clone border color */
|
||||||
|
--event-arrived-bg: rgba(4, 141, 4, 0.4); /* Light mode arrived event background */
|
||||||
|
--event-block-bg: tomato; /* Light mode block event background */
|
||||||
|
--event-selected-bg: slategrey; /* Light mode selected event background */
|
||||||
|
--task-bg: #fff; /* Light mode task center background */
|
||||||
|
--task-text: rgba(0, 0, 0, 0.85); /* Light mode task text */
|
||||||
|
--task-border: #d9d9d9; /* Light mode task border */
|
||||||
|
--task-header-bg: #fafafa; /* Light mode task header background */
|
||||||
|
--task-header-border: #f0f0f0; /* Light mode task header border */
|
||||||
|
--task-section-bg: #f5f5f5; /* Light mode task section background */
|
||||||
|
--task-section-border: #e8e8e8; /* Light mode task section border */
|
||||||
|
--task-row-hover-bg: #f5f5f5; /* Light mode task row hover background */
|
||||||
|
--task-row-border: #f0f0f0; /* Light mode task row border */
|
||||||
|
--task-ro-number: #1677ff; /* Light mode task RO number */
|
||||||
|
--task-due-text: rgba(0, 0, 0, 0.45); /* Light mode task due text */
|
||||||
|
--task-button-bg: #1677ff; /* Light mode task button background */
|
||||||
|
--task-button-hover-bg: #4096ff; /* Light mode task button hover background */
|
||||||
|
--task-button-disabled-bg: #d9d9d9; /* Light mode task button disabled background */
|
||||||
|
--task-button-text: white; /* Light mode task button text */
|
||||||
|
--task-message-text: rgba(0, 0, 0, 0.45); /* Light mode task message text */
|
||||||
|
--mask-bg: rgba(0, 0, 0, 0.05); /* Light mode mask background */
|
||||||
|
--board-text-color: #393939; /* Light mode board text color */
|
||||||
|
--section-bg: #e3e3e3; /* Light mode section background */
|
||||||
|
--detail-text-color: #4d4d4d; /* Light mode detail text color */
|
||||||
|
--card-selected-bg: rgba(128, 128, 128, 0.2); /* Light mode selected card background */
|
||||||
|
--card-stripe-even-bg: #f0f2f5; /* Light mode even card background */
|
||||||
|
--card-stripe-odd-bg: #ffffff; /* Light mode odd card background */
|
||||||
|
--bar-border-color: #f0f2f5; /* Light mode bar border and background */
|
||||||
|
--tag-wrapper-bg: #f0f2f5; /* Light mode tag wrapper background */
|
||||||
|
--tag-wrapper-text: #000; /* Light mode tag wrapper text */
|
||||||
|
--preview-bg: lightgray; /* Light mode preview background */
|
||||||
|
--preview-border-color: #2196F3; /* Light mode preview border color */
|
||||||
|
--event-bg-fallback: #c4c4c4; /* Light mode event background fallback */
|
||||||
|
--card-bg-fallback: #ffffff; /* Light mode card background fallback */
|
||||||
|
--card-text-fallback: black; /* Light mode card text fallback */
|
||||||
|
--table-row-even-bg: rgb(236, 236, 236); /* Light mode table row even background */
|
||||||
|
--status-row-bg-fallback: #ffffff; /* Light mode status row fallback background */
|
||||||
|
--reset-link-color: #0000ff; /* Light mode reset link color */
|
||||||
|
--error-header-text: tomato; /* Light mode error header text */
|
||||||
|
--tooltip-bg: white; /* Light mode tooltip background */
|
||||||
|
--tooltip-border: gray; /* Light mode tooltip border */
|
||||||
|
--tooltip-text-fallback: black; /* Light mode tooltip text fallback */
|
||||||
|
--teams-button-bg: #6264A7; /* Light mode Teams button background */
|
||||||
|
--teams-button-border: #6264A7; /* Light mode Teams button border */
|
||||||
|
--teams-button-text: #FFFFFF; /* Light mode Teams button text and icon */
|
||||||
|
--content-bg: #fff; /* Light mode content background */
|
||||||
|
--legend-bg-fallback: #ffffff; /* Light mode legend background fallback */
|
||||||
|
--tech-content-bg: #fff; /* Light mode tech content background */
|
||||||
|
--today-bg: #ffffff; /* Light mode today background */
|
||||||
|
--today-text: #000000; /* Light mode today text */
|
||||||
|
--off-range-bg: #f8f8f8; /* Light mode off-range background */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--table-stripe-bg: #2a2a2a; /* Dark mode table stripe */
|
||||||
|
--menu-divider-color: #5c5c5c; /* Dark mode menu divider */
|
||||||
|
--menu-submenu-text: rgba(255, 255, 255, 0.85); /* Dark mode submenu text */
|
||||||
|
--kanban-column-bg: #333333; /* Dark mode kanban column */
|
||||||
|
--alert-color: #4da8ff; /* Dark mode alert */
|
||||||
|
--completion-soon-color: #ff8c1a; /* Dark mode completion soon */
|
||||||
|
--completion-past-color: #ff4d4f; /* Dark mode completion past */
|
||||||
|
--job-line-manual-color: #ff6347; /* Dark mode job line manual */
|
||||||
|
--muted-button-color: #666666; /* Dark mode muted button */
|
||||||
|
--muted-button-hover-color: #999999; /* Dark mode muted button hover */
|
||||||
|
--table-border-color: #5c5c5c; /* Dark mode table border */
|
||||||
|
--table-hover-bg: #2a2a2a; /* Dark mode table hover */
|
||||||
|
--popover-bg: #2a2a2a; /* Dark mode popover background */
|
||||||
|
--error-text: #ff4d4f; /* Dark mode error message */
|
||||||
|
--no-jobs-text: #999999; /* Dark mode no jobs message */
|
||||||
|
--message-yours-bg: #2a2a2a; /* Dark mode yours message background */
|
||||||
|
--message-mine-bg-start: #4da8ff; /* Dark mode mine message gradient start */
|
||||||
|
--message-mine-bg-end: #326ade; /* Dark mode mine message gradient end */
|
||||||
|
--message-mine-text: #ffffff; /* Dark mode mine message text */
|
||||||
|
--message-mine-tail-bg: #1f1f1f; /* Dark mode mine/yours message tail */
|
||||||
|
--system-message-bg: #333333; /* Dark mode system message background */
|
||||||
|
--system-message-text: #cccccc; /* Dark mode system message text */
|
||||||
|
--system-label-text: #999999; /* Dark mode system label/date text */
|
||||||
|
--message-icon-color: #cccccc; /* Dark mode message icon */
|
||||||
|
--eula-card-bg: #2a2a2a; /* Dark mode eula card background */
|
||||||
|
--notification-bg: #2a2a2a; /* Dark mode notification background */
|
||||||
|
--notification-text: rgba(255, 255, 255, 0.85); /* Dark mode notification text */
|
||||||
|
--notification-border: #5c5c5c; /* Dark mode notification border */
|
||||||
|
--notification-header-bg: #333333; /* Dark mode notification header background */
|
||||||
|
--notification-header-border: #444444; /* Dark mode notification header border */
|
||||||
|
--notification-header-text: rgba(255, 255, 255, 0.85); /* Dark mode notification header text */
|
||||||
|
--notification-toggle-icon: #4da8ff; /* Dark mode notification toggle icon */
|
||||||
|
--notification-switch-bg: #4da8ff; /* Dark mode notification switch background */
|
||||||
|
--notification-btn-link: #4da8ff; /* Dark mode notification link button */
|
||||||
|
--notification-btn-link-hover: #80c1ff; /* Dark mode notification link button hover */
|
||||||
|
--notification-btn-link-disabled: rgba(255, 255, 255, 0.25); /* Dark mode notification link button disabled */
|
||||||
|
--notification-btn-link-active: #2681ff; /* Dark mode notification link button active */
|
||||||
|
--notification-read-bg: #2a2a2a; /* Dark mode notification read background */
|
||||||
|
--notification-read-text: rgba(255, 255, 255, 0.65); /* Dark mode notification read text */
|
||||||
|
--notification-unread-bg: #333333; /* Dark mode notification unread background */
|
||||||
|
--notification-unread-text: rgba(255, 255, 255, 0.85); /* Dark mode notification unread text */
|
||||||
|
--notification-item-hover-bg: #3a3a3a; /* Dark mode notification item hover background */
|
||||||
|
--notification-ro-number: #4da8ff; /* Dark mode notification RO number */
|
||||||
|
--notification-relative-time: rgba(255, 255, 255, 0.45); /* Dark mode notification relative time */
|
||||||
|
--alert-bg: #3a1a1a; /* Dark mode alert background */
|
||||||
|
--alert-text: rgba(255, 255, 255, 0.85); /* Dark mode alert text */
|
||||||
|
--alert-border: #ff6666; /* Dark mode alert border */
|
||||||
|
--alert-message: #ff6666; /* Dark mode alert message */
|
||||||
|
--share-badge-bg: #666666; /* Dark mode share badge background */
|
||||||
|
--column-header-bg: #333333; /* Dark mode column header background */
|
||||||
|
--footer-bg: #333333; /* Dark mode footer background */
|
||||||
|
--tech-icon-color: #ff4500; /* Dark mode tech icon color */
|
||||||
|
--clone-border-color: #4da8ff; /* Dark mode clone border color */
|
||||||
|
--event-arrived-bg: rgba(4, 141, 4, 0.6); /* Dark mode arrived event background */
|
||||||
|
--event-block-bg: tomato; /* Dark mode block event background */
|
||||||
|
--event-selected-bg: #4a5e6e; /* Dark mode selected event background */
|
||||||
|
--task-bg: #2a2a2a; /* Dark mode task center background */
|
||||||
|
--task-text: rgba(255, 255, 255, 0.85); /* Dark mode task text */
|
||||||
|
--task-border: #5c5c5c; /* Dark mode task border */
|
||||||
|
--task-header-bg: #333333; /* Dark mode task header background */
|
||||||
|
--task-header-border: #444444; /* Dark mode task header border */
|
||||||
|
--task-section-bg: #333333; /* Dark mode task section background */
|
||||||
|
--task-section-border: #444444; /* Dark mode task section border */
|
||||||
|
--task-row-hover-bg: #3a3a3a; /* Dark mode task row hover background */
|
||||||
|
--task-row-border: #444444; /* Dark mode task row border */
|
||||||
|
--task-ro-number: #4da8ff; /* Dark mode task RO number */
|
||||||
|
--task-due-text: rgba(255, 255, 255, 0.45); /* Dark mode task due text */
|
||||||
|
--task-button-bg: #4da8ff; /* Dark mode task button background */
|
||||||
|
--task-button-hover-bg: #80c1ff; /* Dark mode task button hover background */
|
||||||
|
--task-button-disabled-bg: #666666; /* Dark mode task button disabled background */
|
||||||
|
--task-button-text: #ffffff; /* Dark mode task button text */
|
||||||
|
--task-message-text: rgba(255, 255, 255, 0.45); /* Dark mode task message text */
|
||||||
|
--mask-bg: rgba(255, 255, 255, 0.05); /* Dark mode mask background */
|
||||||
|
--board-text-color: #cccccc; /* Dark mode board text color */
|
||||||
|
--section-bg: #333333; /* Dark mode section background */
|
||||||
|
--detail-text-color: #bbbbbb; /* Dark mode detail text color */
|
||||||
|
--card-selected-bg: rgba(255, 255, 255, 0.1); /* Dark mode selected card background */
|
||||||
|
--card-stripe-even-bg: #2a2a2a; /* Dark mode even card background */
|
||||||
|
--card-stripe-odd-bg: #1f1f1f; /* Dark mode odd card background */
|
||||||
|
--bar-border-color: #2a2a2a; /* Dark mode bar border and background */
|
||||||
|
--tag-wrapper-bg: #2a2a2a; /* Dark mode tag wrapper background */
|
||||||
|
--tag-wrapper-text: #cccccc; /* Dark mode tag wrapper text */
|
||||||
|
--preview-bg: #2a2a2a; /* Dark mode preview background */
|
||||||
|
--preview-border-color: #4da8ff; /* Dark mode preview border color */
|
||||||
|
--event-bg-fallback: #262626; /* Dark mode event background fallback */
|
||||||
|
--card-bg-fallback: #2a2a2a; /* Dark mode card background fallback */
|
||||||
|
--card-text-fallback: #cccccc; /* Dark mode card text fallback */
|
||||||
|
--table-row-even-bg: #2a2a2a; /* Dark mode table row even background */
|
||||||
|
--status-row-bg-fallback: #1f1f1f; /* Dark mode status row fallback background */
|
||||||
|
--reset-link-color: #4da8ff; /* Dark mode reset link color */
|
||||||
|
--error-header-text: #ff6347; /* Dark mode error header text */
|
||||||
|
--tooltip-bg: #2a2a2a; /* Dark mode tooltip background */
|
||||||
|
--tooltip-border: #5c5c5c; /* Dark mode tooltip border */
|
||||||
|
--tooltip-text-fallback: #cccccc; /* Dark mode tooltip text fallback */
|
||||||
|
--teams-button-bg: #7b7dc4; /* Dark mode Teams button background */
|
||||||
|
--teams-button-border: #7b7dc4; /* Dark mode Teams button border */
|
||||||
|
--teams-button-text: #ffffff; /* Dark mode Teams button text and icon */
|
||||||
|
--content-bg: #2a2a2a; /* Dark mode content background */
|
||||||
|
--legend-bg-fallback: #2a2a2a; /* Dark mode legend background fallback */
|
||||||
|
--tech-content-bg: #2a2a2a; /* Dark mode tech content background */
|
||||||
|
--today-bg: #4a5e6e; /* Dark mode today background */
|
||||||
|
--today-text: #ffffff; /* Dark mode today text */
|
||||||
|
--off-range-bg: #333333; /* Dark mode off-range background */
|
||||||
|
--svg-background: #FFF; /* Dark mode SVG background */
|
||||||
|
}
|
||||||
|
|
||||||
.ant-menu-item-divider {
|
.ant-menu-item-divider {
|
||||||
border-bottom: 1px solid #74695c !important;
|
border-bottom: 1px solid var(--menu-divider-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Monitor this in dark mode to ensure text visibility
|
||||||
|
.ant-menu-submenu-title {
|
||||||
|
color: var(--menu-submenu-text) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.imex-table-header {
|
.imex-table-header {
|
||||||
@@ -39,7 +256,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ellipses {
|
.ellipses {
|
||||||
display: inline-block; /* for em, a, span, etc (inline by default) */
|
display: inline-block;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
width: calc(95%);
|
width: calc(95%);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -53,22 +270,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scrollbar styles (uncomment if needed, updated for dark mode)
|
||||||
// ::-webkit-scrollbar-track {
|
// ::-webkit-scrollbar-track {
|
||||||
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||||
// border-radius: 0.2rem;
|
// border-radius: 0.2rem;
|
||||||
// background-color: #f5f5f5;
|
// background-color: var(--table-stripe-bg);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// ::-webkit-scrollbar {
|
// ::-webkit-scrollbar {
|
||||||
// width: 0.25rem;
|
// width: 0.25rem;
|
||||||
// max-height: 0.25rem;
|
// max-height: 0.25rem;
|
||||||
// background-color: #f5f5f5;
|
// background-color: var(--table-stripe-bg);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// ::-webkit-scrollbar-thumb {
|
// ::-webkit-scrollbar-thumb {
|
||||||
// border-radius: 0.2rem;
|
// border-radius: 0.2rem;
|
||||||
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||||
// background-color: #188fff;
|
// background-color: var(--alert-color);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
.ant-input-number-input,
|
.ant-input-number-input,
|
||||||
@@ -81,28 +299,27 @@
|
|||||||
|
|
||||||
.production-alert {
|
.production-alert {
|
||||||
animation: alertBlinker 1s linear infinite;
|
animation: alertBlinker 1s linear infinite;
|
||||||
color: blue;
|
color: var(--alert-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes alertBlinker {
|
@keyframes alertBlinker {
|
||||||
50% {
|
50% {
|
||||||
color: red;
|
color: var(--completion-past-color);
|
||||||
opacity: 100;
|
opacity: 100;
|
||||||
//opacity: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.blue {
|
.blue {
|
||||||
color: blue;
|
color: var(--alert-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.production-completion-soon {
|
.production-completion-soon {
|
||||||
color: rgba(255, 140, 0, 0.8);
|
color: var(--completion-soon-color);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.production-completion-past {
|
.production-completion-past {
|
||||||
color: rgba(255, 0, 0, 0.8);
|
color: var(--completion-past-color);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +349,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.react-kanban-column {
|
.react-kanban-column {
|
||||||
background-color: #ddd !important;
|
background-color: var(--kanban-column-bg) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.production-list-table {
|
.production-list-table {
|
||||||
@@ -144,18 +361,18 @@
|
|||||||
.ReactGridGallery_tile-icon-bar {
|
.ReactGridGallery_tile-icon-bar {
|
||||||
div {
|
div {
|
||||||
svg {
|
svg {
|
||||||
fill: #1890ff;
|
fill: var(--alert-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.job-line-manual {
|
.job-line-manual {
|
||||||
color: tomato;
|
color: var(--job-line-manual-color);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
||||||
background-color: #f4f4f4;
|
background-color: var(--table-stripe-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rowWithColor > td {
|
.rowWithColor > td {
|
||||||
@@ -163,13 +380,69 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.muted-button {
|
.muted-button {
|
||||||
color: lightgray;
|
color: var(--muted-button-color);
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 16px; /* Adjust as needed */
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.muted-button:hover {
|
.muted-button:hover {
|
||||||
color: darkgrey;
|
color: var(--muted-button-hover-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notification-alert-unordered-list {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.notification-alert-unordered-list-item {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override react-big-calendar styles for dark mode only
|
||||||
|
[data-theme="dark"] {
|
||||||
|
.car-svg {
|
||||||
|
background-color: var(--svg-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rbc-today {
|
||||||
|
background-color: var(--today-bg);
|
||||||
|
color: var(--today-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rbc-off-range {
|
||||||
|
background-color: var(--off-range-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rbc-day-bg.rbc-today {
|
||||||
|
background-color: var(--today-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dms-equal-height-col {
|
||||||
|
display: flex; // make the Col a flex container
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the direct child is an AntD Card, make it fill the column */
|
||||||
|
.dms-equal-height-col > .ant-card {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional: if you want the card body to fill vertically too */
|
||||||
|
.dms-equal-height-col > .ant-card .ant-card-body {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
//.rbc-time-header-gutter {
|
||||||
|
// padding: 0;
|
||||||
|
//}
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
import React from "react";
|
import { memo } from "react";
|
||||||
import { ProductFruits } from "react-product-fruits";
|
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
import { ProductFruits } from "react-product-fruits";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
const ProductFruitsWrapper = memo(({ currentUser, bodyshop, workspaceCode, isPartsEntry }) => {
|
||||||
|
const featureProps = bodyshop?.features
|
||||||
|
? Object.entries(bodyshop.features).reduce((acc, [key, value]) => {
|
||||||
|
acc[key] = value === true || (typeof value === "string" && dayjs(value).isAfter(dayjs()));
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
: {};
|
||||||
|
|
||||||
const ProductFruitsWrapper = React.memo(({ currentUser, workspaceCode }) => {
|
|
||||||
return (
|
return (
|
||||||
|
!isPartsEntry &&
|
||||||
workspaceCode &&
|
workspaceCode &&
|
||||||
currentUser?.authorized === true &&
|
currentUser?.authorized === true &&
|
||||||
currentUser?.email && (
|
currentUser?.email && (
|
||||||
@@ -14,13 +23,16 @@ const ProductFruitsWrapper = React.memo(({ currentUser, workspaceCode }) => {
|
|||||||
language="en"
|
language="en"
|
||||||
user={{
|
user={{
|
||||||
email: currentUser.email,
|
email: currentUser.email,
|
||||||
username: currentUser.email
|
username: currentUser.email,
|
||||||
|
props: featureProps
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ProductFruitsWrapper.displayName = "ProductFruitsWrapper";
|
||||||
|
|
||||||
export default ProductFruitsWrapper;
|
export default ProductFruitsWrapper;
|
||||||
|
|
||||||
ProductFruitsWrapper.propTypes = {
|
ProductFruitsWrapper.propTypes = {
|
||||||
@@ -28,5 +40,6 @@ ProductFruitsWrapper.propTypes = {
|
|||||||
authorized: PropTypes.bool,
|
authorized: PropTypes.bool,
|
||||||
email: PropTypes.string
|
email: PropTypes.string
|
||||||
}),
|
}),
|
||||||
workspaceCode: PropTypes.string
|
workspaceCode: PropTypes.string,
|
||||||
|
bodyshop: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|||||||
43
client/src/App/SoundWrapper.jsx
Normal file
43
client/src/App/SoundWrapper.jsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useNotification } from "../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import { initNewMessageSound, unlockAudio } from "./../utils/soundManager";
|
||||||
|
import { initSingleTabAudioLeader } from "../utils/singleTabAudioLeader";
|
||||||
|
|
||||||
|
export default function SoundWrapper({ children, bodyshop }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const notification = useNotification();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!bodyshop?.id) return;
|
||||||
|
|
||||||
|
// 1) Init single-tab leader election (only one tab should play sounds), scoped by bodyshopId
|
||||||
|
const cleanupLeader = initSingleTabAudioLeader(bodyshop.id);
|
||||||
|
|
||||||
|
// 2) Initialize base audio
|
||||||
|
initNewMessageSound("https://images.imex.online/app/messageTone.wav", 0.7);
|
||||||
|
|
||||||
|
// 3) Show a one-time prompt when autoplay blocks first play
|
||||||
|
const onNeedsUnlock = () => {
|
||||||
|
notification.info({
|
||||||
|
description: t("audio.manager.description"),
|
||||||
|
duration: 3
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.addEventListener("sound-needs-unlock", onNeedsUnlock);
|
||||||
|
|
||||||
|
// 4) Proactively unlock on first gesture (once per session)
|
||||||
|
const gesture = () => unlockAudio(bodyshop.id);
|
||||||
|
window.addEventListener("click", gesture, { once: true, passive: true });
|
||||||
|
window.addEventListener("touchstart", gesture, { once: true, passive: true });
|
||||||
|
window.addEventListener("keydown", gesture, { once: true });
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cleanupLeader();
|
||||||
|
window.removeEventListener("sound-needs-unlock", onNeedsUnlock);
|
||||||
|
// gesture listeners were added with {once:true}
|
||||||
|
};
|
||||||
|
}, [notification, t, bodyshop?.id]); // include bodyshop.id so this runs when org changes
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
@@ -4,36 +4,42 @@ import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
|||||||
|
|
||||||
const { defaultAlgorithm, darkAlgorithm } = theme;
|
const { defaultAlgorithm, darkAlgorithm } = theme;
|
||||||
|
|
||||||
let isDarkMode = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default theme
|
* Default theme
|
||||||
* @type {{components: {Menu: {itemDividerBorderColor: string}}}}
|
* @type {{components: {Menu: {itemDividerBorderColor: string}}}}
|
||||||
*/
|
*/
|
||||||
const defaultTheme = {
|
const defaultTheme = (isDarkMode) => ({
|
||||||
components: {
|
components: {
|
||||||
Table: {
|
Table: {
|
||||||
rowHoverBg: "#e7f3ff",
|
rowHoverBg: isDarkMode ? "#2a2a2a" : "#e7f3ff",
|
||||||
rowSelectedBg: "#e6f7ff",
|
rowSelectedBg: isDarkMode ? "#333333" : "#e6f7ff",
|
||||||
headerSortHoverBg: "transparent"
|
headerSortHoverBg: "transparent"
|
||||||
},
|
},
|
||||||
Menu: {
|
Menu: {
|
||||||
darkItemHoverBg: "#1890ff",
|
darkItemHoverBg: isDarkMode ? "#004a77" : "#1890ff",
|
||||||
itemHoverBg: "#1890ff",
|
itemHoverBg: isDarkMode ? "#004a77" : "#1890ff",
|
||||||
horizontalItemHoverBg: "#1890ff"
|
horizontalItemHoverBg: isDarkMode ? "#004a77" : "#1890ff"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
token: {
|
token: {
|
||||||
colorPrimary: InstanceRenderMgr({
|
colorPrimary: InstanceRenderMgr(
|
||||||
imex: "#1890ff",
|
{
|
||||||
rome: "#326ade"
|
imex: isDarkMode ? "#4da8ff" : "#1890ff",
|
||||||
}),
|
rome: isDarkMode ? "#5b8ce6" : "#326ade"
|
||||||
colorInfo: InstanceRenderMgr({
|
},
|
||||||
imex: "#1890ff",
|
isDarkMode
|
||||||
rome: "#326ade"
|
),
|
||||||
})
|
colorInfo: InstanceRenderMgr(
|
||||||
|
{
|
||||||
|
imex: isDarkMode ? "#4da8ff" : "#1890ff",
|
||||||
|
rome: isDarkMode ? "#5b8ce6" : "#326ade"
|
||||||
|
},
|
||||||
|
isDarkMode
|
||||||
|
),
|
||||||
|
colorError: isDarkMode ? "#ff4d4f" : "#f5222d",
|
||||||
|
colorBgBase: isDarkMode ? "#1f1f1f" : "#ffffff" // Align with Ant Design dark mode
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Development theme
|
* Development theme
|
||||||
@@ -60,8 +66,9 @@ const prodTheme = {};
|
|||||||
|
|
||||||
const currentTheme = import.meta.env.DEV ? devTheme : prodTheme;
|
const currentTheme = import.meta.env.DEV ? devTheme : prodTheme;
|
||||||
|
|
||||||
const finaltheme = {
|
const getTheme = (isDarkMode) => ({
|
||||||
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
||||||
...defaultsDeep(currentTheme, defaultTheme)
|
...defaultsDeep(currentTheme, defaultTheme)
|
||||||
};
|
});
|
||||||
export default finaltheme;
|
|
||||||
|
export default getTheme;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
function PrivateRoute({ component: Component, isAuthorized, ...rest }) {
|
function PrivateRoute({ isAuthorized }) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Button } from "antd";
|
import { Button } from "antd";
|
||||||
import React from "react";
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Card, Checkbox, Input, Space, Table } from "antd";
|
import { Card, Checkbox, Input, Space, Table } from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@@ -16,12 +16,13 @@ import PayableExportAll from "../payable-export-all-button/payable-export-all-bu
|
|||||||
import PayableExportButton from "../payable-export-button/payable-export-button.component";
|
import PayableExportButton from "../payable-export-button/payable-export-button.component";
|
||||||
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
|
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
|
||||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||||
|
import useLocalStorage from "./../../utils/useLocalStorage";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = () => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [selectedBills, setSelectedBills] = useState([]);
|
const [selectedBills, setSelectedBills] = useState([]);
|
||||||
const [transInProgress, setTransInProgress] = useState(false);
|
const [transInProgress, setTransInProgress] = useState(false);
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useLocalStorage("accounting-payables-table-state", {
|
||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
search: ""
|
search: ""
|
||||||
});
|
});
|
||||||
@@ -181,7 +182,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
|
|||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
onSelectAll: (selected, selectedRows) => setSelectedBills(selectedRows.map((i) => i.id)),
|
onSelectAll: (selected, selectedRows) => setSelectedBills(selectedRows.map((i) => i.id)),
|
||||||
onSelect: (record, selected, selectedRows, nativeEvent) => {
|
onSelect: (record, selected, selectedRows) => {
|
||||||
setSelectedBills(selectedRows.map((i) => i.id));
|
setSelectedBills(selectedRows.map((i) => i.id));
|
||||||
},
|
},
|
||||||
getCheckboxProps: (record) => ({
|
getCheckboxProps: (record) => ({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Card, Input, Space, Table } from "antd";
|
import { Card, Input, Space, Table } from "antd";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@@ -10,6 +10,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
|||||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { exportPageLimit } from "../../utils/config";
|
import { exportPageLimit } from "../../utils/config";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
|
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
|
||||||
@@ -21,7 +22,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = () => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [selectedPayments, setSelectedPayments] = useState([]);
|
const [selectedPayments, setSelectedPayments] = useState([]);
|
||||||
const [transInProgress, setTransInProgress] = useState(false);
|
const [transInProgress, setTransInProgress] = useState(false);
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useLocalStorage("accounting-payments-table-state", {
|
||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
search: ""
|
search: ""
|
||||||
});
|
});
|
||||||
@@ -194,7 +195,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
|
|||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
onSelectAll: (selected, selectedRows) => setSelectedPayments(selectedRows.map((i) => i.id)),
|
onSelectAll: (selected, selectedRows) => setSelectedPayments(selectedRows.map((i) => i.id)),
|
||||||
onSelect: (record, selected, selectedRows, nativeEvent) => {
|
onSelect: (record, selected, selectedRows) => {
|
||||||
setSelectedPayments(selectedRows.map((i) => i.id));
|
setSelectedPayments(selectedRows.map((i) => i.id));
|
||||||
},
|
},
|
||||||
getCheckboxProps: (record) => ({
|
getCheckboxProps: (record) => ({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button, Card, Input, Space, Table } from "antd";
|
import { Button, Card, Input, Space, Table } from "antd";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@@ -10,6 +10,7 @@ import { exportPageLimit } from "../../utils/config";
|
|||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||||
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||||
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
|
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
|
||||||
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
|
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
|
||||||
@@ -20,7 +21,7 @@ import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
|||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = () => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(AccountingReceivablesTableComponent);
|
export default connect(mapStateToProps, mapDispatchToProps)(AccountingReceivablesTableComponent);
|
||||||
@@ -30,7 +31,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
|||||||
const [selectedJobs, setSelectedJobs] = useState([]);
|
const [selectedJobs, setSelectedJobs] = useState([]);
|
||||||
const [transInProgress, setTransInProgress] = useState(false);
|
const [transInProgress, setTransInProgress] = useState(false);
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useLocalStorage("accounting-receivables-table-state", {
|
||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
search: ""
|
search: ""
|
||||||
});
|
});
|
||||||
@@ -141,7 +142,16 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
|||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
/>
|
/>
|
||||||
<Link to={`/manage/jobs/${record.id}/close`}>
|
<Link to={`/manage/jobs/${record.id}/close`}>
|
||||||
<Button>{t("jobs.labels.viewallocations")}</Button>
|
<Button
|
||||||
|
style={{
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
verticalAlign: "middle"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("jobs.labels.viewallocations")}
|
||||||
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
@@ -170,7 +180,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
|||||||
<Card
|
<Card
|
||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && (
|
{!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && !bodyshop.rr_dealerid && (
|
||||||
<>
|
<>
|
||||||
<JobMarkSelectedExported
|
<JobMarkSelectedExported
|
||||||
jobIds={selectedJobs}
|
jobIds={selectedJobs}
|
||||||
@@ -188,7 +198,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && <QboAuthorizeComponent />}
|
{bodyshop.accountingconfig?.qbo && <QboAuthorizeComponent />}
|
||||||
<Input.Search
|
<Input.Search
|
||||||
value={state.search}
|
value={state.search}
|
||||||
onChange={handleSearch}
|
onChange={handleSearch}
|
||||||
@@ -207,7 +217,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
|||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
onSelectAll: (selected, selectedRows) => setSelectedJobs(selectedRows.map((i) => i.id)),
|
onSelectAll: (selected, selectedRows) => setSelectedJobs(selectedRows.map((i) => i.id)),
|
||||||
onSelect: (record, selected, selectedRows, nativeEvent) => {
|
onSelect: (record, selected, selectedRows) => {
|
||||||
setSelectedJobs(selectedRows.map((i) => i.id));
|
setSelectedJobs(selectedRows.map((i) => i.id));
|
||||||
},
|
},
|
||||||
getCheckboxProps: (record) => ({
|
getCheckboxProps: (record) => ({
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`Alert component should render Alert component 1`] = `ShallowWrapper {}`;
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Alert } from "antd";
|
import { Alert } from "antd";
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export default function AlertComponent(props) {
|
export default function AlertComponent(props) {
|
||||||
return <Alert {...props} />;
|
return <Alert {...props} />;
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { shallow } from "enzyme";
|
|
||||||
import React from "react";
|
|
||||||
import Alert from "./alert.component";
|
|
||||||
|
|
||||||
describe("Alert component", () => {
|
|
||||||
let wrapper;
|
|
||||||
beforeEach(() => {
|
|
||||||
const mockProps = {
|
|
||||||
type: "error",
|
|
||||||
message: "Test error message."
|
|
||||||
};
|
|
||||||
|
|
||||||
wrapper = shallow(<Alert {...mockProps} />);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render Alert component", () => {
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
31
client/src/components/alert/alert.component.test.jsx
Normal file
31
client/src/components/alert/alert.component.test.jsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import AlertComponent from "./alert.component";
|
||||||
|
|
||||||
|
describe("AlertComponent", () => {
|
||||||
|
it("renders with default props", () => {
|
||||||
|
render(<AlertComponent message="Default Alert" />);
|
||||||
|
expect(screen.getByText("Default Alert")).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("alert")).toHaveClass("ant-alert");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies type prop correctly", () => {
|
||||||
|
render(<AlertComponent message="Success Alert" type="success" />);
|
||||||
|
const alert = screen.getByRole("alert");
|
||||||
|
expect(screen.getByText("Success Alert")).toBeInTheDocument();
|
||||||
|
expect(alert).toHaveClass("ant-alert-success");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays description when provided", () => {
|
||||||
|
render(<AlertComponent message="Error Alert" description="Something went wrong" type="error" />);
|
||||||
|
expect(screen.getByText("Error Alert")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Something went wrong")).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("alert")).toHaveClass("ant-alert-error");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is closable and shows icon when props are set", () => {
|
||||||
|
render(<AlertComponent message="Warning Alert" type="warning" showIcon closable />);
|
||||||
|
expect(screen.getByText("Warning Alert")).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("button", { name: /close/i })).toBeInTheDocument(); // Close button
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`AllocationsAssignmentComponent component should create an allocation on save 1`] = `ReactWrapper {}`;
|
|
||||||
|
|
||||||
exports[`AllocationsAssignmentComponent component should render AllocationsAssignmentComponent component 1`] = `ReactWrapper {}`;
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Button, InputNumber, Popover, Select } from "antd";
|
import { Button, InputNumber, Popover, Select } from "antd";
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import { mount } from "enzyme";
|
|
||||||
import React from "react";
|
|
||||||
import { MockBodyshop } from "../../utils/TestingHelpers";
|
|
||||||
import { AllocationsAssignmentComponent } from "./allocations-assignment.component";
|
|
||||||
|
|
||||||
describe("AllocationsAssignmentComponent component", () => {
|
|
||||||
let wrapper;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const mockProps = {
|
|
||||||
bodyshop: MockBodyshop,
|
|
||||||
handleAssignment: jest.fn(),
|
|
||||||
assignment: {},
|
|
||||||
setAssignment: jest.fn(),
|
|
||||||
visibilityState: [false, jest.fn()],
|
|
||||||
maxHours: 4
|
|
||||||
};
|
|
||||||
|
|
||||||
wrapper = mount(<AllocationsAssignmentComponent {...mockProps} />);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render AllocationsAssignmentComponent component", () => {
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render a list of employees", () => {
|
|
||||||
const empList = wrapper.find("#employeeSelector");
|
|
||||||
expect(empList.children()).to.have.lengthOf(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should create an allocation on save", () => {
|
|
||||||
wrapper.find("Button").simulate("click");
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import AllocationsAssignmentComponent from "./allocations-assignment.component";
|
import AllocationsAssignmentComponent from "./allocations-assignment.component";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
||||||
@@ -18,7 +18,7 @@ export default function AllocationsAssignmentContainer({ jobLineId, hours, refet
|
|||||||
|
|
||||||
const handleAssignment = () => {
|
const handleAssignment = () => {
|
||||||
insertAllocation({ variables: { alloc: { ...assignment } } })
|
insertAllocation({ variables: { alloc: { ...assignment } } })
|
||||||
.then((r) => {
|
.then(() => {
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("allocations.successes.save")
|
message: t("allocations.successes.save")
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Button, Popover, Select } from "antd";
|
import { Button, Popover, Select } from "antd";
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import AllocationsBulkAssignment from "./allocations-bulk-assignment.component";
|
import AllocationsBulkAssignment from "./allocations-bulk-assignment.component";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
||||||
@@ -24,7 +24,7 @@ export default function AllocationsBulkAssignmentContainer({ jobLines, refetch }
|
|||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
insertAllocation({ variables: { alloc: allocs } }).then((r) => {
|
insertAllocation({ variables: { alloc: allocs } }).then(() => {
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("employees.successes.save")
|
message: t("employees.successes.save")
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import Icon from "@ant-design/icons";
|
import Icon from "@ant-design/icons";
|
||||||
import React from "react";
|
|
||||||
import { MdRemoveCircleOutline } from "react-icons/md";
|
import { MdRemoveCircleOutline } from "react-icons/md";
|
||||||
|
|
||||||
export default function AllocationsLabelComponent({ allocation, handleClick }) {
|
export default function AllocationsLabelComponent({ allocation, handleClick }) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { DELETE_ALLOCATION } from "../../graphql/allocations.queries";
|
import { DELETE_ALLOCATION } from "../../graphql/allocations.queries";
|
||||||
import AllocationsLabelComponent from "./allocations-employee-label.component";
|
import AllocationsLabelComponent from "./allocations-employee-label.component";
|
||||||
@@ -13,13 +12,13 @@ export default function AllocationsLabelContainer({ allocation, refetch }) {
|
|||||||
const handleClick = (e) => {
|
const handleClick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
deleteAllocation({ variables: { id: allocation.id } })
|
deleteAllocation({ variables: { id: allocation.id } })
|
||||||
.then((r) => {
|
.then(() => {
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("allocations.successes.deleted")
|
message: t("allocations.successes.deleted")
|
||||||
});
|
});
|
||||||
if (refetch) refetch();
|
if (refetch) refetch();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(() => {
|
||||||
notification["error"]({ message: t("allocations.errors.deleting") });
|
notification["error"]({ message: t("allocations.errors.deleting") });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Table } from "antd";
|
import { Table } from "antd";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import AuditTrailListComponent from "./audit-trail-list.component";
|
import AuditTrailListComponent from "./audit-trail-list.component";
|
||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Table } from "antd";
|
import { Table } from "antd";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import { List } from "antd";
|
import { List } from "antd";
|
||||||
import Icon from "@ant-design/icons";
|
import Icon from "@ant-design/icons";
|
||||||
import { FaArrowRight } from "react-icons/fa";
|
import { FaArrowRight } from "react-icons/fa";
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Popover, Tag } from "antd";
|
import { Popover, Tag } from "antd";
|
||||||
import React from "react";
|
|
||||||
import Barcode from "react-barcode";
|
import Barcode from "react-barcode";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Checkbox, Form, Skeleton, Typography } from "antd";
|
import { Checkbox, Form, Skeleton, Typography } from "antd";
|
||||||
import React, { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
||||||
import "./bill-cm-returns-table.styles.scss";
|
import "./bill-cm-returns-table.styles.scss";
|
||||||
@@ -33,7 +33,7 @@ export default function BillCmdReturnsTableComponent({ form, returnLoading, retu
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form.List name="outstanding_returns">
|
<Form.List name="outstanding_returns">
|
||||||
{(fields, { add, remove, move }) => {
|
{(fields) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography.Title level={4}>{t("bills.labels.creditsnotreceived")}</Typography.Title>
|
<Typography.Title level={4}>{t("bills.labels.creditsnotreceived")}</Typography.Title>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
td {
|
td {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid var(--table-border-color);
|
||||||
|
|
||||||
.ant-form-item {
|
.ant-form-item {
|
||||||
margin-bottom: 0px !important;
|
margin-bottom: 0px !important;
|
||||||
@@ -14,6 +14,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr:hover {
|
tr:hover {
|
||||||
background-color: #f5f5f5;
|
background-color: var(--table-hover-bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
import { DeleteFilled } from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Popconfirm } from "antd";
|
import { Button, Popconfirm } from "antd";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DELETE_BILL } from "../../graphql/bills.queries";
|
import { DELETE_BILL } from "../../graphql/bills.queries";
|
||||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
@@ -43,7 +43,7 @@ export function BillDeleteButton({ bill, jobid, callback, insertAuditTrail }) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!!!result.errors) {
|
if (!result.errors) {
|
||||||
notification["success"]({ message: t("bills.successes.deleted") });
|
notification["success"]({ message: t("bills.successes.deleted") });
|
||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: jobid,
|
jobid: jobid,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { PageHeader } from "@ant-design/pro-layout";
|
|||||||
import { useMutation, useQuery } from "@apollo/client";
|
import { useMutation, useQuery } from "@apollo/client";
|
||||||
import { Button, Divider, Form, Popconfirm, Space } from "antd";
|
import { Button, Divider, Form, Popconfirm, Space } from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
@@ -10,7 +10,6 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { DELETE_BILL_LINE, INSERT_NEW_BILL_LINES, UPDATE_BILL_LINE } from "../../graphql/bill-lines.queries";
|
import { DELETE_BILL_LINE, INSERT_NEW_BILL_LINES, UPDATE_BILL_LINE } from "../../graphql/bill-lines.queries";
|
||||||
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
@@ -28,13 +27,12 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
|
||||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditcontainer);
|
export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditcontainer);
|
||||||
|
|
||||||
export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail, bodyshop }) {
|
export function BillDetailEditcontainer({ insertAuditTrail, bodyshop }) {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -48,7 +46,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
|||||||
|
|
||||||
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
|
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
|
||||||
variables: { billid: search.billid },
|
variables: { billid: search.billid },
|
||||||
skip: !!!search.billid,
|
skip: !search.billid,
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only"
|
nextFetchPolicy: "network-only"
|
||||||
});
|
});
|
||||||
@@ -71,7 +69,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
|||||||
setUpdateLoading(true);
|
setUpdateLoading(true);
|
||||||
//let adjustmentsToInsert = {};
|
//let adjustmentsToInsert = {};
|
||||||
|
|
||||||
const { billlines, upload, ...bill } = values;
|
const { billlines, ...bill } = values;
|
||||||
const updates = [];
|
const updates = [];
|
||||||
updates.push(
|
updates.push(
|
||||||
update_bill({
|
update_bill({
|
||||||
@@ -98,6 +96,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
|||||||
});
|
});
|
||||||
|
|
||||||
billlines.forEach((billline) => {
|
billlines.forEach((billline) => {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
const { deductedfromlbr, inventories, jobline, original_actual_price, create_ppc, ...il } = billline;
|
const { deductedfromlbr, inventories, jobline, original_actual_price, create_ppc, ...il } = billline;
|
||||||
delete il.__typename;
|
delete il.__typename;
|
||||||
|
|
||||||
@@ -152,8 +151,8 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
|||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
||||||
|
|
||||||
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
const exported = data?.bills_by_pk && data.bills_by_pk.exported;
|
||||||
const isinhouse = data && data.bills_by_pk && data.bills_by_pk.isinhouse;
|
const isinhouse = data?.bills_by_pk && data.bills_by_pk.isinhouse;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -183,8 +182,8 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
|||||||
{t("general.actions.save")}
|
{t("general.actions.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
|
<BillReeportButtonComponent bill={data?.bills_by_pk} />
|
||||||
<BillMarkExportedButton bill={data && data.bills_by_pk} />
|
<BillMarkExportedButton bill={data?.bills_by_pk} />
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -220,11 +219,11 @@ const transformData = (data) => {
|
|||||||
billlines: data.bills_by_pk.billlines.map((i) => {
|
billlines: data.bills_by_pk.billlines.map((i) => {
|
||||||
return {
|
return {
|
||||||
...i,
|
...i,
|
||||||
joblineid: !!i.joblineid ? i.joblineid : "noline",
|
joblineid: i.joblineid ? i.joblineid : "noline",
|
||||||
applicable_taxes: {
|
applicable_taxes: {
|
||||||
federal: (i.applicable_taxes && i.applicable_taxes.federal) || false,
|
federal: i.applicable_taxes?.federal || false,
|
||||||
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
|
state: i.applicable_taxes?.state || false,
|
||||||
local: (i.applicable_taxes && i.applicable_taxes.local) || false
|
local: i.applicable_taxes?.local || false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,26 +1,28 @@
|
|||||||
import { Button, Checkbox, Form, Modal } from "antd";
|
import { Button, Checkbox, Form, Modal } from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({});
|
||||||
bodyshop: selectBodyshop
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
setPartsOrderContext: (context) =>
|
||||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
dispatch(
|
||||||
|
setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: "partsOrder"
|
||||||
|
})
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditReturn);
|
export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditReturn);
|
||||||
|
|
||||||
export function BillDetailEditReturn({ setPartsOrderContext, insertAuditTrail, bodyshop, data, disabled }) {
|
export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -69,13 +71,13 @@ export function BillDetailEditReturn({ setPartsOrderContext, insertAuditTrail, b
|
|||||||
<Modal
|
<Modal
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
title={t("bills.actions.return")}
|
title={t("bills.actions.return")}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
>
|
>
|
||||||
<Form initialValues={data && data.bills_by_pk} onFinish={handleFinish} form={form}>
|
<Form initialValues={data?.bills_by_pk} onFinish={handleFinish} form={form}>
|
||||||
<Form.List name={["billlines"]}>
|
<Form.List name={["billlines"]}>
|
||||||
{(fields, { add, remove, move }) => {
|
{(fields) => {
|
||||||
return (
|
return (
|
||||||
<table style={{ tableLayout: "auto", width: "100%" }}>
|
<table style={{ tableLayout: "auto", width: "100%" }}>
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Drawer, Grid } from "antd";
|
import { Drawer, Grid } from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React from "react";
|
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import BillDetailEditComponent from "./bill-detail-edit-component";
|
import BillDetailEditComponent from "./bill-detail-edit-component";
|
||||||
|
|
||||||
@@ -29,7 +28,7 @@ export default function BillDetailEditcontainer() {
|
|||||||
delete search.billid;
|
delete search.billid;
|
||||||
history({ search: queryString.stringify(search) });
|
history({ search: queryString.stringify(search) });
|
||||||
}}
|
}}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
open={search.billid}
|
open={search.billid}
|
||||||
>
|
>
|
||||||
<BillDetailEditComponent />
|
<BillDetailEditComponent />
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import { useApolloClient, useMutation } from "@apollo/client";
|
|||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Checkbox, Form, Modal, Space } from "antd";
|
import { Button, Checkbox, Form, Modal, Space } from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
import { INSERT_NEW_BILL } from "../../graphql/bills.queries";
|
import { INSERT_NEW_BILL } from "../../graphql/bills.queries";
|
||||||
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
|
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
|
||||||
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
|
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
|
||||||
@@ -24,7 +25,8 @@ import BillFormContainer from "../bill-form/bill-form.container";
|
|||||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||||
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
||||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { handleUpload as handleUploadToImageProxy } from "../documents-upload-imgproxy/documents-upload-imgproxy.utility";
|
||||||
|
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
billEnterModal: selectBillEnterModal,
|
billEnterModal: selectBillEnterModal,
|
||||||
@@ -53,10 +55,10 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
treatments: { Enhanced_Payroll }
|
treatments: { Enhanced_Payroll, Imgproxy }
|
||||||
} = useSplitTreatments({
|
} = useSplitTreatments({
|
||||||
attributes: {},
|
attributes: {},
|
||||||
names: ["Enhanced_Payroll"],
|
names: ["Enhanced_Payroll", "Imgproxy"],
|
||||||
splitKey: bodyshop.imexshopid
|
splitKey: bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,6 +86,8 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
const { upload, location, outstanding_returns, inventory, federal_tax_exempt, ...remainingValues } = values;
|
const { upload, location, outstanding_returns, inventory, federal_tax_exempt, ...remainingValues } = values;
|
||||||
|
|
||||||
let adjustmentsToInsert = {};
|
let adjustmentsToInsert = {};
|
||||||
@@ -101,9 +105,13 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
const {
|
const {
|
||||||
deductedfromlbr,
|
deductedfromlbr,
|
||||||
lbr_adjustment,
|
lbr_adjustment,
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
location: lineLocation,
|
location: lineLocation,
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
part_type,
|
part_type,
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
create_ppc,
|
create_ppc,
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
original_actual_price,
|
original_actual_price,
|
||||||
...restI
|
...restI
|
||||||
} = i;
|
} = i;
|
||||||
@@ -196,7 +204,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
job: { lbr_adjustments: newAdjustments }
|
job: { lbr_adjustments: newAdjustments }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!!jobUpdate.errors) {
|
if (jobUpdate.errors) {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("jobs.errors.saving", {
|
message: t("jobs.errors.saving", {
|
||||||
message: JSON.stringify(jobUpdate.errors)
|
message: JSON.stringify(jobUpdate.errors)
|
||||||
@@ -213,7 +221,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
variables: { partsLineIds: markPolReceived.map((p) => p.id) },
|
variables: { partsLineIds: markPolReceived.map((p) => p.id) },
|
||||||
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"]
|
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"]
|
||||||
});
|
});
|
||||||
if (!!r2.errors) {
|
if (r2.errors) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setEnterAgain(false);
|
setEnterAgain(false);
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
@@ -224,7 +232,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!!r1.errors) {
|
if (r1.errors) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setEnterAgain(false);
|
setEnterAgain(false);
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
@@ -244,7 +252,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
consumedbybillid: billId
|
consumedbybillid: billId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!!r2.errors) {
|
if (r2.errors) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setEnterAgain(false);
|
setEnterAgain(false);
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
@@ -298,20 +306,39 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
upload.forEach((u) => {
|
//Check if using Imgproxy or cloudinary
|
||||||
handleUpload(
|
|
||||||
{ file: u.originFileObj },
|
if (Imgproxy.treatment === "on") {
|
||||||
{
|
upload.forEach((u) => {
|
||||||
bodyshop: bodyshop,
|
handleUploadToImageProxy(
|
||||||
uploaded_by: currentUser.email,
|
{ file: u.originFileObj },
|
||||||
jobId: values.jobid,
|
{
|
||||||
billId: billId,
|
bodyshop: bodyshop,
|
||||||
tagsArray: null,
|
uploaded_by: currentUser.email,
|
||||||
callback: null
|
jobId: values.jobid,
|
||||||
},
|
billId: billId,
|
||||||
notification
|
tagsArray: null,
|
||||||
);
|
callback: null
|
||||||
});
|
},
|
||||||
|
notification
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
upload.forEach((u) => {
|
||||||
|
handleUpload(
|
||||||
|
{ file: u.originFileObj },
|
||||||
|
{
|
||||||
|
bodyshop: bodyshop,
|
||||||
|
uploaded_by: currentUser.email,
|
||||||
|
jobId: values.jobid,
|
||||||
|
billId: billId,
|
||||||
|
tagsArray: null,
|
||||||
|
callback: null
|
||||||
|
},
|
||||||
|
notification
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
///////////////////////////
|
///////////////////////////
|
||||||
@@ -396,7 +423,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
{t("bills.labels.generatepartslabel")}
|
{t("bills.labels.generatepartslabel")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
|
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
|
||||||
<Button loading={loading} onClick={() => form.submit()}>
|
<Button loading={loading} onClick={() => form.submit()} id="save-bill-enter-modal">
|
||||||
{t("general.actions.save")}
|
{t("general.actions.save")}
|
||||||
</Button>
|
</Button>
|
||||||
{billEnterModal.context && billEnterModal.context.id ? null : (
|
{billEnterModal.context && billEnterModal.context.id ? null : (
|
||||||
@@ -406,13 +433,14 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEnterAgain(true);
|
setEnterAgain(true);
|
||||||
}}
|
}}
|
||||||
|
id="save-and-new-bill-enter-modal"
|
||||||
>
|
>
|
||||||
{t("general.actions.saveandnew")}
|
{t("general.actions.saveandnew")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
@@ -423,7 +451,9 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
setEnterAgain(false);
|
setEnterAgain(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BillFormContainer form={form} disableInvNumber={billEnterModal.context.disableInvNumber} />
|
<RbacWrapper action="bills:enter">
|
||||||
|
<BillFormContainer form={form} disableInvNumber={billEnterModal.context.disableInvNumber} />
|
||||||
|
</RbacWrapper>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Form, Input, Table } from "antd";
|
import { Form, Input, Table } from "antd";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import BillFormItemsExtendedFormItem from "./bill-form-lines.extended.formitem.component";
|
import BillFormItemsExtendedFormItem from "./bill-form-lines.extended.formitem.component";
|
||||||
|
|
||||||
export default function BillFormLinesExtended({ lineData, discount, form, responsibilityCenters, disabled }) {
|
export default function BillFormLinesExtended({ lineData, discount, form, responsibilityCenters }) {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const columns = [
|
const columns = [
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import { MinusCircleFilled, PlusCircleFilled, WarningOutlined } from "@ant-design/icons";
|
import { MinusCircleFilled, PlusCircleFilled, WarningOutlined } from "@ant-design/icons";
|
||||||
import { Button, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
import { Button, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -8,11 +7,12 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import CiecaSelect from "../../utils/Ciecaselect";
|
import CiecaSelect from "../../utils/Ciecaselect";
|
||||||
|
import { bodyshopHasDmsKey } from "../../utils/dmsUtils.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = () => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillFormItemsExtendedFormItem);
|
export default connect(mapStateToProps, mapDispatchToProps)(BillFormItemsExtendedFormItem);
|
||||||
@@ -22,7 +22,6 @@ export function BillFormItemsExtendedFormItem({
|
|||||||
bodyshop,
|
bodyshop,
|
||||||
form,
|
form,
|
||||||
record,
|
record,
|
||||||
index,
|
|
||||||
disabled,
|
disabled,
|
||||||
responsibilityCenters,
|
responsibilityCenters,
|
||||||
discount
|
discount
|
||||||
@@ -46,7 +45,7 @@ export function BillFormItemsExtendedFormItem({
|
|||||||
quantity: record.part_qty || 1,
|
quantity: record.part_qty || 1,
|
||||||
actual_price: record.act_price,
|
actual_price: record.act_price,
|
||||||
cost_center: record.part_type
|
cost_center: record.part_type
|
||||||
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
? bodyshopHasDmsKey(bodyshop)
|
||||||
? record.part_type
|
? record.part_type
|
||||||
: responsibilityCenters.defaults && (responsibilityCenters.defaults.costs[record.part_type] || null)
|
: responsibilityCenters.defaults && (responsibilityCenters.defaults.costs[record.part_type] || null)
|
||||||
: null
|
: null
|
||||||
@@ -78,7 +77,7 @@ export function BillFormItemsExtendedFormItem({
|
|||||||
...billlineskeys,
|
...billlineskeys,
|
||||||
[record.id]: {
|
[record.id]: {
|
||||||
...billlineskeys[billlineskeys],
|
...billlineskeys[billlineskeys],
|
||||||
actual_cost: !!billlineskeys[billlineskeys].actual_cost
|
actual_cost: billlineskeys[billlineskeys].actual_cost
|
||||||
? billlineskeys[billlineskeys].actual_cost
|
? billlineskeys[billlineskeys].actual_cost
|
||||||
: Math.round((parseFloat(e.target.value) * (1 - discount) + Number.EPSILON) * 100) / 100
|
: Math.round((parseFloat(e.target.value) * (1 - discount) + Number.EPSILON) * 100) / 100
|
||||||
}
|
}
|
||||||
@@ -93,7 +92,7 @@ export function BillFormItemsExtendedFormItem({
|
|||||||
<Form.Item shouldUpdate>
|
<Form.Item shouldUpdate>
|
||||||
{() => {
|
{() => {
|
||||||
const line = value;
|
const line = value;
|
||||||
if (!!!line) return null;
|
if (!line) return null;
|
||||||
const lineDiscount = (1 - Math.round((line.actual_cost / line.actual_price) * 100) / 100).toPrecision(2);
|
const lineDiscount = (1 - Math.round((line.actual_cost / line.actual_price) * 100) / 100).toPrecision(2);
|
||||||
|
|
||||||
if (lineDiscount - discount === 0) return <div />;
|
if (lineDiscount - discount === 0) return <div />;
|
||||||
@@ -102,7 +101,7 @@ export function BillFormItemsExtendedFormItem({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("billlines.fields.cost_center")} name={["billlineskeys", record.id, "cost_center"]}>
|
<Form.Item label={t("billlines.fields.cost_center")} name={["billlineskeys", record.id, "cost_center"]}>
|
||||||
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
|
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
|
||||||
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
{bodyshopHasDmsKey(bodyshop)
|
||||||
? CiecaSelect(true, false)
|
? CiecaSelect(true, false)
|
||||||
: responsibilityCenters.costs.map((item) => <Select.Option key={item.name}>{item.name}</Select.Option>)}
|
: responsibilityCenters.costs.map((item) => <Select.Option key={item.name}>{item.name}</Select.Option>)}
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Icon, { UploadOutlined } from "@ant-design/icons";
|
|||||||
import { useApolloClient } from "@apollo/client";
|
import { useApolloClient } from "@apollo/client";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload } from "antd";
|
import { Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { MdOpenInNew } from "react-icons/md";
|
import { MdOpenInNew } from "react-icons/md";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -22,11 +22,12 @@ import VendorSearchSelect from "../vendor-search-select/vendor-search-select.com
|
|||||||
import BillFormLines from "./bill-form.lines.component";
|
import BillFormLines from "./bill-form.lines.component";
|
||||||
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
import { bodyshopHasDmsKey } from "../../utils/dmsUtils.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({});
|
const mapDispatchToProps = () => ({});
|
||||||
|
|
||||||
export function BillFormComponent({
|
export function BillFormComponent({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
@@ -254,7 +255,7 @@ export function BillFormComponent({
|
|||||||
required: true
|
required: true
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
},
|
},
|
||||||
({ getFieldValue }) => ({
|
() => ({
|
||||||
validator(rule, value) {
|
validator(rule, value) {
|
||||||
if (ClosingPeriod.treatment === "on" && bodyshop.accountingconfig.ClosingPeriod) {
|
if (ClosingPeriod.treatment === "on" && bodyshop.accountingconfig.ClosingPeriod) {
|
||||||
if (
|
if (
|
||||||
@@ -354,7 +355,7 @@ export function BillFormComponent({
|
|||||||
<Form.Item span={3} label={t("bills.fields.local_tax_rate")} name="local_tax_rate">
|
<Form.Item span={3} label={t("bills.fields.local_tax_rate")} name="local_tax_rate">
|
||||||
<CurrencyInput min={0} />
|
<CurrencyInput min={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
|
{bodyshopHasDmsKey(bodyshop) ? (
|
||||||
<Form.Item span={2} label={t("bills.labels.federal_tax_exempt")} name="federal_tax_exempt">
|
<Form.Item span={2} label={t("bills.labels.federal_tax_exempt")} name="federal_tax_exempt">
|
||||||
<Switch onChange={handleFederalTaxExemptSwitchToggle} />
|
<Switch onChange={handleFederalTaxExemptSwitchToggle} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -374,8 +375,10 @@ export function BillFormComponent({
|
|||||||
let totals;
|
let totals;
|
||||||
if (!!values.total && !!values.billlines && values.billlines.length > 0)
|
if (!!values.total && !!values.billlines && values.billlines.length > 0)
|
||||||
totals = CalculateBillTotal(values);
|
totals = CalculateBillTotal(values);
|
||||||
if (!!totals)
|
if (totals)
|
||||||
return (
|
return (
|
||||||
|
// TODO: Align is not correct
|
||||||
|
// eslint-disable-next-line react/no-unknown-property
|
||||||
<div align="right">
|
<div align="right">
|
||||||
<Space size="large" wrap>
|
<Space size="large" wrap>
|
||||||
<Statistic title={t("bills.labels.subtotal")} value={totals.subtotal.toFormat()} precision={2} />
|
<Statistic title={t("bills.labels.subtotal")} value={totals.subtotal.toFormat()} precision={2} />
|
||||||
@@ -458,7 +461,7 @@ export function BillFormComponent({
|
|||||||
if (Array.isArray(e)) {
|
if (Array.isArray(e)) {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
return e && e.fileList;
|
return e?.fileList;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Upload.Dragger multiple={true} name="logo" beforeUpload={() => false} listType="picture">
|
<Upload.Dragger multiple={true} name="logo" beforeUpload={() => false} listType="picture">
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import React from "react";
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
|
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Checkbox, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip } from "antd";
|
import { Button, Checkbox, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip } from "antd";
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -11,12 +10,13 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
|||||||
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
||||||
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
|
import { bodyshopHasDmsKey } from "../../utils/dmsUtils.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = () => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -27,8 +27,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
discount,
|
discount,
|
||||||
form,
|
form,
|
||||||
responsibilityCenters,
|
responsibilityCenters,
|
||||||
billEdit,
|
billEdit
|
||||||
billid
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
||||||
@@ -47,7 +46,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
title: t("billlines.fields.jobline"),
|
title: t("billlines.fields.jobline"),
|
||||||
dataIndex: "joblineid",
|
dataIndex: "joblineid",
|
||||||
editable: true,
|
editable: true,
|
||||||
width: "20rem",
|
minWidth: "10rem",
|
||||||
formItemProps: (field) => {
|
formItemProps: (field) => {
|
||||||
return {
|
return {
|
||||||
key: `${field.index}joblinename`,
|
key: `${field.index}joblinename`,
|
||||||
@@ -73,9 +72,9 @@ export function BillEnterModalLinesComponent({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
options={lineData}
|
options={lineData}
|
||||||
style={{
|
style={{
|
||||||
width: "20rem",
|
//width: "10rem",
|
||||||
maxWidth: "20rem",
|
// maxWidth: "20rem",
|
||||||
minWidth: "10rem",
|
minWidth: "20rem",
|
||||||
whiteSpace: "normal",
|
whiteSpace: "normal",
|
||||||
height: "auto",
|
height: "auto",
|
||||||
minHeight: "32px" // default height of Ant Design inputs
|
minHeight: "32px" // default height of Ant Design inputs
|
||||||
@@ -92,7 +91,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
actual_price: opt.cost,
|
actual_price: opt.cost,
|
||||||
original_actual_price: opt.cost,
|
original_actual_price: opt.cost,
|
||||||
cost_center: opt.part_type
|
cost_center: opt.part_type
|
||||||
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
? bodyshopHasDmsKey(bodyshop)
|
||||||
? opt.part_type !== "PAE"
|
? opt.part_type !== "PAE"
|
||||||
? opt.part_type
|
? opt.part_type
|
||||||
: null
|
: null
|
||||||
@@ -112,7 +111,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
title: t("billlines.fields.line_desc"),
|
title: t("billlines.fields.line_desc"),
|
||||||
dataIndex: "line_desc",
|
dataIndex: "line_desc",
|
||||||
editable: true,
|
editable: true,
|
||||||
width: "20rem",
|
minWidth: "10rem",
|
||||||
formItemProps: (field) => {
|
formItemProps: (field) => {
|
||||||
return {
|
return {
|
||||||
key: `${field.index}line_desc`,
|
key: `${field.index}line_desc`,
|
||||||
@@ -126,7 +125,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: (record, index) => <Input.TextArea disabled={disabled} autoSize />
|
formInput: () => <Input.TextArea disabled={disabled} autoSize />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.quantity"),
|
title: t("billlines.fields.quantity"),
|
||||||
@@ -158,7 +157,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: (record, index) => <InputNumber precision={0} min={1} disabled={disabled} />
|
formInput: () => <InputNumber precision={0} min={1} disabled={disabled} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.actual_price"),
|
title: t("billlines.fields.actual_price"),
|
||||||
@@ -188,7 +187,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
if (idx === index) {
|
if (idx === index) {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
actual_cost: !!item.actual_cost
|
actual_cost: item.actual_cost
|
||||||
? item.actual_cost
|
? item.actual_cost
|
||||||
: Math.round((parseFloat(e.target.value) * (1 - discount) + Number.EPSILON) * 100) / 100
|
: Math.round((parseFloat(e.target.value) * (1 - discount) + Number.EPSILON) * 100) / 100
|
||||||
};
|
};
|
||||||
@@ -234,7 +233,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
title: t("billlines.fields.actual_cost"),
|
title: t("billlines.fields.actual_cost"),
|
||||||
dataIndex: "actual_cost",
|
dataIndex: "actual_cost",
|
||||||
editable: true,
|
editable: true,
|
||||||
width: "8rem",
|
width: "10rem",
|
||||||
|
|
||||||
formItemProps: (field) => {
|
formItemProps: (field) => {
|
||||||
return {
|
return {
|
||||||
@@ -258,7 +257,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
<Form.Item shouldUpdate noStyle>
|
<Form.Item shouldUpdate noStyle>
|
||||||
{() => {
|
{() => {
|
||||||
const line = getFieldsValue(["billlines"]).billlines[index];
|
const line = getFieldsValue(["billlines"]).billlines[index];
|
||||||
if (!!!line) return null;
|
if (!line) return null;
|
||||||
let lineDiscount = 1 - line.actual_cost / line.actual_price;
|
let lineDiscount = 1 - line.actual_cost / line.actual_price;
|
||||||
if (isNaN(lineDiscount)) lineDiscount = 0;
|
if (isNaN(lineDiscount)) lineDiscount = 0;
|
||||||
return (
|
return (
|
||||||
@@ -322,9 +321,9 @@ export function BillEnterModalLinesComponent({
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: (record, index) => (
|
formInput: () => (
|
||||||
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
|
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
|
||||||
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
{bodyshopHasDmsKey(bodyshop)
|
||||||
? CiecaSelect(true, false)
|
? CiecaSelect(true, false)
|
||||||
: responsibilityCenters.costs.map((item) => <Select.Option key={item.name}>{item.name}</Select.Option>)}
|
: responsibilityCenters.costs.map((item) => <Select.Option key={item.name}>{item.name}</Select.Option>)}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -344,7 +343,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
name: [field.name, "location"]
|
name: [field.name, "location"]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: (record, index) => (
|
formInput: () => (
|
||||||
<Select disabled={disabled}>
|
<Select disabled={disabled}>
|
||||||
{bodyshop.md_parts_locations.map((loc, idx) => (
|
{bodyshop.md_parts_locations.map((loc, idx) => (
|
||||||
<Select.Option key={idx} value={loc}>
|
<Select.Option key={idx} value={loc}>
|
||||||
@@ -359,6 +358,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
title: t("billlines.labels.deductedfromlbr"),
|
title: t("billlines.labels.deductedfromlbr"),
|
||||||
dataIndex: "deductedfromlbr",
|
dataIndex: "deductedfromlbr",
|
||||||
editable: true,
|
editable: true,
|
||||||
|
width: "40px",
|
||||||
formItemProps: (field) => {
|
formItemProps: (field) => {
|
||||||
return {
|
return {
|
||||||
valuePropName: "checked",
|
valuePropName: "checked",
|
||||||
@@ -366,7 +366,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
name: [field.name, "deductedfromlbr"]
|
name: [field.name, "deductedfromlbr"]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
formInput: () => <Switch disabled={disabled} />,
|
||||||
additional: (record, index) => (
|
additional: (record, index) => (
|
||||||
<Form.Item shouldUpdate noStyle style={{ display: "inline-block" }}>
|
<Form.Item shouldUpdate noStyle style={{ display: "inline-block" }}>
|
||||||
{() => {
|
{() => {
|
||||||
@@ -466,7 +466,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
title: t("billlines.fields.federal_tax_applicable"),
|
title: t("billlines.fields.federal_tax_applicable"),
|
||||||
dataIndex: "applicable_taxes.federal",
|
dataIndex: "applicable_taxes.federal",
|
||||||
editable: true,
|
editable: true,
|
||||||
|
width: "40px",
|
||||||
formItemProps: (field) => {
|
formItemProps: (field) => {
|
||||||
return {
|
return {
|
||||||
key: `${field.index}fedtax`,
|
key: `${field.index}fedtax`,
|
||||||
@@ -478,7 +478,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
name: [field.name, "applicable_taxes", "federal"]
|
name: [field.name, "applicable_taxes", "federal"]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: (record, index) => <Switch disabled={disabled} />
|
formInput: () => <Switch disabled={disabled} />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
@@ -487,7 +487,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
title: t("billlines.fields.state_tax_applicable"),
|
title: t("billlines.fields.state_tax_applicable"),
|
||||||
dataIndex: "applicable_taxes.state",
|
dataIndex: "applicable_taxes.state",
|
||||||
editable: true,
|
editable: true,
|
||||||
|
width: "40px",
|
||||||
formItemProps: (field) => {
|
formItemProps: (field) => {
|
||||||
return {
|
return {
|
||||||
key: `${field.index}statetax`,
|
key: `${field.index}statetax`,
|
||||||
@@ -495,7 +495,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
name: [field.name, "applicable_taxes", "state"]
|
name: [field.name, "applicable_taxes", "state"]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: (record, index) => <Switch disabled={disabled} />
|
formInput: () => <Switch disabled={disabled} />
|
||||||
},
|
},
|
||||||
|
|
||||||
...InstanceRenderManager({
|
...InstanceRenderManager({
|
||||||
@@ -505,7 +505,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
title: t("billlines.fields.local_tax_applicable"),
|
title: t("billlines.fields.local_tax_applicable"),
|
||||||
dataIndex: "applicable_taxes.local",
|
dataIndex: "applicable_taxes.local",
|
||||||
editable: true,
|
editable: true,
|
||||||
|
width: "40px",
|
||||||
formItemProps: (field) => {
|
formItemProps: (field) => {
|
||||||
return {
|
return {
|
||||||
key: `${field.index}localtax`,
|
key: `${field.index}localtax`,
|
||||||
@@ -513,7 +513,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
name: [field.name, "applicable_taxes", "local"]
|
name: [field.name, "applicable_taxes", "local"]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: (record, index) => <Switch disabled={disabled} />
|
formInput: () => <Switch disabled={disabled} />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
@@ -575,7 +575,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Table
|
<Table
|
||||||
@@ -612,19 +612,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillEnterModalLinesComponent);
|
export default connect(mapStateToProps, mapDispatchToProps)(BillEnterModalLinesComponent);
|
||||||
|
|
||||||
const EditableCell = ({
|
const EditableCell = ({ dataIndex, record, children, formInput, formItemProps, additional, wrapper, ...restProps }) => {
|
||||||
dataIndex,
|
|
||||||
title,
|
|
||||||
inputType,
|
|
||||||
record,
|
|
||||||
index,
|
|
||||||
children,
|
|
||||||
formInput,
|
|
||||||
formItemProps,
|
|
||||||
additional,
|
|
||||||
wrapper,
|
|
||||||
...restProps
|
|
||||||
}) => {
|
|
||||||
const propsFinal = formItemProps && formItemProps(record);
|
const propsFinal = formItemProps && formItemProps(record);
|
||||||
if (propsFinal && "key" in propsFinal) {
|
if (propsFinal && "key" in propsFinal) {
|
||||||
delete propsFinal.key;
|
delete propsFinal.key;
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ export const CalculateBillTotal = (invoice) => {
|
|||||||
let stateTax = Dinero({ amount: 0 });
|
let stateTax = Dinero({ amount: 0 });
|
||||||
let localTax = Dinero({ amount: 0 });
|
let localTax = Dinero({ amount: 0 });
|
||||||
|
|
||||||
if (!!!billlines) return null;
|
if (!billlines) return null;
|
||||||
|
|
||||||
billlines.forEach((i) => {
|
billlines.forEach((i) => {
|
||||||
if (!!i) {
|
if (i) {
|
||||||
const itemTotal = Dinero({
|
const itemTotal = Dinero({
|
||||||
amount: Math.round((i.actual_cost || 0) * 100)
|
amount: Math.round((i.actual_cost || 0) * 100)
|
||||||
}).multiply(i.quantity || 1);
|
}).multiply(i.quantity || 1);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user