From 39c1cb8c1f4089799bbca2efc52cc635b70bb1cf Mon Sep 17 00:00:00 2001
From: yoution
Date: Fri, 11 Jun 2021 02:47:13 +0800
Subject: [PATCH 1/2] Taas Platform - Role & Skills Intake - Search part of
Front-end
---
config/dev.js | 3 +
config/prod.js | 3 +
src/root.component.jsx | 4 +-
.../components/AddedRolesAccordion/index.jsx | 0
.../AddedRolesAccordion/styles.module.scss | 0
.../Completeness/styles.module.scss | 1 +
.../NoMatchingProfilesResultCard/index.jsx | 15 ++-
.../styles.module.scss | 1 +
.../components/ResultCard/index.jsx | 89 ++++++++-------
.../components/ResultCard/styles.module.scss | 10 +-
.../components/RoleDetailsModal/index.jsx | 22 ++--
.../RoleDetailsModal/styles.module.scss | 44 +++++++-
.../components/SearchableList/index.jsx | 88 +++++++++++++++
.../SearchableList/styles.module.scss | 73 +++++++++++++
src/routes/CreateNewTeam/index.jsx | 26 ++---
.../components/SkillListPopup/index.jsx | 80 --------------
.../SkillListPopup/styles.module.scss | 48 ---------
.../pages/InputJobDescription/index.jsx | 102 +++++++++++-------
.../InputJobDescription/styles.module.scss | 7 ++
.../CreateNewTeam/pages/InputSkills/index.jsx | 93 ++++++++++++----
.../pages/InputSkills/styles.module.scss | 16 +++
.../components/RoleItem/styles.module.scss | 1 +
.../CreateNewTeam/pages/SelectRole/index.jsx | 84 ++++++++++-----
src/services/teams.js | 26 ++---
24 files changed, 527 insertions(+), 309 deletions(-)
rename src/routes/CreateNewTeam/{pages/SelectRole => }/components/AddedRolesAccordion/index.jsx (100%)
rename src/routes/CreateNewTeam/{pages/SelectRole => }/components/AddedRolesAccordion/styles.module.scss (100%)
create mode 100644 src/routes/CreateNewTeam/components/SearchableList/index.jsx
create mode 100644 src/routes/CreateNewTeam/components/SearchableList/styles.module.scss
delete mode 100644 src/routes/CreateNewTeam/pages/InputJobDescription/components/SkillListPopup/index.jsx
delete mode 100644 src/routes/CreateNewTeam/pages/InputJobDescription/components/SkillListPopup/styles.module.scss
diff --git a/config/dev.js b/config/dev.js
index dfe168fd..2daf4829 100644
--- a/config/dev.js
+++ b/config/dev.js
@@ -13,4 +13,7 @@ module.exports = {
V5: "https://api.topcoder-dev.com/v5", //"http://localhost:3030/api/v5"
V3: "https://api.topcoder-dev.com/v3",
},
+
+ // matching rate for search role result page
+ MATCHING_RATE: process.env.MATCHING_RATE || 80,
};
diff --git a/config/prod.js b/config/prod.js
index 21cdc373..5416e540 100644
--- a/config/prod.js
+++ b/config/prod.js
@@ -13,4 +13,7 @@ module.exports = {
V5: "https://api.topcoder.com/v5",
V3: "https://api.topcoder.com/v3",
},
+
+ // matching rate for search role result page
+ MATCHING_RATE: process.env.MATCHING_RATE || 80,
};
diff --git a/src/root.component.jsx b/src/root.component.jsx
index 797270ff..97b8bdcb 100644
--- a/src/root.component.jsx
+++ b/src/root.component.jsx
@@ -35,8 +35,8 @@ export default function Root() {
-
-
+
+
{/* Global config for Toastr popups */}
diff --git a/src/routes/CreateNewTeam/pages/SelectRole/components/AddedRolesAccordion/index.jsx b/src/routes/CreateNewTeam/components/AddedRolesAccordion/index.jsx
similarity index 100%
rename from src/routes/CreateNewTeam/pages/SelectRole/components/AddedRolesAccordion/index.jsx
rename to src/routes/CreateNewTeam/components/AddedRolesAccordion/index.jsx
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 3227642c..8e488b15 100644
--- a/src/routes/CreateNewTeam/components/Completeness/styles.module.scss
+++ b/src/routes/CreateNewTeam/components/Completeness/styles.module.scss
@@ -6,6 +6,7 @@
position: relative;
width: 250px;
color: #fff;
+ overflow: hidden;
button {
border: none;
}
diff --git a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx
index 0bde6659..2938cccf 100644
--- a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx
+++ b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx
@@ -3,13 +3,17 @@
* Card that appears when there are no matching profiles after searching.
*/
import React from "react";
+import PT from "prop-types";
import { navigate } from "@reach/router";
+import {
+ formatMoney,
+} from "utils/format";
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({rates = [{global: 0}], onSearch}) {
return (
@@ -25,11 +29,11 @@ function NoMatchingProfilesResultCard() {
Niche Rate
-
$1,200
+
{formatMoney(rates[0].global)}
/Week
)}
- {showRates && !showSpecialRates && (
+ {showRates && isExternalMember && (
@@ -159,7 +166,7 @@ function ResultCard() {
(40h / week)
-
$1,800
+
{formatMoney(rates[0].global)}
/Week
@@ -169,7 +176,7 @@ function ResultCard() {
(30h / week)
-
$1,250
+
{formatMoney(rates[0].inCountry)}
/Week
@@ -179,7 +186,7 @@ function ResultCard() {
(20h / week)
-
$800
+
{formatMoney(rates[0].offShore)}
/Week
@@ -190,14 +197,14 @@ function ResultCard() {
Qualified candidates within
-
24h
+
{timeToCandidate}h
Interviews can start within
-
48h
+
{timeToInterview}h
@@ -209,11 +216,11 @@ function ResultCard() {
- 80%
+ {config.MATCHING_RATE}%
Matching rate
}
@@ -222,7 +229,7 @@ function ResultCard() {
-
300+
+
{numberOfMembersAvailable}+
Members matched
@@ -246,4 +253,12 @@ function ResultCard() {
);
}
+ResultCard.propTypes = {
+ numberOfMembersAvailable:PT.number,
+ numberOfMembers: PT.string,
+ timeToCandidate: PT.number,
+ timeToInterview: PT.number,
+ isExternalMember: PT.bool,
+ rates: PT.array,
+};
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..cba4f0a9 100644
--- a/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss
+++ b/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss
@@ -3,6 +3,7 @@
.result-card {
@include rounded-card;
max-width: 746px;
+ overflow: hidden;
width: 50vw;
margin-right: 30px;
}
@@ -23,7 +24,6 @@
position: relative;
text-align: center;
border-radius: 8px 8px 0 0;
- cursor: pointer;
svg {
margin-bottom: 8px;
@@ -72,9 +72,12 @@
margin: 0 40px;
}
.progressbar-child {
- margin-top: 5px;
width: 90px;
height: 90px;
+ overflow: hidden;
+ > h4 {
+ margin-top: 10px;
+ }
}
}
@@ -154,6 +157,9 @@
&:not(:first-child) {
margin-left: 40px;
}
+ &:not(:last-child) {
+ border-right: 1px solid #E9E9E9;
+ }
.rate-heading {
display: flex;
diff --git a/src/routes/CreateNewTeam/components/RoleDetailsModal/index.jsx b/src/routes/CreateNewTeam/components/RoleDetailsModal/index.jsx
index 17039e66..179619c3 100644
--- a/src/routes/CreateNewTeam/components/RoleDetailsModal/index.jsx
+++ b/src/routes/CreateNewTeam/components/RoleDetailsModal/index.jsx
@@ -6,17 +6,18 @@ import React, { useState, useEffect } from "react";
import PT from "prop-types";
import Modal from "react-responsive-modal";
import Button from "components/Button";
+import MarkdownEditorViewer from "../../../../components/MarkdownEditorViewer";
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",
+ borderRadius: "10px",
+ padding: "70px 80px 60px",
+ maxWidth: "680px",
width: "100%",
+ height: "650px",
margin: 0,
"overflow-x": "hidden",
};
@@ -44,9 +45,7 @@ function RoleDetailsModal({ roleId, open, onClose }) {
open={open}
center
onClose={onClose}
- closeIcon={
-
- }
+ showCloseIcon={false}
styles={{
modal: modalStyle,
modalContainer: containerStyle,
@@ -56,7 +55,7 @@ function RoleDetailsModal({ roleId, open, onClose }) {
{isLoading ? (
<>
- Loading...
+ Loading...
>
) : (
<>
@@ -70,6 +69,7 @@ function RoleDetailsModal({ roleId, open, onClose }) {
) : (
)}
+ {role?.name}
- {role?.name}
- {role?.description}
+ {!showSkills &&
}
+ {showSkills && {_.map(role?.listOfSkills, (s)=>
+ {s}
+ )}
}
>
)}
diff --git a/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss b/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss
index 81daa623..3f964c9a 100644
--- a/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss
+++ b/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss
@@ -1,5 +1,39 @@
@import "styles/include";
+
+.loading {
+ margin-bottom: 100px;
+ margin-top: 200px;
+}
+
+.skill-tag-list {
+ display: flex;
+ height: 260px;
+ flex-wrap: wrap;
+}
+
+.desc-container {
+ width: 100%;
+ height: 260px;
+ overflow: scroll;
+}
+
+.skill-tag {
+ @include font-roboto;
+
+ background-color: #E9E9E9;
+ border-radius: 5px;
+ padding: 6px 10px;
+ margin-right: 6px;
+ margin-bottom: 10px;
+ color: #2A2A2A;
+ font-size: 12px;
+ letter-spacing: 0.25px;
+ line-height: 16px;
+ height: 26px;
+ display: inline-block;
+}
+
.button-group {
display: flex;
flex-direction: row;
@@ -15,7 +49,8 @@
flex-direction: row;
align-items: center;
justify-content: center;
- margin-bottom: 42px;
+ margin-top: 14px;
+ margin-bottom: 30px;
}
.modal-body {
@@ -23,18 +58,19 @@
flex-direction: column;
justify-content: flex-start;
align-items: center;
- text-align: center;
- margin-bottom: 80px;
+ // text-align: center;
+ margin-bottom: 40px;
.role-icon {
width: 42px;
height: 42px;
+ margin-bottom: 18px;
}
h5 {
@include font-barlow-condensed;
font-size: 34px;
- color: #1e94a3;
+ color: #000;
text-transform: uppercase;
font-weight: 500;
margin-bottom: 10px;
diff --git a/src/routes/CreateNewTeam/components/SearchableList/index.jsx b/src/routes/CreateNewTeam/components/SearchableList/index.jsx
new file mode 100644
index 00000000..88ab6624
--- /dev/null
+++ b/src/routes/CreateNewTeam/components/SearchableList/index.jsx
@@ -0,0 +1,88 @@
+/**
+ * SearchableList
+ * wrap roles list and skills list
+ * Allows selecting and filtering by name.
+ */
+import React, { useEffect, useState } from "react";
+import { useDebounce } from "react-use";
+import PT from "prop-types";
+import Input from "components/Input";
+import PageHeader from "components/PageHeader";
+import "./styles.module.scss";
+import { INPUT_DEBOUNCE_DELAY } from "constants/";
+
+function SearchableList({title, inputPlaceholder, itemList, selectedRoleId, onDescriptionClick, toggleRole, renderItem, countElement}) {
+ const [filteredItems, setFilteredItems] = useState(itemList);
+ const [filter, setFilter] = useState("");
+ const [debouncedFilter, setDebouncedFilter] = useState("");
+
+ const onFilterChange = (e) => {
+ setFilter(e.target.value);
+ };
+
+ useDebounce(
+ () => {
+ setDebouncedFilter(filter);
+ },
+ INPUT_DEBOUNCE_DELAY,
+ [filter]
+ );
+
+ useEffect(() => {
+ if (debouncedFilter.length > 0) {
+ const filterText = debouncedFilter.toLowerCase();
+ setFilteredItems(
+ itemList.filter((item) => item.name.toLowerCase().includes(filterText))
+ );
+ } else {
+ setFilteredItems(itemList);
+ }
+ }, [debouncedFilter, itemList]);
+
+ return (
+
+ }
+ />
+ {countElement}
+
+ {filteredItems.map((item) => (
+ renderItem(item)
+ ))}
+
+
+ );
+}
+
+SearchableList.propTypes = {
+ itemList: PT.array,
+ title: PT.string,
+ inputPlaceholder: PT.string,
+ renderItem: PT.func,
+ countElement: PT.element,
+ toggleRole: PT.func,
+};
+
+export default SearchableList;
diff --git a/src/routes/CreateNewTeam/components/SearchableList/styles.module.scss b/src/routes/CreateNewTeam/components/SearchableList/styles.module.scss
new file mode 100644
index 00000000..e17803f5
--- /dev/null
+++ b/src/routes/CreateNewTeam/components/SearchableList/styles.module.scss
@@ -0,0 +1,73 @@
+@import "styles/include";
+
+
+.input-container {
+ position: relative;
+}
+
+.searchable-list {
+ @include rounded-card;
+ max-width: 746px;
+ margin-right: 20px;
+ position: relative;
+ flex: 1;
+
+ > header {
+ padding: 16px 24px;
+ }
+}
+
+.role-count {
+ position: absolute;
+ font-size: 12px;
+ top: 72px;
+ left: 73px;
+}
+
+// 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;
+ border-radius: 6px;
+ box-sizing: border-box;
+ color: #2a2a2a;
+ font-size: 14px;
+ height: 40px;
+ line-height: 38px;
+ 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;
+ }
+}
+
+.clear-input-button {
+ position: absolute;
+ right: 15px;
+ font-size: 14px;
+ font-weight: 700;
+ line-height: 38px;
+ cursor: pointer;
+ &:hover {
+ color: rgb(216, 24, 24);
+ }
+}
+
+.searchable-container {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ flex-wrap: wrap;
+ margin-right: 24px;
+}
diff --git a/src/routes/CreateNewTeam/index.jsx b/src/routes/CreateNewTeam/index.jsx
index 62fd1826..763791cf 100644
--- a/src/routes/CreateNewTeam/index.jsx
+++ b/src/routes/CreateNewTeam/index.jsx
@@ -14,24 +14,10 @@ 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) => {
- 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 goToJobDescription = () => {
- navigate(`/taas/myteams/createnewteam/jd`);
+ const onSearchClick = (page) => {
+ navigate(`/taas/myteams/createnewteam/${page}`);
};
return (
@@ -45,24 +31,24 @@ 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={() => onSearchClick("role")}
/>
}
backgroundImage="linear-gradient(221.5deg, #2C95D7 0%, #9D41C9 100%)"
- onClick={() => createProjectAndNavigate("skills")}
+ onClick={() => onSearchClick("skills")}
/>
}
backgroundImage="linear-gradient(135deg, #2984BD 0%, #0AB88A 100%)"
- onClick={goToJobDescription}
+ onClick={() => onSearchClick("jd")}
/>
);
}
-export default withAuthentication(CreateNewTeam);
+export default CreateNewTeam;
diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/components/SkillListPopup/index.jsx b/src/routes/CreateNewTeam/pages/InputJobDescription/components/SkillListPopup/index.jsx
deleted file mode 100644
index 60317253..00000000
--- a/src/routes/CreateNewTeam/pages/InputJobDescription/components/SkillListPopup/index.jsx
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * Temporary Popup for skill list
- * show skill list
- */
-import React from "react";
-import _ from "lodash";
-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 IconSingleManAdd from "../../../../../../assets/images/icon-single-man-add.svg";
-import "./styles.module.scss";
-import CenteredSpinner from "components/CenteredSpinner";
-
-const modalStyle = {
- borderRadius: "8px",
- padding: "32px 32px 22px 32px",
- maxWidth: "460px",
- width: "100%",
- margin: 0,
- "overflow-x": "hidden",
-};
-
-const containerStyle = {
- padding: "10px",
-};
-
-function SkillListPopup({ open, skills, onClose, isLoading, onContinueClick }) {
- return (
-
- }
- styles={{
- modal: modalStyle,
- modalContainer: containerStyle,
- }}
- >
-
- {isLoading ? (
- <>
-
-
loading skills
- >
- ) : (
- <>
-
-
skills
- {_.map(skills, (s) => {
- return
{s.tag}
;
- })}
- >
- )}
-
-
-
-
-
- );
-}
-
-SkillListPopup.propTypes = {
- open: PT.bool,
- onClose: PT.func,
- isLoading: PT.bool,
- onContinueClick: PT.func,
- skills: PT.arrayOf(PT.shape()),
-};
-
-export default SkillListPopup;
diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/components/SkillListPopup/styles.module.scss b/src/routes/CreateNewTeam/pages/InputJobDescription/components/SkillListPopup/styles.module.scss
deleted file mode 100644
index b0270326..00000000
--- a/src/routes/CreateNewTeam/pages/InputJobDescription/components/SkillListPopup/styles.module.scss
+++ /dev/null
@@ -1,48 +0,0 @@
-@import "styles/include";
-
-.button-group {
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: flex-end;
- :first-child {
- margin-right: 8px;
- }
-}
-
-.modal-body {
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- align-items: center;
- text-align: center;
- margin-bottom: 80px;
-
- svg {
- width: 48px;
- height: 48px;
- margin-bottom: 16px;
- }
-
- 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;
- }
-}
-
-.cross {
- g {
- stroke: #000;
- }
-}
diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx
index f6bbc2de..8020ce43 100644
--- a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx
+++ b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx
@@ -15,12 +15,13 @@ import MarkdownEditor from "../../../../components/MarkdownEditor";
import { getSkillsByJobDescription } from "../../../../services/teams";
import Completeness from "../../components/Completeness";
import { getSkills } from "services/skills";
+import { sendRoleSearchRequest } from "services/teams";
import SearchCard from "../../components/SearchCard";
import ResultCard from "../../components/ResultCard";
+import NoMatchingProfilesResultCard from "../../components/NoMatchingProfilesResultCard";
import AddAnotherModal from "../../components/AddAnotherModal";
-import SkillListPopup from "./components/SkillListPopup";
import "./styles.module.scss";
-import withAuthentication from "../../../../hoc/withAuthentication";
+import AddedRolesAccordion from "../../components/AddedRolesAccordion";
import IconOfficeFileText from "../../../../assets/images/icon-office-file-text.svg";
function InputJobDescription() {
@@ -29,39 +30,18 @@ function InputJobDescription() {
{ name: "Search Member" },
{ name: "Overview of the Results" },
]);
+ const [roleSearchResult, setRoleSearchResult] = useState(null);
+ const [addedRoles, setAddedRoles] = useState([]);
+ const [
+ previousRoleSearchRequestId,
+ setPreviousRoleSearchRequestId,
+ ] = useState(null);
const [jdString, setJdString] = useState("");
+ const [matchingProfiles, setMatchingProfiles] = useState(null);
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 onSearch = useCallback(
- (value) => {
- setSkillModalOpen(true);
- setIsLoadingSkills(true);
- getSkillsByJobDescription(jdString)
- .then((response) => {
- setSkills(response.data);
- setIsLoadingSkills(false);
- setSkillModalOpen(true);
- })
- .catch(() => {
- setIsLoadingSkills(false);
- });
- },
- [jdString]
- );
-
- const onConfirationClick = useCallback(() => {
- setSearchState("searching");
- setCurrentStage(1, stages, setStages);
- setTimeout(() => {
- setCurrentStage(2, stages, setStages);
- setSearchState("done");
- }, 3000);
- }, []);
const addAnother = useCallback(() => {
// navigate(`/taas/myteams/createnewteam/${projectId}/role`);
@@ -75,6 +55,36 @@ function InputJobDescription() {
}, 3000);
};
+ const search = useCallback(() => {
+ setCurrentStage(1, stages, setStages);
+ setSearchState("searching");
+ sendRoleSearchRequest({
+ jobDescription: jdString,
+ previousRoleSearchRequestId,
+ })
+ .then(({ data }) => {
+ setRoleSearchResult(data);
+ setPreviousRoleSearchRequestId(data.roleSearchRequestId);
+ setCurrentStage(2, stages, setStages);
+ setMatchingProfiles(null); // display no matching profiles screen for a while
+ if (data.name && data.name.toLowerCase() !== "niche") {
+ setMatchingProfiles(true);
+ setAddedRoles((addedRoles) => [
+ ...addedRoles,
+ { id: data.id, name: data.name },
+ ]);
+ } else {
+ setMatchingProfiles(false);
+ }
+ setSearchState("done");
+ })
+ .catch((e) => {
+ setCurrentStage(2, stages, setStages);
+ setMatchingProfiles(false); // display no matching profiles screen for a while
+ setSearchState("done");
+ });
+ }, [jdString]);
+
const onEditChange = useCallback((value) => {
setJdString(value);
}, []);
@@ -94,21 +104,20 @@ function InputJobDescription() {
onChange={onEditChange}
/>
+
+
+ {addedRoles.length > 0 && (
+
+ )}
-
setSkillModalOpen(false)}
- isLoading={isLoadingSkills}
- onContinueClick={onConfirationClick}
- />
+
) : searchState === "searching" ? (
@@ -123,7 +132,19 @@ function InputJobDescription() {
) : (
-
+ {matchingProfiles ? (
+
+ ) : (
+
+ )}
+
+
+ {addedRoles.length > 0 && (
+
+ )}
+
)}
);
@@ -147,4 +169,4 @@ 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 9341e86b..e513ac5c 100644
--- a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx
+++ b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx
@@ -12,17 +12,20 @@ import { useData } from "hooks/useData";
import { navigate } from "@reach/router";
import { toastr } from "react-redux-toastr";
import PT from "prop-types";
-import SkillsList from "./components/SkillsList";
+import SearchableList from "../../components/SearchableList";
+import SkillItem from "./components/SkillItem";
import Completeness from "../../components/Completeness";
import "./styles.module.scss";
import { getSkills } from "services/skills";
+import { sendRoleSearchRequest } from "services/teams";
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 withAuthentication from "../../../../hoc/withAuthentication";
+import AddedRolesAccordion from "../../components/AddedRolesAccordion";
function InputSkills({ projectId }) {
const [stages, setStages] = useState([
@@ -30,10 +33,17 @@ function InputSkills({ projectId }) {
{ name: "Search Member" },
{ name: "Overview of the Results" },
]);
+ const [
+ previousRoleSearchRequestId,
+ setPreviousRoleSearchRequestId,
+ ] = useState(null);
+ const [roleSearchResult, setRoleSearchResult] = useState(null);
const [selectedSkills, setSelectedSkills] = useState([]);
+ const [matchingProfiles, setMatchingProfiles] = useState(null);
const [searchState, setSearchState] = useState(null);
const [modalOpen, setModalOpen] = useState(false);
const [submitDone, setSubmitDone] = useState(false);
+ const [addedRoles, setAddedRoles] = useState([]);
const [skills, loadingError] = useData(getSkills);
@@ -66,6 +76,7 @@ function InputSkills({ projectId }) {
const toggleSkill = useCallback(
(id) => {
+ setPreviousRoleSearchRequestId(null);
if (selectedSkills.includes(id)) {
setSelectedSkills(selectedSkills.filter((skill) => skill !== id));
} else {
@@ -77,27 +88,64 @@ function InputSkills({ projectId }) {
[selectedSkills]
);
- // mocked search for users with given skills
- const search = () => {
- setSearchState("searching");
+ const search = useCallback(() => {
setCurrentStage(1, stages, setStages);
- searchTimer = setTimeout(() => {
- setSearchState("done");
- setCurrentStage(2, stages, setStages);
- }, 3000);
- };
-
- useEffect(() => clearTimeout(searchTimer));
+ setSearchState("searching");
+ sendRoleSearchRequest({
+ skills: selectedSkills,
+ previousRoleSearchRequestId,
+ })
+ .then(({ data }) => {
+ setRoleSearchResult(data);
+ setPreviousRoleSearchRequestId(data.roleSearchRequestId);
+ setCurrentStage(2, stages, setStages);
+ setMatchingProfiles(null); // display no matching profiles screen for a while
+ if (data.name && data.name.toLowerCase() !== "niche") {
+ setAddedRoles((addedRoles) => [
+ ...addedRoles,
+ { id: data.id, name: data.name },
+ ]);
+ setMatchingProfiles(true);
+ } else {
+ setMatchingProfiles(false);
+ }
+ setSearchState("done");
+ })
+ .catch((e) => {
+ setCurrentStage(2, stages, setStages);
+ setMatchingProfiles(false); // display no matching profiles screen for a while
+ setSearchState("done");
+ });
+ }, [selectedSkills]);
return !skills ? (
) : !searchState ? (
-
0 && (
+
+ {selectedSkills.length} skills selected
+
+ )
+ }
+ renderItem={({ id, name }) => {
+ return (
+
+ );
+ }}
/>
+ {addedRoles.length > 0 && }
) : (
-
+ {matchingProfiles ? (
+
+ ) : (
+
+ )}
+
+ {addedRoles.length > 0 && (
+
+ )}
+
);
}
@@ -142,4 +199,4 @@ InputSkills.propTypes = {
projectId: PT.string,
};
-export default withAuthentication(InputSkills);
+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..a6c5fff9 100644
--- a/src/routes/CreateNewTeam/pages/InputSkills/styles.module.scss
+++ b/src/routes/CreateNewTeam/pages/InputSkills/styles.module.scss
@@ -4,4 +4,20 @@
justify-content: center;
align-items: flex-start;
margin: 42px 35px;
+
+ .right-side {
+ display: flex;
+ flex-direction: column;
+ & > div:not(:first-child) {
+ margin-top: 16px;
+ }
+ }
+}
+
+
+.skill-count {
+ position: absolute;
+ font-size: 12px;
+ top: 72px;
+ left: 73px;
}
diff --git a/src/routes/CreateNewTeam/pages/SelectRole/components/RoleItem/styles.module.scss b/src/routes/CreateNewTeam/pages/SelectRole/components/RoleItem/styles.module.scss
index 3f5b7652..a1d5560d 100644
--- a/src/routes/CreateNewTeam/pages/SelectRole/components/RoleItem/styles.module.scss
+++ b/src/routes/CreateNewTeam/pages/SelectRole/components/RoleItem/styles.module.scss
@@ -33,6 +33,7 @@
}
.button {
+ width: 75px;
font-size: 14px;
line-height: 22px;
padding: 0;
diff --git a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx
index a8137e94..9e15b3b7 100644
--- a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx
+++ b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx
@@ -11,7 +11,8 @@ 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 RoleItem from "./components/RoleItem";
+import SearchableList from '../../components/SearchableList'
import Completeness from "../../components/Completeness";
import "./styles.module.scss";
import { getRoles } from "services/roles";
@@ -21,10 +22,11 @@ import SearchCard from "../../components/SearchCard";
import ResultCard from "../../components/ResultCard";
import NoMatchingProfilesResultCard from "../../components/NoMatchingProfilesResultCard";
import { createJob } from "services/jobs";
+import { sendRoleSearchRequest } from "services/teams";
import AddAnotherModal from "../../components/AddAnotherModal";
import RoleDetailsModal from "../../components/RoleDetailsModal";
-import withAuthentication from "../../../../hoc/withAuthentication";
-import AddedRolesAccordion from "./components/AddedRolesAccordion";
+import AddedRolesAccordion from "../../components/AddedRolesAccordion";
+
function SelectRole({ projectId }) {
const [stages, setStages] = useState([
@@ -32,6 +34,8 @@ function SelectRole({ projectId }) {
{ name: "Search Member" },
{ name: "Overview of the Results" },
]);
+ const [roleSearchResult, setRoleSearchResult] = useState(null);
+ const [previousRoleSearchRequestId, setPreviousRoleSearchRequestId] = useState(null);
const [addedRoles, setAddedRoles] = useState([]);
const [selectedRoleId, setSelectedRoleId] = useState(null);
const [searchState, setSearchState] = useState(null);
@@ -76,6 +80,7 @@ function SelectRole({ projectId }) {
const toggleRole = useCallback(
(id) => {
+ setPreviousRoleSearchRequestId(null)
setSelectedRoleId((selectedRoleId) =>
id === selectedRoleId ? null : id
);
@@ -88,22 +93,27 @@ function SelectRole({ projectId }) {
setRoleDetailsModalOpen(true);
}, []);
- // mocked search for users with given roles
- const search = () => {
+ const search = useCallback(() => {
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);
- };
-
- useEffect(() => clearTimeout(searchTimer));
+ sendRoleSearchRequest({roleId: selectedRoleId, previousRoleSearchRequestId}).then(({data})=> {
+ setRoleSearchResult(data)
+ setCurrentStage(2, stages, setStages);
+ setMatchingProfiles(null); // display no matching profiles screen for a while
+ setPreviousRoleSearchRequestId(data.roleSearchRequestId)
+ if (data.name && data.name.toLowerCase() !== 'niche' ) {
+ setAddedRoles((addedRoles) => [...addedRoles, { id: data.id, name: data.name }]);
+ setMatchingProfiles(true)
+ } else {
+ setMatchingProfiles(false)
+ }
+ setSearchState("done");
+ }).catch(e=> {
+ setCurrentStage(2, stages, setStages);
+ setMatchingProfiles(false); // display no matching profiles screen for a while
+ setSearchState("done");
+ })
+ }, [selectedRoleId, previousRoleSearchRequestId]);
if (!roles) {
return ;
@@ -112,11 +122,23 @@ function SelectRole({ projectId }) {
if (roles && !searchState) {
return (
-
{
+ return (
+
+ )
+ }}
/>
{addedRoles.length > 0 && (
@@ -130,11 +152,15 @@ function SelectRole({ projectId }) {
stages={stages}
percentage="26"
/>
- setRoleDetailsModalOpen(false)}
- />
+ {
+ roleDetailsModalOpen && (
+ setRoleDetailsModalOpen(false)}
+ />
+ )
+ }
);
@@ -158,7 +184,7 @@ function SelectRole({ projectId }) {
if (searchState === "done") {
return (
- {matchingProfiles ?
:
}
+ {matchingProfiles ?
:
}
{matchingProfiles &&
}
{
return axios.get(`${config.API.V5}/taas-teams?${query}`);
};
+/**
+ * search role detail.
+ *
+ * @param {object} data search param, roleId | jobDescription | skills
+ * @returns {Promise<{}>} role detail
+ */
+export const sendRoleSearchRequest = (data) => {
+ return axios.post(`${config.API.V5}/taas-teams/sendRoleSearchRequest`, data);
+};
+
/**
* Get v5 user profile.
*
@@ -190,19 +200,3 @@ export const postMembers = (teamId, handles, emails) => {
return axios.post(url, bodyObj);
};
-
-/**
- * Post new project
- *
- * @returns {Promise