From 280f5bf6df842f72dab33d86b8c7bb2fb93c02c5 Mon Sep 17 00:00:00 2001 From: Francisco Cardoso Date: Sat, 4 Feb 2023 12:04:54 +0000 Subject: [PATCH 01/17] Added validation of company application route and unverified state Co-authored-by: Daniel Ferreira --- src/AppRouter.js | 10 ++++ .../ApplicationsReviewTableSchema.js | 2 + src/pages/ValidationPage.js | 46 +++++++++++++++++++ src/services/companyApplicationService.js | 26 +++++++++++ 4 files changed, 84 insertions(+) create mode 100644 src/pages/ValidationPage.js diff --git a/src/AppRouter.js b/src/AppRouter.js index 9ac4b935..1e167573 100644 --- a/src/AppRouter.js +++ b/src/AppRouter.js @@ -38,6 +38,7 @@ import EditOfferPage from "./pages/EditOfferPage"; import PrivacyPolicyPage from "./pages/PrivacyPolicyPage"; import TermsAndConditionsPage from "./pages/TermsAndConditionsPage"; import ChangeLogPage from "./pages/ChangeLogPage"; +import ValidationPage from "./pages/ValidationPage"; /** * @@ -135,6 +136,15 @@ const AppRouter = () => ( + + + + + makeStyles((theme) => ({ + content: { + padding: isMobile ? theme.spacing(2, 2) : theme.spacing(3, 9), + }, +})); + +const ValidationPage = () => { + const isMobile = useMobile(); + const classes = useStyles(isMobile)(); + const { token } = useParams(); + + const [loading, setLoading] = useState(true); + const [success, setSuccess] = useState(null); + + useEffect(() => { + try { + setLoading(false); + setSuccess(true); + validateApplication(token); + } catch { + setLoading(false); + setSuccess(false); + } + }, [token]); + + if (loading) { + return ; + } else if (success) { + // toggle + return ; + } + + return ( + + Error + + ); +}; + +export default ValidationPage; diff --git a/src/services/companyApplicationService.js b/src/services/companyApplicationService.js index 48df788d..f4026b0f 100644 --- a/src/services/companyApplicationService.js +++ b/src/services/companyApplicationService.js @@ -67,3 +67,29 @@ export const submitCompanyApplication = (formData) => buildCancelableRequest( } }) ); + +export const validateApplication = async (token) => { + try { + const res = fetch(`${API_HOSTNAME}/apply/company/validate/${token}/confirm`, { + method: "POST", + credentials: "include", + + }); + + const json = await res.json(); + + if (!res.ok) { + + throw json.errors; + } + + return json; + + } catch (error) { + const errorArray = Array.isArray(error) ? error : + [{ msg: Constants.UNEXPECTED_ERROR_MESSAGE }]; + + throw errorArray; + } + +}; From 6284d08fa1a370ec0702d5f28fd01012e901b328 Mon Sep 17 00:00:00 2001 From: Francisco Cardoso Date: Mon, 6 Feb 2023 20:40:48 +0000 Subject: [PATCH 02/17] Implemented validation page design Co-authored-by: Daniel Ferreira --- src/pages/ValidationPage.js | 92 +++++++++++++++++++---- src/services/companyApplicationService.js | 15 ++-- 2 files changed, 84 insertions(+), 23 deletions(-) diff --git a/src/pages/ValidationPage.js b/src/pages/ValidationPage.js index fafc9c9e..8faaa918 100644 --- a/src/pages/ValidationPage.js +++ b/src/pages/ValidationPage.js @@ -1,12 +1,36 @@ +/* eslint-disable react/jsx-indent */ +/* eslint-disable react/jsx-closing-tag-location */ +/* eslint-disable indent */ import React, { useEffect, useState } from "react"; -import { CardContent, CircularProgress, makeStyles } from "@material-ui/core"; +import { CardContent, CircularProgress, makeStyles, Button } from "@material-ui/core"; import { useMobile } from "../utils/media-queries"; import { Redirect, useParams } from "react-router-dom"; import { validateApplication } from "../services/companyApplicationService"; const useStyles = (isMobile) => makeStyles((theme) => ({ content: { - padding: isMobile ? theme.spacing(2, 2) : theme.spacing(3, 9), + padding: isMobile ? theme.spacing(2, 2) : theme.spacing(3, 3), + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + }, + title: { + color: "rgb(80,80,80)", + fontSize: "23px", + }, + text: { + color: "rgb(100,100,100)", + fontSize: "15px", + maxWidth: isMobile ? "100%" : "80%", + textAlign: "center", + + }, + button: { + background: "rgb(250,80,80)", + color: "white", + marginTop: "3vh", + }, })); @@ -14,31 +38,73 @@ const ValidationPage = () => { const isMobile = useMobile(); const classes = useStyles(isMobile)(); const { token } = useParams(); - const [loading, setLoading] = useState(true); - const [success, setSuccess] = useState(null); + const [success, setSuccess] = useState(false); + const [buttonPressed, setButtonPressed] = useState(false); + const [err,setErr] = useState(""); + - useEffect(() => { + useEffect( () => { try { - setLoading(false); - setSuccess(true); - validateApplication(token); - } catch { + async function ola() + { + try { + setLoading(false); + setSuccess(true); + await validateApplication(token); + } catch ( error){ + setLoading(false); + setSuccess(false); + setErr(error.message); + } + + } + ola(); + } catch(error) { setLoading(false); setSuccess(false); + setErr(error.message); } }, [token]); + if (buttonPressed) { + return ; + } + if (loading) { - return ; + return ( + +

Loading...

+ +
+ ); } else if (success) { - // toggle - return ; + return ( + +

Your application has been validated successfully!

+ + We will now review your application, and in case you're approved, + you will receive another email with further instructions in order to complete your registration. + + +
+ ); + } return ( - Error +

{err}!

+ + An error has occur when validating your application for more information contact + nijobs@aefeup.pt + ! + +
); }; diff --git a/src/services/companyApplicationService.js b/src/services/companyApplicationService.js index f4026b0f..ed44eea4 100644 --- a/src/services/companyApplicationService.js +++ b/src/services/companyApplicationService.js @@ -70,25 +70,20 @@ export const submitCompanyApplication = (formData) => buildCancelableRequest( export const validateApplication = async (token) => { try { - const res = fetch(`${API_HOSTNAME}/apply/company/validate/${token}/confirm`, { + const res = await fetch(`${API_HOSTNAME}/apply/company/validate/${token}/confirm`, { method: "POST", credentials: "include", }); - - const json = await res.json(); - - if (!res.ok) { - + const json = res.json(); + if (! res.ok) { throw json.errors; } - return json; } catch (error) { - const errorArray = Array.isArray(error) ? error : - [{ msg: Constants.UNEXPECTED_ERROR_MESSAGE }]; - + console.log(error); + const errorArray = Array.isArray(error) ? new Error(error.message) : new Error(Constants.UNEXPECTED_ERROR_MESSAGE); throw errorArray; } From aa0ac7599389de925e84ae41c9b4e65c1cb622d6 Mon Sep 17 00:00:00 2001 From: Francisco Cardoso Date: Sat, 11 Feb 2023 14:30:16 +0000 Subject: [PATCH 03/17] Added appropriate error messages for application Co-authored-by: Daniel Ferreira --- .../Apply/Company/ApplicationConfirmation.js | 3 +- .../Apply/Company/CompanyApplicationUtils.js | 30 +++++++- src/pages/ValidationPage.js | 77 ++++++++++--------- src/services/companyApplicationService.js | 8 +- 4 files changed, 73 insertions(+), 45 deletions(-) diff --git a/src/components/Apply/Company/ApplicationConfirmation.js b/src/components/Apply/Company/ApplicationConfirmation.js index 9e28e83a..86613e17 100644 --- a/src/components/Apply/Company/ApplicationConfirmation.js +++ b/src/components/Apply/Company/ApplicationConfirmation.js @@ -22,7 +22,8 @@ const ApplicationConfirmation = () => { - Application Submitted, you should receive a confirmation email shortly. If not, please contact us: + Application Submitted, you should receive an email with a link to confirm your application, + please confirm it in 10 minutes or else the link will expire. If you did not receive any email, please contact us: {" "} {Constants.CONTACT_US_EMAIL} diff --git a/src/components/Apply/Company/CompanyApplicationUtils.js b/src/components/Apply/Company/CompanyApplicationUtils.js index 82038643..9810df93 100644 --- a/src/components/Apply/Company/CompanyApplicationUtils.js +++ b/src/components/Apply/Company/CompanyApplicationUtils.js @@ -1,7 +1,5 @@ import { validationRulesGenerator, generalHumanError } from "../../../utils"; import { AuthConstants } from "../../Navbar/Auth/AuthUtils"; - - export const CompanyApplicationConstants = { password: AuthConstants.password, motivation: { @@ -19,6 +17,34 @@ export const generateValidationRule = validationRulesGenerator(CompanyApplicatio const HumanReadableErrors = Object.freeze({ "email-already-exists": "The provided email is already associated to our platform.", "company-application-duplicate-email": "There is already an application associated with that email.", + "company-application-recently-created": "There is an application created less than 10 minutes ago associated with this email.", }); +const ValidationErrors = Object.freeze({ + "invalid-token": { + title: "Error! Application does not exist!", + text: "An error has occured while validating your application! The application you are trying to validate does not exist,", + }, + "expired-token": { + title: "Error! Link has expired!", + text: "An error has occured while validating your application! The link sent to you has expired, \ + you now need to create a new application,", + }, + "application-already-validated": { + title: "Application is already validated!", + text: "This application is already validated", + }, +}); + +export const getValidationError = (error) => { + const errorMsg = { title: "Unexpected Error!", text: "An unexpected error has occured while validating your application, " }; + if (!error) { + return errorMsg; + } + if (typeof ValidationErrors[error] === "object") { + return ValidationErrors[error]; + } + return errorMsg; +}; + export const getHumanError = (error) => generalHumanError(error, HumanReadableErrors); diff --git a/src/pages/ValidationPage.js b/src/pages/ValidationPage.js index 8faaa918..00f9bd88 100644 --- a/src/pages/ValidationPage.js +++ b/src/pages/ValidationPage.js @@ -6,6 +6,7 @@ import { CardContent, CircularProgress, makeStyles, Button } from "@material-ui/ import { useMobile } from "../utils/media-queries"; import { Redirect, useParams } from "react-router-dom"; import { validateApplication } from "../services/companyApplicationService"; +import { getValidationError } from "../components/Apply/Company/CompanyApplicationUtils.js"; const useStyles = (isMobile) => makeStyles((theme) => ({ content: { @@ -27,7 +28,7 @@ const useStyles = (isMobile) => makeStyles((theme) => ({ }, button: { - background: "rgb(250,80,80)", + background: "rgb(250,70,70)", color: "white", marginTop: "3vh", @@ -41,31 +42,44 @@ const ValidationPage = () => { const [loading, setLoading] = useState(true); const [success, setSuccess] = useState(false); const [buttonPressed, setButtonPressed] = useState(false); - const [err,setErr] = useState(""); + const [error, setError] = useState(""); - useEffect( () => { - try { - async function ola() - { - try { - setLoading(false); - setSuccess(true); - await validateApplication(token); - } catch ( error){ - setLoading(false); - setSuccess(false); - setErr(error.message); - } - + useEffect(() => { + async function validate() { + try { + setLoading(false); + setSuccess(true); + await validateApplication(token); + } catch (error_) { + setError(error_[0].msg); + setLoading(false); + setSuccess(false); } - ola(); - } catch(error) { - setLoading(false); - setSuccess(false); - setErr(error.message); + } + validate(); + }, [token]); + const errorMessage = (error) => { + const { title, text } = getValidationError(error); + return ( + +

+ {title} +

+ + {text} + for more information contact us: + nijobs@aefeup.pt + ! + + +
+ ); + }; if (buttonPressed) { return ; @@ -83,30 +97,17 @@ const ValidationPage = () => {

Your application has been validated successfully!

- We will now review your application, and in case you're approved, - you will receive another email with further instructions in order to complete your registration. + You should receive a confirmation email shortly. If not, please contact us: + nijobs@aefeup.pt
); - + } else { + return errorMessage(error); } - return ( - -

{err}!

- - An error has occur when validating your application for more information contact - nijobs@aefeup.pt - ! - - -
- ); }; - export default ValidationPage; diff --git a/src/services/companyApplicationService.js b/src/services/companyApplicationService.js index ed44eea4..2181136c 100644 --- a/src/services/companyApplicationService.js +++ b/src/services/companyApplicationService.js @@ -75,15 +75,15 @@ export const validateApplication = async (token) => { credentials: "include", }); - const json = res.json(); - if (! res.ok) { + const json = await res.json(); + if (!res.ok) { throw json.errors; } return json; } catch (error) { - console.log(error); - const errorArray = Array.isArray(error) ? new Error(error.message) : new Error(Constants.UNEXPECTED_ERROR_MESSAGE); + const errorArray = Array.isArray(error) ? error : + [{ msg: Constants.UNEXPECTED_ERROR_MESSAGE }]; throw errorArray; } From a2467307c7b9a922e3c66c050164dfb4d8714ba8 Mon Sep 17 00:00:00 2001 From: FranciscoCardoso913 Date: Tue, 9 May 2023 02:18:07 +0100 Subject: [PATCH 04/17] Changed hyperlinks and redirect to Link component and changed text to Typography --- src/AppRouter.js | 2 +- src/pages/ValidationPage.js | 41 ++++++++++++++++++++----------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/AppRouter.js b/src/AppRouter.js index 1e167573..4413e5c2 100644 --- a/src/AppRouter.js +++ b/src/AppRouter.js @@ -139,7 +139,7 @@ const AppRouter = () => ( diff --git a/src/pages/ValidationPage.js b/src/pages/ValidationPage.js index 00f9bd88..bbae2de3 100644 --- a/src/pages/ValidationPage.js +++ b/src/pages/ValidationPage.js @@ -2,8 +2,9 @@ /* eslint-disable react/jsx-closing-tag-location */ /* eslint-disable indent */ import React, { useEffect, useState } from "react"; -import { CardContent, CircularProgress, makeStyles, Button } from "@material-ui/core"; +import {CardContent, CircularProgress, makeStyles, Button, Fab, Link, Typography} from "@material-ui/core"; import { useMobile } from "../utils/media-queries"; +import { Link as RouteLink} from "react-router-dom"; import { Redirect, useParams } from "react-router-dom"; import { validateApplication } from "../services/companyApplicationService"; import { getValidationError } from "../components/Apply/Company/CompanyApplicationUtils.js"; @@ -17,22 +18,23 @@ const useStyles = (isMobile) => makeStyles((theme) => ({ justifyContent: "center", }, title: { - color: "rgb(80,80,80)", - fontSize: "23px", + fontWeight:500, }, text: { - color: "rgb(100,100,100)", - fontSize: "15px", + fontSize: theme.typography.body1, maxWidth: isMobile ? "100%" : "80%", textAlign: "center", }, button: { - background: "rgb(250,70,70)", - color: "white", - marginTop: "3vh", - + background: theme.palette.primary.main, + color:theme.palette.dark.contrastText, + '&:hover':{ + background: theme.palette.secondary.main, + } }, + + })); const ValidationPage = () => { @@ -51,8 +53,8 @@ const ValidationPage = () => { setLoading(false); setSuccess(true); await validateApplication(token); - } catch (error_) { - setError(error_[0].msg); + } catch (err) { + setError(err[0].msg); setLoading(false); setSuccess(false); } @@ -65,24 +67,25 @@ const ValidationPage = () => { const { title, text } = getValidationError(error); return ( -

+ {title} -

- +
+ {text} for more information contact us: nijobs@aefeup.pt ! - -
); }; if (buttonPressed) { - return ; + return ; } if (loading) { @@ -98,7 +101,7 @@ const ValidationPage = () => {

Your application has been validated successfully!

You should receive a confirmation email shortly. If not, please contact us: - nijobs@aefeup.pt + nijobs@aefeup.pt + ); }; - if (buttonPressed) { - return ; - } - if (loading) { return ( @@ -95,21 +96,8 @@ const ValidationPage = () => { ); - } else if (success) { - return ( - -

Your application has been validated successfully!

- - You should receive a confirmation email shortly. If not, please contact us: - nijobs@aefeup.pt - - -
- ); } else { - return errorMessage(error); + return getMessageCard(error); } }; From b328a126728ebcab5ae0b9eb5c932103ece9e394 Mon Sep 17 00:00:00 2001 From: FranciscoCardoso913 Date: Tue, 20 Jun 2023 15:59:38 +0100 Subject: [PATCH 06/17] Added a waring to MyOffers page and to the CreateOffer page when company is yet to be approved Co-authored-by: Daniel Ferreira --- .../Offers/Form/form-components/OfferForm.js | 6 +++- .../Form/form-components/offerStyles.js | 9 +++++ src/components/Offers/New/CreateOfferForm.js | 1 - src/components/utils/alert.js | 33 +++++++++++++++++++ src/pages/CompanyOffersManagementPage.js | 3 ++ 5 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 src/components/utils/alert.js diff --git a/src/components/Offers/Form/form-components/OfferForm.js b/src/components/Offers/Form/form-components/OfferForm.js index c2400ba4..cee151ec 100644 --- a/src/components/Offers/Form/form-components/OfferForm.js +++ b/src/components/Offers/Form/form-components/OfferForm.js @@ -7,7 +7,7 @@ import { FormControl, Typography, Collapse, - Button, + Button, makeStyles, } from "@material-ui/core"; import React, { useState, useCallback, useContext } from "react"; import { Redirect } from "react-router-dom"; @@ -36,6 +36,7 @@ import { Controller } from "react-hook-form"; import { useMobile } from "../../../../utils/media-queries"; import "../editor.css"; import ApplyURLComponent from "./ApplyURLComponent"; +import {Alert} from "../../../utils/alert"; export const PAID_OPTIONS = [ { value: "none", label: "Unspecified" }, @@ -43,6 +44,7 @@ export const PAID_OPTIONS = [ { value: false, label: "Unpaid" }, ]; + const OfferForm = ({ context, title }) => { const { submit, @@ -102,11 +104,13 @@ const OfferForm = ({ context, title }) => { }, }; + return ( success ? :
+ {"Your offers will stay hidden from the public until your account is approved!"} makeStyles((theme) => ({ paddingTop: theme.spacing(2), marginTop: theme.spacing(2), }, + warning:{ + fontSize:"1.2em", + "& .MuiAlert-icon": { + fontSize: "1.5em" + }, + marginBottom:"1em" + + + }, })); diff --git a/src/components/Offers/New/CreateOfferForm.js b/src/components/Offers/New/CreateOfferForm.js index 9f65f047..04706ede 100644 --- a/src/components/Offers/New/CreateOfferForm.js +++ b/src/components/Offers/New/CreateOfferForm.js @@ -1,4 +1,3 @@ - import React, { useCallback } from "react"; import { parseRequestErrors } from "../Form/OfferUtils"; import { newOffer } from "../../../services/offerService"; diff --git a/src/components/utils/alert.js b/src/components/utils/alert.js new file mode 100644 index 00000000..091299b9 --- /dev/null +++ b/src/components/utils/alert.js @@ -0,0 +1,33 @@ + +import {Alert as Alert_, AlertTitle} from "@material-ui/lab"; +import {makeStyles} from "@material-ui/core"; +import PropTypes from "prop-types"; +import React, { useCallback, useEffect, useState } from "react"; + +const useStyles = (props) => makeStyles((theme) => ({ + + content:{ + fontSize: props.fontSize+"em", + "& .MuiAlert-icon": { + fontSize: (props.fontSize+0.3)+"em" + }, + margin:"0.5em 0em" + }, +})); + +export const Alert = ({type, title, fontSize = 1, children}) => { + const classes = useStyles({fontSize: fontSize})(); + return ( + + {title ? {title} : null} + {children} + + ) +} + +Alert.propTypes = { + type: PropTypes.oneOf(["error", "warning", "info", "success"]), + title: PropTypes.string, + children: PropTypes.string, + fontSize: PropTypes.number, +} \ No newline at end of file diff --git a/src/pages/CompanyOffersManagementPage.js b/src/pages/CompanyOffersManagementPage.js index 87ed5b01..6a4ed753 100644 --- a/src/pages/CompanyOffersManagementPage.js +++ b/src/pages/CompanyOffersManagementPage.js @@ -2,11 +2,13 @@ import React from "react"; import CompanyOffersManagementWidget from "../components/Company/Offers/Manage/CompanyOffersManagementWidget"; import { CardContent, makeStyles } from "@material-ui/core"; import { useMobile } from "../utils/media-queries"; +import {Alert} from "../components/utils/alert"; const useStyles = (isMobile) => makeStyles((theme) => ({ content: { padding: isMobile ? theme.spacing(2, 2) : theme.spacing(3, 9), }, + })); const CompanyOffersManagementPage = () => { @@ -15,6 +17,7 @@ const CompanyOffersManagementPage = () => { return ( + {"Your offers will stay hidden from the public until your account is approved!"} ); From 502ae8fdfaa2c0cc9698dacec91c63f98ded6346 Mon Sep 17 00:00:00 2001 From: FranciscoCardoso913 Date: Tue, 20 Jun 2023 15:59:38 +0100 Subject: [PATCH 07/17] Added a waring to MyOffers page and to the CreateOffer page when company is yet to be approved Co-authored-by: Daniel Ferreira --- .../Offers/Form/form-components/OfferForm.js | 6 +++- .../Form/form-components/offerStyles.js | 9 +++++ src/components/Offers/New/CreateOfferForm.js | 1 - src/components/utils/alert.js | 33 +++++++++++++++++++ src/pages/CompanyOffersManagementPage.js | 3 ++ src/pages/CreateOfferPage.js | 1 + 6 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 src/components/utils/alert.js diff --git a/src/components/Offers/Form/form-components/OfferForm.js b/src/components/Offers/Form/form-components/OfferForm.js index c2400ba4..cee151ec 100644 --- a/src/components/Offers/Form/form-components/OfferForm.js +++ b/src/components/Offers/Form/form-components/OfferForm.js @@ -7,7 +7,7 @@ import { FormControl, Typography, Collapse, - Button, + Button, makeStyles, } from "@material-ui/core"; import React, { useState, useCallback, useContext } from "react"; import { Redirect } from "react-router-dom"; @@ -36,6 +36,7 @@ import { Controller } from "react-hook-form"; import { useMobile } from "../../../../utils/media-queries"; import "../editor.css"; import ApplyURLComponent from "./ApplyURLComponent"; +import {Alert} from "../../../utils/alert"; export const PAID_OPTIONS = [ { value: "none", label: "Unspecified" }, @@ -43,6 +44,7 @@ export const PAID_OPTIONS = [ { value: false, label: "Unpaid" }, ]; + const OfferForm = ({ context, title }) => { const { submit, @@ -102,11 +104,13 @@ const OfferForm = ({ context, title }) => { }, }; + return ( success ? :
+ {"Your offers will stay hidden from the public until your account is approved!"} makeStyles((theme) => ({ paddingTop: theme.spacing(2), marginTop: theme.spacing(2), }, + warning:{ + fontSize:"1.2em", + "& .MuiAlert-icon": { + fontSize: "1.5em" + }, + marginBottom:"1em" + + + }, })); diff --git a/src/components/Offers/New/CreateOfferForm.js b/src/components/Offers/New/CreateOfferForm.js index 9f65f047..04706ede 100644 --- a/src/components/Offers/New/CreateOfferForm.js +++ b/src/components/Offers/New/CreateOfferForm.js @@ -1,4 +1,3 @@ - import React, { useCallback } from "react"; import { parseRequestErrors } from "../Form/OfferUtils"; import { newOffer } from "../../../services/offerService"; diff --git a/src/components/utils/alert.js b/src/components/utils/alert.js new file mode 100644 index 00000000..091299b9 --- /dev/null +++ b/src/components/utils/alert.js @@ -0,0 +1,33 @@ + +import {Alert as Alert_, AlertTitle} from "@material-ui/lab"; +import {makeStyles} from "@material-ui/core"; +import PropTypes from "prop-types"; +import React, { useCallback, useEffect, useState } from "react"; + +const useStyles = (props) => makeStyles((theme) => ({ + + content:{ + fontSize: props.fontSize+"em", + "& .MuiAlert-icon": { + fontSize: (props.fontSize+0.3)+"em" + }, + margin:"0.5em 0em" + }, +})); + +export const Alert = ({type, title, fontSize = 1, children}) => { + const classes = useStyles({fontSize: fontSize})(); + return ( + + {title ? {title} : null} + {children} + + ) +} + +Alert.propTypes = { + type: PropTypes.oneOf(["error", "warning", "info", "success"]), + title: PropTypes.string, + children: PropTypes.string, + fontSize: PropTypes.number, +} \ No newline at end of file diff --git a/src/pages/CompanyOffersManagementPage.js b/src/pages/CompanyOffersManagementPage.js index 87ed5b01..6a4ed753 100644 --- a/src/pages/CompanyOffersManagementPage.js +++ b/src/pages/CompanyOffersManagementPage.js @@ -2,11 +2,13 @@ import React from "react"; import CompanyOffersManagementWidget from "../components/Company/Offers/Manage/CompanyOffersManagementWidget"; import { CardContent, makeStyles } from "@material-ui/core"; import { useMobile } from "../utils/media-queries"; +import {Alert} from "../components/utils/alert"; const useStyles = (isMobile) => makeStyles((theme) => ({ content: { padding: isMobile ? theme.spacing(2, 2) : theme.spacing(3, 9), }, + })); const CompanyOffersManagementPage = () => { @@ -15,6 +17,7 @@ const CompanyOffersManagementPage = () => { return ( + {"Your offers will stay hidden from the public until your account is approved!"} ); diff --git a/src/pages/CreateOfferPage.js b/src/pages/CreateOfferPage.js index dd565af0..4020b578 100644 --- a/src/pages/CreateOfferPage.js +++ b/src/pages/CreateOfferPage.js @@ -1,5 +1,6 @@ import React from "react"; import CreateOfferForm from "../components/Offers/New/CreateOfferForm"; +import {Alert} from "@material-ui/lab"; const CreateOfferPage = () => ( <> From df27afe9a8355c15c55fca0dd99f1990087db6ed Mon Sep 17 00:00:00 2001 From: dsantosferreira Date: Tue, 27 Jun 2023 16:50:46 +0100 Subject: [PATCH 08/17] Conditionally render the alert for non yet approved companies and change warning icon to its filled version Co-authored-by: Francisco Cardoso --- .../Offers/Form/form-components/OfferForm.js | 36 ++++++++++--- src/components/utils/{alert.js => Alert.js} | 9 ++-- src/pages/CompanyOffersManagementPage.js | 32 ++++++++++-- src/services/companyService.js | 51 +++++++++++++++++++ src/utils/analytics/constants.js | 4 ++ 5 files changed, 117 insertions(+), 15 deletions(-) rename src/components/utils/{alert.js => Alert.js} (75%) create mode 100644 src/services/companyService.js diff --git a/src/components/Offers/Form/form-components/OfferForm.js b/src/components/Offers/Form/form-components/OfferForm.js index cee151ec..6de89c86 100644 --- a/src/components/Offers/Form/form-components/OfferForm.js +++ b/src/components/Offers/Form/form-components/OfferForm.js @@ -9,7 +9,7 @@ import { Collapse, Button, makeStyles, } from "@material-ui/core"; -import React, { useState, useCallback, useContext } from "react"; +import React, {useState, useCallback, useContext, useEffect} from "react"; import { Redirect } from "react-router-dom"; import PropTypes from "prop-types"; import MultiOptionTextField from "../../../utils/form/MultiOptionTextField"; @@ -36,7 +36,10 @@ import { Controller } from "react-hook-form"; import { useMobile } from "../../../../utils/media-queries"; import "../editor.css"; import ApplyURLComponent from "./ApplyURLComponent"; -import {Alert} from "../../../utils/alert"; +import { Alert } from "../../../utils/Alert"; +import { fetchCompanyApplication } from "../../../../services/companyService"; +import useSession from "../../../../hooks/useSession.js"; +import { addSnackbar } from "../../../../actions/notificationActions"; export const PAID_OPTIONS = [ { value: "none", label: "Unspecified" }, @@ -94,6 +97,27 @@ const OfferForm = ({ context, title }) => { const Content = isMobile ? DialogContent : CardContent; const classes = useOfferFormStyles(isMobile)(); + const [application, setApplication] = useState({state: "APPROVED"}); + const session = useSession(); + + useEffect(() => { + if(!session.isValidating && session.isLoggedIn) { + const request = fetchCompanyApplication(session.data?.company?._id) + .then((application) => { + setApplication(application); + }) + .catch(() => { + addSnackbar({ + message: "An unexpected error occurred, please try refreshing the browser window.", + key: `${Date.now()}-fetchCompanyApplicationsError`, + }); + }); + return () => { + request.cancel(); + }; + } + }, [addSnackbar, session.isValidating, session.isLoggedIn]); + const showOwnerComponent = isAdmin && showCompanyField; const SelectStylingProps = { @@ -104,14 +128,14 @@ const OfferForm = ({ context, title }) => { }, }; - return ( success ? :
- {"Your offers will stay hidden from the public until your account is approved!"} - + {(application.state !== "APPROVED") && session.isLoggedin && {"This offer will stay hidden from the public until your account is approved!"}} + { ( makeStyles((theme) => ({ @@ -18,10 +17,10 @@ const useStyles = (props) => makeStyles((theme) => ({ export const Alert = ({type, title, fontSize = 1, children}) => { const classes = useStyles({fontSize: fontSize})(); return ( - + }> {title ? {title} : null} {children} - + ) } diff --git a/src/pages/CompanyOffersManagementPage.js b/src/pages/CompanyOffersManagementPage.js index 6a4ed753..ab9c7c48 100644 --- a/src/pages/CompanyOffersManagementPage.js +++ b/src/pages/CompanyOffersManagementPage.js @@ -1,8 +1,11 @@ -import React from "react"; +import React, {useEffect, useState} from "react"; import CompanyOffersManagementWidget from "../components/Company/Offers/Manage/CompanyOffersManagementWidget"; import { CardContent, makeStyles } from "@material-ui/core"; import { useMobile } from "../utils/media-queries"; -import {Alert} from "../components/utils/alert"; +import { Alert } from "../components/utils/Alert"; +import { fetchCompanyApplication } from "../services/companyService"; +import useSession from "../hooks/useSession"; +import { addSnackbar } from "../actions/notificationActions"; const useStyles = (isMobile) => makeStyles((theme) => ({ content: { @@ -14,11 +17,32 @@ const useStyles = (isMobile) => makeStyles((theme) => ({ const CompanyOffersManagementPage = () => { const isMobile = useMobile(); const classes = useStyles(isMobile)(); + const [application, setApplication] = useState({state: "APPROVED"}); + const session = useSession(); + + useEffect(() => { + if(!session.isValidating && session.isLoggedIn) { + const request = fetchCompanyApplication(session.data?.company?._id) + .then((application) => { + setApplication(application); + }) + .catch(() => { + addSnackbar({ + message: "An unexpected error occurred, please try refreshing the browser window.", + key: `${Date.now()}-fetchCompanyApplicationsError`, + }); + }); + return () => { + request.cancel(); + }; + } + }, [addSnackbar, session.isLoggedIn, session.isValidating]); return ( - {"Your offers will stay hidden from the public until your account is approved!"} - + {(application.state !== "APPROVED") && session.isLoggedIn && {"Your offers will stay hidden from the public until your account is approved!"}} + ); }; diff --git a/src/services/companyService.js b/src/services/companyService.js new file mode 100644 index 00000000..5fc602a4 --- /dev/null +++ b/src/services/companyService.js @@ -0,0 +1,51 @@ +import {buildCancelableRequest} from "../utils"; +import {createErrorEvent, createEvent, measureTime} from "../utils/analytics"; +import {EVENT_TYPES, TIMED_ACTIONS} from "../utils/analytics/constants"; +import ErrorTypes from "../utils/ErrorTypes"; +import Constants from "../utils/Constants"; +import config from "../config"; + +const COMPANY_APPLICATION_FETCH_METRIC_ID = "company_application/fetch"; +const { API_HOSTNAME } = config; + +export const fetchCompanyApplication = buildCancelableRequest(measureTime(TIMED_ACTIONS.COMPANY_APPLICATION_FETCH, async (companyId) => { + + let isErrorRegistered = false; + try { + const res = await fetch(`${API_HOSTNAME}/company/${companyId}/application`, { + method: "GET", + credentials: "include", + }); + const json = await res.json(); + + if (!res.ok) { + + createErrorEvent( + COMPANY_APPLICATION_FETCH_METRIC_ID, + ErrorTypes.BAD_RESPONSE, + json.errors, + res.status + ); + isErrorRegistered = true; + + throw json.errors; + } + + createEvent(EVENT_TYPES.SUCCESS(COMPANY_APPLICATION_FETCH_METRIC_ID)); + return json; + + } catch (error) { + const errorArray = Array.isArray(error) ? error : + [{ msg: Constants.UNEXPECTED_ERROR_MESSAGE }]; + + if (!isErrorRegistered) { + createErrorEvent( + COMPANY_APPLICATION_FETCH_METRIC_ID, + ErrorTypes.NETWORK_FAILURE, + errorArray, + ); + } + + throw errorArray; + } +})); \ No newline at end of file diff --git a/src/utils/analytics/constants.js b/src/utils/analytics/constants.js index 61380e7b..fcd94ca1 100644 --- a/src/utils/analytics/constants.js +++ b/src/utils/analytics/constants.js @@ -63,6 +63,10 @@ export const TIMED_ACTIONS = Object.freeze({ category: "company_offers", variable: "company_offers/fetch", }, + COMPANY_APPLICATION_FETCH: { + category: "company_application", + variable: "company_application/fetch", + }, RELESES_FETCH: { category: "changelog", variable: "changelog/fetch", From 02072ab1030c77a688e17d23a26150e25ea859c6 Mon Sep 17 00:00:00 2001 From: FranciscoCardoso913 Date: Tue, 4 Jul 2023 10:34:43 +0100 Subject: [PATCH 09/17] Rewriting the website messages to match the current flow Co-authored-by: Daniel Ferreira --- .../Apply/Company/ApplicationConfirmation.js | 8 ++++---- .../Apply/Company/CompanyApplicationUtils.js | 10 +++++----- src/pages/ValidationPage.js | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/Apply/Company/ApplicationConfirmation.js b/src/components/Apply/Company/ApplicationConfirmation.js index 86613e17..142367e6 100644 --- a/src/components/Apply/Company/ApplicationConfirmation.js +++ b/src/components/Apply/Company/ApplicationConfirmation.js @@ -5,7 +5,7 @@ import Constants from "../../../utils/Constants"; const useStyles = makeStyles((theme) => ({ content: { - textAlgin: "justify", + textAlign: "justify", }, actions: { margin: theme.spacing(1), @@ -22,15 +22,15 @@ const ApplicationConfirmation = () => { - Application Submitted, you should receive an email with a link to confirm your application, - please confirm it in 10 minutes or else the link will expire. If you did not receive any email, please contact us: + Application Submitted: You should receive an email containing a confirmation link for your application. + Please confirm it within 10 minutes; otherwise, the link will expire. If you have not received an email, please contact us at {" "} {Constants.CONTACT_US_EMAIL} - {"Once you're approved, you will receive an email, and then you can log into NIJobs! "} + {"Once your application is approved, you will receive an email. Then, you can log into NIJobs. "} Do not forget your password, you will need it on the first login. diff --git a/src/components/Apply/Company/CompanyApplicationUtils.js b/src/components/Apply/Company/CompanyApplicationUtils.js index 9810df93..920291c2 100644 --- a/src/components/Apply/Company/CompanyApplicationUtils.js +++ b/src/components/Apply/Company/CompanyApplicationUtils.js @@ -23,21 +23,21 @@ const HumanReadableErrors = Object.freeze({ const ValidationErrors = Object.freeze({ "invalid-token": { title: "Error! Application does not exist!", - text: "An error has occured while validating your application! The application you are trying to validate does not exist,", + text: "An error has occurred while validating your application! The application you are trying to validate does not exist.", }, "expired-token": { title: "Error! Link has expired!", - text: "An error has occured while validating your application! The link sent to you has expired, \ - you now need to create a new application,", + text: "An error has occurred while validating your application. The link that was sent to you has expired." + + " You will need to create a new application.", }, "application-already-validated": { title: "Application is already validated!", - text: "This application is already validated", + text: "This application is already validated. ", }, }); export const getValidationError = (error) => { - const errorMsg = { title: "Unexpected Error!", text: "An unexpected error has occured while validating your application, " }; + const errorMsg = { title: "Unexpected Error!", text: "An unexpected error has occurred while validating your application. " }; if (!error) { return errorMsg; } diff --git a/src/pages/ValidationPage.js b/src/pages/ValidationPage.js index 7d444bcd..56391aee 100644 --- a/src/pages/ValidationPage.js +++ b/src/pages/ValidationPage.js @@ -39,7 +39,7 @@ const useStyles = (isMobile) => makeStyles((theme) => ({ })); const ValidationPage = () => { - const successMessage = {title: "Your application has been validated successfully!", text: "You should receive a confirmation email shortly. If not, please contact us:"}; + const successMessage = {title: "Your application has been validated successfully!", text: "You should receive a confirmation email shortly. If not, please contact us at "}; const isMobile = useMobile(); const classes = useStyles(isMobile)(); const { token } = useParams(); @@ -76,7 +76,7 @@ const ValidationPage = () => { {text} - for more information contact us: + {!success ? "For more information contact us at " : ""} nijobs@aefeup.pt ! From 52dbd3faf553866cc54723470fe0b7a51baf249d Mon Sep 17 00:00:00 2001 From: FranciscoCardoso913 Date: Tue, 4 Jul 2023 10:34:43 +0100 Subject: [PATCH 10/17] Rewriting the website messages to match the current flow Co-authored-by: Daniel Ferreira --- .env | 2 +- src/AppRouter.js | 4 ++-- .../Offers/Form/form-components/OfferForm.js | 12 ++++++------ src/components/utils/Alert.js | 1 + src/pages/CompanyOffersManagementPage.js | 17 +++++++++++------ src/services/companyApplicationService.js | 2 +- src/services/companyService.js | 5 +++-- 7 files changed, 25 insertions(+), 18 deletions(-) diff --git a/.env b/.env index c6392706..b7832b3a 100644 --- a/.env +++ b/.env @@ -4,7 +4,7 @@ HOST_PORT=443 REACT_APP_API_HOSTNAME="https://localhost:8087" # Uncomment to disable devtools -# REACT_APP_ALLOW_DEV_TOOLS=false +#REACT_APP_ALLOW_DEV_TOOLS=false # Google Analytics' Universal ID ANALYTICS_ID= diff --git a/src/AppRouter.js b/src/AppRouter.js index 4413e5c2..8e1e8ac9 100644 --- a/src/AppRouter.js +++ b/src/AppRouter.js @@ -136,9 +136,9 @@ const AppRouter = () => ( - + diff --git a/src/components/Offers/Form/form-components/OfferForm.js b/src/components/Offers/Form/form-components/OfferForm.js index 6de89c86..86d8a415 100644 --- a/src/components/Offers/Form/form-components/OfferForm.js +++ b/src/components/Offers/Form/form-components/OfferForm.js @@ -37,7 +37,7 @@ import { useMobile } from "../../../../utils/media-queries"; import "../editor.css"; import ApplyURLComponent from "./ApplyURLComponent"; import { Alert } from "../../../utils/Alert"; -import { fetchCompanyApplication } from "../../../../services/companyService"; +import { fetchCompanyApplicationState } from "../../../../services/companyService"; import useSession from "../../../../hooks/useSession.js"; import { addSnackbar } from "../../../../actions/notificationActions"; @@ -97,14 +97,14 @@ const OfferForm = ({ context, title }) => { const Content = isMobile ? DialogContent : CardContent; const classes = useOfferFormStyles(isMobile)(); - const [application, setApplication] = useState({state: "APPROVED"}); + const [state, setState] = useState( "APPROVED"); const session = useSession(); useEffect(() => { if(!session.isValidating && session.isLoggedIn) { - const request = fetchCompanyApplication(session.data?.company?._id) - .then((application) => { - setApplication(application); + const request = fetchCompanyApplicationState(session.data?.company?._id) + .then((state) => { + setState(state); }) .catch(() => { addSnackbar({ @@ -133,7 +133,7 @@ const OfferForm = ({ context, title }) => { ? :
- {(application.state !== "APPROVED") && session.isLoggedin && {"This offer will stay hidden from the public until your account is approved!"}} diff --git a/src/components/utils/Alert.js b/src/components/utils/Alert.js index e5d34679..49236787 100644 --- a/src/components/utils/Alert.js +++ b/src/components/utils/Alert.js @@ -2,6 +2,7 @@ import {Alert as MUI_Alert, AlertTitle} from "@material-ui/lab"; import {makeStyles} from "@material-ui/core"; import PropTypes from "prop-types"; import { Warning as WarningIcon } from "@material-ui/icons"; +import React from 'react'; const useStyles = (props) => makeStyles((theme) => ({ diff --git a/src/pages/CompanyOffersManagementPage.js b/src/pages/CompanyOffersManagementPage.js index ab9c7c48..79248cc5 100644 --- a/src/pages/CompanyOffersManagementPage.js +++ b/src/pages/CompanyOffersManagementPage.js @@ -3,7 +3,7 @@ import CompanyOffersManagementWidget from "../components/Company/Offers/Manage/C import { CardContent, makeStyles } from "@material-ui/core"; import { useMobile } from "../utils/media-queries"; import { Alert } from "../components/utils/Alert"; -import { fetchCompanyApplication } from "../services/companyService"; +import { fetchCompanyApplicationState } from "../services/companyService"; import useSession from "../hooks/useSession"; import { addSnackbar } from "../actions/notificationActions"; @@ -17,14 +17,19 @@ const useStyles = (isMobile) => makeStyles((theme) => ({ const CompanyOffersManagementPage = () => { const isMobile = useMobile(); const classes = useStyles(isMobile)(); - const [application, setApplication] = useState({state: "APPROVED"}); + const [state, setState_] = useState( "APPROVED"); const session = useSession(); useEffect(() => { if(!session.isValidating && session.isLoggedIn) { - const request = fetchCompanyApplication(session.data?.company?._id) - .then((application) => { - setApplication(application); + const request = fetchCompanyApplicationState(session.data?.company?._id) + .then((state_) => { + console.log(state_); + console.log("Tipo"); + console.log(typeof state_); + setState_(state_); + console.log(state); + }) .catch(() => { addSnackbar({ @@ -40,7 +45,7 @@ const CompanyOffersManagementPage = () => { return ( - {(application.state !== "APPROVED") && session.isLoggedIn && {"Your offers will stay hidden from the public until your account is approved!"}} diff --git a/src/services/companyApplicationService.js b/src/services/companyApplicationService.js index 2181136c..70042102 100644 --- a/src/services/companyApplicationService.js +++ b/src/services/companyApplicationService.js @@ -70,7 +70,7 @@ export const submitCompanyApplication = (formData) => buildCancelableRequest( export const validateApplication = async (token) => { try { - const res = await fetch(`${API_HOSTNAME}/apply/company/validate/${token}/confirm`, { + const res = await fetch(`${API_HOSTNAME}/apply/company/${token}/validate`, { method: "POST", credentials: "include", diff --git a/src/services/companyService.js b/src/services/companyService.js index 5fc602a4..f55bc35a 100644 --- a/src/services/companyService.js +++ b/src/services/companyService.js @@ -8,15 +8,16 @@ import config from "../config"; const COMPANY_APPLICATION_FETCH_METRIC_ID = "company_application/fetch"; const { API_HOSTNAME } = config; -export const fetchCompanyApplication = buildCancelableRequest(measureTime(TIMED_ACTIONS.COMPANY_APPLICATION_FETCH, async (companyId) => { +export const fetchCompanyApplicationState = buildCancelableRequest(measureTime(TIMED_ACTIONS.COMPANY_APPLICATION_FETCH, async (companyId) => { let isErrorRegistered = false; try { - const res = await fetch(`${API_HOSTNAME}/company/${companyId}/application`, { + const res = await fetch(`${API_HOSTNAME}/company/${companyId}/state`, { method: "GET", credentials: "include", }); const json = await res.json(); + console.log(json); if (!res.ok) { From a327f004c2951af92c7797655c0bf6e325bc2ce8 Mon Sep 17 00:00:00 2001 From: FranciscoCardoso913 Date: Tue, 8 Aug 2023 12:31:59 +0100 Subject: [PATCH 11/17] Fixing failing tests --- .../SearchResultsWidget/SearchURLWidget.spec.js | 4 ++-- .../Applications/ApplicationsReviewWidget.spec.js | 12 +++++++----- src/utils/UndoableActionsHandlerProvider.js | 15 ++++++++++++--- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/components/HomePage/SearchResultsArea/SearchResultsWidget/SearchURLWidget.spec.js b/src/components/HomePage/SearchResultsArea/SearchResultsWidget/SearchURLWidget.spec.js index 9701b341..8ec66ff2 100644 --- a/src/components/HomePage/SearchResultsArea/SearchResultsWidget/SearchURLWidget.spec.js +++ b/src/components/HomePage/SearchResultsArea/SearchResultsWidget/SearchURLWidget.spec.js @@ -23,7 +23,7 @@ describe("Search URL Widget", () => { const testString = "test-url"; - global.window = Object.create(window); + // global.window = Object.create(window); Object.defineProperty(window, "location", { value: { href: testString, @@ -49,7 +49,7 @@ describe("Search URL Widget", () => { const testString = "test-string"; - global.window = Object.create(window); + //global.window = Object.create(window); Object.defineProperty(window, "location", { value: { href: testString, diff --git a/src/components/Review/Applications/ApplicationsReviewWidget.spec.js b/src/components/Review/Applications/ApplicationsReviewWidget.spec.js index 89845aae..588151c4 100644 --- a/src/components/Review/Applications/ApplicationsReviewWidget.spec.js +++ b/src/components/Review/Applications/ApplicationsReviewWidget.spec.js @@ -49,7 +49,7 @@ const generateApplications = (n, forceState) => { for (let i = 0; i < n; i++) { applications.push(generateApplication( i, - forceState || STATES[i % 3] + forceState || STATES[(i) % 4] )); } return applications; @@ -805,6 +805,8 @@ describe("Application Review Widget", () => { it("Should reset filters", async () => { const applications = generateApplications(5); + console.log("Hello"); + console.log(applications); fetch.mockResponse(JSON.stringify({ applications })); @@ -820,13 +822,13 @@ describe("Application Review Widget", () => { fireEvent.click(screen.getByLabelText("Filter list")); - fireEvent.change(screen.getByLabelText("Company Name"), { target: { value: "0" } }); + fireEvent.change(screen.getByLabelText("Company Name"), { target: { value: "1" } }); fireEvent.change(screen.getByLabelText("Date From..."), { target: { value: - format(new Date(applications[0].submittedAt), "yyyy-MM-dd"), + format(new Date(applications[1].submittedAt), "yyyy-MM-dd"), } }); fireEvent.change(screen.getByLabelText("Date To..."), { target: { value: - format(new Date(applications[0].submittedAt), "yyyy-MM-dd"), + format(new Date(applications[1].submittedAt), "yyyy-MM-dd"), } }); await userEvent.click(screen.getByLabelText("State")); @@ -837,7 +839,7 @@ describe("Application Review Widget", () => { expect(screen.getAllByTestId("application-row") .map((el) => el.querySelector("td:nth-child(2)").textContent) - ).toStrictEqual([applications[0].companyName]); + ).toStrictEqual([applications[1].companyName]); fireEvent.click(screen.getByLabelText("Filter list")); diff --git a/src/utils/UndoableActionsHandlerProvider.js b/src/utils/UndoableActionsHandlerProvider.js index a491ba71..2b7898ad 100644 --- a/src/utils/UndoableActionsHandlerProvider.js +++ b/src/utils/UndoableActionsHandlerProvider.js @@ -86,16 +86,25 @@ const ActionNotification = connect(mapStateToProps, mapDispatchToProps)(BaseActi const UndoableActionsHandlerProvider = ({ children }) => { const [actions, setActions] = useState({}); const [closeLock, setCloseLock] = useState(false); - const initialBeforeUnloadEventListener = useRef(); + const initialBeforeUnloadEventListener = useRef(window.onbeforeunload); useEffect(() => { if (Object.keys(actions).length && !closeLock) { setCloseLock(true); initialBeforeUnloadEventListener.current = window.onbeforeunload; - window.onbeforeunload = () => "Some changes might have not taken effect yet..."; + + Object.defineProperty(window, "onbeforeunload", { + value: () => "Some changes might have not taken effect yet...", + writable: true, + }); + } else if (!Object.keys(actions).length && closeLock) { setCloseLock(false); - window.onbeforeunload = initialBeforeUnloadEventListener.current; + + Object.defineProperty(window, "onbeforeunload", { + value: initialBeforeUnloadEventListener.current, + writable: true, + }); } }, [actions, closeLock]); From 4c33b8df3fd51fc6333ee55dcd0be81ff7d521bf Mon Sep 17 00:00:00 2001 From: dsantosferreira Date: Fri, 18 Aug 2023 09:09:55 +0100 Subject: [PATCH 12/17] Created test to check if approve and reject buttons are not rendered in unverified applications --- .../ApplicationsReviewWidget.spec.js | 19 +++++++++++++++++++ src/pages/CreateOfferPage.js | 1 - 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/components/Review/Applications/ApplicationsReviewWidget.spec.js b/src/components/Review/Applications/ApplicationsReviewWidget.spec.js index 588151c4..d42b5a07 100644 --- a/src/components/Review/Applications/ApplicationsReviewWidget.spec.js +++ b/src/components/Review/Applications/ApplicationsReviewWidget.spec.js @@ -272,6 +272,25 @@ describe("Application Review Widget", () => { .toEqual(`${API_HOSTNAME}/applications/company/${applications[0].id}/reject`, { credentials: "include", method: "POST" }); }); + it("Should not have approve and reject buttons in unverified applications", async () => { + const applications = generateApplications(1, "UNVERIFIED"); + + fetch.mockResponse(JSON.stringify({ applications })); + + await act(async () => + renderWithStoreAndTheme( + + + + + + , { initialState: {}, theme }) + ); + + expect(screen.queryByLabelText("Approve Application")).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Reject Application")).not.toBeInTheDocument(); + }) + it("Should maintain state filter after rejecting an application", async () => { const applications = generateApplications(1, "PENDING"); diff --git a/src/pages/CreateOfferPage.js b/src/pages/CreateOfferPage.js index 4020b578..dd565af0 100644 --- a/src/pages/CreateOfferPage.js +++ b/src/pages/CreateOfferPage.js @@ -1,6 +1,5 @@ import React from "react"; import CreateOfferForm from "../components/Offers/New/CreateOfferForm"; -import {Alert} from "@material-ui/lab"; const CreateOfferPage = () => ( <> From 3a5a7218331cfde10d5cabdc9846a70fa9a62c0f Mon Sep 17 00:00:00 2001 From: FranciscoCardoso913 Date: Sat, 19 Aug 2023 10:49:28 +0100 Subject: [PATCH 13/17] Tests for validation page Co-authored-by: Daniel Ferreira --- .../Apply/Company/CompanyApplicationUtils.js | 10 ++- src/pages/ValidationPage.js | 4 +- src/pages/ValidationPage.spec.js | 77 +++++++++++++++++++ 3 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 src/pages/ValidationPage.spec.js diff --git a/src/components/Apply/Company/CompanyApplicationUtils.js b/src/components/Apply/Company/CompanyApplicationUtils.js index 920291c2..750fb3bc 100644 --- a/src/components/Apply/Company/CompanyApplicationUtils.js +++ b/src/components/Apply/Company/CompanyApplicationUtils.js @@ -20,7 +20,11 @@ const HumanReadableErrors = Object.freeze({ "company-application-recently-created": "There is an application created less than 10 minutes ago associated with this email.", }); -const ValidationErrors = Object.freeze({ +const ValidationMessages = Object.freeze({ + "success": { + title: "Your application has been validated successfully!", + text: "You should receive a confirmation email shortly. If not, please contact us at ", + }, "invalid-token": { title: "Error! Application does not exist!", text: "An error has occurred while validating your application! The application you are trying to validate does not exist.", @@ -41,8 +45,8 @@ export const getValidationError = (error) => { if (!error) { return errorMsg; } - if (typeof ValidationErrors[error] === "object") { - return ValidationErrors[error]; + if (typeof ValidationMessages[description] === "object") { + return ValidationMessages[description]; } return errorMsg; }; diff --git a/src/pages/ValidationPage.js b/src/pages/ValidationPage.js index 56391aee..b0396816 100644 --- a/src/pages/ValidationPage.js +++ b/src/pages/ValidationPage.js @@ -6,7 +6,7 @@ import {CardContent, CircularProgress, makeStyles, Button, Link, Typography} fro import { useMobile } from "../utils/media-queries"; import { useParams } from "react-router-dom"; import { validateApplication } from "../services/companyApplicationService"; -import { getValidationError } from "../components/Apply/Company/CompanyApplicationUtils.js"; +import { getValidationMessage } from "../components/Apply/Company/CompanyApplicationUtils.js"; import {RouterLink} from "../utils"; const useStyles = (isMobile) => makeStyles((theme) => ({ @@ -39,7 +39,7 @@ const useStyles = (isMobile) => makeStyles((theme) => ({ })); const ValidationPage = () => { - const successMessage = {title: "Your application has been validated successfully!", text: "You should receive a confirmation email shortly. If not, please contact us at "}; + const successMessage = getValidationMessage("success"); const isMobile = useMobile(); const classes = useStyles(isMobile)(); const { token } = useParams(); diff --git a/src/pages/ValidationPage.spec.js b/src/pages/ValidationPage.spec.js new file mode 100644 index 00000000..9681fb45 --- /dev/null +++ b/src/pages/ValidationPage.spec.js @@ -0,0 +1,77 @@ +import React from "react"; +import { BrowserRouter } from "react-router-dom"; +import AppTheme from "../AppTheme"; +import { render } from "../test-utils"; +import { ThemeProvider } from "@material-ui/core"; +import { validateApplication } from "../services/companyApplicationService"; +import ValidationPage from "./ValidationPage"; +import { getValidationMessage } from "../components/Apply/Company/CompanyApplicationUtils.js"; + + +jest.mock("../services/companyApplicationService.js"); +jest.mock("react-router-dom", () => { + const original = jest.requireActual("react-router-dom"); + return { + ...original, + useParams: jest.fn().mockReturnValue({ token: "test123" }), + }; +}); + + +describe("Validation Page", () => { + it("Should show success message if succeeded to validate", async () => { + validateApplication.mockImplementation(() => true); + + const page = await render( + + + + + , + ); + + const {title, text} = getValidationMessage("success"); + + expect(page.queryByText(title)).toBeInTheDocument(); + }); + + it("Should show error message if token does not exist", async () => { + validateApplication.mockImplementation(() =>{ throw [{ msg: "invalid-token" }]}); + const page = await render( + + + + + , + ); + const {title, text} = getValidationMessage("invalid-token"); + expect(page.queryByText(title)).toBeInTheDocument(); + }); + + it("Should show error message if token has expired", async () => { + validateApplication.mockImplementation(() =>{ throw [{ msg: "expired-token" }]}); + const page = await render( + + + + + , + ); + const {title, text} = getValidationMessage("expired-token"); + expect(page.queryByText(title)).toBeInTheDocument(); + }); + + it("Should show error message if application is already validated", async () => { + validateApplication.mockImplementation(() =>{ throw [{ msg: "application-already-validated" }]}); + const page = await render( + + + + + , + ); + const {title, text} = getValidationMessage("application-already-validated"); + expect(page.queryByText(title)).toBeInTheDocument(); + }); +}); + From 4f85e5f897c1bd340176cf18ca068e05367eac90 Mon Sep 17 00:00:00 2001 From: FranciscoCardoso913 Date: Sat, 19 Aug 2023 10:49:28 +0100 Subject: [PATCH 14/17] Tests for validation page Co-authored-by: Daniel Ferreira --- src/components/Apply/Company/CompanyApplicationUtils.js | 4 ++-- src/pages/ValidationPage.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Apply/Company/CompanyApplicationUtils.js b/src/components/Apply/Company/CompanyApplicationUtils.js index 750fb3bc..72b628a7 100644 --- a/src/components/Apply/Company/CompanyApplicationUtils.js +++ b/src/components/Apply/Company/CompanyApplicationUtils.js @@ -40,9 +40,9 @@ const ValidationMessages = Object.freeze({ }, }); -export const getValidationError = (error) => { +export const getValidationMessage = (description) => { const errorMsg = { title: "Unexpected Error!", text: "An unexpected error has occurred while validating your application. " }; - if (!error) { + if (!description) { return errorMsg; } if (typeof ValidationMessages[description] === "object") { diff --git a/src/pages/ValidationPage.js b/src/pages/ValidationPage.js index b0396816..bc70cfce 100644 --- a/src/pages/ValidationPage.js +++ b/src/pages/ValidationPage.js @@ -35,7 +35,7 @@ const useStyles = (isMobile) => makeStyles((theme) => ({ } }, - + })); const ValidationPage = () => { @@ -68,7 +68,7 @@ const ValidationPage = () => { const getMessageCard = (error) => { - const { title, text } = success ? successMessage : getValidationError(error); + const { title, text } = success ? successMessage : getValidationMessage(error); return ( From 95794d094e8cc2f623554bd177cd6745a28d2db5 Mon Sep 17 00:00:00 2001 From: FranciscoCardoso913 Date: Sat, 19 Aug 2023 12:21:12 +0100 Subject: [PATCH 15/17] Tests for the alerts components in the company offer management page and the create offer form --- .../Offers/Form/form-components/OfferForm.js | 3 -- .../Offers/New/CreateOfferForm.spec.js | 46 ++++++++++++++++- src/components/utils/Alert.js | 4 +- src/pages/CompanyOffersManagementPage.js | 3 -- src/pages/CompanyOffersManagementPage.spec.js | 50 +++++++++++++++++++ 5 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 src/pages/CompanyOffersManagementPage.spec.js diff --git a/src/components/Offers/Form/form-components/OfferForm.js b/src/components/Offers/Form/form-components/OfferForm.js index 86d8a415..fa31f2b7 100644 --- a/src/components/Offers/Form/form-components/OfferForm.js +++ b/src/components/Offers/Form/form-components/OfferForm.js @@ -112,9 +112,6 @@ const OfferForm = ({ context, title }) => { key: `${Date.now()}-fetchCompanyApplicationsError`, }); }); - return () => { - request.cancel(); - }; } }, [addSnackbar, session.isValidating, session.isLoggedIn]); diff --git a/src/components/Offers/New/CreateOfferForm.spec.js b/src/components/Offers/New/CreateOfferForm.spec.js index 95588488..5aefdd77 100644 --- a/src/components/Offers/New/CreateOfferForm.spec.js +++ b/src/components/Offers/New/CreateOfferForm.spec.js @@ -3,7 +3,7 @@ import { createTheme } from "@material-ui/core/styles"; import useComponentController from "../../../hooks/useComponentController"; import { CreateOfferController, CreateOfferControllerContext } from "./CreateOfferForm"; import { BrowserRouter } from "react-router-dom"; -import { screen, fireEvent, renderWithStoreAndTheme } from "../../../test-utils"; +import { screen, fireEvent, renderWithStoreAndTheme, render } from "../../../test-utils"; import useSession from "../../../hooks/useSession"; import CreateOfferPage from "../../../pages/CreateOfferPage"; import { MuiPickersUtilsProvider } from "@material-ui/pickers"; @@ -14,9 +14,17 @@ import { act } from "@testing-library/react"; import { DAY_IN_MS } from "../../../utils/TimeUtils"; import { PAID_OPTIONS } from "../Form/form-components/OfferForm"; import { HumanValidationReasons } from "../../../utils"; +import { validateApplication } from "../../../services/companyApplicationService"; +import { ThemeProvider } from "@material-ui/core"; +import AppTheme from "../../../AppTheme"; +import ValidationPage from "../../../pages/ValidationPage"; +import { getValidationMessage } from "../../Apply/Company/CompanyApplicationUtils"; +import { fetchCompanyApplicationState } from "../../../services/companyService"; +import { Alert } from "../../utils/Alert"; jest.mock("../../../hooks/useSession"); jest.mock("../../../services/locationSearchService"); +jest.mock("../../../services/companyService"); // eslint-disable-next-line react/prop-types const CreateOfferWrapper = ({ children }) => { @@ -40,6 +48,7 @@ describe("Create Offer Form", () => { const initialState = {}; const theme = createTheme({}); + fetchCompanyApplicationState.mockImplementation(async () =>"APPROVED"); // it("Should edit description", () => { // As of today, it is not possible to test contenteditable elements (such as the awesome description editor) @@ -217,6 +226,41 @@ describe("Create Offer Form", () => { expect(element).toBeVisible(); }); }); + + it("Should render alert if company is not approved", async () => { + useSession.mockImplementation(() => ({ isLoggedIn: true, data: { company: { name: "Company Name" } } })); + fetchCompanyApplicationState.mockImplementation(async () =>"UNVERIFIED"); + + await renderWithStoreAndTheme( + + + + + + + , + { initialState, theme } + ); + expect( screen.queryByTestId( 'Alert')).toBeInTheDocument(); + + }); + + it("Should not render alert if company is approved", async () => { + useSession.mockImplementation(() => ({ isLoggedIn: true, data: { company: { name: "Company Name" } } })); + fetchCompanyApplicationState.mockImplementation(async () =>"APPROVED"); + + await renderWithStoreAndTheme( + + + + + + + , + { initialState, theme } + ); + expect(await screen.queryByTestId("Alert")).not.toBeInTheDocument(); + }); }); describe("Should validate Form", () => { diff --git a/src/components/utils/Alert.js b/src/components/utils/Alert.js index 49236787..76bbcbbe 100644 --- a/src/components/utils/Alert.js +++ b/src/components/utils/Alert.js @@ -18,7 +18,7 @@ const useStyles = (props) => makeStyles((theme) => ({ export const Alert = ({type, title, fontSize = 1, children}) => { const classes = useStyles({fontSize: fontSize})(); return ( - }> + } data-testid="Alert"> {title ? {title} : null} {children} @@ -30,4 +30,4 @@ Alert.propTypes = { title: PropTypes.string, children: PropTypes.string, fontSize: PropTypes.number, -} \ No newline at end of file +} diff --git a/src/pages/CompanyOffersManagementPage.js b/src/pages/CompanyOffersManagementPage.js index 79248cc5..09793a97 100644 --- a/src/pages/CompanyOffersManagementPage.js +++ b/src/pages/CompanyOffersManagementPage.js @@ -37,9 +37,6 @@ const CompanyOffersManagementPage = () => { key: `${Date.now()}-fetchCompanyApplicationsError`, }); }); - return () => { - request.cancel(); - }; } }, [addSnackbar, session.isLoggedIn, session.isValidating]); diff --git a/src/pages/CompanyOffersManagementPage.spec.js b/src/pages/CompanyOffersManagementPage.spec.js new file mode 100644 index 00000000..6286e084 --- /dev/null +++ b/src/pages/CompanyOffersManagementPage.spec.js @@ -0,0 +1,50 @@ +import React from "react"; +import { createTheme } from "@material-ui/core/styles"; +import { BrowserRouter } from "react-router-dom"; +import { renderWithStoreAndTheme, screen } from "../test-utils"; +import useSession from "../hooks/useSession"; +import { fetchCompanyApplicationState } from "../services/companyService"; +import { MuiPickersUtilsProvider } from "@material-ui/pickers"; +import DateFnsUtils from "@date-io/date-fns"; +import CompanyOffersManagementPage from "./CompanyOffersManagementPage"; + +jest.mock("../hooks/useSession"); +jest.mock("../services/offerService"); +jest.mock("../services/companyService"); +const theme = createTheme({}); + +// eslint-disable-next-line react/prop-types + +describe("Company Offers Management Page", () => { + + it("Should render alert if company is not approved", async () => { + useSession.mockImplementation(() => ({ isLoggedIn: true, data: { company: { name: "Company Name" } } })); + fetchCompanyApplicationState.mockImplementation(async () =>"UNVERIFIED"); + + await renderWithStoreAndTheme( + + + + + , + { initialState: {}, theme } + ); + expect( screen.queryByTestId( 'Alert')).toBeInTheDocument(); + + }); + + it("Should not render alert if company is approved", async () => { + useSession.mockImplementation(() => ({ isLoggedIn: true, data: { company: { name: "Company Name" } } })); + fetchCompanyApplicationState.mockImplementation(async () =>"APPROVED"); + + await renderWithStoreAndTheme( + + + + + , + { initialState: {}, theme } + ); + expect(await screen.queryByTestId("Alert")).not.toBeInTheDocument(); + }); +}); From 41986812980f5140186864235761f243eb4a199f Mon Sep 17 00:00:00 2001 From: FranciscoCardoso913 Date: Tue, 29 Aug 2023 12:42:45 +0100 Subject: [PATCH 16/17] Making tests for the validate application and the fetch company application services Making tests for the validate application and the fetch company application services --- .../Apply/Company/ApplicationConfirmation.js | 3 +- .../Offers/Form/form-components/OfferForm.js | 32 +++-- .../Form/form-components/offerStyles.js | 8 +- .../Offers/New/CreateOfferForm.spec.js | 21 ++- .../ApplicationsReviewWidget.spec.js | 4 +- src/components/utils/Alert.js | 36 ++--- src/pages/CompanyOffersManagementPage.js | 34 ++--- src/pages/CompanyOffersManagementPage.spec.js | 12 +- src/pages/ValidationPage.js | 27 ++-- src/pages/ValidationPage.spec.js | 29 ++-- .../companyApplicationService.spec.js | 130 ++++++++++++------ src/services/companyService.js | 13 +- src/services/companyService.spec.js | 50 +++++++ 13 files changed, 254 insertions(+), 145 deletions(-) create mode 100644 src/services/companyService.spec.js diff --git a/src/components/Apply/Company/ApplicationConfirmation.js b/src/components/Apply/Company/ApplicationConfirmation.js index 142367e6..51a3b03c 100644 --- a/src/components/Apply/Company/ApplicationConfirmation.js +++ b/src/components/Apply/Company/ApplicationConfirmation.js @@ -23,7 +23,8 @@ const ApplicationConfirmation = () => { Application Submitted: You should receive an email containing a confirmation link for your application. - Please confirm it within 10 minutes; otherwise, the link will expire. If you have not received an email, please contact us at + Please confirm it within 10 minutes; otherwise, the link will expire. If you have not received an email, + please contact us at {" "} {Constants.CONTACT_US_EMAIL} diff --git a/src/components/Offers/Form/form-components/OfferForm.js b/src/components/Offers/Form/form-components/OfferForm.js index fa31f2b7..3b6f0bbe 100644 --- a/src/components/Offers/Form/form-components/OfferForm.js +++ b/src/components/Offers/Form/form-components/OfferForm.js @@ -7,9 +7,9 @@ import { FormControl, Typography, Collapse, - Button, makeStyles, + Button, } from "@material-ui/core"; -import React, {useState, useCallback, useContext, useEffect} from "react"; +import React, { useState, useCallback, useContext, useEffect } from "react"; import { Redirect } from "react-router-dom"; import PropTypes from "prop-types"; import MultiOptionTextField from "../../../utils/form/MultiOptionTextField"; @@ -37,7 +37,7 @@ import { useMobile } from "../../../../utils/media-queries"; import "../editor.css"; import ApplyURLComponent from "./ApplyURLComponent"; import { Alert } from "../../../utils/Alert"; -import { fetchCompanyApplicationState } from "../../../../services/companyService"; +import { fetchCompanyApplication } from "../../../../services/companyService"; import useSession from "../../../../hooks/useSession.js"; import { addSnackbar } from "../../../../actions/notificationActions"; @@ -97,14 +97,14 @@ const OfferForm = ({ context, title }) => { const Content = isMobile ? DialogContent : CardContent; const classes = useOfferFormStyles(isMobile)(); - const [state, setState] = useState( "APPROVED"); + const [state, setState] = useState("APPROVED"); const session = useSession(); useEffect(() => { - if(!session.isValidating && session.isLoggedIn) { - const request = fetchCompanyApplicationState(session.data?.company?._id) - .then((state) => { - setState(state); + if (!session.isValidating && session.isLoggedIn) { + fetchCompanyApplication(session.data?.company?._id) + .then((application) => { + setState(application.state); }) .catch(() => { addSnackbar({ @@ -113,7 +113,7 @@ const OfferForm = ({ context, title }) => { }); }); } - }, [addSnackbar, session.isValidating, session.isLoggedIn]); + }, [session.isValidating, session.isLoggedIn, session.data.company._id]); const showOwnerComponent = isAdmin && showCompanyField; @@ -130,9 +130,17 @@ const OfferForm = ({ context, title }) => { ? :
- {(state !== "APPROVED") && session.isLoggedIn && {"This offer will stay hidden from the public until your account is approved!"}} - + {(state !== "APPROVED") && session.isLoggedIn && + + { + "This offer will stay hidden from the public until your account is approved!" + } + + } + makeStyles((theme) => ({ paddingTop: theme.spacing(2), marginTop: theme.spacing(2), }, - warning:{ - fontSize:"1.2em", + warning: { + fontSize: "1.2em", "& .MuiAlert-icon": { - fontSize: "1.5em" + fontSize: "1.5em", }, - marginBottom:"1em" + marginBottom: "1em", }, diff --git a/src/components/Offers/New/CreateOfferForm.spec.js b/src/components/Offers/New/CreateOfferForm.spec.js index 5aefdd77..1045ada7 100644 --- a/src/components/Offers/New/CreateOfferForm.spec.js +++ b/src/components/Offers/New/CreateOfferForm.spec.js @@ -3,7 +3,7 @@ import { createTheme } from "@material-ui/core/styles"; import useComponentController from "../../../hooks/useComponentController"; import { CreateOfferController, CreateOfferControllerContext } from "./CreateOfferForm"; import { BrowserRouter } from "react-router-dom"; -import { screen, fireEvent, renderWithStoreAndTheme, render } from "../../../test-utils"; +import { screen, fireEvent, renderWithStoreAndTheme } from "../../../test-utils"; import useSession from "../../../hooks/useSession"; import CreateOfferPage from "../../../pages/CreateOfferPage"; import { MuiPickersUtilsProvider } from "@material-ui/pickers"; @@ -14,13 +14,7 @@ import { act } from "@testing-library/react"; import { DAY_IN_MS } from "../../../utils/TimeUtils"; import { PAID_OPTIONS } from "../Form/form-components/OfferForm"; import { HumanValidationReasons } from "../../../utils"; -import { validateApplication } from "../../../services/companyApplicationService"; -import { ThemeProvider } from "@material-ui/core"; -import AppTheme from "../../../AppTheme"; -import ValidationPage from "../../../pages/ValidationPage"; -import { getValidationMessage } from "../../Apply/Company/CompanyApplicationUtils"; -import { fetchCompanyApplicationState } from "../../../services/companyService"; -import { Alert } from "../../utils/Alert"; +import { fetchCompanyApplication } from "../../../services/companyService"; jest.mock("../../../hooks/useSession"); jest.mock("../../../services/locationSearchService"); @@ -48,7 +42,8 @@ describe("Create Offer Form", () => { const initialState = {}; const theme = createTheme({}); - fetchCompanyApplicationState.mockImplementation(async () =>"APPROVED"); + // eslint-disable-next-line require-await + fetchCompanyApplication.mockImplementation(async () => "APPROVED"); // it("Should edit description", () => { // As of today, it is not possible to test contenteditable elements (such as the awesome description editor) @@ -229,7 +224,8 @@ describe("Create Offer Form", () => { it("Should render alert if company is not approved", async () => { useSession.mockImplementation(() => ({ isLoggedIn: true, data: { company: { name: "Company Name" } } })); - fetchCompanyApplicationState.mockImplementation(async () =>"UNVERIFIED"); + // eslint-disable-next-line require-await + fetchCompanyApplication.mockImplementation(async () => ({ state: "PENDING" })); await renderWithStoreAndTheme( @@ -241,13 +237,14 @@ describe("Create Offer Form", () => { , { initialState, theme } ); - expect( screen.queryByTestId( 'Alert')).toBeInTheDocument(); + expect(screen.queryByTestId("Alert")).toBeInTheDocument(); }); it("Should not render alert if company is approved", async () => { useSession.mockImplementation(() => ({ isLoggedIn: true, data: { company: { name: "Company Name" } } })); - fetchCompanyApplicationState.mockImplementation(async () =>"APPROVED"); + // eslint-disable-next-line require-await + fetchCompanyApplication.mockImplementation(async () => ({ state: "APPROVED" })); await renderWithStoreAndTheme( diff --git a/src/components/Review/Applications/ApplicationsReviewWidget.spec.js b/src/components/Review/Applications/ApplicationsReviewWidget.spec.js index d42b5a07..67d50630 100644 --- a/src/components/Review/Applications/ApplicationsReviewWidget.spec.js +++ b/src/components/Review/Applications/ApplicationsReviewWidget.spec.js @@ -286,10 +286,10 @@ describe("Application Review Widget", () => { , { initialState: {}, theme }) ); - + expect(screen.queryByLabelText("Approve Application")).not.toBeInTheDocument(); expect(screen.queryByLabelText("Reject Application")).not.toBeInTheDocument(); - }) + }); it("Should maintain state filter after rejecting an application", async () => { const applications = generateApplications(1, "PENDING"); diff --git a/src/components/utils/Alert.js b/src/components/utils/Alert.js index 76bbcbbe..5f4adf34 100644 --- a/src/components/utils/Alert.js +++ b/src/components/utils/Alert.js @@ -1,33 +1,37 @@ -import {Alert as MUI_Alert, AlertTitle} from "@material-ui/lab"; -import {makeStyles} from "@material-ui/core"; + +import { Alert as MUIAlert, AlertTitle } from "@material-ui/lab"; +import { makeStyles } from "@material-ui/core"; import PropTypes from "prop-types"; import { Warning as WarningIcon } from "@material-ui/icons"; -import React from 'react'; +import React from "react"; -const useStyles = (props) => makeStyles((theme) => ({ +const useStyles = (props) => makeStyles(() => ({ - content:{ - fontSize: props.fontSize+"em", + content: { + fontSize: `${props.fontSize}em`, "& .MuiAlert-icon": { - fontSize: (props.fontSize+0.3)+"em" + fontSize: `${props.fontSize + 0.3}em`, }, - margin:"0.5em 0em" + margin: "0.5em 0em", }, })); -export const Alert = ({type, title, fontSize = 1, children}) => { - const classes = useStyles({fontSize: fontSize})(); +export const Alert = ({ type, title, fontSize = 1, children }) => { + const classes = useStyles({ fontSize: fontSize })(); return ( - } data-testid="Alert"> - {title ? {title} : null} + } data-testid="Alert"> + {title ? + + {title} + : null} {children} - - ) -} + + ); +}; Alert.propTypes = { type: PropTypes.oneOf(["error", "warning", "info", "success"]), title: PropTypes.string, children: PropTypes.string, fontSize: PropTypes.number, -} +}; diff --git a/src/pages/CompanyOffersManagementPage.js b/src/pages/CompanyOffersManagementPage.js index 09793a97..59d166de 100644 --- a/src/pages/CompanyOffersManagementPage.js +++ b/src/pages/CompanyOffersManagementPage.js @@ -1,9 +1,9 @@ -import React, {useEffect, useState} from "react"; +import React, { useEffect, useState } from "react"; import CompanyOffersManagementWidget from "../components/Company/Offers/Manage/CompanyOffersManagementWidget"; import { CardContent, makeStyles } from "@material-ui/core"; import { useMobile } from "../utils/media-queries"; import { Alert } from "../components/utils/Alert"; -import { fetchCompanyApplicationState } from "../services/companyService"; +import { fetchCompanyApplication } from "../services/companyService"; import useSession from "../hooks/useSession"; import { addSnackbar } from "../actions/notificationActions"; @@ -17,19 +17,14 @@ const useStyles = (isMobile) => makeStyles((theme) => ({ const CompanyOffersManagementPage = () => { const isMobile = useMobile(); const classes = useStyles(isMobile)(); - const [state, setState_] = useState( "APPROVED"); + const [state, setState_] = useState("APPROVED"); const session = useSession(); useEffect(() => { - if(!session.isValidating && session.isLoggedIn) { - const request = fetchCompanyApplicationState(session.data?.company?._id) - .then((state_) => { - console.log(state_); - console.log("Tipo"); - console.log(typeof state_); - setState_(state_); - console.log(state); - + if (!session.isValidating && session.isLoggedIn) { + fetchCompanyApplication(session.data?.company?._id) + .then((application) => { + setState_(application.state); }) .catch(() => { addSnackbar({ @@ -38,13 +33,20 @@ const CompanyOffersManagementPage = () => { }); }); } - }, [addSnackbar, session.isLoggedIn, session.isValidating]); + }, [session.data.company._id, session.isLoggedIn, session.isValidating]); return ( - {(state !== "APPROVED") && session.isLoggedIn && {"Your offers will stay hidden from the public until your account is approved!"}} - + { + (state !== "APPROVED") && session.isLoggedIn && + + {"Your offers will stay hidden from the public until your account is approved!"} + + } + ); }; diff --git a/src/pages/CompanyOffersManagementPage.spec.js b/src/pages/CompanyOffersManagementPage.spec.js index 6286e084..ee88d16c 100644 --- a/src/pages/CompanyOffersManagementPage.spec.js +++ b/src/pages/CompanyOffersManagementPage.spec.js @@ -3,7 +3,7 @@ import { createTheme } from "@material-ui/core/styles"; import { BrowserRouter } from "react-router-dom"; import { renderWithStoreAndTheme, screen } from "../test-utils"; import useSession from "../hooks/useSession"; -import { fetchCompanyApplicationState } from "../services/companyService"; +import { fetchCompanyApplication } from "../services/companyService"; import { MuiPickersUtilsProvider } from "@material-ui/pickers"; import DateFnsUtils from "@date-io/date-fns"; import CompanyOffersManagementPage from "./CompanyOffersManagementPage"; @@ -19,7 +19,8 @@ describe("Company Offers Management Page", () => { it("Should render alert if company is not approved", async () => { useSession.mockImplementation(() => ({ isLoggedIn: true, data: { company: { name: "Company Name" } } })); - fetchCompanyApplicationState.mockImplementation(async () =>"UNVERIFIED"); + // eslint-disable-next-line require-await + fetchCompanyApplication.mockImplementation(async () => ({ state: "PENDING" })); await renderWithStoreAndTheme( @@ -29,13 +30,14 @@ describe("Company Offers Management Page", () => { , { initialState: {}, theme } ); - expect( screen.queryByTestId( 'Alert')).toBeInTheDocument(); + expect(screen.queryByTestId("Alert")).toBeInTheDocument(); }); it("Should not render alert if company is approved", async () => { useSession.mockImplementation(() => ({ isLoggedIn: true, data: { company: { name: "Company Name" } } })); - fetchCompanyApplicationState.mockImplementation(async () =>"APPROVED"); + // eslint-disable-next-line require-await + fetchCompanyApplication.mockImplementation(async () => ({ state: "APPROVED" })); await renderWithStoreAndTheme( @@ -45,6 +47,6 @@ describe("Company Offers Management Page", () => { , { initialState: {}, theme } ); - expect(await screen.queryByTestId("Alert")).not.toBeInTheDocument(); + expect(screen.queryByTestId("Alert")).not.toBeInTheDocument(); }); }); diff --git a/src/pages/ValidationPage.js b/src/pages/ValidationPage.js index bc70cfce..4c606a73 100644 --- a/src/pages/ValidationPage.js +++ b/src/pages/ValidationPage.js @@ -1,13 +1,10 @@ -/* eslint-disable react/jsx-indent */ -/* eslint-disable react/jsx-closing-tag-location */ -/* eslint-disable indent */ import React, { useEffect, useState } from "react"; -import {CardContent, CircularProgress, makeStyles, Button, Link, Typography} from "@material-ui/core"; +import { CardContent, CircularProgress, makeStyles, Button, Link, Typography } from "@material-ui/core"; import { useMobile } from "../utils/media-queries"; import { useParams } from "react-router-dom"; import { validateApplication } from "../services/companyApplicationService"; import { getValidationMessage } from "../components/Apply/Company/CompanyApplicationUtils.js"; -import {RouterLink} from "../utils"; +import { RouterLink } from "../utils"; const useStyles = (isMobile) => makeStyles((theme) => ({ content: { @@ -16,10 +13,10 @@ const useStyles = (isMobile) => makeStyles((theme) => ({ flexDirection: "column", alignItems: "center", justifyContent: "center", - gap:"1em", + gap: "1em", }, title: { - fontWeight:500, + fontWeight: 500, }, text: { fontSize: theme.typography.body1, @@ -29,10 +26,10 @@ const useStyles = (isMobile) => makeStyles((theme) => ({ }, button: { background: theme.palette.primary.main, - color:theme.palette.dark.contrastText, - '&:hover':{ + color: theme.palette.dark.contrastText, + "&:hover": { background: theme.palette.secondary.main, - } + }, }, @@ -48,7 +45,7 @@ const ValidationPage = () => { const [error, setError] = useState(""); - useEffect( () => { + useEffect(() => { async function validate() { try { setLoading(false); @@ -71,10 +68,10 @@ const ValidationPage = () => { const { title, text } = success ? successMessage : getValidationMessage(error); return ( - + {title} - - + + {text} {!success ? "For more information contact us at " : ""} nijobs@aefeup.pt @@ -97,7 +94,7 @@ const ValidationPage = () => { ); } else { - return getMessageCard(error); + return getMessageCard(error); } }; diff --git a/src/pages/ValidationPage.spec.js b/src/pages/ValidationPage.spec.js index 9681fb45..57e4f6cd 100644 --- a/src/pages/ValidationPage.spec.js +++ b/src/pages/ValidationPage.spec.js @@ -25,53 +25,58 @@ describe("Validation Page", () => { const page = await render( - + , ); - const {title, text} = getValidationMessage("success"); + const { title } = getValidationMessage("success"); expect(page.queryByText(title)).toBeInTheDocument(); }); it("Should show error message if token does not exist", async () => { - validateApplication.mockImplementation(() =>{ throw [{ msg: "invalid-token" }]}); + validateApplication.mockImplementation(() => { + throw [{ msg: "invalid-token" }]; + }); const page = await render( - + , ); - const {title, text} = getValidationMessage("invalid-token"); + const { title } = getValidationMessage("invalid-token"); expect(page.queryByText(title)).toBeInTheDocument(); }); it("Should show error message if token has expired", async () => { - validateApplication.mockImplementation(() =>{ throw [{ msg: "expired-token" }]}); + validateApplication.mockImplementation(() => { + throw [{ msg: "expired-token" }]; + }); const page = await render( - + , ); - const {title, text} = getValidationMessage("expired-token"); + const { title } = getValidationMessage("expired-token"); expect(page.queryByText(title)).toBeInTheDocument(); }); it("Should show error message if application is already validated", async () => { - validateApplication.mockImplementation(() =>{ throw [{ msg: "application-already-validated" }]}); + validateApplication.mockImplementation(() => { + throw [{ msg: "application-already-validated" }]; + }); const page = await render( - + , ); - const {title, text} = getValidationMessage("application-already-validated"); + const { title } = getValidationMessage("application-already-validated"); expect(page.queryByText(title)).toBeInTheDocument(); }); }); - diff --git a/src/services/companyApplicationService.spec.js b/src/services/companyApplicationService.spec.js index 1be6d5f4..614620bf 100644 --- a/src/services/companyApplicationService.spec.js +++ b/src/services/companyApplicationService.spec.js @@ -1,6 +1,6 @@ import config from "../config"; -import { submitCompanyApplication } from "./companyApplicationService"; +import { submitCompanyApplication, validateApplication } from "./companyApplicationService"; import { setCompanyApplicationSending, setCompanyApplicationSubmissionError, @@ -10,65 +10,109 @@ import Constants from "../utils/Constants"; const { API_HOSTNAME } = config; describe("Company Application Service", () => { - it("should POST the API with the form data in JSON format and dispatch the correct actions", async () => { + describe("Submit Application", () => { + it("should POST the API with the form data in JSON format and dispatch the correct actions", async () => { - // Simulate request success - fetch.mockResponse(JSON.stringify({ mockData: true })); + // Simulate request success + fetch.mockResponse(JSON.stringify({ mockData: true })); - const dispatchMock = jest.fn(); + const dispatchMock = jest.fn(); - const formData = { field1: 1, field2: 2 }; - await submitCompanyApplication(formData)(dispatchMock); + const formData = { field1: 1, field2: 2 }; + await submitCompanyApplication(formData)(dispatchMock); - expect(fetch).toHaveBeenCalledWith(`${API_HOSTNAME}/apply/company`, expect.objectContaining({ - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(formData), - })); + expect(fetch).toHaveBeenCalledWith(`${API_HOSTNAME}/apply/company`, expect.objectContaining({ + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + })); - expect(dispatchMock).toHaveBeenNthCalledWith(1, setCompanyApplicationSending(true)); - expect(dispatchMock).toHaveBeenNthCalledWith(2, setCompanyApplicationSubmissionError([])); - expect(dispatchMock).toHaveBeenNthCalledWith(3, setCompanyApplication({ mockData: true })); - expect(dispatchMock).toHaveBeenNthCalledWith(4, setCompanyApplicationSending(false)); - }); + expect(dispatchMock).toHaveBeenNthCalledWith(1, setCompanyApplicationSending(true)); + expect(dispatchMock).toHaveBeenNthCalledWith(2, setCompanyApplicationSubmissionError([])); + expect(dispatchMock).toHaveBeenNthCalledWith(3, setCompanyApplication({ mockData: true })); + expect(dispatchMock).toHaveBeenNthCalledWith(4, setCompanyApplicationSending(false)); + }); - it("should handle network error", async () => { + it("should handle network error", async () => { - // Simulate network failure - fetch.mockAbort(); + // Simulate network failure + fetch.mockAbort(); - const dispatchMock = jest.fn(); + const dispatchMock = jest.fn(); - const formData = { field1: 1, field2: 2 }; - await submitCompanyApplication(formData)(dispatchMock); + const formData = { field1: 1, field2: 2 }; + await submitCompanyApplication(formData)(dispatchMock); - expect(dispatchMock).toHaveBeenNthCalledWith(1, setCompanyApplicationSending(true)); - expect(dispatchMock).toHaveBeenNthCalledWith(2, setCompanyApplicationSubmissionError([])); - expect(dispatchMock).toHaveBeenNthCalledWith(3, setCompanyApplicationSubmissionError([ - { msg: Constants.UNEXPECTED_ERROR_MESSAGE }, - ])); - expect(dispatchMock).toHaveBeenNthCalledWith(4, setCompanyApplicationSending(false)); - }); + expect(dispatchMock).toHaveBeenNthCalledWith(1, setCompanyApplicationSending(true)); + expect(dispatchMock).toHaveBeenNthCalledWith(2, setCompanyApplicationSubmissionError([])); + expect(dispatchMock).toHaveBeenNthCalledWith(3, setCompanyApplicationSubmissionError([ + { msg: Constants.UNEXPECTED_ERROR_MESSAGE }, + ])); + expect(dispatchMock).toHaveBeenNthCalledWith(4, setCompanyApplicationSending(false)); + }); - it("should handle not-ok response from API", async () => { + it("should handle not-ok response from API", async () => { - const errors = [{ msg: "error1" }, { msg: "error2" }]; + const errors = [{ msg: "error1" }, { msg: "error2" }]; - // Simulate request error - fetch.mockResponse(JSON.stringify({ errors }), { status: 422 }); + // Simulate request error + fetch.mockResponse(JSON.stringify({ errors }), { status: 422 }); - const dispatchMock = jest.fn(); + const dispatchMock = jest.fn(); - const formData = { field1: 1, field2: 2 }; - await submitCompanyApplication(formData)(dispatchMock); + const formData = { field1: 1, field2: 2 }; + await submitCompanyApplication(formData)(dispatchMock); - expect(dispatchMock).toHaveBeenNthCalledWith(1, setCompanyApplicationSending(true)); - expect(dispatchMock).toHaveBeenNthCalledWith(2, setCompanyApplicationSubmissionError([])); - expect(dispatchMock).toHaveBeenNthCalledWith(3, setCompanyApplicationSubmissionError(errors)); - expect(dispatchMock).toHaveBeenNthCalledWith(4, setCompanyApplicationSending(false)); + expect(dispatchMock).toHaveBeenNthCalledWith(1, setCompanyApplicationSending(true)); + expect(dispatchMock).toHaveBeenNthCalledWith(2, setCompanyApplicationSubmissionError([])); + expect(dispatchMock).toHaveBeenNthCalledWith(3, setCompanyApplicationSubmissionError(errors)); + expect(dispatchMock).toHaveBeenNthCalledWith(4, setCompanyApplicationSending(false)); + }); + }); + describe("Validate Application", () => { + it("should POST the API to validate application ", async () => { + + // Simulate request success + fetch.mockResponse(JSON.stringify({})); + + await validateApplication(0); + expect(fetch).toHaveBeenCalledWith( + `${API_HOSTNAME}/apply/company/${0}/validate`, + { + method: "POST", + credentials: "include", + } + ); + }); + + it("should handle network error", async () => { + + // Simulate network failure + fetch.mockAbort(); + try { + await validateApplication(0); + + } catch (err) { + expect(err).toStrictEqual([{ msg: Constants.UNEXPECTED_ERROR_MESSAGE }]); + } + }); + + it("should handle not-ok response from API", async () => { + + const errors = [{ msg: "error1" }, { msg: "error2" }]; + + // Simulate request error + fetch.mockResponse(JSON.stringify({ errors }), { status: 422 }); + + try { + await validateApplication(0); + } catch (e) { + expect(e).toStrictEqual(errors); + } + }); }); }); diff --git a/src/services/companyService.js b/src/services/companyService.js index f55bc35a..12d13b79 100644 --- a/src/services/companyService.js +++ b/src/services/companyService.js @@ -1,6 +1,6 @@ -import {buildCancelableRequest} from "../utils"; -import {createErrorEvent, createEvent, measureTime} from "../utils/analytics"; -import {EVENT_TYPES, TIMED_ACTIONS} from "../utils/analytics/constants"; +import { buildCancelableRequest } from "../utils"; +import { createErrorEvent, createEvent, measureTime } from "../utils/analytics"; +import { EVENT_TYPES, TIMED_ACTIONS } from "../utils/analytics/constants"; import ErrorTypes from "../utils/ErrorTypes"; import Constants from "../utils/Constants"; import config from "../config"; @@ -8,16 +8,15 @@ import config from "../config"; const COMPANY_APPLICATION_FETCH_METRIC_ID = "company_application/fetch"; const { API_HOSTNAME } = config; -export const fetchCompanyApplicationState = buildCancelableRequest(measureTime(TIMED_ACTIONS.COMPANY_APPLICATION_FETCH, async (companyId) => { +export const fetchCompanyApplication = buildCancelableRequest(measureTime(TIMED_ACTIONS.COMPANY_APPLICATION_FETCH, async (companyId) => { let isErrorRegistered = false; try { - const res = await fetch(`${API_HOSTNAME}/company/${companyId}/state`, { + const res = await fetch(`${API_HOSTNAME}/company/${companyId}/application`, { method: "GET", credentials: "include", }); const json = await res.json(); - console.log(json); if (!res.ok) { @@ -49,4 +48,4 @@ export const fetchCompanyApplicationState = buildCancelableRequest(measureTime(T throw errorArray; } -})); \ No newline at end of file +})); diff --git a/src/services/companyService.spec.js b/src/services/companyService.spec.js new file mode 100644 index 00000000..a54f8e50 --- /dev/null +++ b/src/services/companyService.spec.js @@ -0,0 +1,50 @@ +import Constants from "../utils/Constants"; +import { fetchCompanyApplication } from "./companyService"; +import config from "../config"; +const { API_HOSTNAME } = config; +describe("Company Service", () => { + describe("Fetch Company Application", () => { + const companyId = 0; + it("should GET the API to fetch the company application ", async () => { + + // Simulate request success + fetch.mockResponse(JSON.stringify({ mockData: true })); + + await fetchCompanyApplication(companyId); + expect(fetch).toHaveBeenCalledWith( + `${API_HOSTNAME}/company/${companyId}/application`, + { + method: "GET", + credentials: "include", + } + ); + }); + + it("should handle network error", async () => { + + // Simulate network failure + fetch.mockAbort(); + try { + await fetchCompanyApplication(companyId); + + } catch (err) { + expect(err).toStrictEqual([{ msg: Constants.UNEXPECTED_ERROR_MESSAGE }]); + } + }); + + it("should handle not-ok response from API", async () => { + + const errors = [{ msg: "error1" }, { msg: "error2" }]; + + // Simulate request error + fetch.mockResponse(JSON.stringify({ errors }), { status: 422 }); + + try { + await fetchCompanyApplication(companyId); + } catch (e) { + expect(e).toStrictEqual(errors); + } + }); + }); + +}); From decd1555538fcdf8f925f6ad210e8a2ddc195cc8 Mon Sep 17 00:00:00 2001 From: Francisco Cardoso Date: Sat, 16 Sep 2023 18:15:16 +0100 Subject: [PATCH 17/17] Add extra error message to validation page, fixed: can enter create offer page with admin account --- .env | 4 ++-- .../Apply/Company/CompanyApplicationUtils.js | 8 ++++++-- .../Offers/Form/form-components/OfferForm.js | 7 ++++--- src/pages/CompanyOffersManagementPage.js | 6 +++--- src/pages/ValidationPage.spec.js | 15 +++++++++++++++ 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/.env b/.env index b7832b3a..f0d89845 100644 --- a/.env +++ b/.env @@ -4,10 +4,10 @@ HOST_PORT=443 REACT_APP_API_HOSTNAME="https://localhost:8087" # Uncomment to disable devtools -#REACT_APP_ALLOW_DEV_TOOLS=false +# REACT_APP_ALLOW_DEV_TOOLS=false # Google Analytics' Universal ID ANALYTICS_ID= # GeoLocation Rapid Api key -GEO_API_KEY="APIKEY" \ No newline at end of file +GEO_API_KEY="APIKEY" diff --git a/src/components/Apply/Company/CompanyApplicationUtils.js b/src/components/Apply/Company/CompanyApplicationUtils.js index 72b628a7..db9e5206 100644 --- a/src/components/Apply/Company/CompanyApplicationUtils.js +++ b/src/components/Apply/Company/CompanyApplicationUtils.js @@ -35,8 +35,12 @@ const ValidationMessages = Object.freeze({ " You will need to create a new application.", }, "application-already-validated": { - title: "Application is already validated!", - text: "This application is already validated. ", + title: "Application was already validated!", + text: "This application was already validated. ", + }, + "account-already-using-email": { + title: "Error! Duplicated Email", + text: "There is already an account with this email, please create an application with another email. ", }, }); diff --git a/src/components/Offers/Form/form-components/OfferForm.js b/src/components/Offers/Form/form-components/OfferForm.js index 3b6f0bbe..5560f408 100644 --- a/src/components/Offers/Form/form-components/OfferForm.js +++ b/src/components/Offers/Form/form-components/OfferForm.js @@ -99,10 +99,11 @@ const OfferForm = ({ context, title }) => { const [state, setState] = useState("APPROVED"); const session = useSession(); - + const companyId = session.data?.company?._id; useEffect(() => { + if (isAdmin) return; if (!session.isValidating && session.isLoggedIn) { - fetchCompanyApplication(session.data?.company?._id) + fetchCompanyApplication(companyId) .then((application) => { setState(application.state); }) @@ -113,7 +114,7 @@ const OfferForm = ({ context, title }) => { }); }); } - }, [session.isValidating, session.isLoggedIn, session.data.company._id]); + }, [session.isValidating, session.isLoggedIn, isAdmin, companyId]); const showOwnerComponent = isAdmin && showCompanyField; diff --git a/src/pages/CompanyOffersManagementPage.js b/src/pages/CompanyOffersManagementPage.js index 59d166de..e7377eb1 100644 --- a/src/pages/CompanyOffersManagementPage.js +++ b/src/pages/CompanyOffersManagementPage.js @@ -19,10 +19,10 @@ const CompanyOffersManagementPage = () => { const classes = useStyles(isMobile)(); const [state, setState_] = useState("APPROVED"); const session = useSession(); - + const companyId = session.data?.company?._id; useEffect(() => { if (!session.isValidating && session.isLoggedIn) { - fetchCompanyApplication(session.data?.company?._id) + fetchCompanyApplication(companyId) .then((application) => { setState_(application.state); }) @@ -33,7 +33,7 @@ const CompanyOffersManagementPage = () => { }); }); } - }, [session.data.company._id, session.isLoggedIn, session.isValidating]); + }, [companyId, session.isLoggedIn, session.isValidating]); return ( diff --git a/src/pages/ValidationPage.spec.js b/src/pages/ValidationPage.spec.js index 57e4f6cd..aac2af8d 100644 --- a/src/pages/ValidationPage.spec.js +++ b/src/pages/ValidationPage.spec.js @@ -79,4 +79,19 @@ describe("Validation Page", () => { const { title } = getValidationMessage("application-already-validated"); expect(page.queryByText(title)).toBeInTheDocument(); }); + + it("Should show error message if there is an account with the same email of the application", async () => { + validateApplication.mockImplementation(() => { + throw [{ msg: "account-already-using-email" }]; + }); + const page = await render( + + + + + , + ); + const { title } = getValidationMessage("account-already-using-email"); + expect(page.queryByText(title)).toBeInTheDocument(); + }); });