diff --git a/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx b/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx
new file mode 100644
index 000000000..a07ccde6c
--- /dev/null
+++ b/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx
@@ -0,0 +1,16 @@
+import { useTheme } from "@mui/material/styles";
+
+import Stack from "@mui/material/Stack";
+
+export const BottomControls = ({}) => {
+ const theme = useTheme();
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx
new file mode 100644
index 000000000..5b3ed705e
--- /dev/null
+++ b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx
@@ -0,0 +1,58 @@
+import Notifications from "@/assets/icons/notifications.svg?react";
+import Monitors from "@/assets/icons/monitors.svg?react";
+import PageSpeed from "@/assets/icons/page-speed.svg?react";
+import Integrations from "@/assets/icons/integrations.svg?react";
+import Incidents from "@/assets/icons/incidents.svg?react";
+import StatusPages from "@/assets/icons/status-pages.svg?react";
+import Maintenance from "@/assets/icons/maintenance.svg?react";
+import Logs from "@/assets/icons/logs.svg?react";
+import Settings from "@/assets/icons/settings.svg?react";
+import Support from "@/assets/icons/support.svg?react";
+import Discussions from "@/assets/icons/discussions.svg?react";
+import Docs from "@/assets/icons/docs.svg?react";
+import ChangeLog from "@/assets/icons/changeLog.svg?react";
+
+export const getMenu = (t: Function) => [
+ { name: t("menu.uptime"), path: "v2/uptime", icon: },
+ { name: t("menu.pagespeed"), path: "pagespeed", icon: },
+
+ { name: t("menu.infrastructure"), path: "infrastructure", icon: },
+ {
+ name: t("menu.notifications"),
+ path: "notifications",
+ icon: ,
+ },
+ { name: t("menu.incidents"), path: "incidents", icon: },
+
+ { name: t("menu.statusPages"), path: "status", icon: },
+ { name: t("menu.maintenance"), path: "maintenance", icon: },
+ { name: t("menu.logs"), path: "logs", icon: },
+
+ {
+ name: t("menu.settings"),
+ icon: ,
+ path: "settings",
+ },
+];
+
+export const getBottomMenu = (t: Function) => [
+ { name: t("menu.support"), path: "support", icon: , url: "invite" },
+ {
+ name: t("menu.discussions"),
+ path: "discussions",
+ icon: ,
+ url: "https://github.com/bluewave-labs/checkmate/discussions",
+ },
+ {
+ name: t("menu.docs"),
+ path: "docs",
+ icon: ,
+ url: "https://bluewavelabs.gitbook.io/checkmate",
+ },
+ {
+ name: t("menu.changelog"),
+ path: "changelog",
+ icon: ,
+ url: "https://github.com/bluewave-labs/checkmate/releases",
+ },
+];
diff --git a/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx b/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx
new file mode 100644
index 000000000..8f7f5d68c
--- /dev/null
+++ b/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx
@@ -0,0 +1,103 @@
+import Tooltip from "@mui/material/Tooltip";
+import ListItemButton from "@mui/material/ListItemButton";
+import ListItemIcon from "@mui/material/ListItemIcon";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import { useTheme } from "@mui/material/styles";
+
+export interface NavData {
+ name: string;
+ icon: JSX.Element;
+}
+
+export const NavItem = ({
+ item,
+ collapsed,
+ selected,
+ onClick,
+}: {
+ item: NavData;
+ collapsed: boolean;
+ selected: boolean;
+ onClick: (event: React.MouseEvent) => void;
+}) => {
+ const theme = useTheme();
+ const iconStroke = selected
+ ? theme.palette.primary.contrastText
+ : theme.palette.primary.contrastTextTertiary;
+
+ const buttonBgColor = selected ? theme.palette.secondary.main : "transparent";
+ const buttonBgHoverColor = selected
+ ? theme.palette.secondary.main
+ : theme.palette.tertiary.main;
+ const fontWeight = selected ? 600 : 400;
+ return (
+
+
+
+ {item.icon}
+
+
+
+ {item.name}
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/Layouts/Sidebar/index.tsx b/client/src/Components/v2/Layouts/Sidebar/index.tsx
index 43975adfc..b05716aee 100644
--- a/client/src/Components/v2/Layouts/Sidebar/index.tsx
+++ b/client/src/Components/v2/Layouts/Sidebar/index.tsx
@@ -3,10 +3,17 @@ import { useSelector, useDispatch } from "react-redux";
import { setCollapsed } from "@/Features/UI/uiSlice";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";
+import { useTranslation } from "react-i18next";
+import { useNavigate, useLocation } from "react-router-dom";
import { CollapseButton } from "@/Components/v2/Layouts/Sidebar/CollapseButton";
import Stack from "@mui/material/Stack";
+import List from "@mui/material/List";
+import Divider from "@mui/material/Divider";
import { Logo } from "@/Components/v2/Layouts/Sidebar/Logo";
+import { getMenu, getBottomMenu } from "@/Components/v2/Layouts/Sidebar/Menu";
+import { NavItem } from "@/Components/v2/Layouts/Sidebar/NavItem";
+import { BottomControls } from "@/Components/v2/Layouts/Sidebar/BottomControls";
export const COLLAPSED_WIDTH = 64;
export const EXPANDED_WIDTH = 250;
@@ -16,6 +23,11 @@ export const SideBar = () => {
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const dispatch = useDispatch();
const collapsed = useSelector((state: any) => state.ui.sidebar.collapsed);
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const location = useLocation();
+ const menu = getMenu(t);
+ const bottomMenu = getBottomMenu(t);
useEffect(() => {
dispatch(setCollapsed({ collapsed: isSmall }));
@@ -23,6 +35,7 @@ export const SideBar = () => {
return (
{
borderRight={`1px solid ${theme.palette.primary.lowContrast}`}
width={collapsed ? COLLAPSED_WIDTH : EXPANDED_WIDTH}
sx={{
- transition: "width 0.3s ease",
+ transition: "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)",
}}
>
+
+ {menu.map((item) => {
+ const selected = location.pathname.startsWith(`/${item.path}`);
+ return (
+ navigate(`/${item.path}`)}
+ />
+ );
+ })}
+
+
+ {bottomMenu.map((item) => {
+ const selected = location.pathname.startsWith(`/${item.path}`);
+
+ return (
+ {
+ if (item.url) {
+ window.open(item.url, "_blank", "noreferrer");
+ } else {
+ navigate(`/${item.path}`);
+ }
+ }}
+ />
+ );
+ })}
+
+
+
);
};
diff --git a/client/src/Features/Auth/v2AuthSlice.ts b/client/src/Features/Auth/v2AuthSlice.ts
new file mode 100644
index 000000000..28a18b659
--- /dev/null
+++ b/client/src/Features/Auth/v2AuthSlice.ts
@@ -0,0 +1,19 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+const initialState = {
+ isAuthenticated: false,
+};
+
+const v2AuthSlice = createSlice({
+ name: "v2Auth",
+ initialState,
+ reducers: {
+ setIsAuthenticated: (state, action) => {
+ const { authenticated } = action.payload;
+ state.isAuthenticated = authenticated;
+ },
+ },
+});
+
+export default v2AuthSlice.reducer;
+export const { setIsAuthenticated } = v2AuthSlice.actions;
diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx
index fd4a08e93..2a5629917 100644
--- a/client/src/Pages/v2/Auth/Login.tsx
+++ b/client/src/Pages/v2/Auth/Login.tsx
@@ -3,7 +3,9 @@ import { z } from "zod";
import { useTranslation } from "react-i18next";
import { useForm, Controller } from "react-hook-form";
import { useTheme } from "@mui/material/styles";
-
+import { useDispatch } from "react-redux";
+import { setIsAuthenticated } from "@/Features/Auth/v2AuthSlice";
+import { useNavigate } from "react-router";
import { TextInput } from "@/Components/v2/Inputs/TextInput";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
@@ -19,8 +21,10 @@ type FormData = z.infer;
const Login = () => {
const { t } = useTranslation();
+ const dispatch = useDispatch();
const theme = useTheme();
const { post, loading, error } = usePost("/auth/login");
+ const navigate = useNavigate();
const {
handleSubmit,
@@ -37,9 +41,10 @@ const Login = () => {
const onSubmit = async (data: FormData) => {
const result = await post(data);
if (result) {
- console.log(result.message);
+ dispatch(setIsAuthenticated({ authenticated: true }));
+ navigate("/v2/uptime");
} else {
- console.error("Login failed:", error);
+ dispatch(setIsAuthenticated({ authenticated: false }));
}
};
diff --git a/client/src/Routes/v2router.tsx b/client/src/Routes/v2router.tsx
index 941550026..12d3eb595 100644
--- a/client/src/Routes/v2router.tsx
+++ b/client/src/Routes/v2router.tsx
@@ -25,7 +25,11 @@ const V2Routes = ({ mode = "light" }) => {
element={}
>
Uptime}
+ />
+ Test Page}
/>
diff --git a/client/src/Utils/Theme/v2/palette.ts b/client/src/Utils/Theme/v2/palette.ts
index 2e41a159c..855f7b6b8 100644
--- a/client/src/Utils/Theme/v2/palette.ts
+++ b/client/src/Utils/Theme/v2/palette.ts
@@ -54,6 +54,7 @@ export const lightPalette = {
main: colors.offWhite,
contrastText: colors.blueGray800,
contrastTextSecondary: colors.blueGray600,
+ contrastTextTertiary: colors.blueGray500,
lowContrast: colors.gray250,
},
secondary: {
@@ -78,6 +79,7 @@ export const darkPalette = {
main: colors.offBlack,
contrastText: colors.blueGray50,
contrastTextSecondary: colors.gray200,
+ contrastTextTertiary: colors.gray500,
lowContrast: colors.blueGray600,
},
secondary: {
diff --git a/client/src/store.js b/client/src/store.js
index 69aa3d50b..565d68519 100644
--- a/client/src/store.js
+++ b/client/src/store.js
@@ -1,6 +1,7 @@
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import authReducer from "./Features/Auth/authSlice";
+import v2AuthReducer from "./Features/Auth/v2AuthSlice";
import uiReducer from "./Features/UI/uiSlice";
import storage from "redux-persist/lib/storage";
import { persistReducer, persistStore, createTransform } from "redux-persist";
@@ -19,12 +20,13 @@ const authTransform = createTransform(
const persistConfig = {
key: "root",
storage,
- whitelist: ["auth", "ui"],
+ whitelist: ["auth", "v2Auth", "ui"],
transforms: [authTransform],
};
const rootReducer = combineReducers({
auth: authReducer,
+ v2Auth: v2AuthReducer,
ui: uiReducer,
});
diff --git a/client/src/types/mui.d.ts b/client/src/types/mui.d.ts
index 5f5c1c38b..84bcd799f 100644
--- a/client/src/types/mui.d.ts
+++ b/client/src/types/mui.d.ts
@@ -12,6 +12,7 @@ declare module "@mui/material/styles" {
interface PaletteColor {
contrastTextSecondary?: string;
+ contrastTextTertiary?: string;
lowContrast?: string;
}
}