From fb90f193b1509dcbcee4bee01d04794fc2178b94 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Oct 2025 12:44:27 -0700 Subject: [PATCH] add fallbacks --- .../Components/v2/DesignElements/BasePage.tsx | 58 ++++++- .../v2/DesignElements/BulletPointCheck.tsx | 45 +++++ .../Components/v2/DesignElements/Fallback.tsx | 158 ++++++++++++++++++ .../Components/v2/DesignElements/index.tsx | 4 +- client/src/Pages/v2/Uptime/UptimeMonitors.tsx | 22 +-- 5 files changed, 275 insertions(+), 12 deletions(-) create mode 100644 client/src/Components/v2/DesignElements/BulletPointCheck.tsx create mode 100644 client/src/Components/v2/DesignElements/Fallback.tsx 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/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/index.tsx b/client/src/Components/v2/DesignElements/index.tsx index 6ba0a19ac..3574daffb 100644 --- a/client/src/Components/v2/DesignElements/index.tsx +++ b/client/src/Components/v2/DesignElements/index.tsx @@ -1,7 +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, 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/Pages/v2/Uptime/UptimeMonitors.tsx b/client/src/Pages/v2/Uptime/UptimeMonitors.tsx index 57865d929..e5a151eb1 100644 --- a/client/src/Pages/v2/Uptime/UptimeMonitors.tsx +++ b/client/src/Pages/v2/Uptime/UptimeMonitors.tsx @@ -1,5 +1,5 @@ import { - BasePage, + BasePageWithStates, UpStatusBox, DownStatusBox, PausedStatusBox, @@ -18,10 +18,10 @@ const UptimeMonitors = () => { const theme = useTheme(); const isSmall = useMediaQuery(theme.breakpoints.down("md")); - const { response, loading, refetch } = useGet( + const { response, isValidating, error, refetch } = useGet( "/monitors?embedChecks=true", {}, - { refreshInterval: 30000 } + { refreshInterval: 30000, keepPreviousData: true } ); const monitors: IMonitor[] = response?.data ?? ([] as IMonitor[]); @@ -43,14 +43,16 @@ const UptimeMonitors = () => { } ); - if (monitors.length === 0 && !loading) { - return "No monitors found"; - } - return ( - + { monitors={monitors} refetch={refetch} /> - + ); };