initial commit

This commit is contained in:
Alex Holliday committed 2026-02-10 17:53:00 +00:00
1 parent 14ba3016f6
commit 7ef10b4fcd
9 files changed
+138 -32

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@ import darkTheme from "./Utils/Theme/darkTheme";
import { CssBaseline, GlobalStyles } from "@mui/material";
import { logger } from "./Utils/Logger"; // Import the logger
import { Routes } from "./Routes";
import AppLayout from "@/Components/v1/Layouts/AppLayout";
import AppLayout from "@/Components/v2/layout/AppLayout";
import type { RootState } from "@/Types/state";
function App() {
@@ -1,31 +0,0 @@
import Box from "@mui/material/Box";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import BackgroundSVG from "../../../../assets/Images/background.svg";
import { useSelector } from "react-redux";
const AppLayout = ({ children }) => {
const theme = useTheme();
const ui = useSelector((state) => state.ui);
return (
<Box
sx={{
minHeight: "100vh",
backgroundColor: theme.palette.primaryBackground.main,
backgroundImage: ui?.mode === "dark" ? `url("${BackgroundSVG}")` : "none",
backgroundSize: "100% 100%",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
color: theme.palette.primary.contrastText,
}}
>
{children}
</Box>
);
};
AppLayout.propTypes = {
children: PropTypes.node,
};
export default AppLayout;
@@ -0,0 +1,76 @@
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
import { WifiOff } from "lucide-react";
interface OfflineBannerProps {
visible: boolean;
onRetry?: () => void;
isRetrying?: boolean;
}
export const OfflineBanner = ({ visible, onRetry, isRetrying }: OfflineBannerProps) => {
const theme = useTheme();
const { t } = useTranslation();
if (!visible) {
return null;
}
return (
<Box
sx={{
position: "fixed",
top: 0,
left: 0,
right: 0,
zIndex: theme.zIndex.snackbar,
backgroundColor: theme.palette.error.main,
color: theme.palette.error.contrastText,
px: theme.spacing(8),
py: theme.spacing(4),
}}
>
<Stack
direction="row"
alignItems="center"
justifyContent="center"
gap={theme.spacing(4)}
>
<WifiOff size={20} />
<Typography
variant="body2"
fontWeight={500}
>
{t("components.offlineBanner.serverUnreachable")}
</Typography>
{onRetry && (
<Button
size="small"
variant="outlined"
onClick={onRetry}
disabled={isRetrying}
sx={{
color: "inherit",
borderColor: "currentColor",
minWidth: "auto",
py: 0.5,
px: 2,
"&:hover": {
borderColor: "currentColor",
backgroundColor: "rgba(255, 255, 255, 0.1)",
},
}}
>
{isRetrying
? t("components.offlineBanner.retrying")
: t("components.offlineBanner.retry")}
</Button>
)}
</Stack>
</Box>
);
};
@@ -19,3 +19,4 @@ export * from "./Gauge";
export * from "./Tabs";
export * from "./SplitBox";
export * from "./TextLink";
export * from "./OfflineBanner";
@@ -0,0 +1,39 @@
import Box from "@mui/material/Box";
import { ThemeProvider, useTheme } from "@mui/material/styles";
import { useSelector } from "react-redux";
import BackgroundSVG from "@/assets/Images/background.svg";
import type { RootState } from "@/Types/state";
import { lightTheme, darkTheme } from "@/Utils/Theme/v2Theme";
import { OfflineBanner } from "@/Components/v2/design-elements";
interface AppLayoutProps {
children: React.ReactNode;
}
const AppLayout = ({ children }: AppLayoutProps) => {
const theme = useTheme();
const mode = useSelector((state: RootState) => state.ui.mode);
const v2theme = mode === "dark" ? darkTheme : lightTheme;
return (
<ThemeProvider theme={v2theme}>
<Box
sx={{
minHeight: "100vh",
// @ts-expect-error custom palette property
backgroundColor: theme.palette.primaryBackground.main,
backgroundImage: mode === "dark" ? `url("${BackgroundSVG}")` : "none",
backgroundSize: "100% 100%",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
color: theme.palette.primary.contrastText,
}}
>
<OfflineBanner visible={false} />
{children}
</Box>
</ThemeProvider>
);
};
export default AppLayout;
Whitespace-only changes.
Whitespace-only changes.
+15
View File
@@ -15,6 +15,15 @@ type StoreType = {
let storeInstance: StoreType | null = null;
let interceptorsInitialized = false;
type ServerUnreachableCallback = (unreachable: boolean) => void;
let serverUnreachableCallback: ServerUnreachableCallback | null = null;
export const setServerUnreachableCallback = (
callback: ServerUnreachableCallback
): void => {
serverUnreachableCallback = callback;
};
export const initApiClient = (store: StoreType): void => {
storeInstance = store;
@@ -41,6 +50,12 @@ export const initApiClient = (store: StoreType): void => {
const onSuccess = (response: AxiosResponse) => response;
const onError = (error: AxiosError) => {
// Handle network errors (server unreachable)
if (error.code === "ERR_NETWORK") {
serverUnreachableCallback?.(true);
return Promise.reject(error);
}
if (error.response?.status === 401) {
if (window.location.pathname !== "/login") {
window.location.href = "/login";
+6
View File
@@ -325,6 +325,12 @@
"week": "Showing statistics for past 7 days.",
"month": "Showing statistics for past 30 days."
}
},
"offlineBanner": {
"serverUnreachable": "Unable to reach server",
"retry": "Retry",
"retrying": "Retrying...",
"reconnected": "Connection restored"
}
},
"configure": "Configure",