diff --git a/Client/src/App.jsx b/Client/src/App.jsx index 0482aec5d..a10228795 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -1,8 +1,10 @@ import { Routes, Route } from "react-router-dom"; +import "react-toastify/dist/ReactToastify.css"; +import { ToastContainer } from "react-toastify"; // import "./App.css"; import NotFound from "./Pages/NotFound"; import Login from "./Pages/Auth/Login"; -import Register from "./Pages/Auth/Register"; +import Register from "./Pages/Auth/Register/Register"; import HomeLayout from "./Layouts/HomeLayout"; import Account from "./Pages/Account"; import Monitors from "./Pages/Monitors"; @@ -18,10 +20,10 @@ import NewPasswordConfirmed from "./Pages/Auth/NewPasswordConfirmed"; import ProtectedRoute from "./Components/ProtectedRoute"; import Details from "./Pages/Monitors/Details"; import Maintenance from "./Pages/Maintenance"; -import "react-toastify/dist/ReactToastify.css"; -import { ToastContainer } from "react-toastify"; +import withAdminCheck from "./HOC/withAdminCheck"; function App() { + const AdminCheckedRegister = withAdminCheck(Register); return ( <> @@ -79,7 +81,9 @@ function App() { } /> - } /> + + } /> + } /> {/* } /> */} } /> } /> diff --git a/Client/src/Components/BasicTable/index.css b/Client/src/Components/BasicTable/index.css index 637e5e6dd..221c66494 100644 --- a/Client/src/Components/BasicTable/index.css +++ b/Client/src/Components/BasicTable/index.css @@ -124,3 +124,6 @@ .MuiPaper-root + .MuiPagination-root .MuiPaginationItem-root.Mui-selected { background-color: var(--env-var-color-15); } +.MuiPaper-root + .MuiPagination-root div.MuiPaginationItem-root{ + user-select: none; +} diff --git a/Client/src/Components/Button/index.jsx b/Client/src/Components/Button/index.jsx index fcf157f5e..c53ece09e 100644 --- a/Client/src/Components/Button/index.jsx +++ b/Client/src/Components/Button/index.jsx @@ -58,15 +58,12 @@ const Button = ({ id, type, level, label, disabled, img, onClick, sx }) => { color={color} disabled={disabled} onClick={onClick} + disableRipple sx={{ textTransform: "none", "&:focus": { outline: "none", }, - "& .MuiTouchRipple-root": { - pointerEvents: "none", - display: "none", - }, ...sx, }} > diff --git a/Client/src/Components/ButtonSpinner/index.jsx b/Client/src/Components/ButtonSpinner/index.jsx index 7f89f1ab9..2d940c539 100644 --- a/Client/src/Components/ButtonSpinner/index.jsx +++ b/Client/src/Components/ButtonSpinner/index.jsx @@ -70,6 +70,7 @@ const ButtonSpinner = ({ endIcon={position === "end" && img} onClick={onClick} loading={isLoading} + disableRipple sx={{ textTransform: "none", "&:focus": { diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.css b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.css index e69de29bb..f85a0bb04 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.css +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.css @@ -0,0 +1,19 @@ +.area-tooltip { + background-color: white; + border: solid 1px var(--env-var-color-4); + border-radius: var(--env-var-radius-1); + padding: var(--env-var-spacing-1) var(--env-var-spacing-2); +} +.area-tooltip p { + margin: 0; + padding: 0; +} +.area-tooltip p:first-of-type { + color: var(--env-var-color-3); + font-size: var(--env-var-font-size-medium); +} +.area-tooltip p:last-of-type { + margin-top: 5px; + color: var(--env-var-color-5); + font-size: var(--env-var-font-size-small); +} diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 1d1ea3758..02729a709 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -1,11 +1,12 @@ import PropTypes from "prop-types"; import { AreaChart, Area, XAxis, Tooltip, ResponsiveContainer } from "recharts"; import { NormalizeData } from "../ChartUtils"; +import "./index.css"; const CustomToolTip = ({ active, payload, label }) => { if (active && payload && payload.length) { return ( -
+

{new Date(label).toLocaleDateString("en-US", { weekday: "short", // Mon @@ -80,7 +81,7 @@ const MonitorDetailsAreaChart = ({ checks, filter }) => { data={normalizedChecks} margin={{ top: 10, - right: 30, + right: 0, left: 0, bottom: 0, }} @@ -89,6 +90,7 @@ const MonitorDetailsAreaChart = ({ checks, filter }) => { dataKey="createdAt" tickFormatter={formatDate} tick={{ fontSize: "13px" }} + tickLine={false} /> } /> { value={user.email} placeholder="Enter your email" autoComplete="email" - // onChange={handleChange} + // TODO - add onChange + onChange={() => console.log("Disabled.")} // error={errors[idToName["edit-email"]]} disabled={true} /> diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx index 471752dfa..5ef356f9c 100644 --- a/Client/src/Components/TabPanels/Account/TeamPanel.jsx +++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx @@ -21,6 +21,8 @@ import ButtonSpinner from "../../ButtonSpinner"; import Button from "../../Button"; import { useState } from "react"; import EditSvg from "../../../assets/icons/edit.svg?react"; +import Field from "../../Inputs/Field"; +import { credentials } from "../../../Validation/validation"; /** * TeamPanel component manages the organization and team members, @@ -106,19 +108,11 @@ const TeamPanel = () => { name: "Bluewave Labs", isLoading: false, isEdit: false, - newName: "", }); const toggleEdit = () => { setOrgStates((prev) => ({ ...prev, isEdit: !prev.isEdit })); }; - const handleChange = (event) => { - const { value } = event.target; - setOrgStates((prev) => ({ - ...prev, - name: value, - })); - }; const handleRename = () => {}; const [teamStates, setTeamStates] = useState({ @@ -163,8 +157,44 @@ const TeamPanel = () => { setIsLoading(false); }, 2000); }; + + const [toInvite, setToInvite] = useState({ + email: "", + role: "", + }); + const [errors, setErrors] = useState({}); + + const handleChange = (event) => { + const { value } = event.target; + setToInvite((prev) => ({ + ...prev, + email: value, + })); + + const validation = credentials.validate( + { email: value }, + { abortEarly: false } + ); + + setErrors((prev) => { + const updatedErrors = { ...prev }; + + if (validation.error) { + updatedErrors.email = validation.error.details[0].message; + } else { + delete updatedErrors.email; + } + return updatedErrors; + }); + }; const [isOpen, setIsOpen] = useState(false); const handleInviteMember = () => {}; + const closeInviteModal = () => { + setIsOpen(false); + setToInvite({ email: "", role: "" }); + setErrors({}); + }; + const handleMembersQuery = (type) => { let count = 0; teamStates.members.forEach((member) => { @@ -188,7 +218,12 @@ const TeamPanel = () => { > + setOrgStates((prev) => ({ + ...prev, + name: event.target.value, + })) + } disabled={!orgStates.isEdit} sx={{ color: theme.palette.otherColors.bluishGray, @@ -409,7 +444,7 @@ const TeamPanel = () => { aria-labelledby="modal-invite-member" aria-describedby="invite-member-to-team" open={isOpen} - onClose={() => setIsOpen(false)} + onClose={closeInviteModal} disablePortal > { Invite new team member - + When you add a new team member, they will get access to all monitors. - - // setOrgStates((prev) => ({ - // ...prev, - // newName: event.target.value, - // })) - // } - > - + placeholder="Email" + value={toInvite.email} + onChange={handleChange} + error={errors.email} + /> + +

); }; - +Register.propTypes = { + isAdmin: PropTypes.bool, +}; export default Register; diff --git a/Client/src/Pages/Monitors/Details/index.css b/Client/src/Pages/Monitors/Details/index.css index e69de29bb..1abd12ef9 100644 --- a/Client/src/Pages/Monitors/Details/index.css +++ b/Client/src/Pages/Monitors/Details/index.css @@ -0,0 +1,49 @@ +.monitor-details h1.MuiTypography-root { + font-size: var(--env-var-font-size-large-plus); + color: var(--env-var-color-1); + font-weight: 600; +} +.monitor-details h2.MuiTypography-root, +.monitor-details h6.MuiTypography-root { + font-size: var(--env-var-font-size-large); +} +.monitor-details h2.MuiTypography-root, +.monitor-details h4.MuiTypography-root { + font-weight: 600; +} +.monitor-details h2.MuiTypography-root, +.monitor-details h4.MuiTypography-root { + color: var(--env-var-color-5); +} +.monitor-details h6.MuiTypography-root { + color: var(--env-var-color-3); +} +.monitor-details button.MuiButtonBase-root, +.monitor-details h4.MuiTypography-root { + font-size: var(--env-var-font-size-medium); +} +.monitor-details button.MuiButtonBase-root { + height: var(--env-var-height-2); + line-height: 1; +} +.monitor-details p.MuiTypography-root, +.monitor-details p.MuiTypography-root span.MuiTypography-root { + font-size: var(--env-var-font-size-small-plus); +} +.monitor-details p.MuiTypography-root { + color: var(--env-var-color-2); +} + +.monitor-details .stat-box { + flex: 1; + min-width: 100px; + border: solid 1px var(--env-var-color-4); + border-radius: var(--env-var-radius-1); + padding: var(--env-var-spacing-1-plus) var(--env-var-spacing-2); +} +.monitor-details .stat-box:not(:first-of-type) { + margin-left: var(--env-var-spacing-2); +} +.monitor-details .stat-box h6.MuiTypography-root { + margin-bottom: var(--env-var-spacing-1); +} \ No newline at end of file diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 7d565d372..8ccf15d01 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -1,14 +1,19 @@ import React, { useEffect, useState } from "react"; import PropTypes from "prop-types"; -import { Box, Typography, useTheme } from "@mui/material"; +import { Box, Stack, Typography, useTheme } from "@mui/material"; import { useSelector } from "react-redux"; -import { useParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import axiosInstance from "../../../Utils/axiosConfig"; import BasicTable from "../../../Components/BasicTable"; import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart"; import { StatusLabel } from "../../../Components/Label"; import ButtonGroup from "@mui/material/ButtonGroup"; import Button from "../../../Components/Button"; +import WestRoundedIcon from "@mui/icons-material/WestRounded"; +import GreenCheck from "../../../assets/icons/checkbox-green.svg?react"; +import RedCheck from "../../../assets/icons/checkbox-red.svg?react"; +import SettingsIcon from "../../../assets/icons/settings.svg?react"; +import "./index.css"; const formatDuration = (ms) => { const seconds = Math.floor(ms / 1000); @@ -28,9 +33,36 @@ const formatDuration = (ms) => { return dateStr; }; +const formatDurationRounded = (ms) => { + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + let time = ""; + if (days > 0) { + time += `${days} day${days !== 1 ? "s" : ""}`; + return time; + } + if (hours > 0) { + time += `${hours} hour${hours !== 1 ? "s" : ""}`; + return time; + } + if (minutes > 0) { + time += `${minutes} minute${minutes !== 1 ? "s" : ""}`; + return time; + } + if (seconds > 0) { + time += `${seconds} second${seconds !== 1 ? "s" : ""}`; + return time; + } + + return time; +}; + const StatBox = ({ title, value }) => { return ( - + {title} {value} @@ -103,6 +135,7 @@ const DetailsPage = () => { }, [monitorId, authToken]); const theme = useTheme(); + const navigate = useNavigate(); /** * Function to calculate uptime duration based on the most recent check. @@ -163,52 +196,110 @@ const DetailsPage = () => { padding: `${theme.content.pY} ${theme.content.pX}`, }} > -
- - - -
-
} + onClick={() => navigate("/monitors")} + sx={{ + backgroundColor: "#f4f4f4", + mb: theme.gap.medium, + px: theme.gap.ml, + "& svg.MuiSvgIcon-root": { + pr: theme.gap.small, + }, }} - > - - Response Times - - + /> + + + {monitor?.status ? : } + + + {monitor.url?.replace(/^https?:\/\//, "") || "..."} + + + + Your site is {monitor?.status ? "up" : "down"}. + {" "} + Checking every {formatDurationRounded(monitor?.interval)}. Last + time checked{" "} + {formatDurationRounded(getLastChecked(monitor?.checks))} ago. + +
-
- -
- - History - - + + + + + + Response Times + + +
); }; diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js index 26dd3d94a..86753e5f9 100644 --- a/Client/src/Validation/validation.js +++ b/Client/src/Validation/validation.js @@ -4,7 +4,7 @@ const nameSchema = joi .string() .max(50) .trim() - .pattern(new RegExp("^[A-Za-z]+$")) + .pattern(/^[A-Za-z]+$/) .messages({ "string.empty": "Name is required", "string.max": "Name must be less than 50 characters long", diff --git a/Client/src/assets/icons/checkbox-green.svg b/Client/src/assets/icons/checkbox-green.svg new file mode 100644 index 000000000..dd5676298 --- /dev/null +++ b/Client/src/assets/icons/checkbox-green.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Client/src/assets/icons/checkbox-red.svg b/Client/src/assets/icons/checkbox-red.svg new file mode 100644 index 000000000..3c43a2042 --- /dev/null +++ b/Client/src/assets/icons/checkbox-red.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Client/src/assets/icons/settings.svg b/Client/src/assets/icons/settings.svg new file mode 100644 index 000000000..d8057286c --- /dev/null +++ b/Client/src/assets/icons/settings.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Client/src/index.css b/Client/src/index.css index 61c961dad..337e490f5 100644 --- a/Client/src/index.css +++ b/Client/src/index.css @@ -56,6 +56,7 @@ --env-var-width-2: 360px; --env-var-height-1: 100vh; + --env-var-height-2: 34px; --env-var-spacing-1: 12px; --env-var-spacing-1-plus: 16px; diff --git a/Server/index.js b/Server/index.js index 9cd8d57ec..306776643 100644 --- a/Server/index.js +++ b/Server/index.js @@ -13,6 +13,9 @@ const { handleErrors } = require("./middleware/handleErrors"); const queueRouter = require("./routes/queueRoute"); const JobQueue = require("./service/jobQueue"); const pageSpeedCheckRouter = require("./routes/pageSpeedCheckRoute"); +const nodemailer = require("nodemailer"); + +const emailService = require("./service/emailService"); // Need to wrap server setup in a function to handle async nature of JobQueue const startApp = async () => { @@ -87,6 +90,41 @@ const startApp = async () => { } }); + // Nodemailer code here + const transporter = nodemailer.createTransport({ + host: process.env.EMAIL_SERVICE_HOST, + port: process.env.EMAIL_SERVICE_PORT, + auth: { + user: process.env.EMAIL_SERVICE_USERNAME, + pass: process.env.EMAIL_SERVICE_PASSWORD, + }, + }); + + app.use("/api/v1/mail", (req, res) => { + console.log("Started"); + // Replacing varibales + const context = { name: "Alex" }; + + // Define mail options + const mailOptions = { + from: "BlueWave Uptime ", // sender address + to: "muhammadkhalilzadeh1998@gmailc.com", // list of receivers + subject: "Testing template emails", // Subject line + html: emailService.sendWelcomeEmail(context), // html body + }; + + // Send mail with defined transport object + transporter.sendMail(mailOptions, (error, info) => { + if (error) { + return res + .status(500) + .send({ message: "Error sending email", error: error }); + } + console.log(info); + res.status(200).send({ message: "Email sent successfully", info: info }); + }); + }); + /** * Error handler middleware * Should be called last diff --git a/Server/package-lock.json b/Server/package-lock.json index 8c437da48..31ac86ad5 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -16,6 +16,7 @@ "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", + "handlebars": "^4.7.8", "helmet": "^7.1.0", "joi": "^17.13.1", "jsonwebtoken": "9.0.2", @@ -23,6 +24,7 @@ "mjml": "^1.3.4", "mongoose": "^8.3.3", "multer": "1.4.5-lts.1", + "nodemailer": "^6.9.14", "ping": "0.4.4", "sharp": "0.33.4", "winston": "^3.13.0" @@ -2405,6 +2407,51 @@ "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==" }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/handlebars/node_modules/uglify-js": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.0.tgz", + "integrity": "sha512-wNKHUY2hYYkf6oSFfhwwiHo4WCHzHmzcXsqXYTN9ja3iApYIFbb2U6ics9hBcYLHcYGQoAlwnZlTrf3oF+BL/Q==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/handlebars/node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -3703,6 +3750,11 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -3765,6 +3817,14 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, + "node_modules/nodemailer": { + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", + "integrity": "sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", diff --git a/Server/package.json b/Server/package.json index d6d8c8d72..bc3a4f299 100644 --- a/Server/package.json +++ b/Server/package.json @@ -18,6 +18,7 @@ "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", + "handlebars": "^4.7.8", "helmet": "^7.1.0", "joi": "^17.13.1", "jsonwebtoken": "9.0.2", @@ -25,6 +26,7 @@ "mjml": "^1.3.4", "mongoose": "^8.3.3", "multer": "1.4.5-lts.1", + "nodemailer": "^6.9.14", "ping": "0.4.4", "sharp": "0.33.4", "winston": "^3.13.0" diff --git a/Server/service/emailService.js b/Server/service/emailService.js new file mode 100644 index 000000000..235913b18 --- /dev/null +++ b/Server/service/emailService.js @@ -0,0 +1,125 @@ +const fs = require("fs"); +const path = require("path"); +const { compile } = require("handlebars"); +const { mjml2html } = require("mjml"); + +// Fetching Templates + +// Welcome Email Template +const welcomeEmailTemplatePath = path.join( + __dirname, + "../templates/welcomeEmail.mjml" +); +const welcomeEmailTemplateContent = fs.readFileSync( + welcomeEmailTemplatePath, + "utf8" +); +const welcomeEmailTemplate = compile(welcomeEmailTemplateContent); + +// Employee Activation Email Template +const employeeActivationTemplatePath = path.join( + __dirname, + "../templates/employeeActivation.mjml" +); +const employeeActivationTemplateContent = fs.readFileSync( + employeeActivationTemplatePath, + "utf8" +); +const employeeActivation = compile(employeeActivationTemplateContent); + +// No Incident This Week Template +const noIncidentsThisWeekTemplatePath = path.join( + __dirname, + "../templates/noIncidentsThisWeek.mjml" +); +const noIncidentsThisWeekTemplateContent = fs.readFileSync( + noIncidentsThisWeekTemplatePath, + "utf8" +); +const noIncidentsThisWeek = compile(noIncidentsThisWeekTemplateContent); + +// Server is Down Template +const serverIsDownTemplatePath = path.join( + __dirname, + "../templates/serverIsDown.mjml" +); +const serverIsDownTemplateContent = fs.readFileSync( + serverIsDownTemplatePath, + "utf8" +); +const serverIsDown = compile(serverIsDownTemplateContent); + +// Server is Up Template +const serverIsUpTemplatePath = path.join( + __dirname, + "../templates/serverIsUp.mjml" +); +const serverIsUpTemplateContent = fs.readFileSync( + serverIsUpTemplatePath, + "utf8" +); +const serverIsUp = compile(serverIsUpTemplateContent); + +// Password Reset Template +const passwordResetTemplatePath = path.join( + __dirname, + "../templates/passwordReset.mjml" +); +const passwordResetTemplateContent = fs.readFileSync( + passwordResetTemplatePath, + "utf8" +); +const passwordReset = compile(passwordResetTemplateContent); + +// *** Application specific functions *** + +function sendWelcomeEmail(context) { + const mjml = welcomeEmailTemplate(context); + const html = mjml2html(mjml); + + return html; +} + +function sendEmployeeActivationEmail(context) { + const mjml = employeeActivation(context); + const html = mjml2html(mjml); + + return html; +} + +function sendNoIncidentsThisWeekEmail(context) { + const mjml = noIncidentsThisWeek(context); + const html = mjml2html(mjml); + + return html; +} + +function sendServerIsDownEmail(context) { + const mjml = serverIsDown(context); + const html = mjml2html(mjml); + + return html; +} + +function sendServerIsUpEmail(context) { + const mjml = serverIsUp(context); + const html = mjml2html(mjml); + + return html; +} + +function sendPasswordResetEmail(context) { + const mjml = passwordReset(context); + const html = mjml2html(mjml); + + return html; +} + +module.exports = { + sendWelcomeEmail, + sendEmployeeActivationEmail, + sendNoIncidentsThisWeekEmail, + sendServerIsDownEmail, + sendServerIsUpEmail, + sendPasswordResetEmail, +}; diff --git a/Server/templates/employeeActivation.mjml b/Server/templates/employeeActivation.mjml index 99d08d489..7fcaebfc2 100644 --- a/Server/templates/employeeActivation.mjml +++ b/Server/templates/employeeActivation.mjml @@ -16,10 +16,10 @@ -

Hello Alex!

+

Hello {{name}}!

One of the admins created an account for you on the BlueWave Uptime server.

You can go ahead and create your account using this link.

-

https://example.com

+

{{link}}

Thank you.

@@ -31,4 +31,4 @@
- + \ No newline at end of file diff --git a/Server/templates/noIncidentsThisWeek.mjml b/Server/templates/noIncidentsThisWeek.mjml index 0c2801e89..960a876fe 100644 --- a/Server/templates/noIncidentsThisWeek.mjml +++ b/Server/templates/noIncidentsThisWeek.mjml @@ -20,7 +20,7 @@ -

Hello Alex!

+

Hello {{name}}!

There were no incidents this week. Good job!

Current monitors:

Google: 100% availability

@@ -35,4 +35,4 @@
- + \ No newline at end of file diff --git a/Server/templates/passwordReset.mjml b/Server/templates/passwordReset.mjml index 7043401f3..de1e23138 100644 --- a/Server/templates/passwordReset.mjml +++ b/Server/templates/passwordReset.mjml @@ -1,51 +1,43 @@ - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - - -

Hello Alex!

-

- You are receiving this email because a password reset request - has been made for gorkem.cetin@bluewavelabs.ca. Please use the - code below on the site to reset your password. -

-

- Verification Code: 4CCDFC45 -

-

If you didn't request this, please ignore this email.

+ + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + + +

Hello {{name}}!

+

+ You are receiving this email because a password reset request + has been made for gorkem.cetin@bluewavelabs.ca. Please use the + code below on the site to reset your password. +

+

+ Verification Code: {{verificationCode}} +

+

If you didn't request this, please ignore this email.

-

Thank you.

-
-
- - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file +

Thank you.

+ + + + + +

This email was sent by BlueWave Uptime.

+
+
+ + + \ No newline at end of file diff --git a/Server/templates/serverIsDown.mjml b/Server/templates/serverIsDown.mjml index 95df602f5..3caa6549c 100644 --- a/Server/templates/serverIsDown.mjml +++ b/Server/templates/serverIsDown.mjml @@ -1,71 +1,57 @@ - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - Google.com is down - - - - - - - -

Hello Alex!

-

- We detected an incident on one of your monitors. Your service is - currently down. We'll send a message to you once it is up again. -

-

- Monitor name: Google.com -

-

- URL: https://google.com -

-

- Problem: Connection timeout -

-

- Start date: Jun 8, 2024 8:15pm -

-
-
- - - - View incident details - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file + + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + Google.com is down + + + + + + + +

Hello {{name}}!

+

+ We detected an incident on one of your monitors. Your service is + currently down. We'll send a message to you once it is up again. +

+

+ Monitor name: {{monitor}} +

+

+ URL: {{url}} +

+

+ Problem: {{problem}} +

+

+ Start date: {{startDate}} +

+
+
+ + + + View incident details + + +

This email was sent by BlueWave Uptime.

+
+
+
+
+ \ No newline at end of file diff --git a/Server/templates/serverIsUp.mjml b/Server/templates/serverIsUp.mjml index aa2caa5ba..9e73a8559 100644 --- a/Server/templates/serverIsUp.mjml +++ b/Server/templates/serverIsUp.mjml @@ -1,77 +1,63 @@ - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - Google.com is up - - - - - - - -

Hello Alex!

-

- Your latest incident is resolved and your monitored service is - up again. -

-

- Monitor name: Google.com -

-

- URL: https://google.com -

-

- Problem: Connection timeout -

-

- Start date: Jun 8, 2024 8:15pm -

-

- Resolved date: Jun 8, 2024 8:15pm -

-

- Duration:3 minutes and 15 seconds -

-
-
- - - - View incident details - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file + + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + {{monitor}} is up + + + + + + + +

Hello {{name}}!

+

+ Your latest incident is resolved and your monitored service is + up again. +

+

+ Monitor name: {{monitor}} +

+

+ URL: {{url}} +

+

+ Problem: {{problem}} +

+

+ Start date: {{startDate}} +

+

+ Resolved date: {{resolvedDate}} +

+

+ Duration:{{duration}} +

+
+
+ + + + View incident details + + +

This email was sent by BlueWave Uptime.

+
+
+
+
+ \ No newline at end of file diff --git a/Server/templates/welcomeEmail.mjml b/Server/templates/welcomeEmail.mjml index 62e5333d8..cb71bb34c 100644 --- a/Server/templates/welcomeEmail.mjml +++ b/Server/templates/welcomeEmail.mjml @@ -1,51 +1,43 @@ - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - - -

Hello Alex!

-

- Thank you for trying out BlueWave Uptime! We developed it with - great care to meet our own needs, and we're excited to share it - with you. -

-

- BlueWave Uptime is an automated way of checking whether a - service such as a website or an application is available or not. -

-

We hope you find our service as valuable as we do.

-

Thank you.

-
-
- - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file + + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + + +

Hello {{name}}!

+

+ Thank you for trying out BlueWave Uptime! We developed it with + great care to meet our own needs, and we're excited to share it + with you. +

+

+ BlueWave Uptime is an automated way of checking whether a + service such as a website or an application is available or not. +

+

We hope you find our service as valuable as we do.

+

Thank you.

+
+
+ + + +

This email was sent by BlueWave Uptime.

+
+
+
+
+ \ No newline at end of file diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 2947406bd..1d2adc084 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -6,14 +6,32 @@ const joi = require("joi"); const loginValidation = joi.object({ email: joi.string().email().required(), - password: joi.string().min(8).required(), + password: joi + .string() + .min(8) + .required() + .pattern( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ + ), }); const registerValidation = joi.object({ - firstname: joi.string().required(), - lastname: joi.string().required(), + firstname: joi + .string() + .required() + .pattern(/^[A-Za-z]+$/), + lastname: joi + .string() + .required() + .pattern(/^[A-Za-z]+$/), email: joi.string().email().required(), - password: joi.string().min(8).required(), + password: joi + .string() + .min(8) + .required() + .pattern( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ + ), profileImage: joi.any(), role: joi.string().required(), }); @@ -23,11 +41,21 @@ const editUserParamValidation = joi.object({ }); const editUserBodyValidation = joi.object({ - firstname: joi.string(), - lastname: joi.string(), + firstname: joi.string().pattern(/^[A-Za-z]+$/), + lastname: joi.string().pattern(/^[A-Za-z]+$/), profileImage: joi.any(), - newPassword: joi.string().min(8), - password: joi.string().min(8), + newPassword: joi + .string() + .min(8) + .pattern( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ + ), + password: joi + .string() + .min(8) + .pattern( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ + ), deleteProfileImage: joi.boolean(), role: joi.string(), }); @@ -45,7 +73,13 @@ const recoveryTokenValidation = joi.object({ const newPasswordValidation = joi.object({ recoveryToken: joi.string().required(), - password: joi.string().min(8).required(), + password: joi + .string() + .min(8) + .required() + .pattern( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ + ), confirm: joi.string(), });