diff --git a/web/ui/dashboard-react/.prettierrc b/web/ui/dashboard-react/.prettierrc index 0dad762c18..140edc0de5 100644 --- a/web/ui/dashboard-react/.prettierrc +++ b/web/ui/dashboard-react/.prettierrc @@ -1,7 +1,7 @@ { "singleQuote": true, - "tabWidth": 4, - "printWidth": 100, + "tabWidth": 2, + "printWidth": 80, "semi": true, "endOfLine": "auto", "arrowParens": "avoid", diff --git a/web/ui/dashboard-react/src/assets/.gitkeep b/web/ui/dashboard-react/assets/.gitkeep similarity index 100% rename from web/ui/dashboard-react/src/assets/.gitkeep rename to web/ui/dashboard-react/assets/.gitkeep diff --git a/web/ui/dashboard-react/src/assets/css/fonts.css b/web/ui/dashboard-react/assets/css/fonts.css similarity index 76% rename from web/ui/dashboard-react/src/assets/css/fonts.css rename to web/ui/dashboard-react/assets/css/fonts.css index bf1606b916..e2758e9a2f 100644 --- a/web/ui/dashboard-react/src/assets/css/fonts.css +++ b/web/ui/dashboard-react/assets/css/fonts.css @@ -4,8 +4,10 @@ font-style: normal; font-weight: 100; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) + format('woff2'); + unicode-range: + U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* cyrillic */ @font-face { @@ -13,7 +15,8 @@ font-style: normal; font-weight: 100; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -22,7 +25,8 @@ font-style: normal; font-weight: 100; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -31,7 +35,8 @@ font-style: normal; font-weight: 100; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -40,10 +45,11 @@ font-style: normal; font-weight: 100; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, - U+20AB; + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, + U+01AF-01B0, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { @@ -51,10 +57,11 @@ font-style: normal; font-weight: 100; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, - U+A720-A7FF; + U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, + U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { @@ -62,10 +69,12 @@ font-style: normal; font-weight: 100; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) + format('woff2'); unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, + U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, + U+FFFD; } /* cyrillic-ext */ @font-face { @@ -73,8 +82,10 @@ font-style: normal; font-weight: 200; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) + format('woff2'); + unicode-range: + U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* cyrillic */ @font-face { @@ -82,7 +93,8 @@ font-style: normal; font-weight: 200; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -91,7 +103,8 @@ font-style: normal; font-weight: 200; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -100,7 +113,8 @@ font-style: normal; font-weight: 200; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -109,10 +123,11 @@ font-style: normal; font-weight: 200; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, - U+20AB; + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, + U+01AF-01B0, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { @@ -120,10 +135,11 @@ font-style: normal; font-weight: 200; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, - U+A720-A7FF; + U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, + U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { @@ -131,10 +147,12 @@ font-style: normal; font-weight: 200; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) + format('woff2'); unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, + U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, + U+FFFD; } /* cyrillic-ext */ @font-face { @@ -142,8 +160,10 @@ font-style: normal; font-weight: 300; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) + format('woff2'); + unicode-range: + U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* cyrillic */ @font-face { @@ -151,7 +171,8 @@ font-style: normal; font-weight: 300; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -160,7 +181,8 @@ font-style: normal; font-weight: 300; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -169,7 +191,8 @@ font-style: normal; font-weight: 300; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -178,10 +201,11 @@ font-style: normal; font-weight: 300; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, - U+20AB; + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, + U+01AF-01B0, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { @@ -189,10 +213,11 @@ font-style: normal; font-weight: 300; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, - U+A720-A7FF; + U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, + U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { @@ -200,10 +225,12 @@ font-style: normal; font-weight: 300; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) + format('woff2'); unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, + U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, + U+FFFD; } /* cyrillic-ext */ @font-face { @@ -211,8 +238,10 @@ font-style: normal; font-weight: 400; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) + format('woff2'); + unicode-range: + U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* cyrillic */ @font-face { @@ -220,7 +249,8 @@ font-style: normal; font-weight: 400; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -229,7 +259,8 @@ font-style: normal; font-weight: 400; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -238,7 +269,8 @@ font-style: normal; font-weight: 400; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -247,10 +279,11 @@ font-style: normal; font-weight: 400; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, - U+20AB; + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, + U+01AF-01B0, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { @@ -258,10 +291,11 @@ font-style: normal; font-weight: 400; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, - U+A720-A7FF; + U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, + U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { @@ -269,10 +303,12 @@ font-style: normal; font-weight: 400; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) + format('woff2'); unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, + U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, + U+FFFD; } /* cyrillic-ext */ @font-face { @@ -280,8 +316,10 @@ font-style: normal; font-weight: 500; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) + format('woff2'); + unicode-range: + U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* cyrillic */ @font-face { @@ -289,7 +327,8 @@ font-style: normal; font-weight: 500; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -298,7 +337,8 @@ font-style: normal; font-weight: 500; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -307,7 +347,8 @@ font-style: normal; font-weight: 500; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -316,10 +357,11 @@ font-style: normal; font-weight: 500; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, - U+20AB; + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, + U+01AF-01B0, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { @@ -327,10 +369,11 @@ font-style: normal; font-weight: 500; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, - U+A720-A7FF; + U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, + U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { @@ -338,10 +381,12 @@ font-style: normal; font-weight: 500; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) + format('woff2'); unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, + U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, + U+FFFD; } /* cyrillic-ext */ @font-face { @@ -349,8 +394,10 @@ font-style: normal; font-weight: 600; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) + format('woff2'); + unicode-range: + U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* cyrillic */ @font-face { @@ -358,7 +405,8 @@ font-style: normal; font-weight: 600; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -367,7 +415,8 @@ font-style: normal; font-weight: 600; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -376,7 +425,8 @@ font-style: normal; font-weight: 600; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -385,10 +435,11 @@ font-style: normal; font-weight: 600; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, - U+20AB; + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, + U+01AF-01B0, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { @@ -396,10 +447,11 @@ font-style: normal; font-weight: 600; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, - U+A720-A7FF; + U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, + U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { @@ -407,10 +459,12 @@ font-style: normal; font-weight: 600; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) + format('woff2'); unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, + U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, + U+FFFD; } /* cyrillic-ext */ @font-face { @@ -418,8 +472,10 @@ font-style: normal; font-weight: 700; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2) + format('woff2'); + unicode-range: + U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* cyrillic */ @font-face { @@ -427,7 +483,8 @@ font-style: normal; font-weight: 700; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -436,7 +493,8 @@ font-style: normal; font-weight: 700; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -445,7 +503,8 @@ font-style: normal; font-weight: 700; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -454,10 +513,11 @@ font-style: normal; font-weight: 700; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, - U+20AB; + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, + U+01AF-01B0, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { @@ -465,10 +525,11 @@ font-style: normal; font-weight: 700; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) + format('woff2'); unicode-range: - U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, - U+A720-A7FF; + U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, + U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { @@ -476,10 +537,12 @@ font-style: normal; font-weight: 700; font-display: swap; - src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) format('woff2'); + src: url(../fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) + format('woff2'); unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, + U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, + U+FFFD; } @font-face { diff --git a/web/ui/dashboard-react/src/assets/css/forms.css b/web/ui/dashboard-react/assets/css/forms.css similarity index 100% rename from web/ui/dashboard-react/src/assets/css/forms.css rename to web/ui/dashboard-react/assets/css/forms.css diff --git a/web/ui/dashboard-react/src/assets/fonts/Menlo-Regular.woff b/web/ui/dashboard-react/assets/fonts/Menlo-Regular.woff similarity index 100% rename from web/ui/dashboard-react/src/assets/fonts/Menlo-Regular.woff rename to web/ui/dashboard-react/assets/fonts/Menlo-Regular.woff diff --git a/web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2 b/web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2 similarity index 100% rename from web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2 rename to web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7W0Q5n-wU.woff2 diff --git a/web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2 b/web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2 similarity index 100% rename from web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2 rename to web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2 diff --git a/web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2 b/web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2 similarity index 100% rename from web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2 rename to web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7W0Q5n-wU.woff2 diff --git a/web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2 b/web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2 similarity index 100% rename from web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2 rename to web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2 diff --git a/web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2 b/web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2 similarity index 100% rename from web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2 rename to web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7W0Q5n-wU.woff2 diff --git a/web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2 b/web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2 similarity index 100% rename from web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2 rename to web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7W0Q5n-wU.woff2 diff --git a/web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2 b/web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2 similarity index 100% rename from web/ui/dashboard-react/src/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2 rename to web/ui/dashboard-react/assets/fonts/inter/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7W0Q5n-wU.woff2 diff --git a/web/ui/dashboard-react/assets/img/button-loader.gif b/web/ui/dashboard-react/assets/img/button-loader.gif new file mode 100644 index 0000000000..07159bad69 Binary files /dev/null and b/web/ui/dashboard-react/assets/img/button-loader.gif differ diff --git a/web/ui/dashboard-react/assets/img/page-loader.gif b/web/ui/dashboard-react/assets/img/page-loader.gif new file mode 100644 index 0000000000..f156eb1722 Binary files /dev/null and b/web/ui/dashboard-react/assets/img/page-loader.gif differ diff --git a/web/ui/dashboard-react/assets/img/public-layout.png b/web/ui/dashboard-react/assets/img/public-layout.png new file mode 100644 index 0000000000..ff5b251975 Binary files /dev/null and b/web/ui/dashboard-react/assets/img/public-layout.png differ diff --git a/web/ui/dashboard-react/assets/svg/.gitkeep b/web/ui/dashboard-react/assets/svg/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/web/ui/dashboard-react/assets/svg/logo.svg b/web/ui/dashboard-react/assets/svg/logo.svg new file mode 100644 index 0000000000..e2b2c2d667 --- /dev/null +++ b/web/ui/dashboard-react/assets/svg/logo.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/ui/dashboard-react/assets/svg/pattern.svg b/web/ui/dashboard-react/assets/svg/pattern.svg new file mode 100644 index 0000000000..7d075be313 --- /dev/null +++ b/web/ui/dashboard-react/assets/svg/pattern.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/ui/dashboard-react/components.json b/web/ui/dashboard-react/components.json index 51d59d26d8..275d4164cc 100644 --- a/web/ui/dashboard-react/components.json +++ b/web/ui/dashboard-react/components.json @@ -18,4 +18,4 @@ "hooks": "@/hooks" }, "iconLibrary": "lucide" -} \ No newline at end of file +} diff --git a/web/ui/dashboard-react/eslint.config.js b/web/ui/dashboard-react/eslint.config.js index add53cd1a7..c83bcb9b35 100644 --- a/web/ui/dashboard-react/eslint.config.js +++ b/web/ui/dashboard-react/eslint.config.js @@ -2,6 +2,7 @@ import js from '@eslint/js'; import globals from 'globals'; import reactHooks from 'eslint-plugin-react-hooks'; import reactRefresh from 'eslint-plugin-react-refresh'; +import eslint from '@eslint/js'; import tseslint from 'typescript-eslint'; import react from 'eslint-plugin-react'; @@ -9,26 +10,44 @@ export default tseslint.config( { ignores: ['dist'] }, { settings: { react: { version: '18.3' } }, - extends: [js.configs.recommended, ...tseslint.configs.strictTypeChecked], + extends: [ + js.configs.recommended, + eslint.configs.recommended, + tseslint.configs.recommended, + // ...tseslint.configs.strictTypeChecked + ], + parser: '@typescript-eslint/parser', files: ['**/*.{ts,tsx}'], languageOptions: { ecmaVersion: 'latest', globals: globals.browser, parserOptions: { project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname - } + tsconfigRootDir: import.meta.dirname, + }, }, plugins: { 'react-hooks': reactHooks, 'react-refresh': reactRefresh, - react + react, }, rules: { ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], ...react.configs.recommended.rules, - ...react.configs['jsx-runtime'].rules - } - } + ...react.configs['jsx-runtime'].rules, + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + ignoreRestSiblings: true, + caughtErrors: 'none', + }, + ], + }, + }, ); diff --git a/web/ui/dashboard-react/index.html b/web/ui/dashboard-react/index.html index d96c668ddb..492e025a98 100644 --- a/web/ui/dashboard-react/index.html +++ b/web/ui/dashboard-react/index.html @@ -9,6 +9,593 @@
+ + diff --git a/web/ui/dashboard-react/package.json b/web/ui/dashboard-react/package.json index 502bf26472..8482c2ab5a 100644 --- a/web/ui/dashboard-react/package.json +++ b/web/ui/dashboard-react/package.json @@ -1,43 +1,53 @@ { - "name": "dashboard-react", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite --port 5006", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview", - "prettify": "prettier --log-level=warn --cache --write ./src && echo 'prettify complete!'" - }, - "dependencies": { - "@radix-ui/react-slot": "^1.1.2", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "lucide-react": "^0.475.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "tailwind-merge": "^3.0.1", - "tailwindcss-animate": "^1.0.7" - }, - "devDependencies": { - "@eslint/js": "^9.19.0", - "@tailwindcss/container-queries": "^0.1.1", - "@types/node": "^22.13.1", - "@types/react": "^18.3.18", - "@types/react-dom": "^18.3.5", - "@vitejs/plugin-react": "^4.3.4", - "autoprefixer": "^10.4.20", - "eslint": "^9.19.0", - "eslint-plugin-react": "^7.37.4", - "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-react-refresh": "^0.4.18", - "globals": "^15.14.0", - "postcss": "^8.5.2", - "prettier": "^3.5.0", - "tailwindcss": "^3.4.17", - "typescript": "~5.7.2", - "typescript-eslint": "^8.22.0", - "vite": "^6.1.0" - } + "name": "dashboard-react", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port 6005", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview", + "prettify": "prettier --log-level=warn --cache --write ./src && echo 'prettify complete!'" + }, + "dependencies": { + "@hookform/resolvers": "^4.0.0", + "@radix-ui/colors": "^3.0.0", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-slot": "^1.1.2", + "@tanstack/react-router": "^1.106.0", + "axios": "^1.7.9", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.475.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-hook-form": "^7.54.2", + "tailwind-merge": "^3.0.1", + "tailwindcss-animate": "^1.0.7", + "zod": "^3.24.2" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "@tailwindcss/container-queries": "^0.1.1", + "@tanstack/router-devtools": "^1.106.0", + "@tanstack/router-plugin": "^1.106.0", + "@types/node": "^22.13.1", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@typescript-eslint/parser": "^8.24.1", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "eslint": "^9.19.0", + "eslint-plugin-react": "^7.37.4", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.18", + "globals": "^15.14.0", + "postcss": "^8.5.2", + "prettier": "^3.5.0", + "tailwindcss": "^3.4.17", + "typescript": "~5.7.2", + "typescript-eslint": "^8.22.0", + "vite": "^6.1.0" + } } diff --git a/web/ui/dashboard-react/src/App.tsx b/web/ui/dashboard-react/src/App.tsx deleted file mode 100644 index 88fd167054..0000000000 --- a/web/ui/dashboard-react/src/App.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import convoyLogo from './assets/img/svg/convoy-logo-full-new.svg'; -import { Button } from './components/ui/button'; - -function App() { - return ( -
-
- convoy -

- The complete solution for secure, scalable, and reliable webhook delivery. -

-
- -
-
-
- ); -} - -export default App; diff --git a/web/ui/dashboard-react/src/app/__root.tsx b/web/ui/dashboard-react/src/app/__root.tsx new file mode 100644 index 0000000000..c8fec1007e --- /dev/null +++ b/web/ui/dashboard-react/src/app/__root.tsx @@ -0,0 +1,22 @@ +import { lazy, Suspense } from 'react'; +import { createRootRoute, Outlet } from '@tanstack/react-router'; +import { isProductionMode } from '@/lib/env'; + +const TanStackRouterDevTools = isProductionMode + ? () => null + : lazy(() => + import('@tanstack/router-devtools').then(res => ({ + default: res.TanStackRouterDevtools, + })), + ); + +export const Route = createRootRoute({ + component: () => ( + <> + + + + + + ), +}); diff --git a/web/ui/dashboard-react/src/app/forgot-password.tsx b/web/ui/dashboard-react/src/app/forgot-password.tsx new file mode 100644 index 0000000000..4b513f7c31 --- /dev/null +++ b/web/ui/dashboard-react/src/app/forgot-password.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/forgot-password')({ + component: RouteComponent, +}); + +function RouteComponent() { + return

Forgot Password

; +} diff --git a/web/ui/dashboard-react/src/app/index.tsx b/web/ui/dashboard-react/src/app/index.tsx new file mode 100644 index 0000000000..7aa57d61ce --- /dev/null +++ b/web/ui/dashboard-react/src/app/index.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { router } from '@/lib/router'; +import { ensureCanAccessPrivatePages } from '@/lib/auth'; + +export const Route = createFileRoute('/')({ + beforeLoad() { + ensureCanAccessPrivatePages(); + router.navigate({ + to: '/projects', + // @ts-expect-error `pathname` is a defined route + from: router.state.location.pathname, + }); + }, + component: Index, +}); + +function Index() { + return
; +} diff --git a/web/ui/dashboard-react/src/app/login.tsx b/web/ui/dashboard-react/src/app/login.tsx new file mode 100644 index 0000000000..fe698b93e1 --- /dev/null +++ b/web/ui/dashboard-react/src/app/login.tsx @@ -0,0 +1,363 @@ +import { Button } from '@/components/ui/button'; +import { + FormField, + FormItem, + FormLabel, + FormControl, + FormMessageWithErrorIcon, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Form } from '@/components/ui/form'; +import { cn } from '@/lib/utils'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { EyeIcon, EyeOffIcon } from 'lucide-react'; +import { useEffect, useReducer, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { createFileRoute, useNavigate } from '@tanstack/react-router'; +import { z } from 'zod'; +import { ConvoyLoader } from '@/components/ConvoyLoader'; +import * as loginService from '@/services/login.service'; +import * as signUpService from '@/services/signup.service'; +import * as privateService from '@/services/private.service'; +import * as licensesService from '@/services/licenses.service'; + +import type { UseFormReturn } from 'react-hook-form'; + +const formSchema = z.object({ + email: z.string().email('Please enter your email'), + password: z.string().min(1, 'Please enter your password'), +}); + +type EmailInputFieldProps = { + form: UseFormReturn>; +}; + +function EmailInputField({ form }: EmailInputFieldProps) { + return ( + ( + +
+ Email +
+ + + + +
+ )} + /> + ); +} + +type PasswordInputFieldProps = { + form: UseFormReturn>; +}; + +function PasswordInputField({ form }: PasswordInputFieldProps) { + const [isPasswordVisible, setIsPasswordVisible] = useState(false); + + return ( + ( + +
+ + Password + +
+ +
+ + +
+
+ +
+ )} + /> + ); +} + +function ForgotPasswordSection() { + const navigate = useNavigate(); + + function navigateToPasswordPage() { + navigate({ + from: '/login', + to: '/forgot-password', + }); + } + + return ( +
+ Forgot password? + +
+ ); +} + +function LoginButton(props: { isButtonEnabled?: boolean }) { + const { isButtonEnabled } = props; + + return ( + + ); +} + +function LoginWithSAMLButton() { + async function login() { + localStorage.setItem('AUTH_TYPE', 'login'); + + try { + const res = await loginService.loginWithSAML(); + const { redirectUrl } = res.data; + window.open(redirectUrl); + } catch (error) { + // TODO should notify user here with UI + throw error; + } + } + + return ( + + ); +} + +function SignUpButton() { + const navigate = useNavigate(); + + function navigateToSignUpPage() { + navigate({ + from: '/login', + to: '/signup', + }); + } + + return ( + + ); +} + +type ReducerPayload = Partial<{ + isSignUpEnabled: boolean; + isFetchingConfig: boolean; + isLoadingProject: boolean; + hasCreateUserLicense: boolean; + isLoginButtonEnabled: boolean; +}>; + +const initialReducerState = { + isSignUpEnabled: false, + isFetchingConfig: false, + isLoadingProject: false, + isLoginButtonEnabled: true, + hasCreateUserLicense: false, +}; + +function reducer(state: ReducerPayload, payload: ReducerPayload) { + return { + ...state, + ...payload, + }; +} + +function LoginPage() { + const navigate = useNavigate(); + const [state, dispatchState] = useReducer(reducer, initialReducerState); + + useEffect(function () { + getSignUpConfig(); + licensesService.setLicenses(); + const hasCreateUserLicense = licensesService.hasLicense('CREATE_USER'); + dispatchState({ hasCreateUserLicense }); + }, []); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: '', + password: '', + }, + mode: 'onTouched', + }); + + async function login(values: z.infer) { + dispatchState({ isLoginButtonEnabled: false }); + + try { + await loginService.login(values); + dispatchState({ isLoadingProject: true }); + await getOrganisations(); + dispatchState({ isLoginButtonEnabled: true, isLoadingProject: false }); + + navigate({ + to: '/', + from: '/login', + }); + } catch (err) { + // TODO notify user using the UI + console.error(login.name, err); + } + } + + async function getSignUpConfig() { + dispatchState({ isFetchingConfig: true }); + try { + const { data } = await signUpService.getSignUpConfig(); + dispatchState({ isSignUpEnabled: data }); + } catch (err) { + // TODO notify user using the UI + console.error(getSignUpConfig.name, err); + } finally { + dispatchState({ isFetchingConfig: false }); + } + } + + async function getOrganisations() { + try { + await privateService.getOrganisations({ refresh: true }); + } catch (err) { + console.error(getOrganisations.name, err); + } + } + + return ( + <> +
+ +
+
+ convoy logo + +
+
+ void form.handleSubmit(login)(...args)} + > + + + + + + + + + + + + + {state.isSignUpEnabled && state.hasCreateUserLicense && ( + + )} +
+
+
+
+ + + + ); +} + +export const Route = createFileRoute('/login')({ + component: LoginPage, +}); + +// TODO loginService and other impure extraneous deps should be injected as a +// dependency for testing and flexibility/maintainability diff --git a/web/ui/dashboard-react/src/app/projects/index.tsx b/web/ui/dashboard-react/src/app/projects/index.tsx new file mode 100644 index 0000000000..95fa9320f9 --- /dev/null +++ b/web/ui/dashboard-react/src/app/projects/index.tsx @@ -0,0 +1,13 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { ensureCanAccessPrivatePages } from '@/lib/auth'; + +export const Route = createFileRoute('/projects/')({ + beforeLoad() { + ensureCanAccessPrivatePages(); + }, + component: RouteComponent, +}); + +function RouteComponent() { + return

Projects

; +} diff --git a/web/ui/dashboard-react/src/app/signup.tsx b/web/ui/dashboard-react/src/app/signup.tsx new file mode 100644 index 0000000000..b6eb89fceb --- /dev/null +++ b/web/ui/dashboard-react/src/app/signup.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/signup')({ + component: SignUpPage, +}); + +function SignUpPage() { + return

Sign Up

; +} diff --git a/web/ui/dashboard-react/src/assets/img/svg/convoy-logo-full-new.svg b/web/ui/dashboard-react/src/assets/img/svg/convoy-logo-full-new.svg deleted file mode 100644 index 31d71d993e..0000000000 --- a/web/ui/dashboard-react/src/assets/img/svg/convoy-logo-full-new.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/web/ui/dashboard-react/src/components/ConvoyLoader.tsx b/web/ui/dashboard-react/src/components/ConvoyLoader.tsx new file mode 100644 index 0000000000..7784d773a8 --- /dev/null +++ b/web/ui/dashboard-react/src/components/ConvoyLoader.tsx @@ -0,0 +1,29 @@ +import { cn } from '@/lib/utils'; + +type ConvoyLoaderProps = { + isTransparent: boolean; + position?: 'absolute' | 'fixed' | 'relative'; + isVisible?: boolean; +}; + +export function ConvoyLoader(props: ConvoyLoaderProps) { + const { isTransparent, position = 'absolute', isVisible = false } = props; + + if (isVisible == false) return null; + + return ( +
+ loader +
+ ); +} diff --git a/web/ui/dashboard-react/src/components/ui/button.tsx b/web/ui/dashboard-react/src/components/ui/button.tsx index 4127e856b3..85a64c2be9 100644 --- a/web/ui/dashboard-react/src/components/ui/button.tsx +++ b/web/ui/dashboard-react/src/components/ui/button.tsx @@ -9,12 +9,14 @@ const buttonVariants = cva( { variants: { variant: { - default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', + default: + 'bg-primary text-primary-foreground shadow hover:bg-primary/90', destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', - secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', + secondary: + 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'text-primary underline-offset-4 hover:underline', }, diff --git a/web/ui/dashboard-react/src/components/ui/form.tsx b/web/ui/dashboard-react/src/components/ui/form.tsx new file mode 100644 index 0000000000..d91624bed8 --- /dev/null +++ b/web/ui/dashboard-react/src/components/ui/form.tsx @@ -0,0 +1,188 @@ +import * as React from 'react'; +import * as LabelPrimitive from '@radix-ui/react-label'; +import { Slot } from '@radix-ui/react-slot'; +import { Controller, FormProvider, useFormContext } from 'react-hook-form'; + +import { cn } from '@/lib/utils'; +import { Label } from '@/components/ui/label'; + +import type { ControllerProps, FieldPath, FieldValues } from 'react-hook-form'; + +const Form = FormProvider; + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + name: TName; +}; + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue, +); + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + ...props +}: ControllerProps) => { + return ( + + + + ); +}; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState, formState } = useFormContext(); + + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error('useFormField should be used within '); + } + + const { id } = itemContext; + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; +}; + +type FormItemContextValue = { + id: string; +}; + +const FormItemContext = React.createContext( + {} as FormItemContextValue, +); + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId(); + + return ( + +
+ + ); +}); +FormItem.displayName = 'FormItem'; + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField(); + + return ( +