From ae8738dbfa11504326dd9d1d581757e8ee0aad06 Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Wed, 16 Jun 2021 11:48:58 +0400 Subject: [PATCH 1/6] New icon-role-fallback for RoleItem Change SkillItem to use IconSkill Use localStorage to persist searched roles --- src/assets/images/icon-role-fallback-old.svg | 17 +++++++ src/assets/images/icon-role-fallback.svg | 38 ++++++++------ src/routes/CreateNewTeam/actions/index.js | 29 +++++++++-- .../NoMatchingProfilesResultCard/index.jsx | 2 +- .../components/SearchAndSubmit/index.jsx | 14 +++++- .../components/SearchContainer/index.jsx | 34 ++----------- .../components/SubmitContainer/index.jsx | 50 ++++--------------- .../components/SuccessCard/index.jsx | 2 +- src/routes/CreateNewTeam/index.jsx | 20 +------- .../components/SkillItem/index.jsx | 4 +- .../CreateNewTeam/pages/SelectRole/index.jsx | 7 --- src/routes/CreateNewTeam/reducers/index.js | 19 +++++-- 12 files changed, 112 insertions(+), 124 deletions(-) create mode 100644 src/assets/images/icon-role-fallback-old.svg diff --git a/src/assets/images/icon-role-fallback-old.svg b/src/assets/images/icon-role-fallback-old.svg new file mode 100644 index 00000000..9e0f1123 --- /dev/null +++ b/src/assets/images/icon-role-fallback-old.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/icon-role-fallback.svg b/src/assets/images/icon-role-fallback.svg index 9e0f1123..d285b7e6 100644 --- a/src/assets/images/icon-role-fallback.svg +++ b/src/assets/images/icon-role-fallback.svg @@ -1,17 +1,23 @@ - - - - - - - - - - - - - - - + + + + Layer 1 + + + + + + + + + + + + + + - \ No newline at end of file + + + + diff --git a/src/routes/CreateNewTeam/actions/index.js b/src/routes/CreateNewTeam/actions/index.js index 99106fc4..bb78565f 100644 --- a/src/routes/CreateNewTeam/actions/index.js +++ b/src/routes/CreateNewTeam/actions/index.js @@ -1,15 +1,23 @@ import { ACTION_TYPE } from "constants"; -export const clearSearchedRoles = () => ({ +const updateLocalStorage = (state) => { + try { + localStorage.setItem("rolesState", JSON.stringify(state)); + } catch { + console.error("Unable to set localStorage"); + } +}; + +const clearRoles = () => ({ type: ACTION_TYPE.CLEAR_SEARCHED_ROLES, }); -export const addSearchedRole = (searchedRole) => ({ +const addRole = (searchedRole) => ({ type: ACTION_TYPE.ADD_SEARCHED_ROLE, payload: searchedRole, }); -export const addRoleSearchId = (id) => ({ +const addPreviousSearchId = (id) => ({ type: ACTION_TYPE.ADD_ROLE_SEARCH_ID, payload: id, }); @@ -18,3 +26,18 @@ export const replaceSearchedRoles = (roles) => ({ type: ACTION_TYPE.REPLACE_SEARCHED_ROLES, payload: { roles, lastRoleId: roles[roles.length - 1].searchId }, }); + +export const clearSearchedRoles = () => (dispatch, getState) => { + dispatch(clearRoles()); + updateLocalStorage(getState().searchedRoles); +}; + +export const addSearchedRole = (searchedRole) => (dispatch, getState) => { + dispatch(addRole(searchedRole)); + updateLocalStorage(getState().searchedRoles); +}; + +export const addRoleSearchId = (id) => (dispatch, getState) => { + dispatch(addPreviousSearchId(id)); + updateLocalStorage(getState().searchedRoles); +}; diff --git a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx index 18940725..2b1fd779 100644 --- a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx @@ -28,7 +28,7 @@ function NoMatchingProfilesResultCard() {

$1,200

/Week

- + diff --git a/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx index 9f27e3ba..b801e2d1 100644 --- a/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx @@ -1,13 +1,23 @@ import { Router } from "@reach/router"; import React from "react"; +import { useSelector } from "react-redux"; import SearchContainer from "../SearchContainer"; import SubmitContainer from "../SubmitContainer"; function SearchAndSubmit(props) { + const { addedRoles, previousSearchId } = useSelector( + (state) => state.searchedRoles + ); + return ( - - + + ); } diff --git a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx index 4cf96c6f..7b85e495 100644 --- a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx @@ -19,25 +19,6 @@ import { setCurrentStage } from "utils/helpers"; import { addRoleSearchId, addSearchedRole } from "../../actions"; import "./styles.module.scss"; -/** - * Converts an array of role search objects to two data - * lists which can be set as sessionStorage items - * - * @param {object[]} arrayOfObjects array of role objects - */ -const storeStrings = (arrayOfObjects) => { - const objectOfArrays = arrayOfObjects.reduce( - (acc, curr) => ({ - searchId: [...acc.searchId, curr.searchId], - name: [...acc.name, curr.name], - }), - { searchId: [], name: [] } - ); - - sessionStorage.setItem("searchIds", objectOfArrays.searchId.join(",")); - sessionStorage.setItem("roleNames", objectOfArrays.name.join(",")); -}; - function SearchContainer({ stages, setStages, @@ -45,24 +26,18 @@ function SearchContainer({ toRender, searchObject, completenessStyle, - reloadRolesPage, navigate, + addedRoles, + previousSearchId, }) { - const { addedRoles, previousSearchId } = useSelector( - (state) => state.searchedRoles - ); - const [searchState, setSearchState] = useState(null); const [matchingRole, setMatchingRole] = useState(null); - const [addAnotherModalOpen, setAddAnotherModalOpen] = useState(false); - const [submitDone, setSubmitDone] = useState(true); const dispatch = useDispatch(); const onSubmit = useCallback(() => { - storeStrings(addedRoles); navigate("result", { state: { matchingRole } }); - }, [addedRoles, navigate, matchingRole]); + }, [navigate, matchingRole]); const search = () => { setCurrentStage(1, stages, setStages); @@ -135,8 +110,9 @@ SearchContainer.propTypes = { searchObject: PT.object, toRender: PT.node, completenessStyle: PT.string, - reloadRolesPage: PT.func, navigate: PT.func, + addedRoles: PT.array, + previousSearchId: PT.string, }; export default SearchContainer; diff --git a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx index a8223a29..aa1bbf78 100644 --- a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx @@ -19,46 +19,19 @@ import ConfirmationModal from "../ConfirmationModal"; import withAuthentication from "../../../../hoc/withAuthentication"; import "./styles.module.scss"; import { setCurrentStage } from "utils/helpers"; -import { clearSearchedRoles, replaceSearchedRoles } from "../../actions"; +import { clearSearchedRoles } from "../../actions"; import { postTeamRequest } from "services/teams"; import SuccessCard from "../SuccessCard"; -const retrieveRoles = () => { - const searchIdString = sessionStorage.getItem("searchIds"); - const nameString = sessionStorage.getItem("roleNames"); - - if (!searchIdString || !nameString) return []; - const searchIds = searchIdString.split(","); - const names = nameString.split(","); - if (searchIds.length !== names.length) return []; - - const roles = []; - for (let i = 0; i < searchIds.length; i++) { - roles.push({ - searchId: searchIds[i], - name: names[i], - }); - } - - return roles; -}; - -const clearSessionKeys = () => { - sessionStorage.removeItem("searchIds"); - sessionStorage.removeItem("roleNames"); -}; - function SubmitContainer({ stages, setStages, completenessStyle, - reloadRolesPage, location, + addedRoles, }) { const matchingRole = location?.state?.matchingRole; - const { addedRoles } = useSelector((state) => state.searchedRoles); - const [addAnotherOpen, setAddAnotherOpen] = useState(true); const [teamDetailsOpen, setTeamDetailsOpen] = useState(false); const [teamObject, setTeamObject] = useState(null); @@ -66,14 +39,14 @@ function SubmitContainer({ const dispatch = useDispatch(); + // Set correct state for Completeness tab, and redirect + // to main page if path loaded without any selected roles. useEffect(() => { setCurrentStage(2, stages, setStages); - const storedRoles = retrieveRoles(); - if (storedRoles) { - if (!addedRoles || storedRoles.length > addedRoles.length) { - dispatch(replaceSearchedRoles(storedRoles)); - } + if (!addedRoles || addedRoles.length === 0) { + navigate("/taas/myteams/createnewteam"); } + // only needed on initial load, avoids too many re-renders // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -83,11 +56,7 @@ function SubmitContainer({ }; const addAnother = () => { - if (reloadRolesPage) { - setCurrentStage(0, stages, setStages); - reloadRolesPage(); - } - navigate("/taas/myteams/createnewteam/role"); + navigate("/taas/myteams/createnewteam"); }; const assembleTeam = (formData) => { @@ -121,7 +90,6 @@ function SubmitContainer({ postTeamRequest(teamObject) .then((res) => { const projectId = _.get(res, ["data", "projectId"]); - clearSessionKeys(); dispatch(clearSearchedRoles()); navigate(`/taas/myteams/${projectId}`); }) @@ -171,8 +139,8 @@ SubmitContainer.propTypes = { stages: PT.array, setStages: PT.func, completenessStyle: PT.string, - reloadRolesPage: PT.bool, location: PT.object, + addedRoles: PT.array, }; export default withAuthentication(SubmitContainer); diff --git a/src/routes/CreateNewTeam/components/SuccessCard/index.jsx b/src/routes/CreateNewTeam/components/SuccessCard/index.jsx index 1aa05fa8..ff2ee0da 100644 --- a/src/routes/CreateNewTeam/components/SuccessCard/index.jsx +++ b/src/routes/CreateNewTeam/components/SuccessCard/index.jsx @@ -30,7 +30,7 @@ function SuccessCard() { Please use the button to the right to submit your request, or the button below to search for additional roles.

- + diff --git a/src/routes/CreateNewTeam/index.jsx b/src/routes/CreateNewTeam/index.jsx index 4158b2f8..efd722e5 100644 --- a/src/routes/CreateNewTeam/index.jsx +++ b/src/routes/CreateNewTeam/index.jsx @@ -1,9 +1,6 @@ /** * 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 @@ -11,25 +8,14 @@ import React, { useEffect } from "react"; import { navigate } from "@reach/router"; import _ from "lodash"; -import PT from "prop-types"; -import { useDispatch } from "react-redux"; import Page from "components/Page"; import PageHeader from "components/PageHeader"; 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 { clearSearchedRoles } from "./actions"; - -function CreateNewTeam({ location: { state: locationState } }) { - const dispatch = useDispatch(); - - useEffect(() => { - if (!locationState || !locationState.keepAddedRoles) { - dispatch(clearSearchedRoles()); - } - }); +function CreateNewTeam() { const goToRoute = (path) => { navigate(path); }; @@ -65,8 +51,4 @@ function CreateNewTeam({ location: { state: locationState } }) { ); } -CreateNewTeam.propTypes = { - locationState: PT.object, -}; - export default CreateNewTeam; diff --git a/src/routes/CreateNewTeam/pages/InputSkills/components/SkillItem/index.jsx b/src/routes/CreateNewTeam/pages/InputSkills/components/SkillItem/index.jsx index ec94cea5..66a819e7 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/components/SkillItem/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputSkills/components/SkillItem/index.jsx @@ -5,7 +5,7 @@ */ import React from "react"; import PT from "prop-types"; -import IconQuestionCircle from "../../../../../../assets/images/icon-question-circle.svg"; +import IconSkill from "../../../../../../assets/images/icon-skill.svg"; import "./styles.module.scss"; import cn from "classnames"; @@ -28,7 +28,7 @@ function SkillItem({ id, name, onClick, isSelected }) { styleName="image" /> ) : ( - + )}

{name}

diff --git a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx index 5488b60b..7486b59e 100644 --- a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx +++ b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx @@ -33,12 +33,6 @@ function SelectRole() { setRoleDetailsModalOpen(true); }, []); - const resetState = () => { - setSelectedRoleId(null); - setRoleDetailsModalOpen(false); - setRoleDetailsModalId(null); - }; - if (!roles) { return ; } @@ -50,7 +44,6 @@ function SelectRole() { isCompletenessDisabled={!selectedRoleId} searchObject={{ roleId: selectedRoleId }} completenessStyle="role-selection" - reloadRolesPage={resetState} toRender={ <> { + const defaultState = { + previousSearchId: undefined, + addedRoles: [], + }; + try { + const state = localStorage.getItem("rolesState"); + if (state === null) { + return defaultState; + } + return JSON.parse(state); + } catch { + return defaultState; + } }; +const initialState = loadState(); + const reducer = (state = initialState, action) => { switch (action.type) { case ACTION_TYPE.CLEAR_SEARCHED_ROLES: From 0d8961e760a028cf4db3dd0fca46e6b4f4d1988d Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Wed, 16 Jun 2021 12:00:25 +0400 Subject: [PATCH 2/6] Remove unneccesary imports. Redirect user from results page if no added roles before first paint. --- .../components/SearchContainer/index.jsx | 2 +- .../components/SubmitContainer/index.jsx | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx index 7b85e495..8e91bc3b 100644 --- a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx @@ -8,7 +8,7 @@ import React, { useCallback, useState } from "react"; import PT from "prop-types"; import _ from "lodash"; -import { useDispatch, useSelector } from "react-redux"; +import { useDispatch } from "react-redux"; import AddedRolesAccordion from "../AddedRolesAccordion"; import Completeness from "../Completeness"; import SearchCard from "../SearchCard"; diff --git a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx index aa1bbf78..55c493bb 100644 --- a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx @@ -4,9 +4,14 @@ * Requires authentication to complete submission process * and contains a series of popups to lead user through the flow. */ -import React, { useCallback, useEffect, useState } from "react"; +import React, { + useCallback, + useEffect, + useLayoutEffect, + useState, +} from "react"; import PT from "prop-types"; -import { useDispatch, useSelector } from "react-redux"; +import { useDispatch } from "react-redux"; import _ from "lodash"; import { toastr } from "react-redux-toastr"; import { navigate } from "@reach/router"; @@ -39,14 +44,20 @@ function SubmitContainer({ const dispatch = useDispatch(); - // Set correct state for Completeness tab, and redirect - // to main page if path loaded without any selected roles. useEffect(() => { setCurrentStage(2, stages, setStages); if (!addedRoles || addedRoles.length === 0) { navigate("/taas/myteams/createnewteam"); } - // only needed on initial load, avoids too many re-renders + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // redirects user if they enter the page URL directly + // without adding any roles. + useLayoutEffect(() => { + if (!addedRoles || addedRoles.length === 0) { + navigate("/taas/myteams/createnewteam"); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); From 4b8ede8ffc15aef66114ea684801a73d4804085c Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Thu, 17 Jun 2021 14:39:53 +0400 Subject: [PATCH 3/6] Update styling of RoleDetailsModal. Client-side validation for InputJobDescription. Ensure custom/niche role does not appear in SelectRole page. Update ResultCard to read matching rate from search result. --- src/components/MarkdownEditor/index.jsx | 3 ++ .../MarkdownEditor/styles.module.scss | 31 ++++++++++--------- src/constants/index.js | 5 +++ .../components/CircularProgressBar/index.jsx | 2 +- .../components/ResultCard/index.jsx | 15 ++++++--- .../components/RoleDetailsModal/index.jsx | 2 +- .../RoleDetailsModal/styles.module.scss | 23 ++++++++++---- .../components/SearchContainer/index.jsx | 3 +- .../pages/InputJobDescription/index.jsx | 7 ++++- .../CreateNewTeam/pages/SelectRole/index.jsx | 7 ++++- 10 files changed, 67 insertions(+), 31 deletions(-) diff --git a/src/components/MarkdownEditor/index.jsx b/src/components/MarkdownEditor/index.jsx index 6c38b851..83939222 100644 --- a/src/components/MarkdownEditor/index.jsx +++ b/src/components/MarkdownEditor/index.jsx @@ -53,6 +53,9 @@ const MarkdownEditor = (props) => { ]} plugins={[]} /> + {props.errorMessage && ( +
{props.errorMessage}
+ )} ); }; diff --git a/src/components/MarkdownEditor/styles.module.scss b/src/components/MarkdownEditor/styles.module.scss index d444b248..13c0aa00 100644 --- a/src/components/MarkdownEditor/styles.module.scss +++ b/src/components/MarkdownEditor/styles.module.scss @@ -12,21 +12,6 @@ overflow-y: auto; background: #fafafb; } - .message { - @include font-roboto; - - width: 100%; - text-align: center; - min-height: 40px; - line-height: 20px; - padding: 9px 10px; - margin: 10px 0 5px; - font-size: 14px; - color: #ff5b52; - border: 1px solid #ffd5d1; - cursor: auto; - outline: none; - } } .editor-container { :global { @@ -72,3 +57,19 @@ } } } + +.message { + @include font-roboto; + + width: 100%; + text-align: center; + min-height: 40px; + line-height: 20px; + padding: 9px 10px; + margin: 10px 0 5px; + font-size: 14px; + color: #ff5b52; + border: 1px solid #ffd5d1; + cursor: auto; + outline: none; +} \ No newline at end of file diff --git a/src/constants/index.js b/src/constants/index.js index 5bc6228f..c0c822de 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -360,3 +360,8 @@ export const MAX_ALLOWED_INTERVIEWS = 3; * Matching rate to show in CreateNewTeam ResultCard */ export const MATCHING_RATE = "80"; + +/** + * Custom role names to remove from RoleList component + */ +export const CUSTOM_ROLE_NAMES = ["custom", "niche"]; diff --git a/src/routes/CreateNewTeam/components/CircularProgressBar/index.jsx b/src/routes/CreateNewTeam/components/CircularProgressBar/index.jsx index 7a19f03e..06ea736c 100644 --- a/src/routes/CreateNewTeam/components/CircularProgressBar/index.jsx +++ b/src/routes/CreateNewTeam/components/CircularProgressBar/index.jsx @@ -14,7 +14,7 @@ const CircularProgressBar = ({ size, progress, children, strokeWidth }) => { const radius = size / 2 - strokeWidth / 2; const circumference = 2 * Math.PI * radius; useEffect(() => { - const progressOffset = ((100 - progress) / 100) * circumference; + const progressOffset = (1 - progress) * circumference; setOffset(progressOffset); circleRef.current.style = "transition: stroke-dashoffset 850ms ease-in-out"; }, [setOffset, progress, circumference, offset]); diff --git a/src/routes/CreateNewTeam/components/ResultCard/index.jsx b/src/routes/CreateNewTeam/components/ResultCard/index.jsx index c6920c22..09569ac7 100644 --- a/src/routes/CreateNewTeam/components/ResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/ResultCard/index.jsx @@ -23,10 +23,15 @@ function formatRate(value) { return formatMoney(value); } +function formatPercent(value) { + return `${Math.round(value * 100)}%`; +} + function ResultCard({ role }) { const { numberOfMembersAvailable, isExternalMember, + matchingRate, rates: [rates], } = role; const [userHandle, setUserHandle] = useState(null); @@ -34,7 +39,7 @@ function ResultCard({ role }) { useEffect(() => { getAuthUserProfile().then((res) => { - setUserHandle(res.handle || null); + setUserHandle(res?.handle || null); }); }, []); @@ -44,8 +49,8 @@ function ResultCard({ role }) {

We have matching profiles

- We have qualified candidates who match {MATCHING_RATE}% or more of - your job requirements. + We have qualified candidates who match {formatPercent(matchingRate)} + {matchingRate < 1 ? " or more " : " "} of your job requirements.

@@ -217,11 +222,11 @@ function ResultCard({ role }) {
-

{MATCHING_RATE}%

+

{formatPercent(matchingRate)}

Matching rate

} diff --git a/src/routes/CreateNewTeam/components/RoleDetailsModal/index.jsx b/src/routes/CreateNewTeam/components/RoleDetailsModal/index.jsx index a10f2de7..206e4ee5 100644 --- a/src/routes/CreateNewTeam/components/RoleDetailsModal/index.jsx +++ b/src/routes/CreateNewTeam/components/RoleDetailsModal/index.jsx @@ -48,7 +48,7 @@ function RoleDetailsModal({ roleId, open, onClose }) { [role, imgError] ); - const skills = role ? role.listOfSkills : []; + const skills = role && role.listOfSkills ? role.listOfSkills : []; const hideSkills = () => { onClose(); diff --git a/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss b/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss index e7470ce5..d8ad2bf7 100644 --- a/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss +++ b/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss @@ -14,12 +14,23 @@ } .markdown-container { - // not adds specificity to override style - p:not(table) { - @include font-roboto; - color: #2a2a2a; - font-size: 16px; - line-height: 26px; + :global { + // resets styles in markdown-viewer + .tui-editor-contents { + @include font-roboto; + color: #2a2a2a; + font-size: 16px; + line-height: 26px; + ul { + list-style: initial; + >li { + margin-bottom: 10px; + &::before { + background: none; + } + } + } + } } } diff --git a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx index 8e91bc3b..8001cda5 100644 --- a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx @@ -17,6 +17,7 @@ import NoMatchingProfilesResultCard from "../NoMatchingProfilesResultCard"; import { searchRoles } from "services/teams"; import { setCurrentStage } from "utils/helpers"; import { addRoleSearchId, addSearchedRole } from "../../actions"; +import { CUSTOM_ROLE_NAMES } from "constants"; import "./styles.module.scss"; function SearchContainer({ @@ -51,7 +52,7 @@ function SearchContainer({ .then((res) => { const name = _.get(res, "data.name"); const searchId = _.get(res, "data.roleSearchRequestId"); - if (name && !name.toLowerCase().includes("niche")) { + if (name && !CUSTOM_ROLE_NAMES.includes(name.toLowerCase())) { setMatchingRole(res.data); dispatch(addSearchedRole({ searchId, name })); } else if (searchId) { diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx index 776267b3..9bc211e1 100644 --- a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx @@ -26,7 +26,7 @@ function InputJobDescription() { 255} completenessStyle="input-job-description" searchObject={{ jobDescription: jdString }} toRender={ @@ -40,6 +40,11 @@ function InputJobDescription() { height="482px" placeholder="input job description" onChange={onEditChange} + errorMessage={ + jdString.length > 255 + ? "Maximum of 255 characters. Please reduce job description length." + : "" + } /> diff --git a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx index 7486b59e..79522873 100644 --- a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx +++ b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx @@ -8,10 +8,15 @@ import React, { useCallback, useState } from "react"; import { useData } from "hooks/useData"; import RolesList from "./components/RolesList"; import { getRoles } from "services/roles"; +import { CUSTOM_ROLE_NAMES } from "constants"; import LoadingIndicator from "components/LoadingIndicator"; import RoleDetailsModal from "../../components/RoleDetailsModal"; import SearchAndSubmit from "../../components/SearchAndSubmit"; +// Remove custom roles from role list +const removeCustomRoles = (roles) => + roles.filter(({ name }) => !CUSTOM_ROLE_NAMES.includes(name.toLowerCase())); + function SelectRole() { const [stages, setStages] = useState([ { name: "Select a Role", isCurrent: true }, @@ -47,7 +52,7 @@ function SelectRole() { toRender={ <> Date: Thu, 17 Jun 2021 18:41:32 +0400 Subject: [PATCH 4/6] Updates to behaviour of team description popup: Set min=1 on number inputs. Set maxlengths as per https://github.com/topcoder-platform/taas-app/issues/313. Add cross-browser month input component. Change durationWeek and numberOfResources to number for request. Remove description value from form data when toggled off of form. --- package-lock.json | 43 ++++++++++++----- package.json | 2 +- src/components/FormField/index.jsx | 1 + src/components/TextArea/index.jsx | 1 + .../components/SubmitContainer/index.jsx | 7 +-- .../components/TeamDetailsModal/index.jsx | 48 +++++++++++++++---- src/styles/main.vendor.scss | 1 + 7 files changed, 75 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index c5832bbb..da18caae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1249,6 +1249,25 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, + "@hypnosphi/create-react-context": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", + "integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==", + "requires": { + "gud": "^1.0.0", + "warning": "^4.0.3" + }, + "dependencies": { + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -14434,24 +14453,24 @@ } }, "react-datepicker": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.4.1.tgz", - "integrity": "sha512-ASyVb7UmVx1vzeITidD7Cr/EXRXhKyjjbSkBndPc1MipYq4rqQ3eMFgvRQzpsXc3JmIMFgICm7nmN6Otc1GE/Q==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.8.0.tgz", + "integrity": "sha512-iFVNEp8DJoX5yEvEiciM7sJKmLGrvE70U38KhpG13XrulNSijeHw1RZkhd/0UmuXR71dcZB/kdfjiidifstZjw==", "requires": { "classnames": "^2.2.6", "date-fns": "^2.0.1", "prop-types": "^15.7.2", - "react-onclickoutside": "^6.9.0", - "react-popper": "^1.3.4" + "react-onclickoutside": "^6.10.0", + "react-popper": "^1.3.8" }, "dependencies": { "react-popper": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz", - "integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==", + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.11.tgz", + "integrity": "sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg==", "requires": { "@babel/runtime": "^7.1.2", - "create-react-context": "^0.3.0", + "@hypnosphi/create-react-context": "^0.3.1", "deep-equal": "^1.1.1", "popper.js": "^1.14.4", "prop-types": "^15.6.1", @@ -14548,9 +14567,9 @@ } }, "react-onclickoutside": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.10.0.tgz", - "integrity": "sha512-7i2L3ef+0ILXpL6P+Hg304eCQswh4jl3ynwR71BSlMU49PE2uk31k8B2GkP6yE9s2D4jTGKnzuSpzWxu4YxfQQ==" + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.11.2.tgz", + "integrity": "sha512-640486eSwU/t5iD6yeTlefma8dI3bxPXD93hM9JGKyYITAd0P1JFkkcDeyHZRqNpY/fv1YW0Fad9BXr44OY8wQ==" }, "react-outside-click-handler": { "version": "1.3.0", diff --git a/package.json b/package.json index 4215d91d..61cd6967 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "prop-types": "^15.7.2", "react": "^16.12.0", "react-avatar": "^3.9.7", - "react-datepicker": "^3.4.1", + "react-datepicker": "^3.8.0", "react-dom": "^16.12.0", "react-final-form": "^6.5.2", "react-final-form-arrays": "^3.1.3", diff --git a/src/components/FormField/index.jsx b/src/components/FormField/index.jsx index f43d48fc..c7de9520 100644 --- a/src/components/FormField/index.jsx +++ b/src/components/FormField/index.jsx @@ -82,6 +82,7 @@ const FormField = ({ field }) => { onBlur={input.onBlur} onFocus={input.onFocus} className={meta.error && meta.touched ? "error" : ""} + maxLength={field.maxLength} /> )} {field.type === FORM_FIELD_TYPE.DATE && ( diff --git a/src/components/TextArea/index.jsx b/src/components/TextArea/index.jsx index 8ec9e7e5..fc247056 100644 --- a/src/components/TextArea/index.jsx +++ b/src/components/TextArea/index.jsx @@ -18,6 +18,7 @@ function TextArea(props) { autoFocus={props.autoFocus} onBlur={props.onBlur} onFocus={props.onFocus} + maxLength={props.maxLength} /> ); } diff --git a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx index 55c493bb..384ce6bc 100644 --- a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx @@ -78,11 +78,8 @@ function SubmitContainer({ if (key === "teamName" || key === "teamDescription") { continue; } - const position = _.pick( - formData[key], - "numberOfResources", - "durationWeeks", - "startMonth" + const position = _.mapValues(formData[key], (val, key) => + key === "startMonth" ? val : parseInt(val, 10) ); position.roleSearchRequestId = key; diff --git a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx index 716d375d..bb9c10f1 100644 --- a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx +++ b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx @@ -12,12 +12,13 @@ import { FORM_FIELD_TYPE } from "constants/"; import { formatPlural } from "utils/format"; import Button from "components/Button"; import "./styles.module.scss"; +import DatePicker from "react-datepicker"; const Error = ({ name }) => { const { - meta: { touched, error }, - } = useField(name, { subscription: { touched: true, error: true } }); - return touched && error ? {error} : null; + meta: { dirty, error }, + } = useField(name, { subscription: { dirty: true, error: true } }); + return dirty && error ? {error} : null; }; function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { @@ -95,10 +96,21 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { return (
{ + changeValue(state, fieldName, () => undefined); + }, + }} initialValues={{ teamName: "My Great Team" }} validate={validator} > - {({ handleSubmit, hasValidationErrors }) => { + {({ + handleSubmit, + hasValidationErrors, + form: { + mutators: { clearField }, + }, + }) => { return ( @@ -134,13 +146,16 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { name: "teamDescription", label: "Short description about the team/ project", placeholder: "Short description about the team/ project", - maxLength: 1000, + maxLength: 600, }} /> )} +
+ + +
)} diff --git a/src/routes/CreateNewTeam/components/TeamDetailsModal/styles.module.scss b/src/routes/CreateNewTeam/components/TeamDetailsModal/styles.module.scss index eb6080c6..3a43e250 100644 --- a/src/routes/CreateNewTeam/components/TeamDetailsModal/styles.module.scss +++ b/src/routes/CreateNewTeam/components/TeamDetailsModal/styles.module.scss @@ -42,6 +42,10 @@ color: #2a2a2a; border-bottom: 1px solid #e9e9e9; + &.start-month { + padding-right: 0; + } + input { @include font-roboto; font-size: 14px; @@ -56,6 +60,13 @@ } } +.flex-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; +} + .error { font-size: 14px; font-weight: 400; From aeb577927469be90dd54ed03ce63e7bb498cda3d Mon Sep 17 00:00:00 2001 From: Michael Baghel Date: Fri, 18 Jun 2021 18:19:55 +0400 Subject: [PATCH 6/6] New styled Month Picker component. NoMatchingProfiles card uses custom role returned from server. Removed matching rate constant. Cleanup and documentation. --- src/assets/images/icon-thick-calendar.svg | 23 +++++++ src/components/InformationTooltip/index.jsx | 8 +-- src/components/MonthPicker/index.jsx | 44 ++++++++++++++ src/components/MonthPicker/styles.module.scss | 60 +++++++++++++++++++ src/constants/index.js | 5 -- .../NoMatchingProfilesResultCard/index.jsx | 20 +++++-- .../components/SearchContainer/index.jsx | 11 ++-- .../components/SubmitContainer/index.jsx | 4 ++ .../components/SuccessCard/index.jsx | 6 +- .../components/TeamDetailsModal/index.jsx | 11 ++-- .../TeamDetailsModal/styles.module.scss | 11 +++- .../CreateNewTeam/pages/SelectRole/index.jsx | 4 +- src/utils/helpers.js | 9 +++ 13 files changed, 178 insertions(+), 38 deletions(-) create mode 100644 src/assets/images/icon-thick-calendar.svg create mode 100644 src/components/MonthPicker/index.jsx create mode 100644 src/components/MonthPicker/styles.module.scss diff --git a/src/assets/images/icon-thick-calendar.svg b/src/assets/images/icon-thick-calendar.svg new file mode 100644 index 00000000..222202ca --- /dev/null +++ b/src/assets/images/icon-thick-calendar.svg @@ -0,0 +1,23 @@ + + + 89C4F8D6-2320-499E-BC02-40DE84B9F35C + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/InformationTooltip/index.jsx b/src/components/InformationTooltip/index.jsx index 30c3e0f6..996168a2 100644 --- a/src/components/InformationTooltip/index.jsx +++ b/src/components/InformationTooltip/index.jsx @@ -26,24 +26,24 @@ function InformationTooltip({ text, iconSize = "16px" }) { name: "offset", options: { // use offset to move the tooltip slightly up - offset: [0, 10], + offset: [0, 12], }, }, { name: "arrow", // padding should be equal to border-radius of the tooltip - options: { element: arrowElement, padding: 8 }, + options: { element: arrowElement, padding: 5 }, }, ], }); const showTooltip = useCallback(() => { setIsTooltipShown(true); - }, [setIsTooltipShown]); + }, []); const hideTooltip = useCallback(() => { setIsTooltipShown(false); - }, [setIsTooltipShown]); + }, []); const iconStyle = { width: iconSize, diff --git a/src/components/MonthPicker/index.jsx b/src/components/MonthPicker/index.jsx new file mode 100644 index 00000000..0da605f8 --- /dev/null +++ b/src/components/MonthPicker/index.jsx @@ -0,0 +1,44 @@ +/** + * Month Picker + * An styled input component for selecting date by month. + * Compatible with react-final-form + */ +import React from "react"; +import PT from "prop-types"; +import DatePicker from "react-datepicker"; +import "./styles.module.scss"; + +function getCurrMonthYear() { + const now = new Date(); + const year = now.getFullYear(); + const month = now.getMonth(); + return new Date(`${year}-${month + 1}`); +} + +function MonthPicker({ name, value, onChange, onBlur }) { + return ( +
+ +
+ ); +} + +MonthPicker.propTypes = { + name: PT.string, + value: PT.any, + onChange: PT.func, + onBlur: PT.func, +}; + +export default MonthPicker; diff --git a/src/components/MonthPicker/styles.module.scss b/src/components/MonthPicker/styles.module.scss new file mode 100644 index 00000000..ba93e757 --- /dev/null +++ b/src/components/MonthPicker/styles.module.scss @@ -0,0 +1,60 @@ +@import "styles/include"; + +.month-picker { + input { + width: 118px; + background-image: url("../../assets/images/icon-thick-calendar.svg"); + background-repeat: no-repeat; + background-position: 95px 7px; + } + + :global { + .react-datepicker__header { + @include font-barlow; + font-size: 20px; + color: #2a2a2a; + font-weight: 600; + padding-top: 14px; + padding-bottom: 14px; + background-color: transparent; + border-bottom: 1px solid #d4d4d4; + margin: 0 16px; + } + + .react-datepicker__month-text { + @include font-roboto; + font-size: 14px; + font-weight: 400; + line-height: 40px; + width: 40px; + height: 40px; + margin: 8px; + border-radius: 100%; + } + .react-datepicker__month--selected { + border-radius: 100%; + background-color: #0AB88A; + } + + .react-datepicker__navigation { + top: 18px; + border: 2px solid transparent; + height: 12px; + width: 12px; + } + .react-datepicker__navigation--next, .react-datepicker__navigation--previous { + border-right-color: transparent; + border-bottom-color: transparent; + border-left-color: #137D60; + border-top-color: #137D60; + } + .react-datepicker__navigation--next { + right: 24px; + transform: rotate(135deg); + } + .react-datepicker__navigation--previous { + left: 24px; + transform: rotate(-45deg); + } + } +} \ No newline at end of file diff --git a/src/constants/index.js b/src/constants/index.js index c0c822de..45507f02 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -356,11 +356,6 @@ export const INTERVIEW_POPUP_MEDIA_URL = export const MAX_ALLOWED_INTERVIEWS = 3; -/** - * Matching rate to show in CreateNewTeam ResultCard - */ -export const MATCHING_RATE = "80"; - /** * Custom role names to remove from RoleList component */ diff --git a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx index 2b1fd779..e612d1e1 100644 --- a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx @@ -4,12 +4,14 @@ */ import React from "react"; import { Link } from "@reach/router"; +import PT from "prop-types"; 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"; +import { formatMoney } from "utils/format"; -function NoMatchingProfilesResultCard() { +function NoMatchingProfilesResultCard({ role }) { return (
@@ -23,11 +25,13 @@ function NoMatchingProfilesResultCard() { We will be looking internally for members matching your requirements and be back at them in about 2 weeks.

-
-

Niche Rate

-

$1,200

-

/Week

-
+ {role && ( +
+

{role.name} Rate

+

{formatMoney(role.rates[0].global)}

+

/Week

+
+ )}