diff --git a/client/.env.production b/client/.env.production index 7a11848c0..25849c7b9 100644 --- a/client/.env.production +++ b/client/.env.production @@ -1,3 +1,4 @@ VITE_APP_API_BASE_URL=UPTIME_APP_API_BASE_URL +VITE_APP_API_V2_BASE_URL=UPTIME_APP_API_V2_BASE_URL VITE_APP_CLIENT_HOST=UPTIME_APP_CLIENT_HOST VITE_APP_LOG_LEVEL=UPTIME_APP_LOG_LEVEL \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 7d6350f45..7cf0e8631 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -26,6 +26,7 @@ "i18next": "25.4.2", "joi": "17.13.3", "mui-color-input": "^6.0.0", + "pretty-ms": "9.3.0", "react": "18.3.1", "react-dom": "^18.2.0", "react-hook-form": "7.63.0", @@ -5184,6 +5185,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5311,6 +5324,21 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", diff --git a/client/package.json b/client/package.json index 6d4858027..376671942 100644 --- a/client/package.json +++ b/client/package.json @@ -31,6 +31,7 @@ "i18next": "25.4.2", "joi": "17.13.3", "mui-color-input": "^6.0.0", + "pretty-ms": "9.3.0", "react": "18.3.1", "react-dom": "^18.2.0", "react-hook-form": "7.63.0", @@ -46,14 +47,6 @@ "vite-plugin-svgr": "^4.2.0", "zod": "4.1.11" }, - "unusedDepencies": { - "@solana/wallet-adapter-base": "0.9.25", - "@solana/wallet-adapter-material-ui": "0.16.35", - "@solana/wallet-adapter-react": "0.15.37", - "@solana/wallet-adapter-react-ui": "0.9.37", - "@solana/wallet-adapter-wallets": "0.19.34", - "@solana/web3.js": "1.98.0" - }, "devDependencies": { "@types/node": "24.5.2", "@types/react": "^18.2.66", diff --git a/client/src/App.jsx b/client/src/App.jsx index 04f0d5b65..d21850ebb 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -9,7 +9,6 @@ import { CssBaseline, GlobalStyles } from "@mui/material"; import { logger } from "./Utils/Logger"; // Import the logger import { networkService } from "./main"; import { Routes } from "./Routes"; -import WalletProvider from "./Components/WalletProvider"; import AppLayout from "@/Components/v1/Layouts/AppLayout"; function App() { @@ -24,16 +23,12 @@ function App() { }, []); return ( - /* Extract Themeprovider, baseline and global styles to Styles */ - - - - - - - - + + + + + ); } diff --git a/client/src/Components/v2/ActionsMenu/index.tsx b/client/src/Components/v2/ActionsMenu/index.tsx index 5b606e5b0..a143be3f5 100644 --- a/client/src/Components/v2/ActionsMenu/index.tsx +++ b/client/src/Components/v2/ActionsMenu/index.tsx @@ -15,11 +15,13 @@ export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => { const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + setAnchorEl(e.currentTarget); }; - const handleClose = () => { + const handleClose = (e: React.MouseEvent) => { + e.stopPropagation(); setAnchorEl(null); }; @@ -36,8 +38,9 @@ export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => { {items.map((item) => ( { - if (item.closeMenu) handleClose(); + onClick={(e: React.MouseEvent) => { + e.stopPropagation(); + if (item.closeMenu) handleClose(e); item.action(); }} > diff --git a/client/src/Components/v2/Auth/AuthBasePage.tsx b/client/src/Components/v2/Auth/AuthBasePage.tsx new file mode 100644 index 000000000..ba4230a6d --- /dev/null +++ b/client/src/Components/v2/Auth/AuthBasePage.tsx @@ -0,0 +1,48 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import { HeaderAuth } from "@/Components/v2/Auth"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; + +import type { StackProps } from "@mui/material/Stack"; +import { useTheme } from "@mui/material/styles"; +import { Typography } from "@mui/material"; + +interface AuthBasePageProps extends StackProps { + title?: string; + subtitle?: string; + children: React.ReactNode; +} + +export const AuthBasePage: React.FC = ({ + children, + title, + subtitle, + ...props +}) => { + const theme = useTheme(); + return ( + + + + + + + {title} + {subtitle} + {children} + + + ); +}; diff --git a/client/src/Components/v2/Auth/HeaderAuth.jsx b/client/src/Components/v2/Auth/HeaderAuth.jsx new file mode 100644 index 000000000..4e437cd7e --- /dev/null +++ b/client/src/Components/v2/Auth/HeaderAuth.jsx @@ -0,0 +1,26 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import Logo from "@/assets/icons/checkmate-icon.svg?react"; + +import { useTheme } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; +import { LanguageSelector, ThemeSwitch } from "@/Components/v2/Inputs"; + +export const HeaderAuth = () => { + const theme = useTheme(); + const { t } = useTranslation(); + return ( + + + + + ); +}; diff --git a/client/src/Components/v2/Auth/index.jsx b/client/src/Components/v2/Auth/index.jsx new file mode 100644 index 000000000..84b742113 --- /dev/null +++ b/client/src/Components/v2/Auth/index.jsx @@ -0,0 +1,2 @@ +export { HeaderAuth } from "./HeaderAuth"; +export { AuthBasePage } from "./AuthBasePage"; diff --git a/client/src/Components/v2/DesignElements/BaseBox.tsx b/client/src/Components/v2/DesignElements/BaseBox.tsx new file mode 100644 index 000000000..14c4af1c4 --- /dev/null +++ b/client/src/Components/v2/DesignElements/BaseBox.tsx @@ -0,0 +1,23 @@ +import Box from "@mui/material/Box"; +import { useTheme } from "@mui/material/styles"; +import type { SxProps } from "@mui/material/styles"; + +type BaseBoxProps = React.PropsWithChildren<{ sx?: SxProps }>; + +export const BaseBox: React.FC = ({ children, sx }) => { + const theme = useTheme(); + return ( + + {children} + + ); +}; diff --git a/client/src/Components/v2/DesignElements/BasePage.tsx b/client/src/Components/v2/DesignElements/BasePage.tsx index ad516f2c6..f458cbb0a 100644 --- a/client/src/Components/v2/DesignElements/BasePage.tsx +++ b/client/src/Components/v2/DesignElements/BasePage.tsx @@ -1,7 +1,9 @@ import Stack from "@mui/material/Stack"; +import { ErrorFallback, EmptyFallback } from "./Fallback"; + import type { StackProps } from "@mui/material/Stack"; import { useTheme } from "@mui/material/styles"; - +import { useTranslation } from "react-i18next"; interface BasePageProps extends StackProps { children: React.ReactNode; } @@ -22,3 +24,57 @@ export const BasePage: React.FC = ({ ); }; + +interface BasePageWithStatesProps extends StackProps { + loading: boolean; + error: any; + items: any[]; + page: string; + actionLink?: string; + children: React.ReactNode; +} + +const isEmpty = (items: any[]) => { + if (!items) return true; + if (Array.isArray(items) && items.length === 0) return true; + return false; +}; + +export const BasePageWithStates: React.FC = ({ + loading, + error, + items, + page, + actionLink, + children, + ...props +}: BasePageWithStatesProps) => { + const { t } = useTranslation(); + + if (loading) { + return null; + } + + if (error) { + return ( + + ); + } + + if (isEmpty(items)) { + return ( + + ); + } + + return {children}; +}; diff --git a/client/src/Components/v2/DesignElements/BulletPointCheck.tsx b/client/src/Components/v2/DesignElements/BulletPointCheck.tsx new file mode 100644 index 000000000..03147e011 --- /dev/null +++ b/client/src/Components/v2/DesignElements/BulletPointCheck.tsx @@ -0,0 +1,45 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import CheckOutlined from "@/assets/icons/check-outlined.svg?react"; +import { useTheme } from "@mui/material/styles"; + +export const BulletPointCheck = ({ + text, + variant = "info", +}: { + text: string; + noHighlightText?: string; + variant?: "success" | "error" | "info"; +}) => { + const theme = useTheme(); + const colors: Record = { + success: theme.palette.success.main, + error: theme.palette.error.main, + info: theme.palette.primary.contrastTextSecondary, + }; + + return ( + + + + {text} + + + ); +}; diff --git a/client/src/Components/v2/DesignElements/Dot.tsx b/client/src/Components/v2/DesignElements/Dot.tsx new file mode 100644 index 000000000..ca3884cfb --- /dev/null +++ b/client/src/Components/v2/DesignElements/Dot.tsx @@ -0,0 +1,23 @@ +export const Dot = ({ + color = "gray", + size = "4px", + style, +}: { + color?: string; + size?: string; + style?: React.CSSProperties; +}) => { + return ( + + ); +}; diff --git a/client/src/Components/v2/DesignElements/Fallback.tsx b/client/src/Components/v2/DesignElements/Fallback.tsx new file mode 100644 index 000000000..f7b48a113 --- /dev/null +++ b/client/src/Components/v2/DesignElements/Fallback.tsx @@ -0,0 +1,158 @@ +import Box from "@mui/material/Box"; +import Stack from "@mui/material/Stack"; +import OutputAnimation from "@/assets/Animations/output.gif"; +import DarkmodeOutput from "@/assets/Animations/darkmodeOutput.gif"; +import Typography from "@mui/material/Typography"; +import { BulletPointCheck } from "@/Components/v2/DesignElements"; +import { Button } from "@/Components/v2/Inputs"; + +import { useNavigate } from "react-router"; +import { useMediaQuery } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import { useSelector } from "react-redux"; + +import type { BoxProps } from "@mui/material"; + +interface BaseFallbackProps extends BoxProps { + children: React.ReactNode; +} + +export const BaseFallback: React.FC = ({ children, ...props }) => { + const theme = useTheme(); + const mode = useSelector((state: any) => state.ui.mode); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + + return ( + + + + + + {children} + + + + ); +}; + +export const ErrorFallback = ({ + title, + subtitle, +}: { + title: string; + subtitle: string; +}) => { + const theme = useTheme(); + return ( + + + {title} + + {subtitle} + + ); +}; + +export const EmptyFallback = ({ + page, + title, + bullets, + actionButtonText, + actionLink, +}: { + page: string; + title: string; + bullets: any; + actionButtonText: string; + actionLink: string; +}) => { + const theme = useTheme(); + const navigate = useNavigate(); + return ( + + + + {title} + + + {bullets?.map((bullet: string, index: number) => ( + + ))} + + + + + + + ); +}; diff --git a/client/src/Components/v2/DesignElements/PulseDot.tsx b/client/src/Components/v2/DesignElements/PulseDot.tsx new file mode 100644 index 000000000..a785b5a0d --- /dev/null +++ b/client/src/Components/v2/DesignElements/PulseDot.tsx @@ -0,0 +1,44 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import { useTheme } from "@mui/material/styles"; +export const PulseDot = ({ color }: { color: string }) => { + const theme = useTheme(); + return ( + + + + ); +}; diff --git a/client/src/Components/v2/DesignElements/StatBox.tsx b/client/src/Components/v2/DesignElements/StatBox.tsx new file mode 100644 index 000000000..3450444b2 --- /dev/null +++ b/client/src/Components/v2/DesignElements/StatBox.tsx @@ -0,0 +1,57 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/material/styles"; +import { useMediaQuery } from "@mui/material"; +import type { PaletteKey } from "@/Utils/Theme/v2/theme"; +import { BaseBox } from "@/Components/v2/DesignElements"; + +type GradientBox = React.PropsWithChildren<{ palette?: PaletteKey }>; + +export const GradientBox: React.FC = ({ children, palette }) => { + const theme = useTheme(); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + const bg = palette + ? `linear-gradient(to bottom right, ${theme.palette[palette].main} 30%, ${theme.palette[palette].lowContrast} 70%)` + : `linear-gradient(340deg, ${theme.palette.tertiary.main} 10%, ${theme.palette.primary.main} 45%)`; + + return ( + + {children} + + ); +}; + +type StatBoxProps = React.PropsWithChildren<{ + title: string; + subtitle: string; + palette?: PaletteKey; +}>; + +export const StatBox: React.FC = ({ + title, + subtitle, + palette, + children, +}) => { + const theme = useTheme(); + const textColor = palette ? theme.palette[palette].contrastText : "inherit"; + + return ( + + + {title} + {subtitle} + {children} + + + ); +}; diff --git a/client/src/Components/v2/DesignElements/StatusBox.tsx b/client/src/Components/v2/DesignElements/StatusBox.tsx index 540255dfd..8cd5cfbc1 100644 --- a/client/src/Components/v2/DesignElements/StatusBox.tsx +++ b/client/src/Components/v2/DesignElements/StatusBox.tsx @@ -1,6 +1,7 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import Box from "@mui/material/Box"; +import { BaseBox } from "@/Components/v2/DesignElements"; import Background from "@/assets/Images/background-grid.svg?react"; import { useTranslation } from "react-i18next"; @@ -11,15 +12,13 @@ type StatusBoxProps = React.PropsWithChildren<{}>; export const BGBox: React.FC = ({ children }) => { const theme = useTheme(); return ( - = ({ children }) => { {children} - + ); }; diff --git a/client/src/Components/v2/DesignElements/StatusLabel.tsx b/client/src/Components/v2/DesignElements/StatusLabel.tsx new file mode 100644 index 000000000..a314c09b0 --- /dev/null +++ b/client/src/Components/v2/DesignElements/StatusLabel.tsx @@ -0,0 +1,41 @@ +import Box from "@mui/material/Box"; +import { BaseBox } from "@/Components/v2/DesignElements"; +import type { MonitorStatus } from "@/Types/Monitor"; + +import { getStatusPalette } from "@/Utils/MonitorUtils"; +import { useTheme } from "@mui/material/styles"; + +export const StatusLabel = ({ + status, + isActive, +}: { + status: MonitorStatus; + isActive?: boolean; +}) => { + const theme = useTheme(); + const palette = getStatusPalette(status); + const transformedText = status.charAt(0).toUpperCase() + status.slice(1).toLowerCase(); + + return ( + + + {isActive === false ? "Paused" : transformedText} + + ); +}; diff --git a/client/src/Components/v2/DesignElements/Table.tsx b/client/src/Components/v2/DesignElements/Table.tsx index 6296d260c..bf75fd9bf 100644 --- a/client/src/Components/v2/DesignElements/Table.tsx +++ b/client/src/Components/v2/DesignElements/Table.tsx @@ -5,7 +5,19 @@ import TableCell from "@mui/material/TableCell"; import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; + +import IconButton from "@mui/material/IconButton"; +import LastPageIcon from "@mui/icons-material/LastPage"; +import FirstPageIcon from "@mui/icons-material/FirstPage"; +import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft"; +import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight"; + +import Box from "@mui/material/Box"; +import TablePagination from "@mui/material/TablePagination"; +import type { TablePaginationProps } from "@mui/material/TablePagination"; + import { useTheme } from "@mui/material/styles"; +import { useMediaQuery } from "@mui/material"; export type Header = { id: number | string; content: React.ReactNode; @@ -16,12 +28,16 @@ export type Header = { type DataTableProps = { headers: Header[]; data: T[]; + onRowClick?: (row: T) => void; }; -export function DataTable({ - headers, - data, -}: DataTableProps) { +export function DataTable< + T extends { + id?: string | number; + _id?: string | number; + onRowClick?: (row: T) => void; + }, +>({ headers, data, onRowClick }: DataTableProps) { const theme = useTheme(); if (data.length === 0 || headers.length === 0) return
No data
; return ( @@ -65,7 +81,11 @@ export function DataTable + (onRowClick ? onRowClick(row) : null)} + > {headers.map((header, index) => { return ( ); } + +interface TablePaginationActionsProps { + count: number; + page: number; + rowsPerPage: number; + onPageChange: (event: React.MouseEvent, newPage: number) => void; +} + +function TablePaginationActions(props: TablePaginationActionsProps) { + const theme = useTheme(); + const { count, page, rowsPerPage, onPageChange } = props; + + const handleFirstPageButtonClick = (event: React.MouseEvent) => { + onPageChange(event, 0); + }; + + const handleBackButtonClick = (event: React.MouseEvent) => { + onPageChange(event, page - 1); + }; + + const handleNextButtonClick = (event: React.MouseEvent) => { + onPageChange(event, page + 1); + }; + + const handleLastPageButtonClick = (event: React.MouseEvent) => { + onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); + }; + + return ( + + + {theme.direction === "rtl" ? : } + + + {theme.direction === "rtl" ? : } + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="next page" + > + {theme.direction === "rtl" ? : } + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="last page" + > + {theme.direction === "rtl" ? : } + + + ); +} + +export const Pagination: React.FC = ({ ...props }) => { + const isSmall = useMediaQuery((theme: any) => theme.breakpoints.down("sm")); + const theme = useTheme(); + return ( + + ); +}; diff --git a/client/src/Components/v2/DesignElements/index.tsx b/client/src/Components/v2/DesignElements/index.tsx index 41b4b319a..3574daffb 100644 --- a/client/src/Components/v2/DesignElements/index.tsx +++ b/client/src/Components/v2/DesignElements/index.tsx @@ -1,4 +1,9 @@ export { SplitBox as HorizontalSplitBox, ConfigBox } from "./SplitBox"; -export { BasePage } from "./BasePage"; +export { BasePage, BasePageWithStates } from "./BasePage"; export { BGBox, UpStatusBox, DownStatusBox, PausedStatusBox } from "./StatusBox"; -export { DataTable as Table } from "./Table"; +export { DataTable as Table, Pagination } from "./Table"; +export { GradientBox, StatBox } from "./StatBox"; +export { BaseBox } from "./BaseBox"; +export { StatusLabel } from "./StatusLabel"; +export { BaseFallback, ErrorFallback, EmptyFallback } from "./Fallback"; +export { BulletPointCheck } from "./BulletPointCheck"; diff --git a/client/src/Components/v2/Inputs/Button.tsx b/client/src/Components/v2/Inputs/Button.tsx index 0bf1d14e1..2f28e8eda 100644 --- a/client/src/Components/v2/Inputs/Button.tsx +++ b/client/src/Components/v2/Inputs/Button.tsx @@ -1,10 +1,11 @@ import Button from "@mui/material/Button"; import type { ButtonProps } from "@mui/material/Button"; -export const ButtonInput: React.FC = ({ ...props }) => { + +export const ButtonInput: React.FC = ({ sx, ...props }) => { return (