From ef47f76593c246c818f8d1164e4801d5c6489966 Mon Sep 17 00:00:00 2001 From: yoution Date: Tue, 29 Jun 2021 21:16:46 +0800 Subject: [PATCH 1/8] Role Skills Intake Tweaks and Bugs --- src/constants/index.js | 6 +++ src/routes/CreateNewTeam/actions/index.js | 19 +++++++ .../Completeness/styles.module.scss | 12 ++++- .../components/InputContainer/index.jsx | 53 +++++++++++++++++++ .../InputContainer/styles.module.scss | 14 +++++ .../components/ItemList/index.jsx | 2 +- .../components/ItemList/styles.module.scss | 5 +- .../components/SearchAndSubmit/index.jsx | 42 +++++++++++---- .../components/SearchContainer/index.jsx | 5 +- .../components/SubmitContainer/index.jsx | 7 ++- .../components/TeamDetailsModal/index.jsx | 8 +-- .../TeamDetailsModal/styles.module.scss | 5 +- src/routes/CreateNewTeam/index.jsx | 7 ++- .../pages/InputJobDescription/index.jsx | 9 ++-- .../InputJobDescription/styles.module.scss | 6 ++- .../CreateNewTeam/pages/InputSkills/index.jsx | 1 + .../CreateNewTeam/pages/SelectRole/index.jsx | 1 + src/routes/CreateNewTeam/reducers/index.js | 11 ++++ src/routes/CreateNewTeam/styles.module.scss | 3 ++ src/services/skills.js | 5 ++ 20 files changed, 192 insertions(+), 29 deletions(-) create mode 100644 src/routes/CreateNewTeam/components/InputContainer/index.jsx create mode 100644 src/routes/CreateNewTeam/components/InputContainer/styles.module.scss create mode 100644 src/routes/CreateNewTeam/styles.module.scss diff --git a/src/constants/index.js b/src/constants/index.js index 46a60d98..f1c5b67c 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -258,6 +258,12 @@ export const ACTION_TYPE = { ADD_SEARCHED_ROLE: "ADD_SEARCHED_ROLE", ADD_ROLE_SEARCH_ID: "ADD_ROLE_SEARCH_ID", DELETE_SEARCHED_ROLE: "DELETE_SEARCHED_ROLE", + + /* + * matching role + */ + ADD_MATCHING_ROLE: "ADD_MATCHING_ROLE", + DELETE_MATCHING_ROLE: "DELETE_MATCHING_ROLE", }; /** diff --git a/src/routes/CreateNewTeam/actions/index.js b/src/routes/CreateNewTeam/actions/index.js index a67e6569..b90aae4e 100644 --- a/src/routes/CreateNewTeam/actions/index.js +++ b/src/routes/CreateNewTeam/actions/index.js @@ -27,6 +27,15 @@ const deleteRole = (id) => ({ payload: id, }); +const addMatchingRole = (matchingRole) => ({ + type: ACTION_TYPE.ADD_MATCHING_ROLE, + payload: matchingRole, +}); + +const deleteMatchingRole = (matchingRole) => ({ + type: ACTION_TYPE.DELETE_MATCHING_ROLE, +}); + export const clearSearchedRoles = () => (dispatch, getState) => { dispatch(clearRoles()); updateLocalStorage(getState().searchedRoles); @@ -46,3 +55,13 @@ export const deleteSearchedRole = (id) => (dispatch, getState) => { dispatch(deleteRole(id)); updateLocalStorage(getState().searchedRoles); }; + +export const saveMatchingRole = (matchingRole) => (dispatch, getState) => { + dispatch(addMatchingRole(matchingRole)); + updateLocalStorage(getState().searchedRoles); +}; + +export const clearMatchingRole = (matchingRole) => (dispatch, getState) => { + dispatch(deleteMatchingRole()); + updateLocalStorage(getState().searchedRoles); +}; diff --git a/src/routes/CreateNewTeam/components/Completeness/styles.module.scss b/src/routes/CreateNewTeam/components/Completeness/styles.module.scss index 6e12427f..3438d020 100644 --- a/src/routes/CreateNewTeam/components/Completeness/styles.module.scss +++ b/src/routes/CreateNewTeam/components/Completeness/styles.module.scss @@ -2,6 +2,7 @@ .completeness { @include rounded-card; + overflow: hidden; padding: 12px; position: relative; width: 250px; @@ -25,12 +26,19 @@ .list { margin-bottom: 55px; + & + button[disabled] { + background-color: #E9E9E9; + color: #FFF; + opacity: 1; + filter: none; + } } .list-item { margin-bottom: 14px; font-size: 14px; line-height: 16px; + font-weight: 400; &:before { content: ""; @@ -45,14 +53,14 @@ } &.active { - font-weight: 700; + font-weight: 500; &:before { background-color: #fff; } } &.done { - font-weight: 700; + font-weight: 400; color: rgba(255, 255, 255, 0.6); &:before { content: "✓"; diff --git a/src/routes/CreateNewTeam/components/InputContainer/index.jsx b/src/routes/CreateNewTeam/components/InputContainer/index.jsx new file mode 100644 index 00000000..af03d23c --- /dev/null +++ b/src/routes/CreateNewTeam/components/InputContainer/index.jsx @@ -0,0 +1,53 @@ +/** + * InputContainer + * + * A container component for the different + * input pages. Contains logic and supporting + * components for selecting for roles. + */ +import React, { useCallback } from "react"; +import PT from "prop-types"; +import AddedRolesAccordion from "../AddedRolesAccordion"; +import Completeness from "../Completeness"; +import SearchCard from "../SearchCard"; +import ResultCard from "../ResultCard"; +import NoMatchingProfilesResultCard from "../NoMatchingProfilesResultCard"; +import { isCustomRole } from "utils/helpers"; +import "./styles.module.scss"; + +function InputContainer({ + stages, + isCompletenessDisabled, + toRender, + search, + completenessStyle, + addedRoles, +}) { + return ( +
+ {toRender(search)} +
+ + +
+
+ ); +} + +InputContainer.propTypes = { + stages: PT.array, + isCompletenessDisabled: PT.bool, + search: PT.func, + toRender: PT.func, + completenessStyle: PT.string, + addedRoles: PT.array, +}; + +export default InputContainer; diff --git a/src/routes/CreateNewTeam/components/InputContainer/styles.module.scss b/src/routes/CreateNewTeam/components/InputContainer/styles.module.scss new file mode 100644 index 00000000..99cec905 --- /dev/null +++ b/src/routes/CreateNewTeam/components/InputContainer/styles.module.scss @@ -0,0 +1,14 @@ +.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; + } + } +} \ No newline at end of file diff --git a/src/routes/CreateNewTeam/components/ItemList/index.jsx b/src/routes/CreateNewTeam/components/ItemList/index.jsx index 56b6496a..1305eae6 100644 --- a/src/routes/CreateNewTeam/components/ItemList/index.jsx +++ b/src/routes/CreateNewTeam/components/ItemList/index.jsx @@ -36,7 +36,7 @@ function ItemList({ return (
{title}
} backTo="/taas/createnewteam" aside={ <> diff --git a/src/routes/CreateNewTeam/components/ItemList/styles.module.scss b/src/routes/CreateNewTeam/components/ItemList/styles.module.scss index 6e41531e..cf201197 100644 --- a/src/routes/CreateNewTeam/components/ItemList/styles.module.scss +++ b/src/routes/CreateNewTeam/components/ItemList/styles.module.scss @@ -9,6 +9,9 @@ height: 80vh; overflow-y: auto; + .title { + font-weight: 500; + } > header { padding: 16px 24px; } @@ -67,4 +70,4 @@ input:not([type="checkbox"]).filter-input { justify-content: flex-start; flex-wrap: wrap; margin-right: 24px; -} \ No newline at end of file +} diff --git a/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx index 778fd984..339d725a 100644 --- a/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx @@ -1,29 +1,44 @@ -import { Router } from "@reach/router"; +import { Router, navigate } from "@reach/router"; import _ from "lodash"; -import React, { useCallback, useState } from "react"; +import React, { useCallback, useState, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { searchRoles } from "services/teams"; import { isCustomRole, setCurrentStage } from "utils/helpers"; -import { addRoleSearchId, addSearchedRole } from "../../actions"; +import { clearMatchingRole, saveMatchingRole, addRoleSearchId, addSearchedRole } from "../../actions"; +import InputContainer from "../InputContainer"; import SearchContainer from "../SearchContainer"; import SubmitContainer from "../SubmitContainer"; function SearchAndSubmit(props) { - const { stages, setStages, searchObject, onClick } = props; + const { stages, setStages, searchObject, onClick, page } = props; const [searchState, setSearchState] = useState(null); - const [matchingRole, setMatchingRole] = useState(null); - const { addedRoles, previousSearchId } = useSelector( + const { matchingRole } = useSelector( (state) => state.searchedRoles ); + useEffect(()=> { + const isFromInputPage = searchObject.role || searchObject.skills && searchObject.skills.length + || searchObject.jobDescription + // refresh in search page directly + if (matchingRole && !isFromInputPage) { + setCurrentStage(2, stages, setStages); + setSearchState("done"); + } + }, []) + const dispatch = useDispatch(); + const { addedRoles, previousSearchId } = useSelector( + (state) => state.searchedRoles + ); + const search = useCallback(() => { + navigate(`${page}/search`); setCurrentStage(1, stages, setStages); setSearchState("searching"); - setMatchingRole(null); + dispatch(clearMatchingRole()); const searchObjectCopy = { ...searchObject }; if (previousSearchId) { searchObjectCopy.previousRoleSearchRequestId = previousSearchId; @@ -37,7 +52,9 @@ function SearchAndSubmit(props) { } else if (searchId) { dispatch(addRoleSearchId(searchId)); } - setMatchingRole(res.data); + // setMatchingRole(res.data); + + dispatch(saveMatchingRole(res.data)); }) .catch((err) => { console.error(err); @@ -51,11 +68,18 @@ function SearchAndSubmit(props) { return ( - + { - navigate("result"); + navigate("../result"); }, [navigate]); const renderLeftSide = () => { - if (!searchState) return toRender(search); if (searchState === "searching") return ; if (!isCustomRole(matchingRole)) return ; return ; }; const getPercentage = useCallback(() => { - if (!searchState) return "26"; if (searchState === "searching") return "52"; if (matchingRole) return "98"; return "88"; @@ -52,7 +50,6 @@ function SearchContainer({ { - dispatch(clearSearchedRoles()); - navigate("/taas/myteams"); + setTimeout(() => { + dispatch(clearSearchedRoles()); + // Backend api create project has sync issue, so delay 2 seconds + navigate("/taas/myteams"); + }, 2000) }) .catch((err) => { setRequestLoading(false); diff --git a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx index 11802f60..16215341 100644 --- a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx +++ b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx @@ -100,6 +100,8 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { return errors; }; + const validateRequired = value => (value ? undefined : 'Please enter a positive integer') + return (
undefined); }, }} - initialValues={{ teamName: "My Great Team" }} + initialValues={{ teamName: "" }} validate={validator} > {({ @@ -181,7 +183,7 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { {name} - + {({ input, meta }) => ( - + {({ input, meta }) => ( { + dispatch(clearMatchingRole()); navigate(path); }; return ( - + Create New Team} />

Please select how you want to find members that match your requirements.

diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx index 2ba16303..10e1809b 100644 --- a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx @@ -56,14 +56,15 @@ function InputJobDescription() { 255} + isCompletenessDisabled={jdString.length < 10 || jdString.length > 2000} completenessStyle="input-job-description" searchObject={searchObject} + page="jd" onClick={onClick} toRender={(searchFunc) => (
Input Job Description
} backTo="/taas/createnewteam" />
@@ -80,8 +81,8 @@ function InputJobDescription() { placeholder="input job description" onChange={onEditChange} errorMessage={ - jdString.length > 255 - ? "Maximum of 255 characters. Please reduce job description length." + jdString.length > 2000 + ? "Maximum of 2000 characters. Please reduce job description length." : "" } /> diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/styles.module.scss b/src/routes/CreateNewTeam/pages/InputJobDescription/styles.module.scss index 892befbe..6b3f1a38 100644 --- a/src/routes/CreateNewTeam/pages/InputJobDescription/styles.module.scss +++ b/src/routes/CreateNewTeam/pages/InputJobDescription/styles.module.scss @@ -8,6 +8,10 @@ flex: 1; } +.title { + font-weight: 500; +} + .job-title { margin-bottom: 15px; @@ -15,4 +19,4 @@ line-height: normal; padding: 8px 15px; } -} \ No newline at end of file +} diff --git a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx index ac0a7b2b..5e520cb2 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx @@ -50,6 +50,7 @@ function InputSkills() { setStages={setStages} isCompletenessDisabled={selectedSkills.length < 1} searchObject={{ skills: selectedSkills }} + page="skills" completenessStyle="input-skills" toRender={() => ( ( diff --git a/src/routes/CreateNewTeam/reducers/index.js b/src/routes/CreateNewTeam/reducers/index.js index d90316bf..2180d81d 100644 --- a/src/routes/CreateNewTeam/reducers/index.js +++ b/src/routes/CreateNewTeam/reducers/index.js @@ -7,6 +7,7 @@ const loadState = () => { const defaultState = { previousSearchId: undefined, addedRoles: [], + matchingRole: undefined, }; try { const state = localStorage.getItem("rolesState"); @@ -38,6 +39,16 @@ const reducer = (state = initialState, action) => { addedRoles: [], }; + case ACTION_TYPE.ADD_MATCHING_ROLE: + return { + ...state, + matchingRole: action.payload, + }; + case ACTION_TYPE.DELETE_MATCHING_ROLE: + return { + ...state, + matchingRole: null, + }; case ACTION_TYPE.ADD_SEARCHED_ROLE: return { ...state, diff --git a/src/routes/CreateNewTeam/styles.module.scss b/src/routes/CreateNewTeam/styles.module.scss new file mode 100644 index 00000000..379eb6ff --- /dev/null +++ b/src/routes/CreateNewTeam/styles.module.scss @@ -0,0 +1,3 @@ +.title { + font-weight: 500; +} diff --git a/src/services/skills.js b/src/services/skills.js index 36a91af8..6d733350 100644 --- a/src/services/skills.js +++ b/src/services/skills.js @@ -37,6 +37,11 @@ function getAllSkills() { page++; loop(page); } else { + skills.sort((a, b) => { + if (a.name.toLowerCase() < b.name.toLowerCase()) return -1; + if (a.name.toLowerCase() > b.name.toLowerCase()) return 1; + return 0; + }); resolve({ data: skills, }); From 9bdb47712d61d78018d8cf6f0e3e372702ef4f8e Mon Sep 17 00:00:00 2001 From: yoution Date: Wed, 30 Jun 2021 17:16:10 +0800 Subject: [PATCH 2/8] fix: show jd popup --- src/routes/CreateNewTeam/components/InputContainer/index.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/routes/CreateNewTeam/components/InputContainer/index.jsx b/src/routes/CreateNewTeam/components/InputContainer/index.jsx index af03d23c..f5d69e62 100644 --- a/src/routes/CreateNewTeam/components/InputContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/InputContainer/index.jsx @@ -20,6 +20,7 @@ function InputContainer({ isCompletenessDisabled, toRender, search, + onClick, completenessStyle, addedRoles, }) { @@ -30,7 +31,7 @@ function InputContainer({ Date: Wed, 30 Jun 2021 15:51:08 +0400 Subject: [PATCH 3/8] Remove some unused props and imports. --- .../components/InputContainer/index.jsx | 6 +---- .../components/SearchAndSubmit/index.jsx | 23 +++++++++++-------- .../components/SearchContainer/index.jsx | 4 ---- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/routes/CreateNewTeam/components/InputContainer/index.jsx b/src/routes/CreateNewTeam/components/InputContainer/index.jsx index af03d23c..f31cc862 100644 --- a/src/routes/CreateNewTeam/components/InputContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/InputContainer/index.jsx @@ -5,14 +5,10 @@ * input pages. Contains logic and supporting * components for selecting for roles. */ -import React, { useCallback } from "react"; +import React from "react"; import PT from "prop-types"; import AddedRolesAccordion from "../AddedRolesAccordion"; import Completeness from "../Completeness"; -import SearchCard from "../SearchCard"; -import ResultCard from "../ResultCard"; -import NoMatchingProfilesResultCard from "../NoMatchingProfilesResultCard"; -import { isCustomRole } from "utils/helpers"; import "./styles.module.scss"; function InputContainer({ diff --git a/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx index 339d725a..be76462b 100644 --- a/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx @@ -4,7 +4,12 @@ import React, { useCallback, useState, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { searchRoles } from "services/teams"; import { isCustomRole, setCurrentStage } from "utils/helpers"; -import { clearMatchingRole, saveMatchingRole, addRoleSearchId, addSearchedRole } from "../../actions"; +import { + clearMatchingRole, + saveMatchingRole, + addRoleSearchId, + addSearchedRole, +} from "../../actions"; import InputContainer from "../InputContainer"; import SearchContainer from "../SearchContainer"; import SubmitContainer from "../SubmitContainer"; @@ -14,19 +19,19 @@ function SearchAndSubmit(props) { const [searchState, setSearchState] = useState(null); - const { matchingRole } = useSelector( - (state) => state.searchedRoles - ); + const { matchingRole } = useSelector((state) => state.searchedRoles); - useEffect(()=> { - const isFromInputPage = searchObject.role || searchObject.skills && searchObject.skills.length - || searchObject.jobDescription + useEffect(() => { + const isFromInputPage = + searchObject.role || + (searchObject.skills && searchObject.skills.length) || + searchObject.jobDescription; // refresh in search page directly if (matchingRole && !isFromInputPage) { setCurrentStage(2, stages, setStages); setSearchState("done"); } - }, []) + }, []); const dispatch = useDispatch(); @@ -52,8 +57,6 @@ function SearchAndSubmit(props) { } else if (searchId) { dispatch(addRoleSearchId(searchId)); } - // setMatchingRole(res.data); - dispatch(saveMatchingRole(res.data)); }) .catch((err) => { diff --git a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx index 141cdba1..b08b0913 100644 --- a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx @@ -17,8 +17,6 @@ import "./styles.module.scss"; function SearchContainer({ stages, - isCompletenessDisabled, - toRender, onClick, search, completenessStyle, @@ -66,10 +64,8 @@ function SearchContainer({ SearchContainer.propTypes = { stages: PT.array, - isCompletenessDisabled: PT.bool, onClick: PT.func, search: PT.func, - toRender: PT.func, completenessStyle: PT.string, navigate: PT.func, addedRoles: PT.array, From a01c56b70c27b95bccd03e38cbe10f69a5fe18b1 Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Wed, 30 Jun 2021 16:20:39 +0400 Subject: [PATCH 4/8] Require login at Continue stage of create new team. Remove more unused code. --- .../components/InputContainer/index.jsx | 2 +- .../components/SearchAndSubmit/index.jsx | 2 -- .../components/SearchContainer/index.jsx | 25 +++++++++++++------ .../components/SubmitContainer/index.jsx | 6 ++--- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/routes/CreateNewTeam/components/InputContainer/index.jsx b/src/routes/CreateNewTeam/components/InputContainer/index.jsx index f31cc862..8ae24bce 100644 --- a/src/routes/CreateNewTeam/components/InputContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/InputContainer/index.jsx @@ -28,7 +28,7 @@ function InputContainer({ isDisabled={isCompletenessDisabled} onClick={search} extraStyleName={completenessStyle} - buttonLabel={"Search"} + buttonLabel="Search" stages={stages} percentage="26" /> diff --git a/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx index be76462b..bfefd418 100644 --- a/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx @@ -81,8 +81,6 @@ function SearchAndSubmit(props) { { + setAddAnotherOpen(false); navigate("../result"); }, [navigate]); + const addAnother = useCallback(() => { + navigate("/taas/createnewteam"); + }, [navigate]); + const renderLeftSide = () => { if (searchState === "searching") return ; if (!isCustomRole(matchingRole)) return ; @@ -51,21 +57,26 @@ function SearchContainer({ searchState === "searching" || (searchState === "done" && (!addedRoles || !addedRoles.length)) } - onClick={searchState ? onSubmit : onClick ? onClick : search} + onClick={() => setAddAnotherOpen(true)} extraStyleName={completenessStyle} - buttonLabel={searchState ? "Submit Request" : "Search"} + buttonLabel="Submit Request" stages={stages} percentage={getPercentage()} />
+ setAddAnotherOpen(false)} + submitDone={true} + onContinueClick={onSubmit} + addAnother={addAnother} + /> ); } SearchContainer.propTypes = { stages: PT.array, - onClick: PT.func, - search: PT.func, completenessStyle: PT.string, navigate: PT.func, addedRoles: PT.array, diff --git a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx index f739dcac..c682ac62 100644 --- a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx @@ -35,8 +35,8 @@ function SubmitContainer({ matchingRole, addedRoles, }) { - const [addAnotherOpen, setAddAnotherOpen] = useState(true); - const [teamDetailsOpen, setTeamDetailsOpen] = useState(false); + const [addAnotherOpen, setAddAnotherOpen] = useState(false); + const [teamDetailsOpen, setTeamDetailsOpen] = useState(true); const [teamObject, setTeamObject] = useState(null); const [requestLoading, setRequestLoading] = useState(false); @@ -99,7 +99,7 @@ function SubmitContainer({ dispatch(clearSearchedRoles()); // Backend api create project has sync issue, so delay 2 seconds navigate("/taas/myteams"); - }, 2000) + }, 2000); }) .catch((err) => { setRequestLoading(false); From b47476b15f77dad60e444a6987fb27909babeb50 Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Thu, 1 Jul 2021 11:59:14 +0400 Subject: [PATCH 5/8] Allow user to add custom role to added roles list from No Matching Profiles card. --- src/routes/CreateNewTeam/actions/index.js | 4 +- .../NoMatchingProfilesResultCard/index.jsx | 50 +++++++++++++++++-- .../styles.module.scss | 22 +++++++- .../components/SubmitContainer/index.jsx | 4 +- 4 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/routes/CreateNewTeam/actions/index.js b/src/routes/CreateNewTeam/actions/index.js index b90aae4e..0a224017 100644 --- a/src/routes/CreateNewTeam/actions/index.js +++ b/src/routes/CreateNewTeam/actions/index.js @@ -32,7 +32,7 @@ const addMatchingRole = (matchingRole) => ({ payload: matchingRole, }); -const deleteMatchingRole = (matchingRole) => ({ +const deleteMatchingRole = () => ({ type: ACTION_TYPE.DELETE_MATCHING_ROLE, }); @@ -61,7 +61,7 @@ export const saveMatchingRole = (matchingRole) => (dispatch, getState) => { updateLocalStorage(getState().searchedRoles); }; -export const clearMatchingRole = (matchingRole) => (dispatch, getState) => { +export const clearMatchingRole = () => (dispatch, getState) => { dispatch(deleteMatchingRole()); updateLocalStorage(getState().searchedRoles); }; diff --git a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx index 4daee929..2085ab7c 100644 --- a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx @@ -2,9 +2,11 @@ * No Matching Profiles Result Card * Card that appears when there are no matching profiles after searching. */ -import React from "react"; +import React, { useCallback, useMemo } from "react"; import { Link } from "@reach/router"; import PT from "prop-types"; +import { useDispatch, useSelector } from "react-redux"; +import { addSearchedRole } from "../../actions"; import "./styles.module.scss"; import IconEarthX from "../../../../assets/images/icon-earth-x.svg"; import Curve from "../../../../assets/images/curve.svg"; @@ -12,6 +14,30 @@ import Button from "components/Button"; import { formatMoney } from "utils/format"; function NoMatchingProfilesResultCard({ role }) { + const { addedRoles } = useSelector((state) => state.searchedRoles); + + const alreadyAdded = useMemo(() => { + if ( + addedRoles.find( + (addedRole) => addedRole.searchId === role.roleSearchRequestId + ) + ) { + return true; + } + return false; + }, [addedRoles, role]); + + const dispatch = useDispatch(); + + const addRole = useCallback(() => { + const searchId = role.roleSearchRequestId; + let name = "Custom Role"; + if (role.jobTitle && role.jobTitle.length) { + name = role.jobTitle; + } + dispatch(addSearchedRole({ searchId, name })); + }, [dispatch, role]); + return (
@@ -21,6 +47,11 @@ function NoMatchingProfilesResultCard({ role }) {
+

+ {role.jobTitle && role.jobTitle.length + ? role.jobTitle + : "Custom Role"} +

We will be looking internally for members matching your requirements and be back at them in about 2 weeks. @@ -38,11 +69,20 @@ function NoMatchingProfilesResultCard({ role }) {

/Week

)} - - + + - +
); diff --git a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/styles.module.scss b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/styles.module.scss index 7334e07f..b39ab76b 100644 --- a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/styles.module.scss +++ b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/styles.module.scss @@ -13,7 +13,7 @@ justify-content: flex-start; align-items: center; padding: 30px 0 60px 0; - margin-bottom: 30px; + margin-bottom: 14px; color: #fff; background-image: linear-gradient(225deg, #555555 0%, #2A2A2A 100%); position: relative; @@ -41,6 +41,17 @@ flex-direction: column; align-items: center; padding-bottom: 61px; + .job-title { + @include font-barlow; + font-size: 22px; + margin-bottom: 18px; + font-weight: 600; + text-align: center; + text-transform: uppercase; + // position text over bottom of header image + position: relative; + z-index: 100; + } p.info-txt { @include font-roboto; font-size: 14px; @@ -82,8 +93,15 @@ } } - .button { + .button-group { margin-top: 62px; + display: flex; + flex-direction: row; + align-items: center; + + .left { + margin-right: 30px; + } } } diff --git a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx index c682ac62..b7bfdc40 100644 --- a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx @@ -26,7 +26,7 @@ import "./styles.module.scss"; import { isCustomRole, setCurrentStage } from "utils/helpers"; import { clearSearchedRoles } from "../../actions"; import { postTeamRequest } from "services/teams"; -import SuccessCard from "../SuccessCard"; +import NoMatchingProfilesResultCard from "../NoMatchingProfilesResultCard"; function SubmitContainer({ stages, @@ -112,7 +112,7 @@ function SubmitContainer({ {!isCustomRole(matchingRole) ? ( ) : ( - + )}
From 1313b50c6dd56720d6eb7c7e4bf64c453a28b092 Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Thu, 1 Jul 2021 17:33:33 +0400 Subject: [PATCH 6/8] Allow user to remove searched role from added roles accordion. Ensure team details popup form is updated when searched role removed. Move pure functions for validation of team details popup to their own file. Disable focus trap for team details popup. --- .../components/AddedRolesAccordion/index.jsx | 14 ++- .../AddedRolesAccordion/styles.module.scss | 17 +++- .../components/BaseCreateModal/index.jsx | 5 +- .../components/TeamDetailsModal/index.jsx | 97 +++++-------------- .../TeamDetailsModal/utils/validator.js | 60 ++++++++++++ 5 files changed, 117 insertions(+), 76 deletions(-) create mode 100644 src/routes/CreateNewTeam/components/TeamDetailsModal/utils/validator.js diff --git a/src/routes/CreateNewTeam/components/AddedRolesAccordion/index.jsx b/src/routes/CreateNewTeam/components/AddedRolesAccordion/index.jsx index ece5e080..1618b2b3 100644 --- a/src/routes/CreateNewTeam/components/AddedRolesAccordion/index.jsx +++ b/src/routes/CreateNewTeam/components/AddedRolesAccordion/index.jsx @@ -8,11 +8,16 @@ import React, { useState } from "react"; import PT from "prop-types"; import cn from "classnames"; +import { useDispatch } from "react-redux"; +import { deleteSearchedRole } from "../../actions"; import "./styles.module.scss"; +import IconCrossLight from "../../../../assets/images/icon-cross-light.svg"; function AddedRolesAccordion({ addedRoles }) { const [isOpen, setIsOpen] = useState(false); + const dispatch = useDispatch(); + return addedRoles.length ? (
{isOpen && (
- {addedRoles.map(({ name }) => ( -
{name}
+ {addedRoles.map(({ name, searchId: id }) => ( +
+ {name} + +
))}
)} diff --git a/src/routes/CreateNewTeam/components/AddedRolesAccordion/styles.module.scss b/src/routes/CreateNewTeam/components/AddedRolesAccordion/styles.module.scss index 6478f1f0..67f7c05e 100644 --- a/src/routes/CreateNewTeam/components/AddedRolesAccordion/styles.module.scss +++ b/src/routes/CreateNewTeam/components/AddedRolesAccordion/styles.module.scss @@ -55,11 +55,12 @@ .panel { padding: 12px 18px 14px 10px; .role-name { - height: 40px; + position: relative; width: 100%; background-color: #F4F4F4; border-radius: 6px; padding: 10px; + padding-right: 30px; @include font-barlow; font-size: 16px; line-height: 20px; @@ -68,5 +69,19 @@ &:not(:first-child) { margin-top: 5px; } + + >button { + outline: none; + border: none; + background: none; + position: absolute; + top: 12px; + right: 4px; + &:hover { + g { + stroke: red; + } + } + } } } \ No newline at end of file diff --git a/src/routes/CreateNewTeam/components/BaseCreateModal/index.jsx b/src/routes/CreateNewTeam/components/BaseCreateModal/index.jsx index d7fcecbb..17528b3e 100644 --- a/src/routes/CreateNewTeam/components/BaseCreateModal/index.jsx +++ b/src/routes/CreateNewTeam/components/BaseCreateModal/index.jsx @@ -35,6 +35,7 @@ function BaseCreateModal({ loadingMessage, maxWidth = "680px", darkHeader, + disableFocusTrap, children, }) { return ( @@ -51,8 +52,9 @@ function BaseCreateModal({ modalContainer: containerStyle, closeButton: closeButtonStyle, }} + focusTrapped={!disableFocusTrap} > -
+
{isLoading ? (
@@ -86,6 +88,7 @@ BaseCreateModal.propTypes = { loadingMessage: PT.string, maxWidth: PT.string, darkHeader: PT.bool, + disableFocusTrap: PT.bool, children: PT.node, }; diff --git a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx index 16215341..ef97a714 100644 --- a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx +++ b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx @@ -3,7 +3,7 @@ * Popup form to enter details about the * team request before submitting. */ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import PT from "prop-types"; import { Form, Field, useField } from "react-final-form"; import { useDispatch } from "react-redux"; @@ -18,6 +18,7 @@ import { deleteSearchedRole } from "../../actions"; import IconCrossLight from "../../../../assets/images/icon-cross-light.svg"; import "./styles.module.scss"; import NumberInput from "components/NumberInput"; +import validator from "./utils/validator"; const Error = ({ name }) => { const { @@ -28,13 +29,23 @@ const Error = ({ name }) => { function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { const [showDescription, setShowDescription] = useState(false); - const [startMonthVisible, setStartMonthVisible] = useState(() => { - const roles = {}; - addedRoles.forEach(({ searchId }) => { - roles[searchId] = false; - }); - return roles; - }); + const [startMonthVisible, setStartMonthVisible] = useState({}); + + // Ensure role is removed from form state when it is removed from redux store + let getFormState; + let clearFormField; + useEffect(() => { + const values = getFormState().values; + for (let fieldName of Object.keys(values)) { + if (fieldName === "teamName" || fieldName === "teamDescription") { + continue; + } + if (addedRoles.findIndex((role) => role.searchId === fieldName)) { + clearFormField(fieldName); + setStartMonthVisible((state) => ({ ...state, [fieldName]: false })); + } + } + }, [getFormState, addedRoles, clearFormField]); const dispatch = useDispatch(); @@ -42,66 +53,6 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { setShowDescription((prevState) => !prevState); }; - const validateName = (name) => { - if (!name || name.trim().length === 0) { - return "Please enter a team name."; - } - return undefined; - }; - - const validateNumber = (number) => { - const converted = Number(number); - - if ( - Number.isNaN(converted) || - converted !== Math.floor(converted) || - converted < 1 - ) { - return "Please enter a positive integer"; - } - return undefined; - }; - - const validateMonth = (monthString) => { - const then = new Date(monthString); - const now = new Date(); - const thenYear = then.getFullYear(); - const nowYear = now.getFullYear(); - const thenMonth = then.getMonth(); - const nowMonth = now.getMonth(); - - if (thenYear < nowYear || (thenYear === nowYear && thenMonth < nowMonth)) { - return "Start month may not be before current month"; - } - return undefined; - }; - - const validateRole = (role) => { - const roleErrors = {}; - roleErrors.numberOfResources = validateNumber(role.numberOfResources); - roleErrors.durationWeeks = validateNumber(role.durationWeeks); - if (role.startMonth) { - roleErrors.startMonth = validateMonth(role.startMonth); - } - - return roleErrors; - }; - - const validator = (values) => { - const errors = {}; - - errors.teamName = validateName(values.teamName); - - for (const key of Object.keys(values)) { - if (key === "teamDescription" || key === "teamName") continue; - errors[key] = validateRole(values[key]); - } - - return errors; - }; - - const validateRequired = value => (value ? undefined : 'Please enter a positive integer') - return ( undefined); }, }} - initialValues={{ teamName: "" }} validate={validator} > {({ @@ -118,8 +68,11 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { hasValidationErrors, form: { mutators: { clearField }, + getState, }, }) => { + getFormState = getState; + clearFormField = clearField; return ( } + disableFocusTrap >
{name} - + {({ input, meta }) => ( - + {({ input, meta }) => ( { - clearField(id); dispatch(deleteSearchedRole(id)); }} > diff --git a/src/routes/CreateNewTeam/components/TeamDetailsModal/utils/validator.js b/src/routes/CreateNewTeam/components/TeamDetailsModal/utils/validator.js new file mode 100644 index 00000000..ac4f7646 --- /dev/null +++ b/src/routes/CreateNewTeam/components/TeamDetailsModal/utils/validator.js @@ -0,0 +1,60 @@ +const validateName = (name) => { + if (!name || name.trim().length === 0) { + return "Please enter a team name."; + } + return undefined; +}; + +const validateNumber = (number) => { + const converted = Number(number); + + if ( + !number || + Number.isNaN(converted) || + converted !== Math.floor(converted) || + converted < 1 + ) { + return "Please enter a positive integer"; + } + return undefined; +}; + +const validateMonth = (monthString) => { + const then = new Date(monthString); + const now = new Date(); + const thenYear = then.getFullYear(); + const nowYear = now.getFullYear(); + const thenMonth = then.getMonth(); + const nowMonth = now.getMonth(); + + if (thenYear < nowYear || (thenYear === nowYear && thenMonth < nowMonth)) { + return "Start month may not be before current month"; + } + return undefined; +}; + +const validateRole = (role) => { + const roleErrors = {}; + roleErrors.numberOfResources = validateNumber(role.numberOfResources); + roleErrors.durationWeeks = validateNumber(role.durationWeeks); + if (role.startMonth) { + roleErrors.startMonth = validateMonth(role.startMonth); + } + + return roleErrors; +}; + +const validator = (values) => { + const errors = {}; + + errors.teamName = validateName(values.teamName); + + for (const key of Object.keys(values)) { + if (key === "teamDescription" || key === "teamName") continue; + errors[key] = validateRole(values[key]); + } + + return errors; +}; + +export default validator; From 8f8971cf04515ef088aaa69a2fa556b68de35bda Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Thu, 1 Jul 2021 19:36:22 +0400 Subject: [PATCH 7/8] fix: only clear form data for removed search role --- src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx index ef97a714..b2859592 100644 --- a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx +++ b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx @@ -40,7 +40,7 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { if (fieldName === "teamName" || fieldName === "teamDescription") { continue; } - if (addedRoles.findIndex((role) => role.searchId === fieldName)) { + if (addedRoles.findIndex((role) => role.searchId === fieldName) === -1) { clearFormField(fieldName); setStartMonthVisible((state) => ({ ...state, [fieldName]: false })); } From 82f0a34a220cc53a57f519a21d60786df9e9fefc Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Mon, 5 Jul 2021 17:00:44 +0400 Subject: [PATCH 8/8] fix: validate resources and duration fields when both deleted. --- .../components/TeamDetailsModal/index.jsx | 14 +++++++++++--- .../components/TeamDetailsModal/utils/validator.js | 7 +++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx index b2859592..30d89b08 100644 --- a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx +++ b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx @@ -18,7 +18,7 @@ import { deleteSearchedRole } from "../../actions"; import IconCrossLight from "../../../../assets/images/icon-cross-light.svg"; import "./styles.module.scss"; import NumberInput from "components/NumberInput"; -import validator from "./utils/validator"; +import { validator, validateExists } from "./utils/validator"; const Error = ({ name }) => { const { @@ -137,7 +137,11 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { {name} - + {({ input, meta }) => ( - + {({ input, meta }) => ( { const converted = Number(number); if ( - !number || Number.isNaN(converted) || converted !== Math.floor(converted) || converted < 1 @@ -57,4 +56,8 @@ const validator = (values) => { return errors; }; -export default validator; +const validateExists = (value) => { + return value === undefined ? "Please enter a positive integer" : undefined; +}; + +export { validator, validateExists };