From e0ff9c6cbef051efcbe163a5fee04d2cbe89bd93 Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Mon, 7 Jun 2021 17:24:50 +0400 Subject: [PATCH 1/8] - Updated CreateNewTeam page so selecting role does not create a team Bug fixes: - https://github.com/topcoder-platform/taas-app/issues/293 - https://github.com/topcoder-platform/taas-app/issues/292 --- src/root.component.jsx | 2 +- src/routes/CreateNewTeam/index.jsx | 10 +++-- .../components/RoleItem/styles.module.scss | 8 ++-- .../components/RolesList/styles.module.scss | 2 + .../CreateNewTeam/pages/SelectRole/index.jsx | 40 +++++++++++-------- 5 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/root.component.jsx b/src/root.component.jsx index 797270ff..9c22ae99 100644 --- a/src/root.component.jsx +++ b/src/root.component.jsx @@ -36,7 +36,7 @@ export default function Root() { - + {/* Global config for Toastr popups */} diff --git a/src/routes/CreateNewTeam/index.jsx b/src/routes/CreateNewTeam/index.jsx index 62fd1826..5e45e808 100644 --- a/src/routes/CreateNewTeam/index.jsx +++ b/src/routes/CreateNewTeam/index.jsx @@ -14,8 +14,6 @@ import LandingBox from "./components/LandingBox"; import IconMultipleActionsCheck from "../../assets/images/icon-multiple-actions-check-2.svg"; import IconListQuill from "../../assets/images/icon-list-quill.svg"; import IconOfficeFileText from "../../assets/images/icon-office-file-text.svg"; -import { postProject } from "services/teams"; -import withAuthentication from "../../hoc/withAuthentication"; function CreateNewTeam() { const createProjectAndNavigate = async (navigateTo) => { @@ -30,6 +28,10 @@ function CreateNewTeam() { }); }; + const goToSelectRole = () => { + navigate("/taas/myteams/createnewteam/role"); + }; + const goToJobDescription = () => { navigate(`/taas/myteams/createnewteam/jd`); }; @@ -45,7 +47,7 @@ function CreateNewTeam() { description="You know you want a front end developer, or a full stack developer, mobile one or others." icon={} backgroundImage="linear-gradient(101.95deg, #8B41B0 0%, #EF476F 100%)" - onClick={() => createProjectAndNavigate("role")} + onClick={goToSelectRole} /> header { padding: 16px 24px; diff --git a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx index a8137e94..ea5c668a 100644 --- a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx +++ b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx @@ -10,7 +10,6 @@ import React, { useCallback, useEffect, useState } from "react"; import { useData } from "hooks/useData"; import { navigate } from "@reach/router"; import { toastr } from "react-redux-toastr"; -import PT from "prop-types"; import RolesList from "./components/RolesList"; import Completeness from "../../components/Completeness"; import "./styles.module.scss"; @@ -25,8 +24,10 @@ import AddAnotherModal from "../../components/AddAnotherModal"; import RoleDetailsModal from "../../components/RoleDetailsModal"; import withAuthentication from "../../../../hoc/withAuthentication"; import AddedRolesAccordion from "./components/AddedRolesAccordion"; +import { postProject } from "services/teams"; +import _ from "lodash"; -function SelectRole({ projectId }) { +function SelectRole() { const [stages, setStages] = useState([ { name: "Select a Role", isCurrent: true }, { name: "Search Member" }, @@ -47,19 +48,28 @@ function SelectRole({ projectId }) { const submitJob = () => { setSubmitDone(false); - createJob({ - projectId, - title: `job-${Date()}`, - skills: [], - roleIds: addedRoles.map((r) => r.id), - numPositions: 1, - }) - .then(() => { - toastr.success("Job Submitted"); + postProject() + .then((res) => { + const projectId = _.get(res, "data.id"); + + createJob({ + projectId, + title: `job-${Date()}`, + skills: [], + roleIds: addedRoles.map((r) => r.id), + numPositions: 1, + }) + .then(() => { + toastr.success("Job Submitted"); + }) + .catch((err) => { + console.error(err); + toastr.warning("Error Submitting Job"); + }); }) .catch((err) => { console.error(err); - toastr.warning("Error Submitting Job"); + toastr.warning("Error Creating Project"); }) .finally(() => { setSubmitDone(true); @@ -184,8 +194,4 @@ function SelectRole({ projectId }) { } } -SelectRole.propTypes = { - projectId: PT.string, -}; - -export default withAuthentication(SelectRole); +export default SelectRole; From cbe424735f71be58ea1d80170de8b513ddc3cac8 Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Tue, 8 Jun 2021 14:58:00 +0400 Subject: [PATCH 2/8] - Refactored and fixed bugs in RoleDetailsModal - Changed Search in SelectRole Page to no longer be mocked --- .../components/BaseCreateModal/index.jsx | 92 ++++++++++ .../BaseCreateModal/styles.module.scss | 65 +++++++ .../components/RoleDetailsModal/index.jsx | 158 +++++++++--------- .../RoleDetailsModal/styles.module.scss | 70 ++++---- .../CreateNewTeam/pages/SelectRole/index.jsx | 36 ++-- src/services/teams.js | 16 ++ 6 files changed, 308 insertions(+), 129 deletions(-) create mode 100644 src/routes/CreateNewTeam/components/BaseCreateModal/index.jsx create mode 100644 src/routes/CreateNewTeam/components/BaseCreateModal/styles.module.scss diff --git a/src/routes/CreateNewTeam/components/BaseCreateModal/index.jsx b/src/routes/CreateNewTeam/components/BaseCreateModal/index.jsx new file mode 100644 index 00000000..215277cb --- /dev/null +++ b/src/routes/CreateNewTeam/components/BaseCreateModal/index.jsx @@ -0,0 +1,92 @@ +import React from "react"; +import PT from "prop-types"; +import Modal from "react-responsive-modal"; +import IconCrossLight from "../../../../assets/images/icon-cross-light.svg"; +import CenteredSpinner from "components/CenteredSpinner"; +import "./styles.module.scss"; +import cn from "classnames"; + +const modalStyle = { + borderRadius: "8px", + padding: "72px 0 60px 0", + width: "100%", + margin: 0, + "overflow-x": "hidden", +}; + +const containerStyle = { + padding: "10px", +}; + +const closeButtonStyle = { + top: "31px", + right: "31px", +}; + +function BaseCreateModal({ + open, + onClose, + hideCloseIcon, + headerIcon, + title, + subtitle, + buttons, + isLoading, + loadingMessage, + maxWidth = "680px", + darkHeader, + children, +}) { + return ( + + } + styles={{ + modal: { ...modalStyle, maxWidth }, + modalContainer: containerStyle, + closeButton: closeButtonStyle, + }} + > +
+ {isLoading ? ( +
+ + {loadingMessage &&
{loadingMessage}
} +
+ ) : ( + <> +
+
{headerIcon}
+
{title}
+ {subtitle &&

{subtitle}

} +
+ {children} + + )} +
+
{buttons}
+
+ ); +} + +BaseCreateModal.propTypes = { + open: PT.bool, + onClose: PT.func, + hideCloseIcon: PT.bool, + headerIcon: PT.node, + title: PT.string, + subtitle: PT.string, + buttons: PT.node, + isLoading: PT.bool, + loadingMessage: PT.string, + maxWidth: PT.string, + darkHeader: PT.bool, + children: PT.node, +}; + +export default BaseCreateModal; diff --git a/src/routes/CreateNewTeam/components/BaseCreateModal/styles.module.scss b/src/routes/CreateNewTeam/components/BaseCreateModal/styles.module.scss new file mode 100644 index 00000000..086d9564 --- /dev/null +++ b/src/routes/CreateNewTeam/components/BaseCreateModal/styles.module.scss @@ -0,0 +1,65 @@ +@import "styles/include"; + +.button-group { + display: flex; + flex-direction: row; + justify-content: center; + align-items: flex-end; + :first-child { + margin-right: 8px; + } +} + +.modal-body { + margin: 0 80px 40px 80px; +} + +.modal-header { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + text-align: center; + margin-bottom: 24px; + + .header-icon { + margin-bottom: 16px; + width: 42px; + height: 42px; + + img, + svg { + width: 100%; + height: 100%; + object-fit: cover; + } + } + + h5 { + @include font-barlow-condensed; + font-size: 34px; + color: #1e94a3; + text-transform: uppercase; + font-weight: 500; + margin-bottom: 10px; + } + + p { + @include font-roboto; + font-size: 16px; + color: #555555; + line-height: 26px; + } + + &.dark-header { + h5 { + color: #2a2a2a; + } + } +} + +.cross { + g { + stroke: #000; + } +} diff --git a/src/routes/CreateNewTeam/components/RoleDetailsModal/index.jsx b/src/routes/CreateNewTeam/components/RoleDetailsModal/index.jsx index 17039e66..a10f2de7 100644 --- a/src/routes/CreateNewTeam/components/RoleDetailsModal/index.jsx +++ b/src/routes/CreateNewTeam/components/RoleDetailsModal/index.jsx @@ -2,101 +2,109 @@ * Role Details Modal * Display role details. */ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useMemo } from "react"; import PT from "prop-types"; -import Modal from "react-responsive-modal"; import Button from "components/Button"; -import IconCrossLight from "../../../../assets/images/icon-cross-light.svg"; import FallbackIcon from "../../../../assets/images/icon-role-fallback.svg"; import "./styles.module.scss"; -import CenteredSpinner from "components/CenteredSpinner"; import { getRoleById } from "services/roles"; - -const modalStyle = { - borderRadius: "8px", - padding: "32px 32px 22px 32px", - maxWidth: "460px", - width: "100%", - margin: 0, - "overflow-x": "hidden", -}; - -const containerStyle = { - padding: "10px", -}; +import BaseCreateModal from "../BaseCreateModal"; +import MarkdownViewer from "components/MarkdownEditorViewer"; function RoleDetailsModal({ roleId, open, onClose }) { const [isLoading, setIsLoading] = useState(true); const [imgError, setImgError] = useState(false); const [showSkills, setShowSkills] = useState(false); const [role, setRole] = useState(null); + useEffect(() => { - setRole(null); - setIsLoading(true); - getRoleById(roleId).then((response) => { - setRole(response.data); - setIsLoading(false); - }); + if (roleId) { + setImgError(false); + setIsLoading(true); + getRoleById(roleId) + .then((response) => { + setRole(response.data); + }) + .catch(() => { + setRole({ name: "Unable to Load Description" }); + }) + .finally(() => { + setIsLoading(false); + }); + } }, [roleId]); + const headerIcon = useMemo( + () => + role && role.imageUrl && !imgError ? ( + setImgError(true)} + alt={role.name} + /> + ) : ( + + ), + [role, imgError] + ); + + const skills = role ? role.listOfSkills : []; + + const hideSkills = () => { + onClose(); + setTimeout(() => setShowSkills(false), 0); + }; + + const closeButton = ( + + ); + return ( - - } - styles={{ - modal: modalStyle, - modalContainer: containerStyle, - }} + onClose={hideSkills} + hideCloseIcon + isLoading={isLoading} + loadingMessage="Loading..." + title={role?.name} + headerIcon={headerIcon} + buttons={closeButton} + darkHeader > -
- {isLoading ? ( - <> - -
Loading...
- +
+
+ + +
+ {showSkills ? ( +
    + {skills.map((skill, i) => ( +
  • + {skill} +
  • + ))} +
) : ( - <> - {role && role.imageUrl && !imgError ? ( - setImgError(true)} - alt={role.name} - styleName="role-icon" - /> - ) : ( - - )} -
- - -
-
{role?.name}
-

{role?.description}

- +
+ +
)}
-
- -
- + ); } diff --git a/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss b/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss index 81daa623..e7470ce5 100644 --- a/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss +++ b/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss @@ -1,55 +1,49 @@ @import "styles/include"; .button-group { - display: flex; - flex-direction: row; - justify-content: center; - align-items: flex-end; - :first-child { - margin-right: 8px; - } -} - -.tab-button-group { display: flex; flex-direction: row; align-items: center; justify-content: center; - margin-bottom: 42px; + margin-bottom: 30px; } -.modal-body { - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - text-align: center; - margin-bottom: 80px; - - .role-icon { - width: 42px; - height: 42px; - } - - h5 { - @include font-barlow-condensed; - font-size: 34px; - color: #1e94a3; - text-transform: uppercase; - font-weight: 500; - margin-bottom: 10px; - } +.body { + @include font-roboto; + color: #2a2a2a; +} - p { +.markdown-container { + // not adds specificity to override style + p:not(table) { @include font-roboto; + color: #2a2a2a; font-size: 16px; - color: #555555; line-height: 26px; } } -.cross { - g { - stroke: #000; - } +.description-item { + font-size: 16px; + line-height: 26px; + margin-bottom: 10px; + list-style-type: disc; +} + +.skill-list { + margin: 0 30px; + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: flex-start; + flex-wrap: wrap; +} + +.skill-item { + background-color: #e9e9e9; + border-radius: 5px; + padding: 6px 9px; + margin-right: 6px; + margin-bottom: 10px; + font-size: 12px; } diff --git a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx index ea5c668a..2fba9947 100644 --- a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx +++ b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx @@ -6,10 +6,11 @@ * Allows selecting a role, searching for users * with that role, and submitting a job requiring the roles. */ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useState } from "react"; import { useData } from "hooks/useData"; import { navigate } from "@reach/router"; import { toastr } from "react-redux-toastr"; +import _ from "lodash"; import RolesList from "./components/RolesList"; import Completeness from "../../components/Completeness"; import "./styles.module.scss"; @@ -24,8 +25,7 @@ import AddAnotherModal from "../../components/AddAnotherModal"; import RoleDetailsModal from "../../components/RoleDetailsModal"; import withAuthentication from "../../../../hoc/withAuthentication"; import AddedRolesAccordion from "./components/AddedRolesAccordion"; -import { postProject } from "services/teams"; -import _ from "lodash"; +import { postProject, searchRoles } from "services/teams"; function SelectRole() { const [stages, setStages] = useState([ @@ -44,8 +44,6 @@ function SelectRole() { const [roles, loadingError] = useData(getRoles); - let searchTimer; - const submitJob = () => { setSubmitDone(false); postProject() @@ -102,19 +100,25 @@ function SelectRole() { const search = () => { setCurrentStage(1, stages, setStages); setSearchState("searching"); - searchTimer = setTimeout(() => { - setCurrentStage(2, stages, setStages); - setMatchingProfiles(null); // display no matching profiles screen for a while - setSearchState("done"); - setTimeout(() => setMatchingProfiles(true), 2000); - // add selected role - const { id, name } = roles.find((r) => r.id === selectedRoleId); - setAddedRoles((addedRoles) => [...addedRoles, { id, name }]); - }, 3000); + setMatchingProfiles(null); + searchRoles({ roleId: selectedRoleId }) + .then((res) => { + setMatchingProfiles(res.data); + const id = _.get(res, "data.id"); + const name = _.get(res, "data.name"); + setAddedRoles((addedRoles) => [...addedRoles, { id, name }]); + }) + .catch((err) => { + console.error(err); + const { id, name } = _.find(roles, (r) => r.id === selectedRoleId); + setAddedRoles((addedRoles) => [...addedRoles, { id, name }]); + }) + .finally(() => { + setCurrentStage(2, stages, setStages); + setSearchState("done"); + }); }; - useEffect(() => clearTimeout(searchTimer)); - if (!roles) { return ; } diff --git a/src/services/teams.js b/src/services/teams.js index f220a1d6..ce44479f 100644 --- a/src/services/teams.js +++ b/src/services/teams.js @@ -206,3 +206,19 @@ export const postProject = () => { return axios.post(url, bodyObj); }; + +/** + * Search for roles matching a role id, job description + * or list of skills + * + * @param {Object} searchObject object containing data for search + * @param {string} searchObject.roleId a role id to search for + * @param {string} searchObject.jobDescription job description used for search + * @param {string[]} searchObject.skills array of skill ids used for role search + * @param {string} searchObject.previousRoleSearchRequestId id of the last search made + * @returns + */ +export const searchRoles = (searchObject) => { + const url = `${config.API.V5}/taas-teams/sendRoleSearchRequest`; + return axios.post(url, searchObject); +}; From fe81b53a29051d29f9b6b7e4cb57f6b0d98fb349 Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Tue, 8 Jun 2021 16:56:23 +0400 Subject: [PATCH 3/8] Updated InputSkills and InputJobDescription pages to use role search. --- src/root.component.jsx | 2 +- src/routes/CreateNewTeam/index.jsx | 14 +--- .../pages/InputJobDescription/index.jsx | 74 ++++++++++--------- .../CreateNewTeam/pages/InputSkills/index.jsx | 72 +++++++++--------- .../CreateNewTeam/pages/SelectRole/index.jsx | 1 - 5 files changed, 79 insertions(+), 84 deletions(-) diff --git a/src/root.component.jsx b/src/root.component.jsx index 9c22ae99..97b8bdcb 100644 --- a/src/root.component.jsx +++ b/src/root.component.jsx @@ -35,7 +35,7 @@ export default function Root() { - + diff --git a/src/routes/CreateNewTeam/index.jsx b/src/routes/CreateNewTeam/index.jsx index 5e45e808..c6a7747c 100644 --- a/src/routes/CreateNewTeam/index.jsx +++ b/src/routes/CreateNewTeam/index.jsx @@ -16,16 +16,8 @@ import IconListQuill from "../../assets/images/icon-list-quill.svg"; import IconOfficeFileText from "../../assets/images/icon-office-file-text.svg"; function CreateNewTeam() { - const createProjectAndNavigate = async (navigateTo) => { - postProject() - .then((res) => { - const id = _.get(res, "data.id"); - navigate(`/taas/myteams/createnewteam/${id}/${navigateTo}`); - }) - .catch((err) => { - toastr.warning("Error", "Failed to create a new team."); - console.error(err); - }); + const goToInputSkills = () => { + navigate("/taas/myteams/createnewteam/skills"); }; const goToSelectRole = () => { @@ -54,7 +46,7 @@ function CreateNewTeam() { description="You know your developer has specific skills, such as for example: Java, Python, Oracle, etc." icon={} backgroundImage="linear-gradient(221.5deg, #2C95D7 0%, #9D41C9 100%)" - onClick={() => createProjectAndNavigate("skills")} + onClick={goToInputSkills} /> { - setSkillModalOpen(true); - setIsLoadingSkills(true); - getSkillsByJobDescription(jdString) - .then((response) => { - setSkills(response.data); - setIsLoadingSkills(false); - setSkillModalOpen(true); - }) - .catch(() => { - setIsLoadingSkills(false); - }); - }, - [jdString] - ); + const onSearch = useCallback(() => { + setSkillModalOpen(true); + setIsLoadingSkills(true); + getSkillsByJobDescription(jdString) + .then((response) => { + setSkills(response.data); + setIsLoadingSkills(false); + setSkillModalOpen(true); + }) + .catch(() => { + setIsLoadingSkills(false); + }); + }, [jdString]); - const onConfirationClick = useCallback(() => { + const onConfirmationClick = useCallback(() => { setSearchState("searching"); setCurrentStage(1, stages, setStages); - setTimeout(() => { - setCurrentStage(2, stages, setStages); - setSearchState("done"); - }, 3000); - }, []); - const addAnother = useCallback(() => { - // navigate(`/taas/myteams/createnewteam/${projectId}/role`); - }, []); + searchRoles({ jobDescription: jdString }) + .then((res) => { + console.log(res.data); + }) + .catch((err) => { + console.error(err); + }) + .finally(() => { + setCurrentStage(2, stages, setStages); + setSearchState("done"); + }); + }, [stages, jdString]); + + const addAnother = () => { + navigate(`/taas/myteams/createnewteam/role`); + }; const submitJob = () => { setSubmitDone(false); @@ -107,7 +109,7 @@ function InputJobDescription() { skills={skills} onClose={() => setSkillModalOpen(false)} isLoading={isLoadingSkills} - onContinueClick={onConfirationClick} + onContinueClick={onConfirmationClick} />
) : searchState === "searching" ? ( diff --git a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx index 9341e86b..5aac3842 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx @@ -11,7 +11,7 @@ import React, { useCallback, useEffect, useState } from "react"; import { useData } from "hooks/useData"; import { navigate } from "@reach/router"; import { toastr } from "react-redux-toastr"; -import PT from "prop-types"; +import _ from "lodash"; import SkillsList from "./components/SkillsList"; import Completeness from "../../components/Completeness"; import "./styles.module.scss"; @@ -23,8 +23,9 @@ import ResultCard from "../../components/ResultCard"; import { createJob } from "services/jobs"; import AddAnotherModal from "../../components/AddAnotherModal"; import withAuthentication from "../../../../hoc/withAuthentication"; +import { postProject, searchRoles } from "services/teams"; -function InputSkills({ projectId }) { +function InputSkills() { const [stages, setStages] = useState([ { name: "Input Skills", isCurrent: true }, { name: "Search Member" }, @@ -37,32 +38,33 @@ function InputSkills({ projectId }) { const [skills, loadingError] = useData(getSkills); - let searchTimer; - const submitJob = () => { setSubmitDone(false); setModalOpen(true); - createJob({ - projectId, - title: `job-${Date()}`, - skills: selectedSkills, - numPositions: 1, - }) - .then(() => { - toastr.success("Job Submitted"); + postProject().then((res) => { + const projectId = _.get(res, "data.id"); + createJob({ + projectId, + title: `job-${Date()}`, + skills: selectedSkills, + numPositions: 1, }) - .catch((err) => { - console.error(err); - toastr.warning("Error Submitting Job"); - }) - .finally(() => { - setSubmitDone(true); - }); + .then(() => { + toastr.success("Job Submitted"); + }) + .catch((err) => { + console.error(err); + toastr.warning("Error Submitting Job"); + }) + .finally(() => { + setSubmitDone(true); + }); + }); }; - const addAnother = useCallback(() => { - navigate(`/taas/myteams/createnewteam/${projectId}/roles`); - }, [projectId]); + const addAnother = () => { + navigate(`/taas/myteams/createnewteam/roles`); + }; const toggleSkill = useCallback( (id) => { @@ -77,18 +79,22 @@ function InputSkills({ projectId }) { [selectedSkills] ); - // mocked search for users with given skills const search = () => { - setSearchState("searching"); setCurrentStage(1, stages, setStages); - searchTimer = setTimeout(() => { - setSearchState("done"); - setCurrentStage(2, stages, setStages); - }, 3000); + setSearchState("searching"); + searchRoles({ skills: selectedSkills }) + .then((res) => { + console.log(_.get(res, "data")); + }) + .catch((err) => { + console.error(err); + }) + .finally(() => { + setCurrentStage(2, stages, setStages); + setSearchState("done"); + }); }; - useEffect(() => clearTimeout(searchTimer)); - return !skills ? ( ) : !searchState ? ( @@ -138,8 +144,4 @@ function InputSkills({ projectId }) { ); } -InputSkills.propTypes = { - projectId: PT.string, -}; - -export default withAuthentication(InputSkills); +export default InputSkills; diff --git a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx index 2fba9947..8b8ea6c5 100644 --- a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx +++ b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx @@ -96,7 +96,6 @@ function SelectRole() { setRoleDetailsModalOpen(true); }, []); - // mocked search for users with given roles const search = () => { setCurrentStage(1, stages, setStages); setSearchState("searching"); From fcbd05855bc9558b69fe20ef8c1395bff2459191 Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Wed, 9 Jun 2021 13:04:07 +0400 Subject: [PATCH 4/8] updated result card to receive props --- src/constants/index.js | 11 ++- .../components/ResultCard/index.jsx | 79 ++++++++++--------- .../components/ResultCard/styles.module.scss | 21 +++-- .../CreateNewTeam/pages/SelectRole/index.jsx | 6 +- 4 files changed, 64 insertions(+), 53 deletions(-) diff --git a/src/constants/index.js b/src/constants/index.js index 34fd9c69..6eee3717 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -109,8 +109,8 @@ export const CANDIDATE_STATUS = { REJECTED_OTHER: "rejected - other", INTERVIEW: "interview", TOPCODER_REJECTED: "topcoder-rejected", - JOB_CLOSED:"job-closed", - OFFERED:"offered" + JOB_CLOSED: "job-closed", + OFFERED: "offered", }; /** @@ -146,7 +146,7 @@ export const CANDIDATE_STATUS_FILTERS = [ buttonText: "Selected", title: "Selected", noCandidateMessage: "No Selected Candidates", - statuses: [CANDIDATE_STATUS.SELECTED,CANDIDATE_STATUS.OFFERED], + statuses: [CANDIDATE_STATUS.SELECTED, CANDIDATE_STATUS.OFFERED], }, { key: CANDIDATE_STATUS_FILTER_KEY.NOT_INTERESTED, @@ -347,3 +347,8 @@ export const INTERVIEW_POPUP_MEDIA_URL = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; export const MAX_ALLOWED_INTERVIEWS = 3; + +/** + * Matching rate to show in CreateNewTeam ResultCard + */ +export const MATCHING_RATE = "80"; diff --git a/src/routes/CreateNewTeam/components/ResultCard/index.jsx b/src/routes/CreateNewTeam/components/ResultCard/index.jsx index 910162b4..46578830 100644 --- a/src/routes/CreateNewTeam/components/ResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/ResultCard/index.jsx @@ -5,7 +5,7 @@ * about costs and number of matching candidates. */ import React, { useState, useEffect } from "react"; -import cn from "classnames"; +import PT from "prop-types"; import { getAuthUserProfile } from "@topcoder/micro-frontends-navbar-app"; import "./styles.module.scss"; import IconEarthCheck from "../../../../assets/images/icon-earth-check.svg"; @@ -15,10 +15,16 @@ import IconTeamMeetingChat from "../../../../assets/images/icon-team-meeting-cha import Curve from "../../../../assets/images/curve.svg"; import CircularProgressBar from "../CircularProgressBar"; import Button from "components/Button"; +import { MATCHING_RATE } from "constants"; +import { formatMoney } from "utils/format"; -function ResultCard() { +function ResultCard({ role }) { + const { + numberOfMembersAvailable, + isExternalMember, + rates: [rates], + } = role; const [userHandle, setUserHandle] = useState("handle"); - const [showSpecialRates, setShowSpecialRates] = useState(false); const [showRates, setShowRates] = useState(false); useEffect(() => { @@ -29,17 +35,12 @@ function ResultCard() { return (
-
setShowSpecialRates(!showSpecialRates)} - styleName={cn("heading", { ["non-clickable"]: !showRates })} - > +

We have matching profiles

- We have qualified candidates who match 80% or more of your job - requirements. + We have qualified candidates who match {MATCHING_RATE}% or more of + your job requirements.

@@ -60,7 +61,7 @@ function ResultCard() { Rate Details
- {showRates && showSpecialRates && ( + {showRates && !isExternalMember && (

Hi {userHandle}, we have special rates for you as a Xeno User! @@ -72,23 +73,23 @@ function ResultCard() {

(40h / week)

-

Senior Member

+

Global Rate

-

$2,000

+

{formatMoney(rates.global)}

/Week

-

Standard Member

+

In-Country Rate

-

$1,500

+

{formatMoney(rates.inCountry)}

/Week

-

Junior Member

+

Offshore Rate

-

$1,000

+

{formatMoney(rates.offShore)}

/Week

@@ -99,23 +100,23 @@ function ResultCard() {

(30h / week)

-

Senior Member

+

Global Rate

-

$1,800

+

{formatMoney(rates.rate30Global)}

/Week

-

Standard Member

+

In-Country Rate

-

$1,300

+

{formatMoney(rates.rate30InCountry)}

/Week

-

Junior Member

+

Offshore Rate

-

$800

+

{formatMoney(rates.rate30OffShore)}

/Week

@@ -126,23 +127,23 @@ function ResultCard() {

(20h / week)

-

Senior Member

+

Global Rate

-

$1,600

+

{formatMoney(rates.rate20Global)}

/Week

-

Standard Member

+

In-Country Rate

-

$1,100

+

{formatMoney(rates.rate20InCountry)}

/Week

-

Junior Member

+

Offshore Rate

-

$600

+

{formatMoney(rates.rate20OffShore)}

/Week

@@ -150,7 +151,7 @@ function ResultCard() { )} - {showRates && !showSpecialRates && ( + {showRates && isExternalMember && (
@@ -159,7 +160,7 @@ function ResultCard() {

(40h / week)

-
$1,800
+
{formatMoney(rates.global)}

/Week

@@ -169,7 +170,7 @@ function ResultCard() {

(30h / week)

-
$1,250
+
{formatMoney(rates.rate30Global)}

/Week

@@ -179,7 +180,7 @@ function ResultCard() {

(20h / week)

-
$800
+
{formatMoney(rates.rate20Global)}

/Week

@@ -209,11 +210,11 @@ function ResultCard() {
-

80%

+

{MATCHING_RATE}%

Matching rate

} @@ -222,7 +223,7 @@ function ResultCard() {
-

300+

+

{numberOfMembersAvailable}+

Members matched

@@ -246,4 +247,8 @@ function ResultCard() { ); } +ResultCard.propTypes = { + role: PT.object, +}; + export default ResultCard; diff --git a/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss b/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss index 1b4e7347..04744e66 100644 --- a/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss +++ b/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss @@ -7,10 +7,6 @@ margin-right: 30px; } -.non-clickable { - pointer-events: none; -} - .heading { display: flex; flex-direction: column; @@ -23,7 +19,6 @@ position: relative; text-align: center; border-radius: 8px 8px 0 0; - cursor: pointer; svg { margin-bottom: 8px; @@ -172,7 +167,9 @@ margin-left: 3px; } } - .senior, .standard, .junior { + .senior, + .standard, + .junior { display: flex; flex-direction: column; position: relative; @@ -190,7 +187,7 @@ font-weight: 700; letter-spacing: 1px; line-height: 16px; - color: #2A2A2A; + color: #2a2a2a; text-transform: uppercase; } .cost { @@ -201,7 +198,7 @@ font-size: 34px; font-weight: 500; line-height: 38px; - color: #2A2A2A; + color: #2a2a2a; } p { @include font-roboto; @@ -214,13 +211,13 @@ } } .senior::before { - background-color: #C99014; + background-color: #c99014; } .standard::before { - background-color: #716D67; + background-color: #716d67; } .junior::before { - background-color: #854E29; + background-color: #854e29; } } } @@ -298,7 +295,7 @@ @include font-barlow; font-size: 16px; text-transform: uppercase; - font-weight: 900; + font-weight: 700; line-height: 20px; } p { diff --git a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx index 8b8ea6c5..f7871d40 100644 --- a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx +++ b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx @@ -171,7 +171,11 @@ function SelectRole() { if (searchState === "done") { return (
- {matchingProfiles ? : } + {matchingProfiles ? ( + + ) : ( + + )}
{matchingProfiles && } Date: Wed, 9 Jun 2021 16:20:12 +0400 Subject: [PATCH 5/8] Updated InputJobDescription and InputSkills to search correctly and display the results. Fixed color on Completeness card --- .../Completeness/styles.module.scss | 2 +- .../pages/InputJobDescription/index.jsx | 133 +++++++++++------- .../InputJobDescription/styles.module.scss | 7 + .../CreateNewTeam/pages/InputSkills/index.jsx | 130 ++++++++++------- .../pages/InputSkills/styles.module.scss | 7 + .../CreateNewTeam/pages/SelectRole/index.jsx | 11 +- 6 files changed, 182 insertions(+), 108 deletions(-) diff --git a/src/routes/CreateNewTeam/components/Completeness/styles.module.scss b/src/routes/CreateNewTeam/components/Completeness/styles.module.scss index 3227642c..4a3c4daa 100644 --- a/src/routes/CreateNewTeam/components/Completeness/styles.module.scss +++ b/src/routes/CreateNewTeam/components/Completeness/styles.module.scss @@ -16,7 +16,7 @@ } .input-skills { - background-image: linear-gradient(221.5deg, #2c95d7 0%, #9d41c9 100%); + background-image: linear-gradient(221.5deg, #646CD0 0%, #9d41c9 100%); } .role-selection { diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx index 1de9991c..ec04cb66 100644 --- a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx @@ -6,6 +6,7 @@ import React, { useCallback, useState } from "react"; import { setCurrentStage } from "utils/helpers"; import { navigate } from "@reach/router"; import PT from "prop-types"; +import _ from "lodash"; import PageHeader from "components/PageHeader"; import MarkdownEditor from "../../../../components/MarkdownEditor"; import { @@ -19,6 +20,8 @@ import AddAnotherModal from "../../components/AddAnotherModal"; import SkillListPopup from "./components/SkillListPopup"; import "./styles.module.scss"; import withAuthentication from "../../../../hoc/withAuthentication"; +import NoMatchingProfilesResultCard from "../../components/NoMatchingProfilesResultCard"; +import AddedRolesAccordion from "../SelectRole/components/AddedRolesAccordion"; function InputJobDescription() { const [stages, setStages] = useState([ @@ -33,6 +36,8 @@ function InputJobDescription() { const [submitDone, setSubmitDone] = useState(false); const [skills, setSkills] = useState([]); const [isLoadingSkills, setIsLoadingSkills] = useState(false); + const [matchingProfiles, setMatchingProfiles] = useState(null); + const [addedRoles, setAddedRoles] = useState([]); const onSearch = useCallback(() => { setSkillModalOpen(true); @@ -51,10 +56,17 @@ function InputJobDescription() { const onConfirmationClick = useCallback(() => { setSearchState("searching"); setCurrentStage(1, stages, setStages); + setMatchingProfiles(null); searchRoles({ jobDescription: jdString }) .then((res) => { - console.log(res.data); + const id = _.get(res, "data.id"); + const name = _.get(res, "data.name"); + const prevSearchId = _.get(res, "data.roleSearchRequestId"); + if (name && !name.toLowerCase().includes("niche")) { + setMatchingProfiles(res.data); + setAddedRoles((addedRoles) => [...addedRoles, { id, name }]); + } }) .catch((err) => { console.error(err); @@ -81,72 +93,85 @@ function InputJobDescription() { setJdString(value); }, []); - return ( -
- {!searchState ? ( -
-
- - -
- +
+ - setSkillModalOpen(false)} - isLoading={isLoadingSkills} - onContinueClick={onConfirmationClick} +
- ) : searchState === "searching" ? ( -
- - -
- ) : ( -
- + + setSkillModalOpen(false)} + isLoading={isLoadingSkills} + onContinueClick={onConfirmationClick} + /> +
+ ); + } + if (searchState === "searching") { + return ( +
+ + +
+ ); + } + + if (searchState === "done") { + return ( +
+ {matchingProfiles ? ( + + ) : ( + + )} +
+ {addedRoles.length && } - setModalOpen(false)} - submitDone={submitDone} - addAnother={addAnother} - />
- )} -
- ); + setModalOpen(false)} + submitDone={submitDone} + addAnother={addAnother} + /> +
+ ); + } } InputJobDescription.propTypes = { projectId: PT.string, }; -export default withAuthentication(InputJobDescription); +export default InputJobDescription; diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/styles.module.scss b/src/routes/CreateNewTeam/pages/InputJobDescription/styles.module.scss index 31e3ca4b..a1b3188b 100644 --- a/src/routes/CreateNewTeam/pages/InputJobDescription/styles.module.scss +++ b/src/routes/CreateNewTeam/pages/InputJobDescription/styles.module.scss @@ -14,4 +14,11 @@ padding: 0 30px 30px; flex: 1; } + .right-side { + display: flex; + flex-direction: column; + & > div:not(:first-child) { + margin-top: 16px; + } + } } diff --git a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx index 5aac3842..d2b63c0a 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx @@ -24,6 +24,8 @@ import { createJob } from "services/jobs"; import AddAnotherModal from "../../components/AddAnotherModal"; import withAuthentication from "../../../../hoc/withAuthentication"; import { postProject, searchRoles } from "services/teams"; +import NoMatchingProfilesResultCard from "../../components/NoMatchingProfilesResultCard"; +import AddedRolesAccordion from "../SelectRole/components/AddedRolesAccordion"; function InputSkills() { const [stages, setStages] = useState([ @@ -35,6 +37,8 @@ function InputSkills() { const [searchState, setSearchState] = useState(null); const [modalOpen, setModalOpen] = useState(false); const [submitDone, setSubmitDone] = useState(false); + const [matchingProfiles, setMatchingProfiles] = useState(null); + const [addedRoles, setAddedRoles] = useState([]); const [skills, loadingError] = useData(getSkills); @@ -82,9 +86,17 @@ function InputSkills() { const search = () => { setCurrentStage(1, stages, setStages); setSearchState("searching"); + setMatchingProfiles(null); + searchRoles({ skills: selectedSkills }) .then((res) => { - console.log(_.get(res, "data")); + const id = _.get(res, "data.id"); + const name = _.get(res, "data.name"); + const prevSearchId = _.get(res, "data.roleSearchRequestId"); + if (name && !name.toLowerCase().includes("niche")) { + setMatchingProfiles(res.data); + setAddedRoles((addedRoles) => [...addedRoles, { id, name }]); + } }) .catch((err) => { console.error(err); @@ -95,53 +107,75 @@ function InputSkills() { }); }; - return !skills ? ( - - ) : !searchState ? ( -
- - -
- ) : searchState === "searching" ? ( -
- - -
- ) : ( -
- - - setModalOpen(false)} - submitDone={submitDone} - addAnother={addAnother} - /> -
- ); + if (!skills) { + return ; + } + + if (skills && !searchState) { + return ( +
+ + +
+ ); + } + + if (searchState === "searching") { + return ( +
+ + +
+ ); + } + + if (searchState === "done") { + return ( +
+ {matchingProfiles ? ( + + ) : ( + + )} +
+ {addedRoles.length && } + { + setSubmitDone(true); + setModalOpen(true); + }} + /> +
+ setModalOpen(false)} + submitDone={submitDone} + addAnother={addAnother} + /> +
+ ); + } } export default InputSkills; diff --git a/src/routes/CreateNewTeam/pages/InputSkills/styles.module.scss b/src/routes/CreateNewTeam/pages/InputSkills/styles.module.scss index b47da072..7bacc294 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/styles.module.scss +++ b/src/routes/CreateNewTeam/pages/InputSkills/styles.module.scss @@ -4,4 +4,11 @@ justify-content: center; align-items: flex-start; margin: 42px 35px; + .right-side { + display: flex; + flex-direction: column; + & > div:not(:first-child) { + margin-top: 16px; + } + } } diff --git a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx index f7871d40..d764324f 100644 --- a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx +++ b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx @@ -102,15 +102,16 @@ function SelectRole() { setMatchingProfiles(null); searchRoles({ roleId: selectedRoleId }) .then((res) => { - setMatchingProfiles(res.data); const id = _.get(res, "data.id"); const name = _.get(res, "data.name"); - setAddedRoles((addedRoles) => [...addedRoles, { id, name }]); + const prevSearchId = _.get(res, "data.roleSearchRequestId"); + if (name && !name.toLowerCase().includes("niche")) { + setMatchingProfiles(res.data); + setAddedRoles((addedRoles) => [...addedRoles, { id, name }]); + } }) .catch((err) => { console.error(err); - const { id, name } = _.find(roles, (r) => r.id === selectedRoleId); - setAddedRoles((addedRoles) => [...addedRoles, { id, name }]); }) .finally(() => { setCurrentStage(2, stages, setStages); @@ -177,7 +178,7 @@ function SelectRole() { )}
- {matchingProfiles && } + {addedRoles.length && } Date: Thu, 10 Jun 2021 14:58:55 +0400 Subject: [PATCH 6/8] - Moved AddedRolesAccordion to use in InputSkills and InputJobDescription components - Refactored search logic into a new SearchContainer component - Refactored SelectRole, InputSkills and InputJobDescription to use SearchContainer - Choosing "Modify search" in NoMatchingProfiles component keeps added roles and previous search id - Chooosing "Add another role" keeps added roles and previous search id --- .../components/AddedRolesAccordion/index.jsx | 4 +- .../AddedRolesAccordion/styles.module.scss | 0 .../Completeness/styles.module.scss | 3 +- .../NoMatchingProfilesResultCard/index.jsx | 17 +- .../components/SearchContainer/index.jsx | 169 +++++++++++++++ .../SearchContainer}/styles.module.scss | 2 +- src/routes/CreateNewTeam/index.jsx | 21 +- .../pages/InputJobDescription/index.jsx | 169 ++------------- .../InputJobDescription/styles.module.scss | 31 +-- .../CreateNewTeam/pages/InputSkills/index.jsx | 157 ++------------ .../CreateNewTeam/pages/SelectRole/index.jsx | 196 +++--------------- .../pages/SelectRole/styles.module.scss | 14 -- src/services/teams.js | 4 +- 13 files changed, 275 insertions(+), 512 deletions(-) rename src/routes/CreateNewTeam/{pages/SelectRole => }/components/AddedRolesAccordion/index.jsx (96%) rename src/routes/CreateNewTeam/{pages/SelectRole => }/components/AddedRolesAccordion/styles.module.scss (100%) create mode 100644 src/routes/CreateNewTeam/components/SearchContainer/index.jsx rename src/routes/CreateNewTeam/{pages/InputSkills => components/SearchContainer}/styles.module.scss (99%) delete mode 100644 src/routes/CreateNewTeam/pages/SelectRole/styles.module.scss diff --git a/src/routes/CreateNewTeam/pages/SelectRole/components/AddedRolesAccordion/index.jsx b/src/routes/CreateNewTeam/components/AddedRolesAccordion/index.jsx similarity index 96% rename from src/routes/CreateNewTeam/pages/SelectRole/components/AddedRolesAccordion/index.jsx rename to src/routes/CreateNewTeam/components/AddedRolesAccordion/index.jsx index e09a64e3..2c1d9a6e 100644 --- a/src/routes/CreateNewTeam/pages/SelectRole/components/AddedRolesAccordion/index.jsx +++ b/src/routes/CreateNewTeam/components/AddedRolesAccordion/index.jsx @@ -13,7 +13,7 @@ import "./styles.module.scss"; function AddedRolesAccordion({ addedRoles }) { const [isOpen, setIsOpen] = useState(false); - return ( + return addedRoles.length ? (
- ); + ) : null; } AddedRolesAccordion.propTypes = { diff --git a/src/routes/CreateNewTeam/pages/SelectRole/components/AddedRolesAccordion/styles.module.scss b/src/routes/CreateNewTeam/components/AddedRolesAccordion/styles.module.scss similarity index 100% rename from src/routes/CreateNewTeam/pages/SelectRole/components/AddedRolesAccordion/styles.module.scss rename to src/routes/CreateNewTeam/components/AddedRolesAccordion/styles.module.scss diff --git a/src/routes/CreateNewTeam/components/Completeness/styles.module.scss b/src/routes/CreateNewTeam/components/Completeness/styles.module.scss index 4a3c4daa..6e12427f 100644 --- a/src/routes/CreateNewTeam/components/Completeness/styles.module.scss +++ b/src/routes/CreateNewTeam/components/Completeness/styles.module.scss @@ -34,6 +34,7 @@ &:before { content: ""; + color: #fff; border: 1px solid #ffffff; border-radius: 100%; width: 16px; @@ -57,7 +58,7 @@ content: "✓"; font-size: 9px; line-height: 14px; - padding-left: 3px; + padding-left: 2px; } } } diff --git a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx index 0bde6659..492fd2b7 100644 --- a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx @@ -3,13 +3,13 @@ * Card that appears when there are no matching profiles after searching. */ import React from "react"; -import { navigate } from "@reach/router"; +import { Link } from "@reach/router"; import "./styles.module.scss"; import IconEarthX from "../../../../assets/images/icon-earth-x.svg"; import Curve from "../../../../assets/images/curve.svg"; import Button from "components/Button"; -function NoMatchingProfilesResultCard() { +function NoMatchingProfilesResultCard({ prevSearchId, addedRoles }) { return (
@@ -28,13 +28,14 @@ function NoMatchingProfilesResultCard() {

$1,200

/Week

- + +
); diff --git a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx new file mode 100644 index 00000000..a4fe3eaf --- /dev/null +++ b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx @@ -0,0 +1,169 @@ +import React, { useCallback, useState } from "react"; +import PT from "prop-types"; +import { toastr } from "react-redux-toastr"; +import { navigate } from "@reach/router"; +import _ from "lodash"; +import AddedRolesAccordion from "../AddedRolesAccordion"; +import Completeness from "../Completeness"; +import SearchCard from "../SearchCard"; +import ResultCard from "../ResultCard"; +import NoMatchingProfilesResultCard from "../NoMatchingProfilesResultCard"; +import { createJob } from "services/jobs"; +import { postProject, searchRoles } from "services/teams"; +import { setCurrentStage } from "utils/helpers"; +import AddAnotherModal from "../AddAnotherModal"; +import "./styles.module.scss"; + +function SearchContainer({ + stages, + setStages, + isCompletenessDisabled, + children, + searchObject, + completenessStyle, + locationState, + reloadRolesPage, +}) { + const [addedRoles, setAddedRoles] = useState( + locationState.addedRoles ? locationState.addedRoles : [] + ); + const [searchState, setSearchState] = useState(null); + const [matchingRole, setMatchingRole] = useState(null); + const [addAnotherModalOpen, setAddAnotherModalOpen] = useState(false); + const [submitDone, setSubmitDone] = useState(true); + const [prevSearchId, setPrevSearchId] = useState(locationState.prevSearchId); + + const submitJob = () => { + setSubmitDone(false); + postProject() + .then((res) => { + const projectId = _.get(res, "data.id"); + + createJob({ + projectId, + title: `job-${Date()}`, + skills: [], + roleIds: addedRoles.map((r) => r.id), + numPositions: 1, + }) + .then(() => { + toastr.success("Job Submitted"); + }) + .catch((err) => { + console.error(err); + toastr.warning("Error Submitting Job"); + }); + }) + .catch((err) => { + console.error(err); + toastr.warning("Error Creating Project"); + }) + .finally(() => { + setSubmitDone(true); + navigate("/taas/myteams"); + }); + }; + + const addAnother = () => { + if (!reloadRolesPage) { + navigate("/taas/myteams/createnewteam/role", { + state: { addedRoles, prevSearchId }, + }); + return; + } + setCurrentStage(0, stages, setStages); + setSearchState(null); + setMatchingRole(null); + setAddAnotherModalOpen(false); + reloadRolesPage(); + }; + + const search = () => { + setCurrentStage(1, stages, setStages); + setSearchState("searching"); + setMatchingRole(null); + const searchObjectCopy = { ...searchObject }; + if (prevSearchId) { + searchObjectCopy.previousRoleSearchRequestId = prevSearchId; + } + searchRoles(searchObjectCopy) + .then((res) => { + const id = _.get(res, "data.id"); + const name = _.get(res, "data.name"); + setPrevSearchId(_.get(res, "data.roleSearchRequestId")); + if (name && !name.toLowerCase().includes("niche")) { + setMatchingRole(res.data); + setAddedRoles((addedRoles) => [...addedRoles, { id, name }]); + } + }) + .catch((err) => { + console.error(err); + }) + .finally(() => { + setCurrentStage(2, stages, setStages); + setSearchState("done"); + }); + }; + + const renderLeftSide = () => { + if (!searchState) return children; + if (searchState === "searching") return ; + if (matchingRole) return ; + return ( + + ); + }; + + const getPercentage = useCallback(() => { + if (!searchState) return "26"; + if (searchState === "searching") return "52"; + if (matchingRole) return "98"; + return "88"; + }, [searchState, matchingRole]); + + return ( +
+ {renderLeftSide()} +
+ + setAddAnotherModalOpen(true) : search} + extraStyleName={completenessStyle} + buttonLabel={searchState ? "Submit Request" : "Search"} + stages={stages} + percentage={getPercentage()} + /> +
+ {searchState === "done" && matchingRole && ( + setAddAnotherModalOpen(false)} + submitDone={submitDone} + onContinueClick={submitJob} + addAnother={addAnother} + /> + )} +
+ ); +} + +SearchContainer.propTypes = { + stages: PT.array, + setStages: PT.func, + isCompletenessDisabled: PT.bool, + searchObject: PT.object, + children: PT.node, + completenessStyle: PT.string, + locationState: PT.object, + reloadRolesPage: PT.func, +}; + +export default SearchContainer; diff --git a/src/routes/CreateNewTeam/pages/InputSkills/styles.module.scss b/src/routes/CreateNewTeam/components/SearchContainer/styles.module.scss similarity index 99% rename from src/routes/CreateNewTeam/pages/InputSkills/styles.module.scss rename to src/routes/CreateNewTeam/components/SearchContainer/styles.module.scss index 7bacc294..99cec905 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/styles.module.scss +++ b/src/routes/CreateNewTeam/components/SearchContainer/styles.module.scss @@ -11,4 +11,4 @@ margin-top: 16px; } } -} +} \ No newline at end of file diff --git a/src/routes/CreateNewTeam/index.jsx b/src/routes/CreateNewTeam/index.jsx index c6a7747c..07b472ba 100644 --- a/src/routes/CreateNewTeam/index.jsx +++ b/src/routes/CreateNewTeam/index.jsx @@ -15,17 +15,12 @@ import IconMultipleActionsCheck from "../../assets/images/icon-multiple-actions- import IconListQuill from "../../assets/images/icon-list-quill.svg"; import IconOfficeFileText from "../../assets/images/icon-office-file-text.svg"; -function CreateNewTeam() { - const goToInputSkills = () => { - navigate("/taas/myteams/createnewteam/skills"); - }; - - const goToSelectRole = () => { - navigate("/taas/myteams/createnewteam/role"); - }; +function CreateNewTeam({ location: { state: locationState } }) { + const { prevSearchId } = locationState; + const { addedRoles } = locationState; - const goToJobDescription = () => { - navigate(`/taas/myteams/createnewteam/jd`); + const goToRoute = (path) => { + navigate(path, { state: { prevSearchId, addedRoles } }); }; return ( @@ -39,21 +34,21 @@ function CreateNewTeam() { description="You know you want a front end developer, or a full stack developer, mobile one or others." icon={} backgroundImage="linear-gradient(101.95deg, #8B41B0 0%, #EF476F 100%)" - onClick={goToSelectRole} + onClick={() => goToRoute("/taas/myteams/createnewteam/role")} /> } backgroundImage="linear-gradient(221.5deg, #2C95D7 0%, #9D41C9 100%)" - onClick={goToInputSkills} + onClick={() => goToRoute("/taas/myteams/createnewteam/skills")} /> } backgroundImage="linear-gradient(135deg, #2984BD 0%, #0AB88A 100%)" - onClick={goToJobDescription} + onClick={() => goToRoute("/taas/myteams/createnewteam/jd")} /> ); diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx index ec04cb66..58808b0f 100644 --- a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx @@ -3,175 +3,50 @@ * */ import React, { useCallback, useState } from "react"; -import { setCurrentStage } from "utils/helpers"; -import { navigate } from "@reach/router"; import PT from "prop-types"; -import _ from "lodash"; import PageHeader from "components/PageHeader"; import MarkdownEditor from "../../../../components/MarkdownEditor"; -import { - getSkillsByJobDescription, - searchRoles, -} from "../../../../services/teams"; -import Completeness from "../../components/Completeness"; -import SearchCard from "../../components/SearchCard"; -import ResultCard from "../../components/ResultCard"; -import AddAnotherModal from "../../components/AddAnotherModal"; -import SkillListPopup from "./components/SkillListPopup"; import "./styles.module.scss"; -import withAuthentication from "../../../../hoc/withAuthentication"; -import NoMatchingProfilesResultCard from "../../components/NoMatchingProfilesResultCard"; -import AddedRolesAccordion from "../SelectRole/components/AddedRolesAccordion"; +import SearchContainer from "../../components/SearchContainer"; -function InputJobDescription() { +function InputJobDescription({ location: { state: locationState } }) { const [stages, setStages] = useState([ { name: "Input Job Description", isCurrent: true }, { name: "Search Member" }, { name: "Overview of the Results" }, ]); const [jdString, setJdString] = useState(""); - const [searchState, setSearchState] = useState(null); - const [modalOpen, setModalOpen] = useState(false); - const [skillModalOpen, setSkillModalOpen] = useState(false); - const [submitDone, setSubmitDone] = useState(false); - const [skills, setSkills] = useState([]); - const [isLoadingSkills, setIsLoadingSkills] = useState(false); - const [matchingProfiles, setMatchingProfiles] = useState(null); - const [addedRoles, setAddedRoles] = useState([]); - - const onSearch = useCallback(() => { - setSkillModalOpen(true); - setIsLoadingSkills(true); - getSkillsByJobDescription(jdString) - .then((response) => { - setSkills(response.data); - setIsLoadingSkills(false); - setSkillModalOpen(true); - }) - .catch(() => { - setIsLoadingSkills(false); - }); - }, [jdString]); - - const onConfirmationClick = useCallback(() => { - setSearchState("searching"); - setCurrentStage(1, stages, setStages); - setMatchingProfiles(null); - - searchRoles({ jobDescription: jdString }) - .then((res) => { - const id = _.get(res, "data.id"); - const name = _.get(res, "data.name"); - const prevSearchId = _.get(res, "data.roleSearchRequestId"); - if (name && !name.toLowerCase().includes("niche")) { - setMatchingProfiles(res.data); - setAddedRoles((addedRoles) => [...addedRoles, { id, name }]); - } - }) - .catch((err) => { - console.error(err); - }) - .finally(() => { - setCurrentStage(2, stages, setStages); - setSearchState("done"); - }); - }, [stages, jdString]); - - const addAnother = () => { - navigate(`/taas/myteams/createnewteam/role`); - }; - - const submitJob = () => { - setSubmitDone(false); - setModalOpen(true); - setTimeout(() => { - setSubmitDone(true); - }, 3000); - }; const onEditChange = useCallback((value) => { setJdString(value); }, []); - if (!searchState) { - return ( -
-
- - -
- +
+ - setSkillModalOpen(false)} - isLoading={isLoadingSkills} - onContinueClick={onConfirmationClick} - /> -
- ); - } - if (searchState === "searching") { - return ( -
- - -
- ); - } - - if (searchState === "done") { - return ( -
- {matchingProfiles ? ( - - ) : ( - - )} -
- {addedRoles.length && } - -
- setModalOpen(false)} - submitDone={submitDone} - addAnother={addAnother} +
- ); - } + + ); } InputJobDescription.propTypes = { - projectId: PT.string, + locationState: PT.object, }; export default InputJobDescription; diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/styles.module.scss b/src/routes/CreateNewTeam/pages/InputJobDescription/styles.module.scss index a1b3188b..9fe69610 100644 --- a/src/routes/CreateNewTeam/pages/InputJobDescription/styles.module.scss +++ b/src/routes/CreateNewTeam/pages/InputJobDescription/styles.module.scss @@ -1,24 +1,9 @@ -.page { - display: flex; - flex-direction: row; - justify-content: center; - align-items: flex-start; - margin: 42px 35px; - - .edit-container { - background-color: #ffffff; - border-radius: 8px; - max-width: 746px; - position: relative; - margin-right: 30px; - padding: 0 30px 30px; - flex: 1; - } - .right-side { - display: flex; - flex-direction: column; - & > div:not(:first-child) { - margin-top: 16px; - } - } +.edit-container { + background-color: #ffffff; + border-radius: 8px; + max-width: 746px; + position: relative; + margin-right: 30px; + padding: 0 30px 30px; + flex: 1; } diff --git a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx index d2b63c0a..1d37bda3 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx @@ -7,69 +7,23 @@ * Allows selecting a number of skills, searching for users * with those skills, and submitting a job requiring the skills. */ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useState } from "react"; import { useData } from "hooks/useData"; -import { navigate } from "@reach/router"; -import { toastr } from "react-redux-toastr"; -import _ from "lodash"; import SkillsList from "./components/SkillsList"; -import Completeness from "../../components/Completeness"; -import "./styles.module.scss"; import { getSkills } from "services/skills"; -import { setCurrentStage } from "utils/helpers"; import LoadingIndicator from "components/LoadingIndicator"; -import SearchCard from "../../components/SearchCard"; -import ResultCard from "../../components/ResultCard"; -import { createJob } from "services/jobs"; -import AddAnotherModal from "../../components/AddAnotherModal"; -import withAuthentication from "../../../../hoc/withAuthentication"; -import { postProject, searchRoles } from "services/teams"; -import NoMatchingProfilesResultCard from "../../components/NoMatchingProfilesResultCard"; -import AddedRolesAccordion from "../SelectRole/components/AddedRolesAccordion"; +import SearchContainer from "../../components/SearchContainer"; -function InputSkills() { +function InputSkills({ location: { state: locationState } }) { const [stages, setStages] = useState([ { name: "Input Skills", isCurrent: true }, { name: "Search Member" }, { name: "Overview of the Results" }, ]); const [selectedSkills, setSelectedSkills] = useState([]); - const [searchState, setSearchState] = useState(null); - const [modalOpen, setModalOpen] = useState(false); - const [submitDone, setSubmitDone] = useState(false); - const [matchingProfiles, setMatchingProfiles] = useState(null); - const [addedRoles, setAddedRoles] = useState([]); const [skills, loadingError] = useData(getSkills); - const submitJob = () => { - setSubmitDone(false); - setModalOpen(true); - postProject().then((res) => { - const projectId = _.get(res, "data.id"); - createJob({ - projectId, - title: `job-${Date()}`, - skills: selectedSkills, - numPositions: 1, - }) - .then(() => { - toastr.success("Job Submitted"); - }) - .catch((err) => { - console.error(err); - toastr.warning("Error Submitting Job"); - }) - .finally(() => { - setSubmitDone(true); - }); - }); - }; - - const addAnother = () => { - navigate(`/taas/myteams/createnewteam/roles`); - }; - const toggleSkill = useCallback( (id) => { if (selectedSkills.includes(id)) { @@ -83,99 +37,26 @@ function InputSkills() { [selectedSkills] ); - const search = () => { - setCurrentStage(1, stages, setStages); - setSearchState("searching"); - setMatchingProfiles(null); - - searchRoles({ skills: selectedSkills }) - .then((res) => { - const id = _.get(res, "data.id"); - const name = _.get(res, "data.name"); - const prevSearchId = _.get(res, "data.roleSearchRequestId"); - if (name && !name.toLowerCase().includes("niche")) { - setMatchingProfiles(res.data); - setAddedRoles((addedRoles) => [...addedRoles, { id, name }]); - } - }) - .catch((err) => { - console.error(err); - }) - .finally(() => { - setCurrentStage(2, stages, setStages); - setSearchState("done"); - }); - }; - if (!skills) { return ; } - if (skills && !searchState) { - return ( -
- - -
- ); - } - - if (searchState === "searching") { - return ( -
- - -
- ); - } - - if (searchState === "done") { - return ( -
- {matchingProfiles ? ( - - ) : ( - - )} -
- {addedRoles.length && } - { - setSubmitDone(true); - setModalOpen(true); - }} - /> -
- setModalOpen(false)} - submitDone={submitDone} - addAnother={addAnother} - /> -
- ); - } + return ( + + + + ); } export default InputSkills; diff --git a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx index d764324f..a9cf3295 100644 --- a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx +++ b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx @@ -8,198 +8,66 @@ */ import React, { useCallback, useState } from "react"; import { useData } from "hooks/useData"; -import { navigate } from "@reach/router"; -import { toastr } from "react-redux-toastr"; -import _ from "lodash"; import RolesList from "./components/RolesList"; -import Completeness from "../../components/Completeness"; -import "./styles.module.scss"; import { getRoles } from "services/roles"; -import { setCurrentStage } from "utils/helpers"; import LoadingIndicator from "components/LoadingIndicator"; -import SearchCard from "../../components/SearchCard"; -import ResultCard from "../../components/ResultCard"; -import NoMatchingProfilesResultCard from "../../components/NoMatchingProfilesResultCard"; -import { createJob } from "services/jobs"; -import AddAnotherModal from "../../components/AddAnotherModal"; import RoleDetailsModal from "../../components/RoleDetailsModal"; -import withAuthentication from "../../../../hoc/withAuthentication"; -import AddedRolesAccordion from "./components/AddedRolesAccordion"; -import { postProject, searchRoles } from "services/teams"; +import SearchContainer from "../../components/SearchContainer"; -function SelectRole() { +function SelectRole({ location: { state: locationState } }) { const [stages, setStages] = useState([ { name: "Select a Role", isCurrent: true }, { name: "Search Member" }, { name: "Overview of the Results" }, ]); - const [addedRoles, setAddedRoles] = useState([]); const [selectedRoleId, setSelectedRoleId] = useState(null); - const [searchState, setSearchState] = useState(null); - const [matchingProfiles, setMatchingProfiles] = useState(null); - const [addAnotherModalOpen, setAddAnotherModalOpen] = useState(false); const [roleDetailsModalOpen, setRoleDetailsModalOpen] = useState(false); const [roleDetailsModalId, setRoleDetailsModalId] = useState(null); - const [submitDone, setSubmitDone] = useState(true); const [roles, loadingError] = useData(getRoles); - const submitJob = () => { - setSubmitDone(false); - postProject() - .then((res) => { - const projectId = _.get(res, "data.id"); - - createJob({ - projectId, - title: `job-${Date()}`, - skills: [], - roleIds: addedRoles.map((r) => r.id), - numPositions: 1, - }) - .then(() => { - toastr.success("Job Submitted"); - }) - .catch((err) => { - console.error(err); - toastr.warning("Error Submitting Job"); - }); - }) - .catch((err) => { - console.error(err); - toastr.warning("Error Creating Project"); - }) - .finally(() => { - setSubmitDone(true); - navigate("/taas/myteams"); - }); - }; - - const addAnother = useCallback(() => { - setSelectedRoleId(null); - setCurrentStage(0, stages, setStages); - setAddAnotherModalOpen(false); - setSearchState(null); - }, [stages]); - - const toggleRole = useCallback( - (id) => { - setSelectedRoleId((selectedRoleId) => - id === selectedRoleId ? null : id - ); - }, - [setSelectedRoleId] - ); + const toggleRole = useCallback((id) => { + setSelectedRoleId((selectedRoleId) => (id === selectedRoleId ? null : id)); + }, []); const onDescriptionClick = useCallback((roleId) => { setRoleDetailsModalId(roleId); setRoleDetailsModalOpen(true); }, []); - const search = () => { - setCurrentStage(1, stages, setStages); - setSearchState("searching"); - setMatchingProfiles(null); - searchRoles({ roleId: selectedRoleId }) - .then((res) => { - const id = _.get(res, "data.id"); - const name = _.get(res, "data.name"); - const prevSearchId = _.get(res, "data.roleSearchRequestId"); - if (name && !name.toLowerCase().includes("niche")) { - setMatchingProfiles(res.data); - setAddedRoles((addedRoles) => [...addedRoles, { id, name }]); - } - }) - .catch((err) => { - console.error(err); - }) - .finally(() => { - setCurrentStage(2, stages, setStages); - setSearchState("done"); - }); + const resetState = () => { + setSelectedRoleId(null); + setRoleDetailsModalId(false); + setRoleDetailsModalId(null); }; if (!roles) { return ; } - if (roles && !searchState) { - return ( -
- -
- {addedRoles.length > 0 && ( - - )} - - setRoleDetailsModalOpen(false)} - /> -
-
- ); - } - - if (searchState === "searching") { - return ( -
- - -
- ); - } - - if (searchState === "done") { - return ( -
- {matchingProfiles ? ( - - ) : ( - - )} -
- {addedRoles.length && } - setAddAnotherModalOpen(true)} - /> -
- {matchingProfiles && ( - setAddAnotherModalOpen(false)} - submitDone={submitDone} - onContinueClick={submitJob} - addAnother={addAnother} - /> - )} -
- ); - } + return ( + + + setRoleDetailsModalOpen(false)} + /> + + ); } export default SelectRole; diff --git a/src/routes/CreateNewTeam/pages/SelectRole/styles.module.scss b/src/routes/CreateNewTeam/pages/SelectRole/styles.module.scss deleted file mode 100644 index 7bacc294..00000000 --- a/src/routes/CreateNewTeam/pages/SelectRole/styles.module.scss +++ /dev/null @@ -1,14 +0,0 @@ -.page { - display: flex; - flex-direction: row; - justify-content: center; - align-items: flex-start; - margin: 42px 35px; - .right-side { - display: flex; - flex-direction: column; - & > div:not(:first-child) { - margin-top: 16px; - } - } -} diff --git a/src/services/teams.js b/src/services/teams.js index ce44479f..658f98f8 100644 --- a/src/services/teams.js +++ b/src/services/teams.js @@ -219,6 +219,8 @@ export const postProject = () => { * @returns */ export const searchRoles = (searchObject) => { + const newObject = { ...searchObject }; + delete newObject.previousRoleSearchRequestId; const url = `${config.API.V5}/taas-teams/sendRoleSearchRequest`; - return axios.post(url, searchObject); + return axios.post(url, newObject); }; From e6cb7a0bc90313c26e8df2f63dc568784682f102 Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Thu, 10 Jun 2021 15:27:54 +0400 Subject: [PATCH 7/8] - UI fixes to SkillsList, SkillsItem and ResultCard - Documentation and cleanup --- .../components/ResultCard/index.jsx | 29 +++++++++++-------- .../components/SearchContainer/index.jsx | 7 +++++ src/routes/CreateNewTeam/index.jsx | 10 ++++++- .../pages/InputJobDescription/index.jsx | 4 +++ .../components/SkillItem/styles.module.scss | 3 ++ .../components/SkillsList/styles.module.scss | 9 ++++++ .../CreateNewTeam/pages/InputSkills/index.jsx | 7 ++++- .../CreateNewTeam/pages/SelectRole/index.jsx | 7 ++++- 8 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/routes/CreateNewTeam/components/ResultCard/index.jsx b/src/routes/CreateNewTeam/components/ResultCard/index.jsx index 46578830..ac88dcfe 100644 --- a/src/routes/CreateNewTeam/components/ResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/ResultCard/index.jsx @@ -18,6 +18,11 @@ import Button from "components/Button"; import { MATCHING_RATE } from "constants"; import { formatMoney } from "utils/format"; +function formatRate(value) { + if (!value) return "N/A"; + return formatMoney(value); +} + function ResultCard({ role }) { const { numberOfMembersAvailable, @@ -75,21 +80,21 @@ function ResultCard({ role }) {

Global Rate

-

{formatMoney(rates.global)}

+

{formatRate(rates.global)}

/Week

In-Country Rate

-

{formatMoney(rates.inCountry)}

+

{formatRate(rates.inCountry)}

/Week

Offshore Rate

-

{formatMoney(rates.offShore)}

+

{formatRate(rates.offShore)}

/Week

@@ -102,21 +107,21 @@ function ResultCard({ role }) {

Global Rate

-

{formatMoney(rates.rate30Global)}

+

{formatRate(rates.rate30Global)}

/Week

In-Country Rate

-

{formatMoney(rates.rate30InCountry)}

+

{formatRate(rates.rate30InCountry)}

/Week

Offshore Rate

-

{formatMoney(rates.rate30OffShore)}

+

{formatRate(rates.rate30OffShore)}

/Week

@@ -129,21 +134,21 @@ function ResultCard({ role }) {

Global Rate

-

{formatMoney(rates.rate20Global)}

+

{formatRate(rates.rate20Global)}

/Week

In-Country Rate

-

{formatMoney(rates.rate20InCountry)}

+

{formatRate(rates.rate20InCountry)}

/Week

Offshore Rate

-

{formatMoney(rates.rate20OffShore)}

+

{formatRate(rates.rate20OffShore)}

/Week

@@ -160,7 +165,7 @@ function ResultCard({ role }) {

(40h / week)

-
{formatMoney(rates.global)}
+
{formatRate(rates.global)}

/Week

@@ -170,7 +175,7 @@ function ResultCard({ role }) {

(30h / week)

-
{formatMoney(rates.rate30Global)}
+
{formatRate(rates.rate30Global)}

/Week

@@ -180,7 +185,7 @@ function ResultCard({ role }) {

(20h / week)

-
{formatMoney(rates.rate20Global)}
+
{formatRate(rates.rate20Global)}

/Week

diff --git a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx index a4fe3eaf..fb7f6be7 100644 --- a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx @@ -1,3 +1,10 @@ +/** + * SearchContainer + * + * A container component for the different + * search pages. Contains logic and supporting + * components for searching for roles. + */ import React, { useCallback, useState } from "react"; import PT from "prop-types"; import { toastr } from "react-redux-toastr"; diff --git a/src/routes/CreateNewTeam/index.jsx b/src/routes/CreateNewTeam/index.jsx index 07b472ba..61ccf3f1 100644 --- a/src/routes/CreateNewTeam/index.jsx +++ b/src/routes/CreateNewTeam/index.jsx @@ -1,5 +1,9 @@ /** * Create New Team + * + * Gets location state from router to pass + * along to search pages + * * Landing page for creating new teams * by selecting a role, inputting skills, * or inputting a job description @@ -7,7 +11,7 @@ import React from "react"; import { navigate } from "@reach/router"; import _ from "lodash"; -import { toastr } from "react-redux-toastr"; +import PT from "prop-types"; import Page from "components/Page"; import PageHeader from "components/PageHeader"; import LandingBox from "./components/LandingBox"; @@ -54,4 +58,8 @@ function CreateNewTeam({ location: { state: locationState } }) { ); } +CreateNewTeam.propTypes = { + locationState: PT.object, +}; + export default CreateNewTeam; diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx index 58808b0f..6182c9a7 100644 --- a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx @@ -1,6 +1,10 @@ /** * Input Job Description page * + * Gets location state from router + * + * Allows user to search for roles by + * job description */ import React, { useCallback, useState } from "react"; import PT from "prop-types"; diff --git a/src/routes/CreateNewTeam/pages/InputSkills/components/SkillItem/styles.module.scss b/src/routes/CreateNewTeam/pages/InputSkills/components/SkillItem/styles.module.scss index ecc6b566..8d3c180a 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/components/SkillItem/styles.module.scss +++ b/src/routes/CreateNewTeam/pages/InputSkills/components/SkillItem/styles.module.scss @@ -9,11 +9,14 @@ align-items: center; margin: 0 0 24px 24px; cursor: pointer; + color: #555; + font-weight: 500; &.selected { border-color: #0ab88a; background-color: #e0faf3; font-weight: 500; + color: #2a2a2a; } } diff --git a/src/routes/CreateNewTeam/pages/InputSkills/components/SkillsList/styles.module.scss b/src/routes/CreateNewTeam/pages/InputSkills/components/SkillsList/styles.module.scss index 15b6fbce..452f06f1 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/components/SkillsList/styles.module.scss +++ b/src/routes/CreateNewTeam/pages/InputSkills/components/SkillsList/styles.module.scss @@ -22,6 +22,8 @@ // adding "input:not([type="checkbox"])" to make sure that we override reset styles input:not([type="checkbox"]).filter-input { + display: inline-block; + position: relative; width: 300px; background-color: #ffffff; border: 1px solid #aaaaaa; @@ -34,6 +36,13 @@ input:not([type="checkbox"]).filter-input { outline: none; padding: 0 15px; + &:not(:focus) { + background-image: url("../../../../../../assets/images/icon-search.svg"); + background-repeat: no-repeat; + background-position: 10px center; + text-indent: 20px; + } + &::placeholder { color: #aaaaaa; } diff --git a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx index 1d37bda3..6a492ab9 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx @@ -2,12 +2,13 @@ * Input Skills page * Page that user reaches after choosing to input job skills. * - * Gets a project id from the router. + * Gets location state from the router. * * Allows selecting a number of skills, searching for users * with those skills, and submitting a job requiring the skills. */ import React, { useCallback, useState } from "react"; +import PT from "prop-types"; import { useData } from "hooks/useData"; import SkillsList from "./components/SkillsList"; import { getSkills } from "services/skills"; @@ -59,4 +60,8 @@ function InputSkills({ location: { state: locationState } }) { ); } +InputSkills.propTypes = { + locationState: PT.object, +}; + export default InputSkills; diff --git a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx index a9cf3295..d2dda3fe 100644 --- a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx +++ b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx @@ -1,12 +1,13 @@ /** * Select Role Page * - * Gets project id from the router. + * Gets locationState from the router. * * Allows selecting a role, searching for users * with that role, and submitting a job requiring the roles. */ import React, { useCallback, useState } from "react"; +import PT from "prop-types"; import { useData } from "hooks/useData"; import RolesList from "./components/RolesList"; import { getRoles } from "services/roles"; @@ -70,4 +71,8 @@ function SelectRole({ location: { state: locationState } }) { ); } +SelectRole.propTypes = { + locationState: PT.object, +}; + export default SelectRole; From 90d28e3dd5d4956dfe76ff66d6e130271a2494ee Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Thu, 10 Jun 2021 18:21:47 +0400 Subject: [PATCH 8/8] Fixed crash when opening pages by entering url directly. Improved error handling in InputSkills page --- .../CreateNewTeam/components/SearchContainer/index.jsx | 4 ++-- src/routes/CreateNewTeam/index.jsx | 4 ++-- src/routes/CreateNewTeam/pages/InputSkills/index.jsx | 6 +++++- src/services/skills.js | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx index fb7f6be7..7e8c2bb4 100644 --- a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx @@ -32,13 +32,13 @@ function SearchContainer({ reloadRolesPage, }) { const [addedRoles, setAddedRoles] = useState( - locationState.addedRoles ? locationState.addedRoles : [] + locationState?.addedRoles ? locationState.addedRoles : [] ); const [searchState, setSearchState] = useState(null); const [matchingRole, setMatchingRole] = useState(null); const [addAnotherModalOpen, setAddAnotherModalOpen] = useState(false); const [submitDone, setSubmitDone] = useState(true); - const [prevSearchId, setPrevSearchId] = useState(locationState.prevSearchId); + const [prevSearchId, setPrevSearchId] = useState(locationState?.prevSearchId); const submitJob = () => { setSubmitDone(false); diff --git a/src/routes/CreateNewTeam/index.jsx b/src/routes/CreateNewTeam/index.jsx index 61ccf3f1..34eab93e 100644 --- a/src/routes/CreateNewTeam/index.jsx +++ b/src/routes/CreateNewTeam/index.jsx @@ -20,8 +20,8 @@ import IconListQuill from "../../assets/images/icon-list-quill.svg"; import IconOfficeFileText from "../../assets/images/icon-office-file-text.svg"; function CreateNewTeam({ location: { state: locationState } }) { - const { prevSearchId } = locationState; - const { addedRoles } = locationState; + const prevSearchId = locationState?.prevSearchId; + const addedRoles = locationState?.addedRoles; const goToRoute = (path) => { navigate(path, { state: { prevSearchId, addedRoles } }); diff --git a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx index 6a492ab9..f105df60 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx @@ -38,10 +38,14 @@ function InputSkills({ location: { state: locationState } }) { [selectedSkills] ); - if (!skills) { + if (!Array.isArray(skills)) { return ; } + if (skills.length === 0) { + return

Failed to load skills

; + } + return ( { console.error("Error loading skills", ex); cachedSkillsAsPromise = null; - return []; + return { data: [] }; }); return cachedSkillsAsPromise;