diff --git a/bun.lockb b/bun.lockb index 1b90782..13aaf51 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/docs/servimainUI.excalidraw b/docs/servimainUI.excalidraw index 3f2190b..c1870fe 100644 --- a/docs/servimainUI.excalidraw +++ b/docs/servimainUI.excalidraw @@ -263,8 +263,8 @@ }, { "type": "text", - "version": 859, - "versionNonce": 531548862, + "version": 860, + "versionNonce": 1169460443, "isDeleted": false, "id": "97fLf76gEdKS_GvQ6ALpU", "fillStyle": "solid", @@ -286,7 +286,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007569, + "updated": 1706874741849, "link": null, "locked": false, "fontSize": 36, @@ -459,8 +459,8 @@ }, { "type": "text", - "version": 170, - "versionNonce": 260491298, + "version": 171, + "versionNonce": 122389077, "isDeleted": false, "id": "lIMGkmHcJPDoBybM182aS", "fillStyle": "solid", @@ -480,7 +480,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007569, + "updated": 1706874741849, "link": null, "locked": false, "fontSize": 20, @@ -531,8 +531,8 @@ }, { "type": "text", - "version": 843, - "versionNonce": 658686718, + "version": 844, + "versionNonce": 115620219, "isDeleted": false, "id": "czL3rOtvV4NqzRGs0GVjq", "fillStyle": "cross-hatch", @@ -554,7 +554,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007570, + "updated": 1706874741850, "link": null, "locked": false, "fontSize": 16, @@ -569,8 +569,8 @@ }, { "type": "text", - "version": 613, - "versionNonce": 1971155938, + "version": 614, + "versionNonce": 1941073845, "isDeleted": false, "id": "UyOYTvi_ehfK_7ZeUqKNh", "fillStyle": "cross-hatch", @@ -592,7 +592,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007570, + "updated": 1706874741850, "link": null, "locked": false, "fontSize": 16, @@ -653,8 +653,8 @@ }, { "type": "text", - "version": 585, - "versionNonce": 1110291262, + "version": 586, + "versionNonce": 716526107, "isDeleted": false, "id": "b5cYSaZuXHVCg_wgIGqyf", "fillStyle": "cross-hatch", @@ -677,7 +677,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007570, + "updated": 1706874741852, "link": null, "locked": false, "fontSize": 16, @@ -724,8 +724,8 @@ }, { "type": "text", - "version": 721, - "versionNonce": 120104866, + "version": 722, + "versionNonce": 60076309, "isDeleted": false, "id": "vK17TLw3lSPYKis4oRv10", "fillStyle": "cross-hatch", @@ -748,7 +748,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007571, + "updated": 1706874741852, "link": null, "locked": false, "fontSize": 16, @@ -795,8 +795,8 @@ }, { "type": "text", - "version": 771, - "versionNonce": 1523620734, + "version": 772, + "versionNonce": 840586939, "isDeleted": false, "id": "YaQApvBsokdg3uc1F-yqm", "fillStyle": "cross-hatch", @@ -819,7 +819,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007571, + "updated": 1706874741852, "link": null, "locked": false, "fontSize": 16, @@ -866,8 +866,8 @@ }, { "type": "text", - "version": 896, - "versionNonce": 1823957858, + "version": 897, + "versionNonce": 1416087157, "isDeleted": false, "id": "DpHSN121nXDlx6NRNdrhz", "fillStyle": "cross-hatch", @@ -889,7 +889,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007571, + "updated": 1706874741853, "link": null, "locked": false, "fontSize": 16, @@ -904,8 +904,8 @@ }, { "type": "text", - "version": 575, - "versionNonce": 1735162814, + "version": 576, + "versionNonce": 1538639707, "isDeleted": false, "id": "dD_ckfrdnLxCAC6Xr96mr", "fillStyle": "cross-hatch", @@ -927,7 +927,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007571, + "updated": 1706874741853, "link": null, "locked": false, "fontSize": 16, @@ -2046,8 +2046,8 @@ }, { "type": "text", - "version": 613, - "versionNonce": 1886955298, + "version": 614, + "versionNonce": 955330517, "isDeleted": false, "id": "TWGywr2MmOeMW4gs8000_", "fillStyle": "solid", @@ -2067,7 +2067,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007572, + "updated": 1706874741853, "link": null, "locked": false, "fontSize": 20, @@ -2111,8 +2111,8 @@ }, { "type": "text", - "version": 642, - "versionNonce": 1847743486, + "version": 643, + "versionNonce": 1843012603, "isDeleted": false, "id": "xBzNkZFVCqb_jKgjmeqPR", "fillStyle": "solid", @@ -2132,7 +2132,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007572, + "updated": 1706874741854, "link": null, "locked": false, "fontSize": 36, @@ -2350,8 +2350,8 @@ }, { "type": "text", - "version": 659, - "versionNonce": 987460322, + "version": 660, + "versionNonce": 256273717, "isDeleted": false, "id": "ZROg7dmoN-YH48DAzVkVm", "fillStyle": "solid", @@ -2371,7 +2371,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007573, + "updated": 1706874741854, "link": null, "locked": false, "fontSize": 16, @@ -2596,8 +2596,8 @@ }, { "type": "text", - "version": 787, - "versionNonce": 150152254, + "version": 788, + "versionNonce": 1082780827, "isDeleted": false, "id": "27g6d6zkbFMakk4yjYlMe", "fillStyle": "solid", @@ -2622,7 +2622,7 @@ "type": "arrow" } ], - "updated": 1706857007573, + "updated": 1706874741854, "link": null, "locked": false, "fontSize": 16, @@ -2637,8 +2637,8 @@ }, { "type": "text", - "version": 821, - "versionNonce": 242140834, + "version": 822, + "versionNonce": 1456275093, "isDeleted": false, "id": "2n275KB8dVMGKR5z78AzE", "fillStyle": "solid", @@ -2658,7 +2658,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007573, + "updated": 1706874741854, "link": null, "locked": false, "fontSize": 16, @@ -2673,8 +2673,8 @@ }, { "type": "text", - "version": 858, - "versionNonce": 1774185598, + "version": 859, + "versionNonce": 125280571, "isDeleted": false, "id": "HtmF7uDtsZerEiCY9Wrr2", "fillStyle": "solid", @@ -2694,7 +2694,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007573, + "updated": 1706874741855, "link": null, "locked": false, "fontSize": 16, @@ -2867,8 +2867,8 @@ }, { "type": "text", - "version": 425, - "versionNonce": 65118818, + "version": 426, + "versionNonce": 104489973, "isDeleted": false, "id": "lHG7_KEcLq8Jnlob9oJs-", "fillStyle": "cross-hatch", @@ -2893,7 +2893,7 @@ "type": "arrow" } ], - "updated": 1706857007574, + "updated": 1706874741855, "link": null, "locked": false, "fontSize": 16, @@ -2960,8 +2960,8 @@ }, { "type": "text", - "version": 639, - "versionNonce": 1581886654, + "version": 640, + "versionNonce": 1543881179, "isDeleted": false, "id": "DLa0lPVXghvlzyQUiLnzi", "fillStyle": "cross-hatch", @@ -2986,7 +2986,7 @@ "type": "arrow" } ], - "updated": 1706857007574, + "updated": 1706874741855, "link": null, "locked": false, "fontSize": 16, @@ -3097,8 +3097,8 @@ }, { "type": "text", - "version": 867, - "versionNonce": 1174603298, + "version": 868, + "versionNonce": 563354965, "isDeleted": false, "id": "X6ZL8ZTMvlKjWI3ba1hvI", "fillStyle": "solid", @@ -3118,7 +3118,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007574, + "updated": 1706874741856, "link": null, "locked": false, "fontSize": 16, @@ -3167,8 +3167,8 @@ }, { "type": "text", - "version": 761, - "versionNonce": 1412523262, + "version": 762, + "versionNonce": 122080891, "isDeleted": false, "id": "ZEo99QAbAO47p8--HBfd-", "fillStyle": "solid", @@ -3188,7 +3188,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007574, + "updated": 1706874741856, "link": null, "locked": false, "fontSize": 20, @@ -3203,8 +3203,8 @@ }, { "type": "text", - "version": 808, - "versionNonce": 1304556002, + "version": 809, + "versionNonce": 271559349, "isDeleted": false, "id": "CkCABc8KuyOn0P-BdVmd6", "fillStyle": "solid", @@ -3224,7 +3224,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007575, + "updated": 1706874741856, "link": null, "locked": false, "fontSize": 20, @@ -3239,8 +3239,8 @@ }, { "type": "text", - "version": 1019, - "versionNonce": 1559762238, + "version": 1020, + "versionNonce": 201148187, "isDeleted": false, "id": "myQuI1HagifoucfsqyYKJ", "fillStyle": "solid", @@ -3260,7 +3260,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007575, + "updated": 1706874741856, "link": null, "locked": false, "fontSize": 20, @@ -3275,8 +3275,8 @@ }, { "type": "text", - "version": 815, - "versionNonce": 506291618, + "version": 816, + "versionNonce": 850562069, "isDeleted": false, "id": "-Yf8k6Dj3Lj-jOgr249Af", "fillStyle": "solid", @@ -3296,7 +3296,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007575, + "updated": 1706874741857, "link": null, "locked": false, "fontSize": 20, @@ -3311,8 +3311,8 @@ }, { "type": "text", - "version": 852, - "versionNonce": 838778238, + "version": 853, + "versionNonce": 1889250235, "isDeleted": false, "id": "IoXP3HH9PXErWUBPpkQME", "fillStyle": "solid", @@ -3332,7 +3332,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007575, + "updated": 1706874741857, "link": null, "locked": false, "fontSize": 20, @@ -3347,8 +3347,8 @@ }, { "type": "text", - "version": 1072, - "versionNonce": 7627106, + "version": 1073, + "versionNonce": 957497717, "isDeleted": false, "id": "NFjnbU6And4fMiUyl8kGC", "fillStyle": "solid", @@ -3368,7 +3368,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007575, + "updated": 1706874741857, "link": null, "locked": false, "fontSize": 20, @@ -3383,8 +3383,8 @@ }, { "type": "text", - "version": 1111, - "versionNonce": 948329918, + "version": 1112, + "versionNonce": 1229225051, "isDeleted": false, "id": "87zM95Ly4uuN74flGhu5I", "fillStyle": "solid", @@ -3404,7 +3404,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007576, + "updated": 1706874741857, "link": null, "locked": false, "fontSize": 20, @@ -3851,8 +3851,8 @@ }, { "type": "text", - "version": 781, - "versionNonce": 1072031010, + "version": 782, + "versionNonce": 949242581, "isDeleted": false, "id": "bFWjBJqnUAi5N3n2ZWux_", "fillStyle": "solid", @@ -3872,7 +3872,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007576, + "updated": 1706874741858, "link": null, "locked": false, "fontSize": 20, @@ -4008,8 +4008,8 @@ }, { "type": "text", - "version": 1092, - "versionNonce": 95417854, + "version": 1093, + "versionNonce": 30030075, "isDeleted": false, "id": "AngCsxgQFZ8FlJDffEu--", "fillStyle": "solid", @@ -4029,7 +4029,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007576, + "updated": 1706874741858, "link": null, "locked": false, "fontSize": 36, @@ -4044,8 +4044,8 @@ }, { "type": "text", - "version": 1123, - "versionNonce": 1710289122, + "version": 1124, + "versionNonce": 30287925, "isDeleted": false, "id": "y_U_FPL_9JR3gVTy88NCf", "fillStyle": "solid", @@ -4065,7 +4065,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007577, + "updated": 1706874741858, "link": null, "locked": false, "fontSize": 28, @@ -4080,8 +4080,8 @@ }, { "type": "text", - "version": 1158, - "versionNonce": 1652327998, + "version": 1159, + "versionNonce": 1739547035, "isDeleted": false, "id": "mOf1HdeIlUxFfE7a1VcGR", "fillStyle": "solid", @@ -4101,7 +4101,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007577, + "updated": 1706874741859, "link": null, "locked": false, "fontSize": 28, @@ -4116,8 +4116,8 @@ }, { "type": "text", - "version": 1090, - "versionNonce": 1982104738, + "version": 1091, + "versionNonce": 916806037, "isDeleted": false, "id": "DR4VwW8oFIQ4bTi23EuNf", "fillStyle": "solid", @@ -4137,7 +4137,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007577, + "updated": 1706874741859, "link": null, "locked": false, "fontSize": 28, @@ -4152,8 +4152,8 @@ }, { "type": "text", - "version": 771, - "versionNonce": 1338838654, + "version": 772, + "versionNonce": 1948677691, "isDeleted": false, "id": "J9irHVDB_SLk4-2nUzUsU", "fillStyle": "solid", @@ -4173,7 +4173,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007578, + "updated": 1706874741859, "link": null, "locked": false, "fontSize": 16, @@ -4188,8 +4188,8 @@ }, { "type": "text", - "version": 795, - "versionNonce": 707764322, + "version": 796, + "versionNonce": 542072565, "isDeleted": false, "id": "c19gjwN8b8L3I0q4go5Bf", "fillStyle": "cross-hatch", @@ -4209,7 +4209,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007578, + "updated": 1706874741859, "link": null, "locked": false, "fontSize": 16, @@ -4224,8 +4224,8 @@ }, { "type": "text", - "version": 826, - "versionNonce": 174231230, + "version": 827, + "versionNonce": 1444017883, "isDeleted": false, "id": "oW9r6Oj1KNFEDfDrTobE0", "fillStyle": "cross-hatch", @@ -4245,7 +4245,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007578, + "updated": 1706874741860, "link": null, "locked": false, "fontSize": 16, @@ -4260,8 +4260,8 @@ }, { "type": "text", - "version": 853, - "versionNonce": 769737762, + "version": 854, + "versionNonce": 1162983509, "isDeleted": false, "id": "3FPR-httcvZJQOpdp5nlq", "fillStyle": "cross-hatch", @@ -4281,7 +4281,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007578, + "updated": 1706874741860, "link": null, "locked": false, "fontSize": 16, @@ -4296,8 +4296,8 @@ }, { "type": "text", - "version": 828, - "versionNonce": 249431806, + "version": 829, + "versionNonce": 1006920571, "isDeleted": false, "id": "Q5UuTLzxJDwFmgRxTm2kC", "fillStyle": "cross-hatch", @@ -4317,7 +4317,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007578, + "updated": 1706874741860, "link": null, "locked": false, "fontSize": 16, @@ -4332,8 +4332,8 @@ }, { "type": "text", - "version": 575, - "versionNonce": 1529376738, + "version": 576, + "versionNonce": 1065477557, "isDeleted": false, "id": "Wz3mVdVr-D1sldEkuMc-C", "fillStyle": "solid", @@ -4353,7 +4353,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007579, + "updated": 1706874741860, "link": null, "locked": false, "fontSize": 16, @@ -4368,8 +4368,8 @@ }, { "type": "text", - "version": 703, - "versionNonce": 499152702, + "version": 704, + "versionNonce": 1581267995, "isDeleted": false, "id": "5BRLdUcImdXd-sAkul1NW", "fillStyle": "solid", @@ -4389,7 +4389,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007579, + "updated": 1706874741860, "link": null, "locked": false, "fontSize": 16, @@ -4404,8 +4404,8 @@ }, { "type": "text", - "version": 735, - "versionNonce": 1981031330, + "version": 736, + "versionNonce": 1376085781, "isDeleted": false, "id": "NIRZl827FKmclhhKsmfJG", "fillStyle": "solid", @@ -4425,7 +4425,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007579, + "updated": 1706874741861, "link": null, "locked": false, "fontSize": 16, @@ -4440,8 +4440,8 @@ }, { "type": "text", - "version": 767, - "versionNonce": 1063968638, + "version": 768, + "versionNonce": 1220018363, "isDeleted": false, "id": "nwfWQopAIVcO11jOf_vRF", "fillStyle": "solid", @@ -4461,7 +4461,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007579, + "updated": 1706874741861, "link": null, "locked": false, "fontSize": 16, @@ -4592,8 +4592,8 @@ }, { "type": "text", - "version": 702, - "versionNonce": 1758682978, + "version": 703, + "versionNonce": 1062321269, "isDeleted": false, "id": "rzBAAYkybbEjgW-X1nldy", "fillStyle": "solid", @@ -4613,7 +4613,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007580, + "updated": 1706874741861, "link": null, "locked": false, "fontSize": 28, @@ -4628,8 +4628,8 @@ }, { "type": "text", - "version": 718, - "versionNonce": 1209941950, + "version": 719, + "versionNonce": 1695428955, "isDeleted": false, "id": "oxovXJjAobce88xbeLU8R", "fillStyle": "solid", @@ -4651,7 +4651,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007580, + "updated": 1706874741861, "link": null, "locked": false, "fontSize": 28, @@ -4666,8 +4666,8 @@ }, { "type": "text", - "version": 707, - "versionNonce": 2142577442, + "version": 708, + "versionNonce": 1596745173, "isDeleted": false, "id": "bsapaFzL8TDKJ9MbrtJEa", "fillStyle": "solid", @@ -4689,7 +4689,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007580, + "updated": 1706874741862, "link": null, "locked": false, "fontSize": 28, @@ -4704,8 +4704,8 @@ }, { "type": "text", - "version": 754, - "versionNonce": 1543215102, + "version": 755, + "versionNonce": 1150635515, "isDeleted": false, "id": "98b_U589sHYl_q_k7dRbc", "fillStyle": "solid", @@ -4729,7 +4729,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007580, + "updated": 1706874741862, "link": null, "locked": false, "fontSize": 28, @@ -4744,8 +4744,8 @@ }, { "type": "text", - "version": 452, - "versionNonce": 623406818, + "version": 453, + "versionNonce": 1957356341, "isDeleted": false, "id": "PMAVESvlWDWmWj93013fH", "fillStyle": "solid", @@ -4765,7 +4765,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007581, + "updated": 1706874741862, "link": null, "locked": false, "fontSize": 28, @@ -4838,8 +4838,8 @@ }, { "type": "text", - "version": 98, - "versionNonce": 1929635902, + "version": 99, + "versionNonce": 709875355, "isDeleted": false, "id": "IQb1FgS9Z9XPkqAlLeR35", "fillStyle": "solid", @@ -4859,7 +4859,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007581, + "updated": 1706874741863, "link": null, "locked": false, "fontSize": 28, @@ -4874,8 +4874,8 @@ }, { "type": "text", - "version": 138, - "versionNonce": 498877090, + "version": 139, + "versionNonce": 1144173717, "isDeleted": false, "id": "uoXtQ_5jmNBJ6aOSh8TG7", "fillStyle": "solid", @@ -4895,7 +4895,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007581, + "updated": 1706874741863, "link": null, "locked": false, "fontSize": 28, @@ -4910,8 +4910,8 @@ }, { "type": "text", - "version": 122, - "versionNonce": 1561866366, + "version": 123, + "versionNonce": 1425034043, "isDeleted": false, "id": "YKlce3NhSAO2cOpVFM--B", "fillStyle": "solid", @@ -4931,7 +4931,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007583, + "updated": 1706874741863, "link": null, "locked": false, "fontSize": 16, @@ -4946,8 +4946,8 @@ }, { "type": "text", - "version": 163, - "versionNonce": 1142922850, + "version": 164, + "versionNonce": 1769560565, "isDeleted": false, "id": "j_Vkhf5mLxVsrltY5uihc", "fillStyle": "solid", @@ -4967,7 +4967,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007583, + "updated": 1706874741863, "link": null, "locked": false, "fontSize": 16, @@ -4982,8 +4982,8 @@ }, { "type": "text", - "version": 795, - "versionNonce": 1173975230, + "version": 796, + "versionNonce": 148450267, "isDeleted": false, "id": "u50Phb99MTDiTPuc37JeQ", "fillStyle": "solid", @@ -5003,7 +5003,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007583, + "updated": 1706874741864, "link": null, "locked": false, "fontSize": 28, @@ -5142,8 +5142,8 @@ }, { "type": "text", - "version": 775, - "versionNonce": 1159823906, + "version": 776, + "versionNonce": 1837388629, "isDeleted": false, "id": "wNiAAV6zgGuZTvY5864FZ", "fillStyle": "solid", @@ -5163,7 +5163,7 @@ "frameId": null, "roundness": null, "boundElements": [], - "updated": 1706857007584, + "updated": 1706874741864, "link": null, "locked": false, "fontSize": 36, @@ -5371,8 +5371,8 @@ }, { "type": "text", - "version": 112, - "versionNonce": 961063166, + "version": 113, + "versionNonce": 1511848059, "isDeleted": false, "id": "vR3E88Ypr64zTJDIqP-Pw", "fillStyle": "solid", @@ -5397,7 +5397,7 @@ "type": "arrow" } ], - "updated": 1706857007584, + "updated": 1706874741864, "link": null, "locked": false, "fontSize": 16, diff --git a/package.json b/package.json index d11567e..2da940e 100644 --- a/package.json +++ b/package.json @@ -3,17 +3,12 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@hookform/resolvers": "^3.3.2", - "@radix-ui/react-popover": "^1.0.7", - "@radix-ui/react-tooltip": "^1.0.7", "@tanstack/react-table": "^8.10.7", "axios": "^1.6.1", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", "daisyui": "latest", "echarts": "^5.4.3", "i18next": "^23.7.18", "i18next-browser-languagedetector": "^7.2.0", - "lucide-react": "^0.320.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.48.2", diff --git a/src/App.tsx b/src/App.tsx index 3d9b0b0..7201d4a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,3 @@ -// TODO: - [x] Rewrite new switch generating function based on custom routes object -// TODO: - [x] Make condition for base path in Router, for "/" in main.base_path don't add to Router -// TODO: - [x] Rewrite DevControlPanel to use custom routes object - import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { Outlet, useLocation, useNavigate } from "react-router-dom"; @@ -15,16 +11,28 @@ function App() { const navigate = useNavigate(); const location = useLocation(); - // Effect to change language based on state useEffect(() => { + // Load language from local storage i18n.changeLanguage(language); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [language]); - // Redirect to login page if not logged in + + // Here was a problem with detecting updates of isLoggedIn state. Solution was to manually trigger events on storage change when storage was updated. + // Refer to useLocalStorage hook in ./hooks/useLocalStorage.ts at line 39 for more details useEffect(() => { - if (!isLoggedIn && location.pathname !== "/login") { + // Redirections based on login state + if (!isLoggedIn) { + console.log("NOT LOGGED IN"); navigate("/login"); } - }, [isLoggedIn]); + if (isLoggedIn && location.pathname === "/login") { + console.log("LOGGED IN"); + navigate("/orders"); + } else { + console.log(`ALREADY ${!isLoggedIn ? "NOT " : ""}LOGGED IN`); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoggedIn, navigate]); // Conditional rendering based on base path return ; diff --git a/src/components/atoms/MenuContainer.tsx b/src/components/atoms/MenuContainer.tsx index 0056a3c..9f9095a 100644 --- a/src/components/atoms/MenuContainer.tsx +++ b/src/components/atoms/MenuContainer.tsx @@ -19,10 +19,10 @@ const MenuContainer: React.FC = ({ }) => { const chooseContainerPosition = useCallback(() => { const commonClasses = `z-50 menu bg-base-300 rounded-box gap-4 absolute ${horizontal ? "menu-horizontal" : ""} `; - if (position === "top-left") return commonClasses + "top-4 left-4"; - if (position === "top-right") return commonClasses + "top-4 right-4"; - if (position === "bottom-left") return commonClasses + "bottom-4 left-4"; - if (position === "bottom-right") return commonClasses + "bottom-4 right-4"; + if (position === "top-left") return commonClasses + "top-2 left-2"; + if (position === "top-right") return commonClasses + "top-2 right-2"; + if (position === "bottom-left") return commonClasses + "bottom-2 left-2"; + if (position === "bottom-right") return commonClasses + "bottom-2 right-2"; }, [horizontal, position]); const overlayButtonContainer = () => { diff --git a/src/components/organisms/LogoutButton.tsx b/src/components/organisms/LogoutButton.tsx index 35a3595..2a3afb3 100644 --- a/src/components/organisms/LogoutButton.tsx +++ b/src/components/organisms/LogoutButton.tsx @@ -1,21 +1,21 @@ import { useTranslation } from "react-i18next"; import { FiLogOut } from "react-icons/fi"; -import { useNavigate } from "react-router-dom"; import useLocalStorage from "../../hooks/useLocalStorage"; import RoundButtonBase from "../atoms/RoundButtonBase"; import IconBase from "../molecules/IconBase"; const LogoutButton = () => { const [, setIsLoggedIn] = useLocalStorage("isLoggedIn", false); - const navigate = useNavigate(); const { t } = useTranslation(); + + const handleLogout = () => { + setIsLoggedIn(false); + }; + return ( { - setIsLoggedIn(false); - navigate("/login"); - }, + onClick: handleLogout, }} tooltipText={t("button.action.logout")} > diff --git a/src/components/templates/HomePageTemplate.tsx b/src/components/templates/HomePageTemplate.tsx deleted file mode 100644 index cf7cfeb..0000000 --- a/src/components/templates/HomePageTemplate.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useTranslation } from "react-i18next"; -import MenuContainer from "../atoms/MenuContainer"; -import LogoutButton from "../organisms/LogoutButton"; -import ThemeButton from "../organisms/ThemeButton"; -import LanguageButton from "../organisms/LanguageButton"; - -export interface HomePageTemplateProps { - buttonLabel: string; - buttonOnClick: () => void; - value: number; -} - -const HomePageTemplate = (props: HomePageTemplateProps) => { - const { t } = useTranslation(); - return ( -
- - - - - -
-

HomePage

-
-
- {t("homePage.counter")}: {props.value} -
- -
-
-
- ); -}; - -export default HomePageTemplate; diff --git a/src/components/templates/LoginPageTemplate.tsx b/src/components/templates/LoginPageTemplate.tsx index 0356009..fdb6020 100644 --- a/src/components/templates/LoginPageTemplate.tsx +++ b/src/components/templates/LoginPageTemplate.tsx @@ -51,10 +51,10 @@ const LoginPageTemplate = ({
diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts index e3a6e72..1a74f74 100644 --- a/src/hooks/useLocalStorage.ts +++ b/src/hooks/useLocalStorage.ts @@ -1,26 +1,18 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; /** - * Custom hook for persisting state in local storage. + * useLocalStorage is a custom hook that allows you to work with LocalStorage like useState. + * It also syncs data between tabs in real-time. * - * This hook works similarly to the standard `useState` hook but also stores the state in local storage, - * allowing the state to persist across browser sessions. The state is initialized from local storage - * if it exists; otherwise, it falls back to the provided initial value. + * @param {string} key - A unique identifier for the localStorage entry. + * @param {T} initialValue - The initial value for the localStorage entry. * - * @template T The type of the value to be stored. - * @param {string} key The key under which the value is stored in local storage. - * @param {T} initialValue The initial value to be used if there is no item in local storage with the given key. - * @returns {[T, (value: T | ((val: T) => T)) => void]} A tuple containing: - * - `storedValue`: the current value stored in local storage. - * - `setValue`: a function to set the value, which updates both the local state and local storage. - * - * @example - * const [name, setName] = useLocalStorage("name", "Initial Name"); - * // Use setName to update the name and it will be stored in local storage. + * @returns {Array} An array where the first item is the stored value and the second item is a setter function. */ const useLocalStorage = ( key: string, initialValue: T, ): [T, (value: T) => void] => { + // Store the initial value in localStorage or return the current value const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); @@ -31,16 +23,45 @@ const useLocalStorage = ( } }); + // Define a setter function for the storedValue const setValue = (value: T) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); + + // Store the new value in localStorage window.localStorage.setItem(key, JSON.stringify(valueToStore)); + + // Manually trigger a storage event + const storageEvent = new StorageEvent("storage", { + key: key, + oldValue: JSON.stringify(storedValue), + newValue: JSON.stringify(valueToStore), + storageArea: localStorage, + }); + window.dispatchEvent(storageEvent); } catch (error) { console.log(error); } }; + // Add an event listener for the storage event + useEffect(() => { + const handleStorageChange = (e: StorageEvent) => { + if (e.key === key && e.newValue !== null) { + setStoredValue(JSON.parse(e.newValue)); + } + }; + + window.addEventListener("storage", handleStorageChange); + + // Cleanup function + return () => { + window.removeEventListener("storage", handleStorageChange); + }; + }, [key]); + + // Return the stored value and the setter function return [storedValue, setValue]; }; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx deleted file mode 100644 index 7b10178..0000000 --- a/src/pages/HomePage.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { Outlet } from "react-router-dom"; -import HomePageTemplate from "../components/templates/HomePageTemplate"; - -const HomePage = () => { - const [counter, setCounter] = useState(0); - const { t } = useTranslation(); - - return ( - <> - { - if (counter >= 10) { - setCounter(0); - return; - } else setCounter(counter + 1); - }} - value={counter} - /> - - - ); -}; - -export default HomePage; diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 0b35ead..87e2903 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -7,11 +7,11 @@ const LoginPage = () => { const navigate = useNavigate(); const handleLogin = () => { setIsLoggedIn(true); - navigate("/"); }; const onClickGetStarted = () => { navigate("/more"); }; + return ( { + return ( +
+ + + + + + +
+ ); +}; + +export default MainOverlayPage; diff --git a/src/pages/OrdersPage.tsx b/src/pages/OrdersPage.tsx new file mode 100644 index 0000000..37f4e26 --- /dev/null +++ b/src/pages/OrdersPage.tsx @@ -0,0 +1,12 @@ +import { useEffect } from "react"; +import { useLoaderData } from "react-router-dom"; + +const OrdersPage = () => { + const users = useLoaderData(); + useEffect(() => { + console.log("OrdersPage -> useEffect"); + }); + return
{JSON.stringify(users)}
; +}; + +export default OrdersPage; diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index 9ccbb1e..020a906 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -1,8 +1,9 @@ -import HomePage from "../pages/HomePage"; -import LoginPage from "../pages/LoginPage"; import { RouteObject } from "react-router-dom"; -import NotFound_404 from "../pages/NotFound_404"; import App from "../App"; +import LoginPage from "../pages/LoginPage"; +import MainOverlayPage from "../pages/MainOverlayPage"; +import NotFound_404 from "../pages/NotFound_404"; +import OrdersPage from "../pages/OrdersPage"; // ----- ROUTES CONFIGURATION ----- // Configuration of the application's route structure @@ -12,11 +13,18 @@ const routes: RouteObject[] = [ element: , children: [ { - path: "/", - element: , + element: , + children: [ + { + path: "/orders", + element: , + loader: () => + fetch("https://dummyjson.com/users").then((res) => res.json()), + }, + ], }, { - path: "login", + path: "/login", element: , }, ],