+
Offshore Rate
{formatRate(rates.rate20OffShore)}
diff --git a/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss b/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss
index 04744e66..95ec69ae 100644
--- a/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss
+++ b/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss
@@ -167,9 +167,9 @@
margin-left: 3px;
}
}
- .senior,
- .standard,
- .junior {
+ .global,
+ .in-country,
+ .offshore {
display: flex;
flex-direction: column;
position: relative;
@@ -210,13 +210,13 @@
}
}
}
- .senior::before {
+ .global::before {
background-color: #c99014;
}
- .standard::before {
+ .in-country::before {
background-color: #716d67;
}
- .junior::before {
+ .offshore::before {
background-color: #854e29;
}
}
diff --git a/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx
new file mode 100644
index 00000000..9f27e3ba
--- /dev/null
+++ b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx
@@ -0,0 +1,15 @@
+import { Router } from "@reach/router";
+import React from "react";
+import SearchContainer from "../SearchContainer";
+import SubmitContainer from "../SubmitContainer";
+
+function SearchAndSubmit(props) {
+ return (
+
+
+
+
+ );
+}
+
+export default SearchAndSubmit;
diff --git a/src/routes/CreateNewTeam/components/SearchCard/styles.module.scss b/src/routes/CreateNewTeam/components/SearchCard/styles.module.scss
index 52c5b5a6..ef9e55a8 100644
--- a/src/routes/CreateNewTeam/components/SearchCard/styles.module.scss
+++ b/src/routes/CreateNewTeam/components/SearchCard/styles.module.scss
@@ -5,7 +5,7 @@
max-width: 746px;
width: 50vw;
margin-right: 30px;
- height: 80vh;
+ padding-bottom: 96px;
}
.heading {
diff --git a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx
index 7e8c2bb4..4cf96c6f 100644
--- a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx
+++ b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx
@@ -7,100 +7,80 @@
*/
import React, { useCallback, useState } from "react";
import PT from "prop-types";
-import { toastr } from "react-redux-toastr";
-import { navigate } from "@reach/router";
import _ from "lodash";
+import { useDispatch, useSelector } from "react-redux";
import AddedRolesAccordion from "../AddedRolesAccordion";
import Completeness from "../Completeness";
import SearchCard from "../SearchCard";
import ResultCard from "../ResultCard";
import NoMatchingProfilesResultCard from "../NoMatchingProfilesResultCard";
-import { createJob } from "services/jobs";
-import { postProject, searchRoles } from "services/teams";
+import { searchRoles } from "services/teams";
import { setCurrentStage } from "utils/helpers";
-import AddAnotherModal from "../AddAnotherModal";
+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,
isCompletenessDisabled,
- children,
+ toRender,
searchObject,
completenessStyle,
- locationState,
reloadRolesPage,
+ navigate,
}) {
- const [addedRoles, setAddedRoles] = useState(
- locationState?.addedRoles ? locationState.addedRoles : []
+ 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 [prevSearchId, setPrevSearchId] = useState(locationState?.prevSearchId);
- const submitJob = () => {
- setSubmitDone(false);
- postProject()
- .then((res) => {
- const projectId = _.get(res, "data.id");
-
- createJob({
- projectId,
- title: `job-${Date()}`,
- skills: [],
- roleIds: addedRoles.map((r) => r.id),
- numPositions: 1,
- })
- .then(() => {
- toastr.success("Job Submitted");
- })
- .catch((err) => {
- console.error(err);
- toastr.warning("Error Submitting Job");
- });
- })
- .catch((err) => {
- console.error(err);
- toastr.warning("Error Creating Project");
- })
- .finally(() => {
- setSubmitDone(true);
- navigate("/taas/myteams");
- });
- };
+ const dispatch = useDispatch();
- const addAnother = () => {
- if (!reloadRolesPage) {
- navigate("/taas/myteams/createnewteam/role", {
- state: { addedRoles, prevSearchId },
- });
- return;
- }
- setCurrentStage(0, stages, setStages);
- setSearchState(null);
- setMatchingRole(null);
- setAddAnotherModalOpen(false);
- reloadRolesPage();
- };
+ const onSubmit = useCallback(() => {
+ storeStrings(addedRoles);
+ navigate("result", { state: { matchingRole } });
+ }, [addedRoles, navigate, matchingRole]);
const search = () => {
setCurrentStage(1, stages, setStages);
setSearchState("searching");
setMatchingRole(null);
const searchObjectCopy = { ...searchObject };
- if (prevSearchId) {
- searchObjectCopy.previousRoleSearchRequestId = prevSearchId;
+ if (previousSearchId) {
+ searchObjectCopy.previousRoleSearchRequestId = previousSearchId;
}
searchRoles(searchObjectCopy)
.then((res) => {
- const id = _.get(res, "data.id");
const name = _.get(res, "data.name");
- setPrevSearchId(_.get(res, "data.roleSearchRequestId"));
+ const searchId = _.get(res, "data.roleSearchRequestId");
if (name && !name.toLowerCase().includes("niche")) {
setMatchingRole(res.data);
- setAddedRoles((addedRoles) => [...addedRoles, { id, name }]);
+ dispatch(addSearchedRole({ searchId, name }));
+ } else if (searchId) {
+ dispatch(addRoleSearchId(searchId));
}
})
.catch((err) => {
@@ -113,15 +93,10 @@ function SearchContainer({
};
const renderLeftSide = () => {
- if (!searchState) return children;
+ if (!searchState) return toRender;
if (searchState === "searching") return ;
if (matchingRole) return ;
- return (
-
- );
+ return ;
};
const getPercentage = useCallback(() => {
@@ -142,22 +117,13 @@ function SearchContainer({
searchState === "searching" ||
(searchState === "done" && !matchingRole)
}
- onClick={searchState ? () => setAddAnotherModalOpen(true) : search}
+ onClick={searchState ? onSubmit : search}
extraStyleName={completenessStyle}
buttonLabel={searchState ? "Submit Request" : "Search"}
stages={stages}
percentage={getPercentage()}
/>
- {searchState === "done" && matchingRole && (
-
setAddAnotherModalOpen(false)}
- submitDone={submitDone}
- onContinueClick={submitJob}
- addAnother={addAnother}
- />
- )}
);
}
@@ -167,10 +133,10 @@ SearchContainer.propTypes = {
setStages: PT.func,
isCompletenessDisabled: PT.bool,
searchObject: PT.object,
- children: PT.node,
+ toRender: PT.node,
completenessStyle: PT.string,
- locationState: PT.object,
reloadRolesPage: PT.func,
+ navigate: PT.func,
};
export default SearchContainer;
diff --git a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx
new file mode 100644
index 00000000..a8223a29
--- /dev/null
+++ b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx
@@ -0,0 +1,178 @@
+/**
+ * Submit Container
+ * Container for the submission flow.
+ * 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 PT from "prop-types";
+import { useDispatch, useSelector } from "react-redux";
+import _ from "lodash";
+import { toastr } from "react-redux-toastr";
+import { navigate } from "@reach/router";
+import ResultCard from "../ResultCard";
+import AddedRolesAccordion from "../AddedRolesAccordion";
+import Completeness from "../Completeness";
+import AddAnotherModal from "../AddAnotherModal";
+import TeamDetailsModal from "../TeamDetailsModal";
+import ConfirmationModal from "../ConfirmationModal";
+import withAuthentication from "../../../../hoc/withAuthentication";
+import "./styles.module.scss";
+import { setCurrentStage } from "utils/helpers";
+import { clearSearchedRoles, replaceSearchedRoles } 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,
+}) {
+ 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);
+ const [requestLoading, setRequestLoading] = useState(false);
+
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ setCurrentStage(2, stages, setStages);
+ const storedRoles = retrieveRoles();
+ if (storedRoles) {
+ if (!addedRoles || storedRoles.length > addedRoles.length) {
+ dispatch(replaceSearchedRoles(storedRoles));
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const openTeamDetails = () => {
+ setAddAnotherOpen(false);
+ setTeamDetailsOpen(true);
+ };
+
+ const addAnother = () => {
+ if (reloadRolesPage) {
+ setCurrentStage(0, stages, setStages);
+ reloadRolesPage();
+ }
+ navigate("/taas/myteams/createnewteam/role");
+ };
+
+ const assembleTeam = (formData) => {
+ const teamObject = _.pick(formData, ["teamName", "teamDescription"]);
+
+ const positions = [];
+ for (let key of Object.keys(formData)) {
+ if (key === "teamName" || key === "teamDescription") {
+ continue;
+ }
+ const position = _.pick(
+ formData[key],
+ "numberOfResources",
+ "durationWeeks",
+ "startMonth"
+ );
+
+ position.roleSearchRequestId = key;
+ position.roleName = addedRoles.find((role) => role.searchId === key).name;
+
+ positions.push(position);
+ }
+ teamObject.positions = positions;
+
+ setTeamDetailsOpen(false);
+ setTeamObject(teamObject);
+ };
+
+ const requestTeam = useCallback(() => {
+ setRequestLoading(true);
+ postTeamRequest(teamObject)
+ .then((res) => {
+ const projectId = _.get(res, ["data", "projectId"]);
+ clearSessionKeys();
+ dispatch(clearSearchedRoles());
+ navigate(`/taas/myteams/${projectId}`);
+ })
+ .catch((err) => {
+ setRequestLoading(false);
+ toastr.error("Error Requesting Team", err.message);
+ });
+ }, [dispatch, teamObject]);
+
+ return (
+
+ {matchingRole ?
:
}
+
+
+
setAddAnotherOpen(true)}
+ extraStyleName={completenessStyle}
+ buttonLabel="Submit Request"
+ stages={stages}
+ percentage="98"
+ />
+
+
setAddAnotherOpen(false)}
+ submitDone={true}
+ onContinueClick={openTeamDetails}
+ addAnother={addAnother}
+ />
+ setTeamDetailsOpen(false)}
+ submitForm={assembleTeam}
+ addedRoles={addedRoles}
+ />
+ setTeamObject(null)}
+ onSubmit={requestTeam}
+ isLoading={requestLoading}
+ />
+
+ );
+}
+
+SubmitContainer.propTypes = {
+ stages: PT.array,
+ setStages: PT.func,
+ completenessStyle: PT.string,
+ reloadRolesPage: PT.bool,
+ location: PT.object,
+};
+
+export default withAuthentication(SubmitContainer);
diff --git a/src/routes/CreateNewTeam/components/SubmitContainer/styles.module.scss b/src/routes/CreateNewTeam/components/SubmitContainer/styles.module.scss
new file mode 100644
index 00000000..99cec905
--- /dev/null
+++ b/src/routes/CreateNewTeam/components/SubmitContainer/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/SuccessCard/index.jsx b/src/routes/CreateNewTeam/components/SuccessCard/index.jsx
new file mode 100644
index 00000000..1aa05fa8
--- /dev/null
+++ b/src/routes/CreateNewTeam/components/SuccessCard/index.jsx
@@ -0,0 +1,43 @@
+/**
+ * Success Card
+ * Card to show after returning from login in submit
+ * new team request flow. Shows success state, but does
+ * not require receiving props
+ */
+import React from "react";
+import { Link } from "@reach/router";
+import IconEarthCheck from "../../../../assets/images/icon-earth-check.svg";
+import Curve from "../../../../assets/images/curve.svg";
+import { MATCHING_RATE } from "constants";
+import "./styles.module.scss";
+import Button from "components/Button";
+
+function SuccessCard() {
+ return (
+
+
+
+
We have matching profiles
+
+ We have qualified candidates who match {MATCHING_RATE}% or more of
+ your job requirements.
+
+
+
+
+
+
+ Please use the button to the right to submit your request, or the
+ button below to search for additional roles.
+
+
+
+ Continue Search
+
+
+
+
+ );
+}
+
+export default SuccessCard;
diff --git a/src/routes/CreateNewTeam/components/SuccessCard/styles.module.scss b/src/routes/CreateNewTeam/components/SuccessCard/styles.module.scss
new file mode 100644
index 00000000..686a711d
--- /dev/null
+++ b/src/routes/CreateNewTeam/components/SuccessCard/styles.module.scss
@@ -0,0 +1,70 @@
+@import "styles/include";
+
+.result-card {
+ @include rounded-card;
+ max-width: 746px;
+ width: 50vw;
+ margin-right: 30px;
+}
+
+.heading {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ padding: 30px 0 60px 0;
+ margin-bottom: 30px;
+ color: #fff;
+ background-image: linear-gradient(225deg, #0ab88a 0%, #137d60 100%);
+ position: relative;
+ text-align: center;
+ border-radius: 8px 8px 0 0;
+
+ svg {
+ margin-bottom: 8px;
+ g {
+ stroke: #fff;
+ }
+ }
+
+ h3 {
+ @include font-barlow-condensed;
+ text-transform: uppercase;
+ font-size: 34px;
+ margin-bottom: 8px;
+ font-weight: 500;
+ }
+}
+
+.content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding-bottom: 61px;
+ p.info-txt {
+ @include font-roboto;
+ font-size: 14px;
+ line-height: 22px;
+ width: 357px;
+ text-align: center;
+ }
+ .button {
+ margin-top: 240px;
+ }
+}
+
+.curve {
+ position: absolute;
+ left: 0;
+ bottom: -70px;
+ width: 100%;
+}
+
+.transparent-icon {
+ position: absolute;
+ top: -40px;
+ right: 10px;
+ opacity: 12%;
+ height: 142px;
+ width: 142px;
+}
\ No newline at end of file
diff --git a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx
new file mode 100644
index 00000000..716d375d
--- /dev/null
+++ b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx
@@ -0,0 +1,220 @@
+/**
+ * Team Details Modal
+ * Popup form to enter details about the
+ * team request before submitting.
+ */
+import React, { useState } from "react";
+import PT from "prop-types";
+import { Form, Field, useField } from "react-final-form";
+import FormField from "components/FormField";
+import BaseCreateModal from "../BaseCreateModal";
+import { FORM_FIELD_TYPE } from "constants/";
+import { formatPlural } from "utils/format";
+import Button from "components/Button";
+import "./styles.module.scss";
+
+const Error = ({ name }) => {
+ const {
+ meta: { touched, error },
+ } = useField(name, { subscription: { touched: true, error: true } });
+ return touched && error ?
{error} : null;
+};
+
+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 toggleDescription = () => {
+ 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;
+ };
+
+ return (
+
+ );
+}
+
+TeamDetailsModal.propTypes = {
+ open: PT.bool,
+ onClose: PT.func,
+ submitForm: PT.func,
+ addedRoles: PT.array,
+};
+
+export default TeamDetailsModal;
diff --git a/src/routes/CreateNewTeam/components/TeamDetailsModal/styles.module.scss b/src/routes/CreateNewTeam/components/TeamDetailsModal/styles.module.scss
new file mode 100644
index 00000000..eb6080c6
--- /dev/null
+++ b/src/routes/CreateNewTeam/components/TeamDetailsModal/styles.module.scss
@@ -0,0 +1,70 @@
+@import "styles/include";
+
+.toggle-button {
+ @include font-roboto;
+ outline: none;
+ border: none;
+ background: none;
+ font-size: 12px;
+ font-weight: 500;
+ color: #137D60;
+
+ &.toggle-description {
+ margin-top: 12px;
+ > span {
+ font-size: 18px;
+ vertical-align: middle;
+ }
+ }
+}
+
+.table {
+ margin-top: 40px;
+ th {
+ @include font-roboto;
+ font-size: 12px;
+ color: #555;
+ padding-bottom: 7px;
+ border-bottom: 1px solid #d4d4d4;
+
+ &.bold {
+ font-weight: 700;
+ }
+ }
+
+ .role-row {
+ td {
+ padding: 18px 18px 18px 0;
+ vertical-align: middle;
+ @include font-barlow;
+ font-weight: 600;
+ font-size: 16px;
+ color: #2a2a2a;
+ border-bottom: 1px solid #e9e9e9;
+
+ input {
+ @include font-roboto;
+ font-size: 14px;
+ line-height: 22px;
+ height: 34px;
+ width: 98px;
+ &[type="month"] {
+ width: 170px;
+ }
+ }
+ }
+ }
+}
+
+.error {
+ font-size: 14px;
+ font-weight: 400;
+ color: red;
+ display: block;
+}
+
+.modal-body {
+ textarea {
+ height: 95px;
+ }
+}
\ No newline at end of file
diff --git a/src/routes/CreateNewTeam/index.jsx b/src/routes/CreateNewTeam/index.jsx
index 34eab93e..4158b2f8 100644
--- a/src/routes/CreateNewTeam/index.jsx
+++ b/src/routes/CreateNewTeam/index.jsx
@@ -8,23 +8,30 @@
* by selecting a role, inputting skills,
* or inputting a job description
*/
-import React from "react";
+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 prevSearchId = locationState?.prevSearchId;
- const addedRoles = locationState?.addedRoles;
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ if (!locationState || !locationState.keepAddedRoles) {
+ dispatch(clearSearchedRoles());
+ }
+ });
const goToRoute = (path) => {
- navigate(path, { state: { prevSearchId, addedRoles } });
+ navigate(path);
};
return (
diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx
index 6182c9a7..776267b3 100644
--- a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx
+++ b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx
@@ -1,19 +1,16 @@
/**
* Input Job Description page
*
- * Gets location state from router
- *
* Allows user to search for roles by
* job description
*/
import React, { useCallback, useState } from "react";
-import PT from "prop-types";
import PageHeader from "components/PageHeader";
import MarkdownEditor from "../../../../components/MarkdownEditor";
import "./styles.module.scss";
-import SearchContainer from "../../components/SearchContainer";
+import SearchAndSubmit from "../../components/SearchAndSubmit";
-function InputJobDescription({ location: { state: locationState } }) {
+function InputJobDescription() {
const [stages, setStages] = useState([
{ name: "Input Job Description", isCurrent: true },
{ name: "Search Member" },
@@ -26,31 +23,29 @@ function InputJobDescription({ location: { state: locationState } }) {
}, []);
return (
-
-
-
+ toRender={
+ <>
+
+ >
+ }
+ />
);
}
-InputJobDescription.propTypes = {
- locationState: PT.object,
-};
-
export default InputJobDescription;
diff --git a/src/routes/CreateNewTeam/pages/InputSkills/components/SkillsList/index.jsx b/src/routes/CreateNewTeam/pages/InputSkills/components/SkillsList/index.jsx
index a152a541..effefd34 100644
--- a/src/routes/CreateNewTeam/pages/InputSkills/components/SkillsList/index.jsx
+++ b/src/routes/CreateNewTeam/pages/InputSkills/components/SkillsList/index.jsx
@@ -4,71 +4,50 @@
* and search for. Allows selecting skills and filtering
* by name.
*/
-import React, { useEffect, useState } from "react";
-import { useDebounce } from "react-use";
+import React, { useCallback, useState } from "react";
import PT from "prop-types";
-import Input from "components/Input";
-import PageHeader from "components/PageHeader";
-import "./styles.module.scss";
import SkillItem from "../SkillItem";
-import { INPUT_DEBOUNCE_DELAY } from "constants/";
+import ItemList from "../../../../components/ItemList";
+import { formatPlural } from "utils/format";
function SkillsList({ skills, selectedSkills, toggleSkill }) {
const [filteredSkills, setFilteredSkills] = useState(skills);
- const [filter, setFilter] = useState("");
- const [debouncedFilter, setDebouncedFilter] = useState("");
- const onFilterChange = (e) => {
- setFilter(e.target.value);
- };
-
- useDebounce(
- () => {
- setDebouncedFilter(filter);
+ const filterSkills = useCallback(
+ (filterText) => {
+ if (filterText === "") {
+ setFilteredSkills(skills);
+ } else {
+ const filtered = skills.filter((skill) =>
+ skill.name.toLowerCase().includes(filterText)
+ );
+ setFilteredSkills(filtered);
+ }
},
- INPUT_DEBOUNCE_DELAY,
- [filter]
+ [skills]
);
- useEffect(() => {
- if (debouncedFilter.length > 0) {
- const filterText = debouncedFilter.toLowerCase();
- setFilteredSkills(
- skills.filter((skill) => skill.name.toLowerCase().includes(filterText))
- );
- } else {
- setFilteredSkills(skills);
- }
- }, [debouncedFilter, skills]);
-
return (
-
-
- }
- />
- {selectedSkills.length > 0 && (
-
{selectedSkills.length} skills selected
- )}
-
- {filteredSkills.map(({ id, name }) => (
-
- ))}
-
-
+
+ {filteredSkills.map(({ id, name }) => (
+
+ ))}
+
);
}
diff --git a/src/routes/CreateNewTeam/pages/InputSkills/components/SkillsList/styles.module.scss b/src/routes/CreateNewTeam/pages/InputSkills/components/SkillsList/styles.module.scss
deleted file mode 100644
index 452f06f1..00000000
--- a/src/routes/CreateNewTeam/pages/InputSkills/components/SkillsList/styles.module.scss
+++ /dev/null
@@ -1,57 +0,0 @@
-@import "styles/include";
-
-.skills-list {
- @include rounded-card;
- max-width: 746px;
- margin-right: 30px;
- height: 80vh;
- overflow-y: scroll;
- position: relative;
-
- > header {
- padding: 16px 24px;
- }
-}
-
-.skill-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;
- }
-}
-
-.skill-container {
- display: flex;
- flex-direction: row;
- justify-content: flex-start;
- flex-wrap: wrap;
- margin-right: 24px;
-}
diff --git a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx
index f105df60..a9ace938 100644
--- a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx
+++ b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx
@@ -2,20 +2,18 @@
* Input Skills page
* Page that user reaches after choosing to input job skills.
*
- * Gets location state from the router.
- *
* Allows selecting a number of skills, searching for users
* with those skills, and submitting a job requiring the skills.
*/
import React, { useCallback, useState } from "react";
-import PT from "prop-types";
import { useData } from "hooks/useData";
import SkillsList from "./components/SkillsList";
import { getSkills } from "services/skills";
import LoadingIndicator from "components/LoadingIndicator";
import SearchContainer from "../../components/SearchContainer";
+import SearchAndSubmit from "../../components/SearchAndSubmit";
-function InputSkills({ location: { state: locationState } }) {
+function InputSkills() {
const [stages, setStages] = useState([
{ name: "Input Skills", isCurrent: true },
{ name: "Search Member" },
@@ -47,25 +45,21 @@ function InputSkills({ location: { state: locationState } }) {
}
return (
-
-
-
+ toRender={
+
+ }
+ />
);
}
-InputSkills.propTypes = {
- locationState: PT.object,
-};
-
export default InputSkills;
diff --git a/src/routes/CreateNewTeam/pages/SelectRole/components/RolesList/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/components/RolesList/index.jsx
index 88bd66f7..de077b6f 100644
--- a/src/routes/CreateNewTeam/pages/SelectRole/components/RolesList/index.jsx
+++ b/src/routes/CreateNewTeam/pages/SelectRole/components/RolesList/index.jsx
@@ -4,90 +4,53 @@
* and search for. Allows selecting roles and filtering
* by name.
*/
-import React, { useEffect, useState } from "react";
-import { useDebounce } from "react-use";
+import React, { useCallback, useState } from "react";
import PT from "prop-types";
-import Input from "components/Input";
-import PageHeader from "components/PageHeader";
-import "./styles.module.scss";
import RoleItem from "../RoleItem";
-import { INPUT_DEBOUNCE_DELAY } from "constants/";
+import ItemList from "../../../../components/ItemList";
function RolesList({ roles, selectedRoleId, onDescriptionClick, toggleRole }) {
const [filteredRoles, setFilteredRoles] = useState(roles);
- const [filter, setFilter] = useState("");
- const [debouncedFilter, setDebouncedFilter] = useState("");
- const onFilterChange = (e) => {
- setFilter(e.target.value);
- };
-
- useDebounce(
- () => {
- setDebouncedFilter(filter);
+ const filterRoles = useCallback(
+ (filterText) => {
+ if (filterText === "") {
+ setFilteredRoles(roles);
+ } else {
+ const filtered = roles.filter((role) =>
+ role.name.toLowerCase().includes(filterText)
+ );
+ setFilteredRoles(filtered);
+ }
},
- INPUT_DEBOUNCE_DELAY,
- [filter]
+ [roles]
);
- useEffect(() => {
- if (debouncedFilter.length > 0) {
- const filterText = debouncedFilter.toLowerCase();
- setFilteredRoles(
- roles.filter((role) => role.name.toLowerCase().includes(filterText))
- );
- } else {
- setFilteredRoles(roles);
- }
- }, [debouncedFilter, roles]);
-
return (
-
+
+ {filteredRoles.map(({ id, name, imageUrl }) => (
+
+ ))}
+
);
}
RolesList.propTypes = {
roles: PT.array,
selectedRoleId: PT.string,
+ onDescriptionClick: PT.func,
toggleRole: PT.func,
};
diff --git a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx
index d2dda3fe..5488b60b 100644
--- a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx
+++ b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx
@@ -1,21 +1,18 @@
/**
* Select Role Page
*
- * Gets locationState from the router.
- *
* Allows selecting a role, searching for users
* with that role, and submitting a job requiring the roles.
*/
import React, { useCallback, useState } from "react";
-import PT from "prop-types";
import { useData } from "hooks/useData";
import RolesList from "./components/RolesList";
import { getRoles } from "services/roles";
import LoadingIndicator from "components/LoadingIndicator";
import RoleDetailsModal from "../../components/RoleDetailsModal";
-import SearchContainer from "../../components/SearchContainer";
+import SearchAndSubmit from "../../components/SearchAndSubmit";
-function SelectRole({ location: { state: locationState } }) {
+function SelectRole() {
const [stages, setStages] = useState([
{ name: "Select a Role", isCurrent: true },
{ name: "Search Member" },
@@ -38,7 +35,7 @@ function SelectRole({ location: { state: locationState } }) {
const resetState = () => {
setSelectedRoleId(null);
- setRoleDetailsModalId(false);
+ setRoleDetailsModalOpen(false);
setRoleDetailsModalId(null);
};
@@ -47,32 +44,30 @@ function SelectRole({ location: { state: locationState } }) {
}
return (
-
-
- setRoleDetailsModalOpen(false)}
- />
-
+ toRender={
+ <>
+
+
setRoleDetailsModalOpen(false)}
+ />
+ >
+ }
+ />
);
}
-SelectRole.propTypes = {
- locationState: PT.object,
-};
-
export default SelectRole;
diff --git a/src/routes/CreateNewTeam/reducers/index.js b/src/routes/CreateNewTeam/reducers/index.js
new file mode 100644
index 00000000..de3ff1a3
--- /dev/null
+++ b/src/routes/CreateNewTeam/reducers/index.js
@@ -0,0 +1,44 @@
+/**
+ * Reducer for CreateNewTeam flow
+ */
+import { ACTION_TYPE } from "constants";
+
+const initialState = {
+ previousSearchId: undefined,
+ addedRoles: [],
+};
+
+const reducer = (state = initialState, action) => {
+ switch (action.type) {
+ case ACTION_TYPE.CLEAR_SEARCHED_ROLES:
+ return {
+ ...state,
+ addedRoles: [],
+ };
+
+ case ACTION_TYPE.ADD_SEARCHED_ROLE:
+ return {
+ ...state,
+ previousSearchId: action.payload.searchId,
+ addedRoles: [...state.addedRoles, action.payload],
+ };
+
+ case ACTION_TYPE.ADD_ROLE_SEARCH_ID:
+ return {
+ ...state,
+ previousSearchId: action.payload,
+ };
+
+ case ACTION_TYPE.REPLACE_SEARCHED_ROLES:
+ return {
+ ...state,
+ addedRoles: action.payload.roles,
+ previousSearchId: action.payload.lastRoleId,
+ };
+
+ default:
+ return state;
+ }
+};
+
+export default reducer;
diff --git a/src/services/teams.js b/src/services/teams.js
index 658f98f8..7fedd403 100644
--- a/src/services/teams.js
+++ b/src/services/teams.js
@@ -216,7 +216,8 @@ export const postProject = () => {
* @param {string} searchObject.jobDescription job description used for search
* @param {string[]} searchObject.skills array of skill ids used for role search
* @param {string} searchObject.previousRoleSearchRequestId id of the last search made
- * @returns
+ *
+ * @returns {Promise} the role found
*/
export const searchRoles = (searchObject) => {
const newObject = { ...searchObject };
@@ -224,3 +225,15 @@ export const searchRoles = (searchObject) => {
const url = `${config.API.V5}/taas-teams/sendRoleSearchRequest`;
return axios.post(url, newObject);
};
+
+/**
+ * Create a new team request
+ *
+ * @param {Object} teamObject object containing team details
+ *
+ * @returns {Promise} object containing new projectId
+ */
+export const postTeamRequest = (teamObject) => {
+ const url = `${config.API.V5}/taas-teams/submitTeamRequest`;
+ return axios.post(url, teamObject);
+};