diff --git a/Client/src/Components/Inputs/Field/index.jsx b/Client/src/Components/Inputs/Field/index.jsx index acf6877be..1d9034c66 100644 --- a/Client/src/Components/Inputs/Field/index.jsx +++ b/Client/src/Components/Inputs/Field/index.jsx @@ -84,6 +84,17 @@ const Field = ({ value={value} onChange={onChange} disabled={disabled} + sx={ + type === "url" + ? { + "& .MuiInputBase-root": { padding: 0 }, + "& .MuiStack-root": { + borderTopLeftRadius: `${theme.shape.borderRadius}px`, + borderBottomLeftRadius: `${theme.shape.borderRadius}px`, + }, + } + : {} + } InputProps={{ startAdornment: type === "url" && ( diff --git a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js new file mode 100644 index 000000000..6524a3417 --- /dev/null +++ b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js @@ -0,0 +1,146 @@ +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; +import { jwtDecode } from "jwt-decode"; +import axiosInstance from "../../Utils/axiosConfig"; +const initialState = { + isLoading: false, + monitors: [], + success: null, + msg: null, +}; + +export const createPageSpeed = createAsyncThunk( + "pageSpeedMonitors/createPageSpeed", + async (data, thunkApi) => { + try { + const { authToken, monitor } = data; + + const res = await axiosInstance.post(`/monitors`, monitor, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + return thunkApi.rejectWithValue(error.message); + } + } +); + +export const getPageSpeedMonitors = createAsyncThunk( + "pageSpeedMonitors/getPageSpeedMonitors", + async (token, thunkApi) => { + try { + const res = await axiosInstance.get("/monitors"); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + return thunkApi.rejectWithValue(error.message); + } + } +); + +export const getPageSpeedByUserId = createAsyncThunk( + "pageSpeedMonitors/getPageSpeedByUserId", + async (token, thunkApi) => { + const user = jwtDecode(token); + try { + const res = await axiosInstance.get( + `/monitors/user/${user._id}?limit=25&type=pagespeed&sortOrder=desc`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + return thunkApi.rejectWithValue(error.message); + } + } +); + +const pageSpeedMonitorSlice = createSlice({ + name: "pageSpeedMonitor", + initialState, + reducers: { + clearMonitorState: (state) => { + state.isLoading = false; + state.monitors = []; + state.success = null; + state.msg = null; + }, + }, + extraReducers: (builder) => { + builder + // ***************************************************** + // All Monitors + // ***************************************************** + .addCase(getPageSpeedMonitors.pending, (state) => { + state.isLoading = true; + }) + .addCase(getPageSpeedMonitors.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + state.monitors = action.payload.data; + }) + .addCase(getPageSpeedMonitors.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Getting montiors failed"; + }) + // ***************************************************** + // Monitors by userId + // ***************************************************** + + .addCase(getPageSpeedByUserId.pending, (state) => { + state.isLoading = true; + }) + .addCase(getPageSpeedByUserId.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.msg; + state.monitors = action.payload.data; + }) + .addCase(getPageSpeedByUserId.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Getting page speed monitors failed"; + }) + + // ***************************************************** + // Create Monitor + // ***************************************************** + .addCase(createPageSpeed.pending, (state) => { + state.isLoading = true; + }) + .addCase(createPageSpeed.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(createPageSpeed.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to create page speed monitor"; + }); + }, +}); + +export const { setMonitors, clearMonitorState } = pageSpeedMonitorSlice.actions; + +export default pageSpeedMonitorSlice.reducer; diff --git a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.css b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.css index ee447365c..3152d0e28 100644 --- a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.css +++ b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.css @@ -1,4 +1,4 @@ -.create-page-speed > .MuiStack-root { +.create-page-speed form { position: relative; margin: auto; margin-top: var(--env-var-default-2); diff --git a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx index 5952b1c2f..c85f6f77e 100644 --- a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx +++ b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx @@ -1,18 +1,25 @@ import { Box, IconButton, Stack, Typography } from "@mui/material"; +import { useState } from "react"; import { useTheme } from "@emotion/react"; -import CloseRoundedIcon from "@mui/icons-material/CloseRounded"; +import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router"; +import CloseRoundedIcon from "@mui/icons-material/CloseRounded"; import Field from "../../../Components/Inputs/Field"; import Select from "../../../Components/Inputs/Select"; import Button from "../../../Components/Button"; import Checkbox from "../../../Components/Inputs/Checkbox"; -import { useState } from "react"; import { monitorValidation } from "../../../Validation/validation"; +import { createToast } from "../../../Utils/toastUtils"; +import { createPageSpeed } from "../../../Features/PageSpeedMonitor/pageSpeedMonitorSlice"; import "./index.css"; const CreatePageSpeed = () => { const theme = useTheme(); const navigate = useNavigate(); + const dispatch = useDispatch(); + + const MS_PER_MINUTE = 60000; + const { user, authToken } = useSelector((state) => state.auth); const frequencies = [ { _id: 1, name: "1 minute" }, @@ -26,7 +33,6 @@ const CreatePageSpeed = () => { name: "", url: "", interval: 1, - type: "pagespeed", }); const [errors, setErrors] = useState({}); @@ -34,25 +40,68 @@ const CreatePageSpeed = () => { const { value } = event.target; setForm((prev) => ({ ...prev, [id]: value })); - const validation = monitorValidation.validate( + const { error } = monitorValidation.validate( { [id]: value }, { abortEarly: false } ); setErrors((prev) => { - const updatedErrors = { ...prev }; - if (validation.error) { - updatedErrors[id] = validation.error.details[0].message; - } else { - delete updatedErrors[id]; - } - return updatedErrors; + const newErrors = { ...prev }; + if (error) newErrors[id] = error.details[0].message; + else delete newErrors[id]; + return newErrors; }); }; + const handleCreate = async (event) => { + event.preventDefault(); + + let monitor = { + url: "http://" + form.url, + name: form.name === "" ? form.url : form.name, + }; + + const { error } = monitorValidation.validate(form, { abortEarly: false }); + + if (error) { + const newErrors = {}; + error.details.forEach((err) => { + newErrors[err.path[0]] = err.message; + }); + setErrors(newErrors); + createToast({ body: "Error validating data." }); + } else { + monitor = { + ...monitor, + description: monitor.name, + userId: user._id, + interval: form.interval * MS_PER_MINUTE, + type: "pagespeed", + }; + try { + const action = await dispatch(createPageSpeed({ authToken, monitor })); + if (action.meta.requestStatus === "fulfilled") { + navigate("/page-speed"); + } + } catch (error) { + createToast({ + body: + error.details && error.details.length > 0 + ? error.details[0].message + : "Unknown error.", + }); + } + } + }; + return ( - +
navigate("/page-speed")} @@ -111,12 +160,12 @@ const CreatePageSpeed = () => { { -