diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx index 8c4630b19..9809ac710 100644 --- a/Client/src/Components/TabPanels/Account/TeamPanel.jsx +++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx @@ -1,25 +1,13 @@ import { useTheme } from "@emotion/react"; import TabPanel from "@mui/lab/TabPanel"; -import { - Box, - Button, - ButtonGroup, - Divider, - IconButton, - Modal, - Stack, - TextField, - Typography, -} from "@mui/material"; +import { Button, ButtonGroup, Modal, Stack, Typography } from "@mui/material"; import { useEffect, useState } from "react"; -import EditSvg from "../../../assets/icons/edit.svg?react"; import Field from "../../Inputs/Field"; import { credentials } from "../../../Validation/validation"; import { networkService } from "../../../main"; import { createToast } from "../../../Utils/toastUtils"; import { useSelector } from "react-redux"; import BasicTable from "../../BasicTable"; -import Remove from "../../../assets/icons/trash-bin.svg?react"; import Select from "../../Inputs/Select"; import LoadingButton from "@mui/lab/LoadingButton"; @@ -54,6 +42,7 @@ const TeamPanel = () => { const [members, setMembers] = useState([]); const [filter, setFilter] = useState("all"); const [errors, setErrors] = useState({}); + const [isSendingInvite, setIsSendingInvite] = useState(false); useEffect(() => { const fetchTeam = async () => { @@ -173,6 +162,7 @@ const TeamPanel = () => { }; const handleInviteMember = async () => { + setIsSendingInvite(true); if (!toInvite.role.includes("user") || !toInvite.role.includes("admin")) setToInvite((prev) => ({ ...prev, role: ["user"] })); @@ -185,24 +175,28 @@ const TeamPanel = () => { if (error) { setErrors((prev) => ({ ...prev, email: error.details[0].message })); - } else - try { - await networkService.requestInvitationToken( - authToken, - toInvite.email, - toInvite.role - ); + return; + } - closeInviteModal(); - createToast({ - body: "Member invited. They will receive an email with details on how to create their account.", - }); - } catch (error) { - createToast({ - body: error.message || "Unknown error.", - }); - } + try { + await networkService.requestInvitationToken( + authToken, + toInvite.email, + toInvite.role + ); + closeInviteModal(); + createToast({ + body: "Member invited. They will receive an email with details on how to create their account.", + }); + } catch (error) { + createToast({ + body: error.message || "Unknown error.", + }); + } finally { + setIsSendingInvite(false); + } }; + const closeInviteModal = () => { setIsOpen(false); setToInvite({ email: "", role: ["0"] }); @@ -307,13 +301,14 @@ const TeamPanel = () => { - + { mt={theme.spacing(8)} justifyContent="flex-end" > - + Send invite diff --git a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js index e32d673cc..317ab1320 100644 --- a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js +++ b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js @@ -28,6 +28,26 @@ export const createPageSpeed = createAsyncThunk( } ); +export const getPagespeedMonitorById = createAsyncThunk( + "monitors/getMonitorById", + async (data, thunkApi) => { + try { + const { authToken, monitorId } = data; + const res = await networkService.getMonitorByid(authToken, monitorId); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + export const getPageSpeedByTeamId = createAsyncThunk( "pageSpeedMonitors/getPageSpeedByTeamId", async (token, thunkApi) => { @@ -109,6 +129,25 @@ export const deletePageSpeed = createAsyncThunk( } } ); +export const pausePageSpeed = createAsyncThunk( + "pageSpeedMonitors/pausePageSpeed", + async (data, thunkApi) => { + try { + const { authToken, monitorId } = data; + const res = await networkService.pauseMonitorById(authToken, monitorId); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); const pageSpeedMonitorSlice = createSlice({ name: "pageSpeedMonitor", @@ -124,7 +163,7 @@ const pageSpeedMonitorSlice = createSlice({ extraReducers: (builder) => { builder // ***************************************************** - // Monitors by userId + // Monitors by teamId // ***************************************************** .addCase(getPageSpeedByTeamId.pending, (state) => { @@ -143,6 +182,23 @@ const pageSpeedMonitorSlice = createSlice({ : "Getting page speed monitors failed"; }) + // ***************************************************** + .addCase(getPagespeedMonitorById.pending, (state) => { + state.isLoading = true; + }) + .addCase(getPagespeedMonitorById.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(getPagespeedMonitorById.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to get pagespeed monitor"; + }) + // ***************************************************** // Create Monitor // ***************************************************** @@ -163,7 +219,7 @@ const pageSpeedMonitorSlice = createSlice({ }) // ***************************************************** - // Create Monitor + // Update Monitor // ***************************************************** .addCase(updatePageSpeed.pending, (state) => { state.isLoading = true; @@ -198,6 +254,24 @@ const pageSpeedMonitorSlice = createSlice({ state.msg = action.payload ? action.payload.msg : "Failed to delete page speed monitor"; + }) + // ***************************************************** + // Pause Monitor + // ***************************************************** + .addCase(pausePageSpeed.pending, (state) => { + state.isLoading = true; + }) + .addCase(pausePageSpeed.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(pausePageSpeed.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to pause page speed monitor"; }); }, }); diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index 2c381812c..895bd1493 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -236,7 +236,7 @@ const uptimeMonitorsSlice = createSlice({ state.success = false; state.msg = action.payload ? action.payload.msg - : "Failed to pause uptime monitor"; + : "Failed to get uptime monitor"; }) // ***************************************************** // update Monitor diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx index 483a2fd79..6e3caeff7 100644 --- a/Client/src/Pages/Monitors/Configure/index.jsx +++ b/Client/src/Pages/Monitors/Configure/index.jsx @@ -275,14 +275,15 @@ const Configure = () => { )} - + diff --git a/Client/src/Pages/PageSpeed/Configure/index.jsx b/Client/src/Pages/PageSpeed/Configure/index.jsx index 92dd5bb36..005583f2c 100644 --- a/Client/src/Pages/PageSpeed/Configure/index.jsx +++ b/Client/src/Pages/PageSpeed/Configure/index.jsx @@ -1,12 +1,14 @@ import { useEffect, useState } from "react"; import { useTheme } from "@emotion/react"; -import { Box, Button, Modal, Skeleton, Stack, Typography } from "@mui/material"; +import { Box, Button, Modal, Stack, Typography } from "@mui/material"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate, useParams } from "react-router"; import { deletePageSpeed, + getPagespeedMonitorById, getPageSpeedByTeamId, updatePageSpeed, + pausePageSpeed, } from "../../../Features/PageSpeedMonitor/pageSpeedMonitorSlice"; import { monitorValidation } from "../../../Validation/validation"; import { createToast } from "../../../Utils/toastUtils"; @@ -17,63 +19,18 @@ import Checkbox from "../../../Components/Inputs/Checkbox"; import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; - +import LoadingButton from "@mui/lab/LoadingButton"; +import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; +import SkeletonLayout from "./skeleton"; import "./index.css"; -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - const theme = useTheme(); - - return ( - <> - - - - - - - - - - - - - - - - - - - - ); -}; - const PageSpeedConfigure = () => { const theme = useTheme(); const navigate = useNavigate(); const dispatch = useDispatch(); const MS_PER_MINUTE = 60000; const { authToken } = useSelector((state) => state.auth); - const { monitors } = useSelector((state) => state.pageSpeedMonitors); + const { isLoading } = useSelector((state) => state.pageSpeedMonitors); const { monitorId } = useParams(); const [monitor, setMonitor] = useState({}); const [errors, setErrors] = useState({}); @@ -89,15 +46,25 @@ const PageSpeedConfigure = () => { ]; useEffect(() => { - const data = monitors.find((monitor) => monitor._id === monitorId); - if (!data) { - logger.error("Error fetching pagespeed monitor of id: " + monitorId); - navigate("/not-found", { replace: true }); - } - setMonitor({ - ...data, - }); - }, [monitorId, monitors, navigate]); + const fetchMonitor = async () => { + try { + const action = await dispatch( + getPagespeedMonitorById({ authToken, monitorId }) + ); + + if (getPagespeedMonitorById.fulfilled.match(action)) { + const monitor = action.payload.data; + setMonitor(monitor); + } else if (getPagespeedMonitorById.rejected.match(action)) { + throw new Error(action.error.message); + } + } catch (error) { + logger.error("Error fetching monitor of id: " + monitorId); + navigate("/not-found", { replace: true }); + } + }; + fetchMonitor(); + }, [dispatch, authToken, monitorId, navigate]); const handleChange = (event, id) => { let { value } = event.target; @@ -119,6 +86,21 @@ const PageSpeedConfigure = () => { }); }; + const handlePause = async () => { + try { + const action = await dispatch(pausePageSpeed({ authToken, monitorId })); + if (pausePageSpeed.fulfilled.match(action)) { + const monitor = action.payload.data; + setMonitor(monitor); + } else if (pausePageSpeed.rejected.match(action)) { + throw new Error(action.error.message); + } + } catch (error) { + logger.error("Error pausing monitor: " + monitorId); + createToast({ body: "Failed to pause monitor" }); + } + }; + const handleSave = async (event) => { event.preventDefault(); const action = await dispatch( @@ -143,11 +125,9 @@ const PageSpeedConfigure = () => { } }; - let loading = Object.keys(monitor).length === 0; - return ( - {loading ? ( + {Object.keys(monitor).length === 0 ? ( ) : ( <> @@ -195,7 +175,9 @@ const PageSpeedConfigure = () => { - - + { - + diff --git a/Client/src/Pages/PageSpeed/Configure/skeleton.jsx b/Client/src/Pages/PageSpeed/Configure/skeleton.jsx new file mode 100644 index 000000000..301a00e52 --- /dev/null +++ b/Client/src/Pages/PageSpeed/Configure/skeleton.jsx @@ -0,0 +1,51 @@ +import { Box, Skeleton, Stack } from "@mui/material"; +import { useTheme } from "@emotion/react"; + +/** + * Renders a skeleton layout. + * + * @returns {JSX.Element} + */ +const SkeletonLayout = () => { + const theme = useTheme(); + + return ( + <> + + + + + + + + + + + + + + + + + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx index 00c02a362..4c8204153 100644 --- a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx +++ b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx @@ -1,4 +1,6 @@ -import { Box, Button, Stack, Typography } from "@mui/material"; +import { Box, Stack, Typography } from "@mui/material"; +import LoadingButton from "@mui/lab/LoadingButton"; + import { useState } from "react"; import { useTheme } from "@emotion/react"; import { useDispatch, useSelector } from "react-redux"; @@ -12,11 +14,11 @@ import { createPageSpeed } from "../../../Features/PageSpeedMonitor/pageSpeedMon import Breadcrumbs from "../../../Components/Breadcrumbs"; import "./index.css"; import { logger } from "../../../Utils/Logger"; - const CreatePageSpeed = () => { const theme = useTheme(); const navigate = useNavigate(); const dispatch = useDispatch(); + const { isLoading } = useSelector((state) => state.pageSpeedMonitors); const MS_PER_MINUTE = 60000; const { user, authToken } = useSelector((state) => state.auth); @@ -226,7 +228,8 @@ const CreatePageSpeed = () => { - + diff --git a/Client/src/Pages/Settings/index.jsx b/Client/src/Pages/Settings/index.jsx index fc615d848..d1f15ea8c 100644 --- a/Client/src/Pages/Settings/index.jsx +++ b/Client/src/Pages/Settings/index.jsx @@ -16,6 +16,9 @@ const Settings = ({ isAdmin }) => { const { isLoading } = useSelector((state) => state.uptimeMonitors); const dispatch = useDispatch(); + + // TODO Handle saving + const handleClearStats = async () => { try { const action = await dispatch( @@ -156,13 +159,14 @@ const Settings = ({ isAdmin }) => { - +