mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-20 16:39:07 -05:00
Merge pull request #3005 from bluewave-labs/feat/v2/uptime
Feat/v2/uptime
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import React, { useState } from "react";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Settings from "@/assets/icons/settings-bold.svg?react";
|
||||
|
||||
export type ActionMenuItem = {
|
||||
id: number | string;
|
||||
label: React.ReactNode;
|
||||
action: Function;
|
||||
closeMenu?: boolean;
|
||||
};
|
||||
|
||||
export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => {
|
||||
const [anchorEl, setAnchorEl] = useState<null | any>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
const handleClick = (event: React.MouseEvent<any>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<IconButton onClick={handleClick}>
|
||||
<Settings />
|
||||
</IconButton>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
onClick={() => {
|
||||
if (item.closeMenu) handleClose();
|
||||
item.action();
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Box from "@mui/material/Box";
|
||||
import Background from "@/assets/Images/background-grid.svg?react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
|
||||
type StatusBoxProps = React.PropsWithChildren<{}>;
|
||||
|
||||
export const BGBox: React.FC<StatusBoxProps> = ({ children }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
position="relative"
|
||||
flex={1}
|
||||
border={1}
|
||||
bgcolor={theme.palette.primary.main}
|
||||
borderColor={theme.palette.primary.lowContrast}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
p={theme.spacing(8)}
|
||||
overflow="hidden"
|
||||
>
|
||||
<Box
|
||||
position="absolute"
|
||||
top="-10%"
|
||||
left="5%"
|
||||
>
|
||||
<Background />
|
||||
</Box>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const StatusBox = ({
|
||||
label,
|
||||
n,
|
||||
color,
|
||||
}: {
|
||||
label: string;
|
||||
n: number;
|
||||
color: string | undefined;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<BGBox>
|
||||
<Stack spacing={theme.spacing(8)}>
|
||||
<Typography
|
||||
variant={"h2"}
|
||||
textTransform="uppercase"
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h1"
|
||||
color={color}
|
||||
>
|
||||
{n}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</BGBox>
|
||||
);
|
||||
};
|
||||
|
||||
export const UpStatusBox = ({ n }: { n: number }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StatusBox
|
||||
label={t("monitorStatus.up")}
|
||||
n={n}
|
||||
color={theme.palette.success.lowContrast}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const DownStatusBox = ({ n }: { n: number }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StatusBox
|
||||
label={t("monitorStatus.down")}
|
||||
n={n}
|
||||
color={theme.palette.error.lowContrast}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const PausedStatusBox = ({ n }: { n: number }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StatusBox
|
||||
label={t("monitorStatus.paused")}
|
||||
n={n}
|
||||
color={theme.palette.warning.lowContrast}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Table from "@mui/material/Table";
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
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 { useTheme } from "@mui/material/styles";
|
||||
export type Header<T> = {
|
||||
id: number | string;
|
||||
content: React.ReactNode;
|
||||
onClick?: (event: React.MouseEvent<HTMLTableCellElement | null>, row: T) => void;
|
||||
render: (row: T) => React.ReactNode;
|
||||
};
|
||||
|
||||
type DataTableProps<T extends { id?: string | number; _id?: string | number }> = {
|
||||
headers: Header<T>[];
|
||||
data: T[];
|
||||
};
|
||||
|
||||
export function DataTable<T extends { id?: string | number; _id?: string | number }>({
|
||||
headers,
|
||||
data,
|
||||
}: DataTableProps<T>) {
|
||||
const theme = useTheme();
|
||||
if (data.length === 0 || headers.length === 0) return <div>No data</div>;
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table
|
||||
stickyHeader
|
||||
sx={{
|
||||
"&.MuiTable-root :is(.MuiTableHead-root, .MuiTableBody-root) :is(th, td)": {
|
||||
paddingLeft: theme.spacing(8),
|
||||
},
|
||||
"& :is(th)": {
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
color: theme.palette.secondary.contrastText,
|
||||
fontWeight: 600,
|
||||
},
|
||||
"& :is(td)": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
},
|
||||
"& .MuiTableBody-root .MuiTableRow-root:last-child .MuiTableCell-root": {
|
||||
borderBottom: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{headers.map((header, idx) => {
|
||||
return (
|
||||
<TableCell
|
||||
align={idx === 0 ? "left" : "center"}
|
||||
key={header.id}
|
||||
>
|
||||
{header.content}
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.map((row) => {
|
||||
const key = row.id || row._id || Math.random();
|
||||
|
||||
return (
|
||||
<TableRow key={key}>
|
||||
{headers.map((header, index) => {
|
||||
return (
|
||||
<TableCell
|
||||
align={index === 0 ? "left" : "center"}
|
||||
key={header.id}
|
||||
onClick={
|
||||
header.onClick ? (e) => header.onClick!(e, row) : undefined
|
||||
}
|
||||
>
|
||||
{header.render(row)}
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
export { SplitBox as HorizontalSplitBox, ConfigBox } from "./SplitBox";
|
||||
export { BasePage } from "./BasePage";
|
||||
export { BGBox, UpStatusBox, DownStatusBox, PausedStatusBox } from "./StatusBox";
|
||||
export { DataTable as Table } from "./Table";
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import Button from "@mui/material/Button";
|
||||
import type { ButtonProps } from "@mui/material/Button";
|
||||
export const ButtonInput: React.FC<ButtonProps> = ({ ...props }) => {
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
sx={{ textTransform: "none", height: 34, fontWeight: 400, borderRadius: 2 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { ButtonInput as Button } from "./Button";
|
||||
@@ -6,6 +6,7 @@ const RootLayout = () => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
overflow={"hidden"}
|
||||
direction="row"
|
||||
minHeight="100vh"
|
||||
>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { Button } from "@/Components/v2/Inputs";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router";
|
||||
export const HeaderCreate = ({
|
||||
label,
|
||||
isLoading,
|
||||
path,
|
||||
}: {
|
||||
label?: string;
|
||||
isLoading: boolean;
|
||||
path: string;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="end"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => navigate(path)}
|
||||
>
|
||||
{label || t("createNew")}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Box from "@mui/material/Box";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import type { Check } from "@/Types/Check";
|
||||
import { HistogramResponseTimeTooltip } from "@/Components/v2/Monitors/HistogramResponseTimeTooltip";
|
||||
|
||||
export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => {
|
||||
let data = Array<any>();
|
||||
|
||||
data = checks.map((c) => Math.max(c.responseTime, 1));
|
||||
|
||||
const logResponses = data.map((r) => Math.log10(r));
|
||||
const logMin = Math.min(...logResponses);
|
||||
const logMax = Math.max(...logResponses);
|
||||
|
||||
if (!checks) {
|
||||
return null;
|
||||
}
|
||||
if (checks.length !== 25) {
|
||||
const placeholders = Array(25 - checks.length).fill("placeholder");
|
||||
data = [...checks, ...placeholders];
|
||||
} else {
|
||||
data = checks;
|
||||
}
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
flexWrap="nowrap"
|
||||
gap={theme.spacing(1.5)}
|
||||
height="50px"
|
||||
width="fit-content"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
sx={{
|
||||
cursor: "default",
|
||||
}}
|
||||
>
|
||||
{data.map((check, index) => {
|
||||
const safeResponse = Math.max(check.responseTime, 1);
|
||||
const logValue = Math.log10(safeResponse);
|
||||
const minHeight = 10;
|
||||
const barHeight =
|
||||
logMax === logMin
|
||||
? 100
|
||||
: Math.max(minHeight, ((logValue - logMin) / (logMax - logMin)) * 100);
|
||||
|
||||
if (check === "placeholder") {
|
||||
return (
|
||||
<Box
|
||||
key={`${check}-${index}`}
|
||||
position="relative"
|
||||
width={theme.spacing(4.5)}
|
||||
height="100%"
|
||||
bgcolor={theme.palette.primary.lowContrast}
|
||||
sx={{
|
||||
borderRadius: theme.spacing(1.5),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<HistogramResponseTimeTooltip
|
||||
key={`${check}-${index}`}
|
||||
check={check}
|
||||
>
|
||||
<Box
|
||||
position="relative"
|
||||
width="9px"
|
||||
height="100%"
|
||||
bgcolor={theme.palette.primary.lowContrast}
|
||||
sx={{
|
||||
borderRadius: theme.spacing(1.5),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position="absolute"
|
||||
bottom={0}
|
||||
width="100%"
|
||||
height={`${barHeight}%`}
|
||||
bgcolor={
|
||||
check.status
|
||||
? theme.palette.success.lowContrast
|
||||
: theme.palette.error.lowContrast
|
||||
}
|
||||
sx={{
|
||||
borderRadius: theme.spacing(1.5),
|
||||
transition: "height 600ms cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</HistogramResponseTimeTooltip>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
import { useSelector } from "react-redux";
|
||||
import type { LatestCheck } from "@/Types/Check";
|
||||
|
||||
export const HistogramResponseTimeTooltip: React.FC<{
|
||||
children: React.ReactElement;
|
||||
check: LatestCheck;
|
||||
}> = ({ children, check }) => {
|
||||
const uiTimezone = useSelector((state: any) => state.ui.timezone);
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
<Stack>
|
||||
<Typography>
|
||||
{formatDateWithTz(check?.checkedAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
|
||||
</Typography>
|
||||
{check?.responseTime && (
|
||||
<Typography>Response Time: {check.responseTime} ms</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { HeaderCreate } from "./HeaderCreate";
|
||||
@@ -0,0 +1,131 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { HistogramResponseTime } from "@/Components/v2/Monitors/HistogramResponseTime";
|
||||
import type { Header } from "@/Components/v2/DesignElements/Table";
|
||||
import type { IMonitor } from "@/Types/Monitor";
|
||||
import { Table } from "@/Components/v2/DesignElements";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMediaQuery } from "@mui/material";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { ActionsMenu } from "@/Components/v2/ActionsMenu";
|
||||
import type { ActionMenuItem } from "@/Components/v2/ActionsMenu";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
const getActions = (theme: any): ActionMenuItem[] => {
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
label: "Open site",
|
||||
action: () => {
|
||||
console.log("Open site");
|
||||
},
|
||||
closeMenu: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: "Details",
|
||||
action: () => {
|
||||
console.log("Open details");
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: "Incidents",
|
||||
action: () => {
|
||||
console.log("Open incidents");
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
label: "Configure",
|
||||
action: () => {
|
||||
console.log("Open configure");
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
label: "Clone",
|
||||
action: () => {
|
||||
console.log("Open clone");
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
label: "Pause",
|
||||
action: () => {
|
||||
console.log("Open pause");
|
||||
},
|
||||
closeMenu: true,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
label: <Typography color={theme.palette.error.main}>Remove</Typography>,
|
||||
action: () => {
|
||||
console.log("Open delete");
|
||||
},
|
||||
closeMenu: true,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const getHeaders = (theme: any, t: Function) => {
|
||||
const headers: Header<IMonitor>[] = [
|
||||
{
|
||||
id: "name",
|
||||
content: t("host"),
|
||||
render: (row) => {
|
||||
return row.name;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
content: t("status"),
|
||||
render: (row) => {
|
||||
return row.status;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "histogram",
|
||||
content: t("responseTime"),
|
||||
render: (row) => {
|
||||
return (
|
||||
<Stack alignItems={"center"}>
|
||||
<HistogramResponseTime checks={row.latestChecks} />
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "type",
|
||||
content: t("type"),
|
||||
render: (row) => {
|
||||
return row.type;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
content: t("actions"),
|
||||
render: () => {
|
||||
return <ActionsMenu items={getActions(theme)} />;
|
||||
},
|
||||
},
|
||||
];
|
||||
return headers;
|
||||
};
|
||||
|
||||
export const MonitorTable = ({ monitors }: { monitors: IMonitor[] }) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
||||
|
||||
let headers = getHeaders(theme, t);
|
||||
|
||||
if (isSmall) {
|
||||
headers = headers.filter((h) => h.id !== "histogram");
|
||||
}
|
||||
return (
|
||||
<Table
|
||||
headers={headers}
|
||||
data={monitors}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
BasePage,
|
||||
UpStatusBox,
|
||||
DownStatusBox,
|
||||
PausedStatusBox,
|
||||
} from "@/Components/v2/DesignElements";
|
||||
import { HeaderCreate } from "@/Components/v2/Monitors";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { MonitorTable } from "@/Pages/v2/Uptime/MonitorTable";
|
||||
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useGet } from "@/Hooks/v2/UseApi";
|
||||
import type { ApiResponse } from "@/Hooks/v2/UseApi";
|
||||
import type { IMonitor } from "@/Types/Monitor";
|
||||
import { useMediaQuery } from "@mui/material";
|
||||
|
||||
const UptimeMonitors = () => {
|
||||
const theme = useTheme();
|
||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
||||
|
||||
const { response, loading } = useGet<ApiResponse>("/monitors?embedChecks=true");
|
||||
const monitors = response?.data ?? ([] as IMonitor[]);
|
||||
|
||||
if (monitors.length === 0 && !loading) {
|
||||
return "No monitors found";
|
||||
}
|
||||
|
||||
return (
|
||||
<BasePage>
|
||||
<HeaderCreate
|
||||
isLoading={loading}
|
||||
path="/v2/uptime/create"
|
||||
/>
|
||||
<Stack
|
||||
direction={isSmall ? "column" : "row"}
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<UpStatusBox n={1} />
|
||||
<DownStatusBox n={1} />
|
||||
<PausedStatusBox n={1} />
|
||||
</Stack>
|
||||
<MonitorTable monitors={monitors} />
|
||||
</BasePage>
|
||||
);
|
||||
};
|
||||
|
||||
export default UptimeMonitors;
|
||||
@@ -4,6 +4,7 @@ import { lightTheme, darkTheme } from "@/Utils/Theme/v2/theme";
|
||||
|
||||
import AuthLoginV2 from "@/Pages/v2/Auth/Login";
|
||||
import AuthRegisterV2 from "@/Pages/v2/Auth/Register";
|
||||
import UptimeMonitorsPage from "@/Pages/v2/Uptime/UptimeMonitors";
|
||||
import CreateUptimePage from "@/Pages/v2/Uptime/Create";
|
||||
import RootLayout from "@/Components/v2/Layouts/RootLayout";
|
||||
|
||||
@@ -27,11 +28,11 @@ const V2Routes = ({ mode = "light" }) => {
|
||||
>
|
||||
<Route
|
||||
index
|
||||
element={<h1>Uptime</h1>}
|
||||
element={<UptimeMonitorsPage />}
|
||||
/>
|
||||
<Route
|
||||
path="uptime"
|
||||
element={<h1>Test Page</h1>}
|
||||
element={<UptimeMonitorsPage />}
|
||||
/>
|
||||
<Route
|
||||
path="uptime/create"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
export interface Check {
|
||||
_id: string;
|
||||
status: string;
|
||||
responseTime: number;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface GroupedCheck {
|
||||
_id: string;
|
||||
avgResponseTime: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface LatestCheck {
|
||||
status: string;
|
||||
responseTime: number;
|
||||
checkedAt: string;
|
||||
_id: string;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { Check } from "@/Types/Check";
|
||||
|
||||
export interface IMonitor {
|
||||
checks: Check[];
|
||||
createdAt: string;
|
||||
createdBy: string;
|
||||
interval: number;
|
||||
isActive: boolean;
|
||||
latestChecks: Check[];
|
||||
n: number;
|
||||
name: string;
|
||||
status: string;
|
||||
type: string;
|
||||
updatedAt: string;
|
||||
updatedBy: string;
|
||||
url: string;
|
||||
__v: number;
|
||||
_id: string;
|
||||
}
|
||||
@@ -58,8 +58,8 @@ const baseTheme = (palette) => ({
|
||||
variants: [
|
||||
{
|
||||
props: (props) => props.variant === "contained" && props.color === "accent",
|
||||
backgroundColor: theme.palette.accent.main,
|
||||
style: {
|
||||
backgroundColor: theme.palette.accent.main,
|
||||
color: theme.palette.primary.contrastTextSecondaryDarkBg,
|
||||
letterSpacing: "0.5px",
|
||||
textShadow: "0 0 1px rgba(0, 0, 0, 0.15)",
|
||||
|
||||
@@ -66,6 +66,21 @@ export const lightPalette = {
|
||||
main: colors.gray100,
|
||||
contrastText: colors.blueGray800,
|
||||
},
|
||||
success: {
|
||||
main: colors.green700,
|
||||
contrastText: colors.offWhite,
|
||||
lowContrast: colors.green400,
|
||||
},
|
||||
warning: {
|
||||
main: colors.orange700,
|
||||
contrastText: colors.offWhite,
|
||||
lowContrast: colors.orange100,
|
||||
},
|
||||
error: {
|
||||
main: colors.red700,
|
||||
contrastText: colors.offWhite,
|
||||
lowContrast: colors.red400,
|
||||
},
|
||||
};
|
||||
|
||||
export const darkPalette = {
|
||||
@@ -91,4 +106,19 @@ export const darkPalette = {
|
||||
main: colors.blueGray800,
|
||||
contrastText: colors.gray100,
|
||||
},
|
||||
success: {
|
||||
main: colors.green100,
|
||||
contrastText: colors.offBlack,
|
||||
lowContrast: colors.green200,
|
||||
},
|
||||
warning: {
|
||||
main: colors.orange200,
|
||||
contrastText: colors.offBlack,
|
||||
lowContrast: colors.orange600,
|
||||
},
|
||||
error: {
|
||||
main: colors.red100,
|
||||
contrastText: colors.offBlack,
|
||||
lowContrast: colors.red600,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { createTheme } from "@mui/material";
|
||||
import { lightPalette, darkPalette, typographyLevels } from "./palette";
|
||||
const fontFamilyPrimary = '"Inter" , sans-serif';
|
||||
const shadow =
|
||||
"0px 4px 24px -4px rgba(16, 24, 40, 0.08), 0px 3px 3px -3px rgba(16, 24, 40, 0.03)";
|
||||
|
||||
export const theme = (mode: string, palette: any) =>
|
||||
createTheme({
|
||||
@@ -66,6 +68,24 @@ export const theme = (mode: string, palette: any) =>
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
MuiPaper: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => {
|
||||
return {
|
||||
marginTop: 4,
|
||||
padding: 0,
|
||||
border: 1,
|
||||
borderStyle: "solid",
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderRadius: 4,
|
||||
boxShadow: shadow,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
backgroundImage: "none",
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 2,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import dayjs from "dayjs";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(duration);
|
||||
|
||||
export const MS_PER_SECOND = 1000;
|
||||
export const MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
||||
export const MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
||||
export const MS_PER_DAY = 24 * MS_PER_HOUR;
|
||||
export const MS_PER_WEEK = MS_PER_DAY * 7;
|
||||
|
||||
export const formatDateWithTz = (timestamp: string, format: string, timezone: string) => {
|
||||
if (!timestamp) {
|
||||
return "Unknown time";
|
||||
}
|
||||
|
||||
const formattedDate = dayjs(timestamp).tz(timezone).format(format);
|
||||
return formattedDate;
|
||||
};
|
||||
Reference in New Issue
Block a user