diff --git a/client/src/Components/v1/FilterHeader/index.jsx b/client/src/Components/v1/FilterHeader/index.jsx
deleted file mode 100644
index de2c7f0e9..000000000
--- a/client/src/Components/v1/FilterHeader/index.jsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import { Checkbox, FormControl, ListItemText, MenuItem, Select } from "@mui/material";
-import { useTheme } from "@emotion/react";
-import PropTypes from "prop-types";
-import Icon from "../Icon";
-
-/**
- * A reusable filter header component that displays a dropdown menu with selectable options.
- *
- * @component
- * @param {Object} props - The component props.
- * @param {string} props.header - The header text to display when no options are selected.
- * @param {Array} props.options - An array of options to display in the dropdown menu. Each option should have a `value` and `label`.
- * @param {Array} [props.value] - The currently selected values.
- * @param {Function} props.onChange - The callback function to handle changes in the selected values.
- * @param {boolean} [props.multiple=true] - Whether multiple options can be selected.
- * @returns {JSX.Element} The rendered FilterHeader component.
- */
-
-const FilterHeader = ({ header, options, value, onChange, multiple = true }) => {
- const theme = useTheme();
-
- const controlledValue = value === undefined ? [] : value; // Ensure value is always treated as an array for controlled component purposes
-
- return (
-
-
-
- );
-};
-
-FilterHeader.propTypes = {
- header: PropTypes.string.isRequired,
- options: PropTypes.arrayOf(
- PropTypes.shape({
- value: PropTypes.string.isRequired,
- label: PropTypes.string.isRequired,
- })
- ).isRequired,
- value: PropTypes.arrayOf(PropTypes.string),
- onChange: PropTypes.func.isRequired,
- multiple: PropTypes.bool,
-};
-
-export default FilterHeader;
diff --git a/client/src/Components/v1/MonitorActions/index.jsx b/client/src/Components/v1/MonitorActions/index.jsx
deleted file mode 100644
index f63fc23fe..000000000
--- a/client/src/Components/v1/MonitorActions/index.jsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import * as React from "react";
-import Button from "@mui/material/Button";
-import ButtonGroup from "@mui/material/ButtonGroup";
-import Icon from "../Icon";
-import ClickAwayListener from "@mui/material/ClickAwayListener";
-import Grow from "@mui/material/Grow";
-import Paper from "@mui/material/Paper";
-import Popper from "@mui/material/Popper";
-import MenuItem from "@mui/material/MenuItem";
-import MenuList from "@mui/material/MenuList";
-import { useNavigate } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-import { createToast } from "../../../Utils/toastUtils.jsx";
-import { useExportMonitors } from "../../../Hooks/monitorHooks.js";
-
-const MonitorActions = ({ isLoading }) => {
- const [open, setOpen] = React.useState(false);
- const anchorRef = React.useRef(null);
- const [selectedIndex, setSelectedIndex] = React.useState(0);
- const navigate = useNavigate();
- const { t } = useTranslation();
- const [exportMonitors, isExporting] = useExportMonitors();
-
- const options = [t("monitorActions.import"), t("monitorActions.export")];
-
- const handleClick = async () => {
- if (selectedIndex === 0) {
- // Import
- navigate("/uptime/bulk-import");
- } else {
- // Export
- const [success, error] = await exportMonitors();
- if (!success) {
- createToast({ body: error || t("export.failed") });
- }
- }
- };
-
- const handleMenuItemClick = (event, index) => {
- setSelectedIndex(index);
- setOpen(false);
- };
-
- const handleToggle = () => {
- setOpen((prevOpen) => !prevOpen);
- };
-
- const handleClose = (event) => {
- if (anchorRef.current && anchorRef.current.contains(event.target)) {
- return;
- }
- setOpen(false);
- };
-
- return (
-
-
-
-
-
-
- {({ TransitionProps, placement }) => (
-
-
-
-
-
-
-
- )}
-
-
- );
-};
-
-export default MonitorActions;
diff --git a/client/src/Components/v1/MonitorCountHeader/index.jsx b/client/src/Components/v1/MonitorCountHeader/index.jsx
deleted file mode 100644
index eba644d26..000000000
--- a/client/src/Components/v1/MonitorCountHeader/index.jsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { Stack, Typography } from "@mui/material";
-import PropTypes from "prop-types";
-import { useTheme } from "@emotion/react";
-import SkeletonLayout from "./skeleton.jsx";
-
-const MonitorCountHeader = ({
- isLoading = false,
- monitorCount,
- heading = "monitors",
- sx,
- children,
-}) => {
- const theme = useTheme();
- if (isLoading) return ;
-
- if (monitorCount === 1) {
- heading = "monitor";
- }
-
- return (
-
- {monitorCount} {heading}
- {children}
-
- );
-};
-
-MonitorCountHeader.propTypes = {
- isLoading: PropTypes.bool,
- monitorCount: PropTypes.number,
- heading: PropTypes.string,
- children: PropTypes.node,
- sx: PropTypes.object,
-};
-
-export default MonitorCountHeader;
diff --git a/client/src/Components/v1/MonitorCountHeader/skeleton.jsx b/client/src/Components/v1/MonitorCountHeader/skeleton.jsx
deleted file mode 100644
index 9b4971c32..000000000
--- a/client/src/Components/v1/MonitorCountHeader/skeleton.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Stack, Skeleton } from "@mui/material";
-import { useTheme } from "@emotion/react";
-
-const SkeletonLayout = () => {
- const theme = useTheme();
- return (
-
-
-
-
- );
-};
-
-export default SkeletonLayout;
diff --git a/client/src/Components/v1/MonitorCreateHeader/index.jsx b/client/src/Components/v1/MonitorCreateHeader/index.jsx
index 90c12f5ec..c3a979eb9 100644
--- a/client/src/Components/v1/MonitorCreateHeader/index.jsx
+++ b/client/src/Components/v1/MonitorCreateHeader/index.jsx
@@ -3,7 +3,6 @@ import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
-import MonitorActions from "../MonitorActions/index.jsx";
const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath }) => {
const navigate = useNavigate();
diff --git a/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx
deleted file mode 100644
index 0793e7149..000000000
--- a/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import Stack from "@mui/material/Stack";
-import Status from "./status.jsx";
-import Skeleton from "./skeleton.jsx";
-import Button from "@mui/material/Button";
-import { Tooltip } from "@mui/material";
-import Icon from "../Icon";
-
-// Utils
-import PropTypes from "prop-types";
-import { useNavigate } from "react-router-dom";
-import { useTheme } from "@mui/material/styles";
-import { usePauseMonitor } from "../../../Hooks/monitorHooks.js";
-import { useSendTestEmail } from "../../../Hooks/useSendTestEmail.js";
-import { useTranslation } from "react-i18next";
-import { useTestAllNotifications } from "../../../Hooks/useNotifications.js";
-/**
- * MonitorDetailsControlHeader component displays the control header for monitor details.
- * It includes status display, pause/resume button, and a configure button for admins.
- *
- * @component
- * @param {Object} props - Component props
- * @param {string} props.path - The base path for navigation
- * @param {boolean} [props.isLoading=false] - Flag indicating if the data is loading
- * @param {boolean} [props.isAdmin=false] - Flag indicating if the user is an admin
- * @param {Object} props.monitor - The monitor object containing details
- * @param {Function} props.triggerUpdate - Function to trigger an update
- * @returns {JSX.Element} The rendered component
- */
-const MonitorDetailsControlHeader = ({
- path,
- isLoading = false,
- isAdmin = false,
- monitor,
- triggerUpdate,
-}) => {
- const navigate = useNavigate();
- const theme = useTheme();
- const { t } = useTranslation();
- const [pauseMonitor, isPausing, error] = usePauseMonitor();
-
- const isTestNotificationsDisabled = monitor?.notifications?.length === 0;
-
- const tooltipTitle = isTestNotificationsDisabled ? t("testNotificationsDisabled") : "";
-
- // const [isSending, emailError, sendTestEmail] = useSendTestEmail();
-
- const [testAllNotifications, isSending, errorAllNotifications] =
- useTestAllNotifications();
-
- if (isLoading) {
- return ;
- }
-
- return (
-
-
-
-
-
-
-
- }
- disabled={isTestNotificationsDisabled}
- onClick={() => {
- testAllNotifications({ monitorId: monitor?.id });
- }}
- sx={{
- whiteSpace: "nowrap",
- }}
- >
- {t("sendTestNotifications")}
-
-
-
-
- {isAdmin && (
-
- ) : (
-
- )
- }
- onClick={() => {
- pauseMonitor({
- monitorId: monitor?.id,
- triggerUpdate,
- });
- }}
- >
- {monitor?.isActive ? t("pause") : t("resume")}
-
- )}
- {isAdmin && (
-
- }
- onClick={() => navigate(`/${path}/configure/${monitor.id}`)}
- >
- Configure
-
- )}
-
-
- );
-};
-
-MonitorDetailsControlHeader.propTypes = {
- path: PropTypes.string,
- isLoading: PropTypes.bool,
- isAdmin: PropTypes.bool,
- monitor: PropTypes.object,
- triggerUpdate: PropTypes.func,
-};
-
-export default MonitorDetailsControlHeader;
diff --git a/client/src/Components/v1/MonitorDetailsControlHeader/skeleton.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/skeleton.jsx
deleted file mode 100644
index 64dc0547f..000000000
--- a/client/src/Components/v1/MonitorDetailsControlHeader/skeleton.jsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Stack, Skeleton } from "@mui/material";
-
-const SkeletonLayout = () => {
- return (
-
-
-
-
- );
-};
-
-export default SkeletonLayout;
diff --git a/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx
deleted file mode 100644
index c6a3cfcfa..000000000
--- a/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx
+++ /dev/null
@@ -1,51 +0,0 @@
-// Components
-import Stack from "@mui/material/Stack";
-import PulseDot from "../Animated/PulseDot.jsx";
-import Typography from "@mui/material/Typography";
-import Dot from "../Dot/index.jsx";
-// Utils
-import { formatDurationRounded } from "../../../Utils/timeUtilsLegacy.js";
-import PropTypes from "prop-types";
-import { useTheme } from "@emotion/react";
-import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
-import { formatMonitorUrl } from "../../../Utils/utils.js";
-/**
- * Status component displays the status information of a monitor.
- * It includes the monitor's name, URL, and check interval.
- *
- * @component
- * @param {Object} props - Component props
- * @param {Object} props.monitor - The monitor object containing details
- * @param {string} props.monitor.name - The name of the monitor
- * @param {string} props.monitor.url - The URL of the monitor
- * @param {number} props.monitor.interval - The interval at which the monitor checks
- * @returns {JSX.Element} The rendered component
- */
-const Status = ({ monitor }) => {
- const theme = useTheme();
- const { statusColor, determineState } = useMonitorUtils();
-
- return (
-
- {monitor?.name}
-
-
- {formatMonitorUrl(monitor?.url)}
-
-
- Checking every {formatDurationRounded(monitor?.interval)}.
-
-
-
- );
-};
-
-Status.propTypes = {
- monitor: PropTypes.object,
-};
-
-export default Status;
diff --git a/client/src/Components/v1/NotificationConfig/index.jsx b/client/src/Components/v1/NotificationConfig/index.jsx
deleted file mode 100644
index f1ed30ad1..000000000
--- a/client/src/Components/v1/NotificationConfig/index.jsx
+++ /dev/null
@@ -1,127 +0,0 @@
-// Components
-import Stack from "@mui/material/Stack";
-import Typography from "@mui/material/Typography";
-import Divider from "@mui/material/Divider";
-import Icon from "../Icon";
-import Search from "../Inputs/Search/index.jsx";
-
-// Utils
-import { useState, useEffect } from "react";
-import { useTheme } from "@mui/material/styles";
-import PropTypes from "prop-types";
-
-const NotificationConfig = ({
- notifications,
- setMonitor,
- setNotifications,
- //FieldWrapper's props
- gap,
- labelMb,
- labelFontWeight,
- labelVariant,
- labelSx = {},
- sx = {},
-}) => {
- // Local state
- const [notificationsSearch, setNotificationsSearch] = useState("");
- const [selectedNotifications, setSelectedNotifications] = useState([]);
-
- const handleSearch = (value) => {
- setSelectedNotifications(value);
- setMonitor((prev) => {
- return {
- ...prev,
- notifications: value.map((notification) => notification.id),
- };
- });
- };
-
- // Handlers
- const handleDelete = (id) => {
- const updatedNotifications = selectedNotifications.filter(
- (notification) => notification.id !== id
- );
-
- setSelectedNotifications(updatedNotifications);
- setMonitor((prev) => {
- return {
- ...prev,
- notifications: updatedNotifications.map((notification) => notification.id),
- };
- });
- };
-
- // Setup
- const theme = useTheme();
-
- useEffect(() => {
- if (setNotifications) {
- const toSet = setNotifications.map((notification) => {
- return notifications.find((n) => n.id === notification);
- });
- setSelectedNotifications(toSet);
- }
- }, [setNotifications, notifications]);
-
- return (
-
- {
- handleSearch(value);
- }}
- labelMb={labelMb}
- labelVariant={labelVariant}
- labelFontWeight={labelFontWeight}
- labelSx={labelSx}
- gap={gap}
- sx={{
- ...sx,
- }}
- />
-
- {selectedNotifications.map((notification, index) => (
-
-
- {notification.notificationName}
-
- {
- handleDelete(notification.id);
- }}
- style={{ cursor: "pointer" }}
- />
- {index < selectedNotifications.length - 1 && }
-
- ))}
-
-
- );
-};
-
-NotificationConfig.propTypes = {
- notifications: PropTypes.array,
- setMonitor: PropTypes.func,
- setNotifications: PropTypes.array,
-};
-
-export default NotificationConfig;
diff --git a/client/src/Components/v2/common/index.ts b/client/src/Components/v2/common/index.ts
index d0d1262b7..2ea3bf3f7 100644
--- a/client/src/Components/v2/common/index.ts
+++ b/client/src/Components/v2/common/index.ts
@@ -2,5 +2,4 @@ export * from "../common/charts/HistogramResponseTime";
export * from "../common/charts/HeatmapResponseTime";
export * from "../common/charts/HeatmapResponseTimeTooltip";
export * from "../common/controls/HeaderCreate";
-export * from "../common/controls/HeaderMonitorControls";
export * from "../common/controls/HeaderTimeRange";
diff --git a/client/src/Components/v2/inputs/Radio.tsx b/client/src/Components/v2/inputs/Radio.tsx
new file mode 100644
index 000000000..ad8dc3374
--- /dev/null
+++ b/client/src/Components/v2/inputs/Radio.tsx
@@ -0,0 +1,88 @@
+import Radio from "@mui/material/Radio";
+import type { RadioProps } from "@mui/material/Radio";
+import { useTheme } from "@mui/material/styles";
+import { Circle, CircleDot } from "lucide-react";
+import FormControlLabel from "@mui/material/FormControlLabel";
+import Typography from "@mui/material/Typography";
+
+interface RadioInputProps extends RadioProps {}
+
+export const RadioInput = ({ ...props }: RadioInputProps) => {
+ const theme = useTheme();
+ return (
+
+ }
+ checkedIcon={
+
+ }
+ sx={{
+ padding: 0,
+ mt: theme.spacing(0.5),
+ color: theme.palette.text.secondary,
+ "&.Mui-checked": {
+ color: theme.palette.primary.main,
+ "& svg circle": {
+ fill: theme.palette.primary.main,
+ },
+ },
+ "& .MuiSvgIcon-root": {
+ fontSize: 16,
+ },
+ "& svg": {
+ stroke: "currentColor",
+ },
+ "& svg path, & svg line, & svg polyline, & svg rect, & svg circle": {
+ stroke: "currentColor",
+ fill: "none",
+ },
+ }}
+ />
+ );
+};
+
+export const RadioWithDescription = ({
+ label,
+ description,
+ ...props
+}: RadioInputProps & { label: string; description: string }) => {
+ const theme = useTheme();
+ return (
+ }
+ label={
+ <>
+ {label}
+
+ {description}
+
+ >
+ }
+ sx={{
+ alignItems: "flex-start",
+ p: theme.spacing(2.5),
+ m: theme.spacing(-2.5),
+ borderRadius: theme.shape.borderRadius,
+
+ "&:hover": {
+ backgroundColor: theme.palette.background.paper,
+ },
+ "& .MuiButtonBase-root": {
+ p: 0,
+ mr: theme.spacing(6),
+ },
+ }}
+ />
+ );
+};
diff --git a/client/src/Components/v2/inputs/Select.tsx b/client/src/Components/v2/inputs/Select.tsx
index 057b0e72c..b9675acd7 100644
--- a/client/src/Components/v2/inputs/Select.tsx
+++ b/client/src/Components/v2/inputs/Select.tsx
@@ -1,7 +1,7 @@
import Select from "@mui/material/Select";
-import React from "react";
-import { useTheme } from "@mui/material/styles";
import type { SelectProps } from "@mui/material/Select";
+import React, { forwardRef } from "react";
+import { useTheme } from "@mui/material/styles";
import Stack from "@mui/material/Stack";
import { FieldLabel } from "./FieldLabel";
import { ChevronDown } from "lucide-react";
@@ -14,18 +14,15 @@ interface SelectInputProps extends Omit, "label"> {
placeholderColor?: string;
}
-export const SelectInput = ({
- fieldLabel,
- required,
- placeholder,
- placeholderColor,
- ...props
-}: SelectInputProps) => {
+const SelectInputInner = (
+ { fieldLabel, required, placeholder, placeholderColor, ...props }: SelectInputProps,
+ ref: React.ForwardedRef
+) => {
const theme = useTheme();
const emptyPlaceholderColor = placeholderColor || theme.palette.text.disabled;
- const renderValue = (selected: any) => {
- const isMultiple = Boolean((props as any).multiple);
+ const renderValue = (selected: unknown) => {
+ const isMultiple = Boolean((props as { multiple?: boolean }).multiple);
const isEmpty = isMultiple
? !Array.isArray(selected) || selected.length === 0
: selected === undefined || selected === null || selected === "";
@@ -39,7 +36,7 @@ export const SelectInput = ({
const capitalized = items.map(
(item) => item.charAt(0).toUpperCase() + item.slice(1)
);
- return ({capitalized.join(" | ")}) as any;
+ return {capitalized.join(" | ")};
}
const nodes = React.Children.toArray(props.children as React.ReactNode);
@@ -59,6 +56,7 @@ export const SelectInput = ({
const select = (