diff --git a/src/components/Checkbox/index.jsx b/src/components/Checkbox/index.jsx new file mode 100644 index 00000000..b4cbb52d --- /dev/null +++ b/src/components/Checkbox/index.jsx @@ -0,0 +1,21 @@ +import React from "react"; +import PT from "prop-types"; +import "./styles.module.scss"; + +function Checkbox({ label, checked, onClick }) { + return ( + + ); +} + +Checkbox.propTypes = { + label: PT.string, + checked: PT.bool, + onClick: PT.func, +}; + +export default Checkbox; diff --git a/src/components/Checkbox/styles.module.scss b/src/components/Checkbox/styles.module.scss new file mode 100644 index 00000000..b80c99bd --- /dev/null +++ b/src/components/Checkbox/styles.module.scss @@ -0,0 +1,75 @@ +@import "styles/include"; + +/* The container */ +.container { + @include font-roboto; + color: #2a2a2a; + font-size: 14px; + line-height: 20px; + display: block; + position: relative; + padding-left: 30px; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +/* Hide the browser's default checkbox */ +.container input { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; +} + +/* Create a custom checkbox */ +.checkmark { + position: absolute; + top: 0; + left: 0; + height: 20px; + width: 20px; + background-color: #fff; + border: 1px solid #AAA; + border-radius: 3px; +} + +/* On mouse-over, add a grey background color */ +.container:hover input ~ .checkmark { + background-color: #eee; +} + +/* When the checkbox is checked, add a green background */ +.container input:checked ~ .checkmark { + background-color: #0AB88A; + box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.29); +} + +/* Create the checkmark/indicator (hidden when not checked) */ +.checkmark:after { + content: ""; + position: absolute; + display: none; +} + +/* Show the checkmark when checked */ +.container input:checked ~ .checkmark:after { + display: block; +} + +/* Style the checkmark/indicator */ +.container .checkmark:after { + left: 6px; + top: 3px; + width: 6px; + height: 11px; + border: solid white; + border-width: 0 3px 3px 0; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.35); +} \ No newline at end of file diff --git a/src/constants/index.js b/src/constants/index.js index 6eee3717..5bc6228f 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -250,6 +250,14 @@ export const ACTION_TYPE = { ADD_MEMBERS_ERROR: "ADD_MEMBERS_ERROR", RESET_MEMBERS_STATE: "RESET_MEMBERS_STATE", CLEAR_MEMBERS_SUGGESTIONS: "CLEAR_MEMBERS_SUGGESTIONS", + + /* + Searched Roles + */ + CLEAR_SEARCHED_ROLES: "CLEAR_SEARCHED_ROLES", + ADD_SEARCHED_ROLE: "ADD_SEARCHED_ROLE", + ADD_ROLE_SEARCH_ID: "ADD_ROLE_SEARCH_ID", + REPLACE_SEARCHED_ROLES: "REPLACE_SEARCHED_ROLES", }; /** diff --git a/src/reducers/index.js b/src/reducers/index.js index 14f1b87c..79321745 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -7,6 +7,7 @@ import positionDetailsReducer from "../routes/PositionDetails/reducers"; import teamMembersReducer from "../routes/TeamAccess/reducers"; import emailPopupReducer from "../components/EmailPopup/reducers"; import authUserReducer from "../hoc/withAuthentication/reducers"; +import searchedRolesReducer from "../routes/CreateNewTeam/reducers"; const rootReducer = combineReducers({ toastr: toastrReducer, @@ -14,6 +15,7 @@ const rootReducer = combineReducers({ teamMembers: teamMembersReducer, emailPopup: emailPopupReducer, authUser: authUserReducer, + searchedRoles: searchedRolesReducer, }); export default rootReducer; diff --git a/src/root.component.jsx b/src/root.component.jsx index 97b8bdcb..f1e5c3b7 100644 --- a/src/root.component.jsx +++ b/src/root.component.jsx @@ -34,9 +34,9 @@ export default function Root() { - - - + + + {/* Global config for Toastr popups */} diff --git a/src/routes/CreateNewTeam/actions/index.js b/src/routes/CreateNewTeam/actions/index.js new file mode 100644 index 00000000..99106fc4 --- /dev/null +++ b/src/routes/CreateNewTeam/actions/index.js @@ -0,0 +1,20 @@ +import { ACTION_TYPE } from "constants"; + +export const clearSearchedRoles = () => ({ + type: ACTION_TYPE.CLEAR_SEARCHED_ROLES, +}); + +export const addSearchedRole = (searchedRole) => ({ + type: ACTION_TYPE.ADD_SEARCHED_ROLE, + payload: searchedRole, +}); + +export const addRoleSearchId = (id) => ({ + type: ACTION_TYPE.ADD_ROLE_SEARCH_ID, + payload: id, +}); + +export const replaceSearchedRoles = (roles) => ({ + type: ACTION_TYPE.REPLACE_SEARCHED_ROLES, + payload: { roles, lastRoleId: roles[roles.length - 1].searchId }, +}); diff --git a/src/routes/CreateNewTeam/components/AddAnotherModal/index.jsx b/src/routes/CreateNewTeam/components/AddAnotherModal/index.jsx index c99efcb9..ff1b337b 100644 --- a/src/routes/CreateNewTeam/components/AddAnotherModal/index.jsx +++ b/src/routes/CreateNewTeam/components/AddAnotherModal/index.jsx @@ -6,25 +6,9 @@ */ import React from "react"; import PT from "prop-types"; -import Modal from "react-responsive-modal"; import Button from "components/Button"; -import IconCrossLight from "../../../../assets/images/icon-cross-light.svg"; import 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", -}; +import BaseCreateModal from "../BaseCreateModal"; function AddAnotherModal({ open, @@ -33,52 +17,38 @@ function AddAnotherModal({ submitDone, addAnother, }) { + const buttons = ( + <> + + + + ); + return ( - - } - styles={{ - modal: modalStyle, - modalContainer: containerStyle, - }} - > -
- {!submitDone ? ( - <> - -
Submitting Request...
- - ) : ( - <> - -
Add Another Position
-

You can add another position to your request if you want to.

- - )} -
-
- - -
-
+ headerIcon={} + title="Add Another Position" + subtitle="You can add another position to your request if you want to." + buttons={buttons} + isLoading={!submitDone} + maxWidth="480px" + /> ); } diff --git a/src/routes/CreateNewTeam/components/AddAnotherModal/styles.module.scss b/src/routes/CreateNewTeam/components/AddAnotherModal/styles.module.scss deleted file mode 100644 index b0270326..00000000 --- a/src/routes/CreateNewTeam/components/AddAnotherModal/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/components/BaseCreateModal/index.jsx b/src/routes/CreateNewTeam/components/BaseCreateModal/index.jsx index 215277cb..d7fcecbb 100644 --- a/src/routes/CreateNewTeam/components/BaseCreateModal/index.jsx +++ b/src/routes/CreateNewTeam/components/BaseCreateModal/index.jsx @@ -61,7 +61,7 @@ function BaseCreateModal({ ) : ( <>
-
{headerIcon}
+ {headerIcon &&
{headerIcon}
}
{title}
{subtitle &&

{subtitle}

}
diff --git a/src/routes/CreateNewTeam/components/ConfirmationModal/index.jsx b/src/routes/CreateNewTeam/components/ConfirmationModal/index.jsx new file mode 100644 index 00000000..3f633722 --- /dev/null +++ b/src/routes/CreateNewTeam/components/ConfirmationModal/index.jsx @@ -0,0 +1,72 @@ +/** + * Confirmation Modal + * Final popup to accept user's agreement to + * commitment and confirm submission of request. + */ +import React, { useState } from "react"; +import PT from "prop-types"; +import BaseCreateModal from "../BaseCreateModal"; +import Button from "components/Button"; +import "./styles.module.scss"; +import Checkbox from "components/Checkbox"; + +function ConfirmationModal({ open, onClose, onSubmit, isLoading }) { + const [agreed, setAgreed] = useState(false); + + const toggleAgreed = () => { + setAgreed((agreed) => !agreed); + }; + + const confirmButton = ( + + ); + + return ( + +
+
Our Commitment to You
+

+ We will do everything we can to find the talent you need within the + Topcoder community. +

+
Your Commitment to Us
+

+ You will only post genuine job opportunities, and will be responsive + and communicative with the candidates provided. You recognize the + freelancers in the Topcoder community are real people making big + decisions based on your engagement with them. +

+
+
+ +
+
+ ); +} + +ConfirmationModal.propTypes = { + open: PT.bool, + onClose: PT.func, + onSubmit: PT.func, + isLoading: PT.bool, +}; + +export default ConfirmationModal; diff --git a/src/routes/CreateNewTeam/components/ConfirmationModal/styles.module.scss b/src/routes/CreateNewTeam/components/ConfirmationModal/styles.module.scss new file mode 100644 index 00000000..65252c41 --- /dev/null +++ b/src/routes/CreateNewTeam/components/ConfirmationModal/styles.module.scss @@ -0,0 +1,33 @@ +@import "styles/include"; + +.agreement { + padding: 24px; + background-color: #fbfbfb; + border: 1px solid #f4f4f4; + border-radius: 8px; + margin-bottom: 24px; + + h5 { + @include font-barlow; + font-weight: 700; + color: #2a2a2a; + font-size: 20px; + margin-bottom: 8px; + text-transform: uppercase; + } + + p { + @include font-roboto; + font-size: 16px; + line-height: 26px; + color: #555; + margin-bottom: 32px; + } +} + +.check-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} \ No newline at end of file diff --git a/src/routes/CreateNewTeam/components/ItemList/index.jsx b/src/routes/CreateNewTeam/components/ItemList/index.jsx new file mode 100644 index 00000000..12033b13 --- /dev/null +++ b/src/routes/CreateNewTeam/components/ItemList/index.jsx @@ -0,0 +1,77 @@ +import React, { useEffect, useState } from "react"; +import PT from "prop-types"; +import { useDebounce } from "react-use"; +import { INPUT_DEBOUNCE_DELAY } from "constants/"; +import PageHeader from "components/PageHeader"; +import "./styles.module.scss"; +import Input from "components/Input"; + +function ItemList({ + filterItems, + title, + filterPlaceholder, + subtitle, + children, +}) { + const [filter, setFilter] = useState(""); + const [debouncedFilter, setDebouncedFilter] = useState(""); + + const onFilterChange = (e) => { + setFilter(e.target.value); + }; + + useDebounce( + () => { + setDebouncedFilter(filter); + }, + INPUT_DEBOUNCE_DELAY, + [filter] + ); + + useEffect(() => { + const filterText = debouncedFilter.toLowerCase(); + filterItems(filterText); + }, [debouncedFilter, filterItems]); + + return ( +
+ + + {filter && ( + setFilter("")} + > + X + + )} + + } + /> + {subtitle &&

{subtitle}

} +
{children}
+
+ ); +} + +ItemList.propTypes = { + filterItems: PT.func, + title: PT.string, + filterPlaceholder: PT.string, + subtitle: PT.string, + children: PT.node, +}; + +export default ItemList; diff --git a/src/routes/CreateNewTeam/pages/SelectRole/components/RolesList/styles.module.scss b/src/routes/CreateNewTeam/components/ItemList/styles.module.scss similarity index 89% rename from src/routes/CreateNewTeam/pages/SelectRole/components/RolesList/styles.module.scss rename to src/routes/CreateNewTeam/components/ItemList/styles.module.scss index 5cd71f66..eaa1099f 100644 --- a/src/routes/CreateNewTeam/pages/SelectRole/components/RolesList/styles.module.scss +++ b/src/routes/CreateNewTeam/components/ItemList/styles.module.scss @@ -1,8 +1,9 @@ @import "styles/include"; -.roles-list { +.item-list { @include rounded-card; max-width: 746px; + width: 100%; margin-right: 20px; position: relative; height: 80vh; @@ -13,7 +14,7 @@ } } -.role-count { +.subtitle { position: absolute; font-size: 12px; top: 72px; @@ -37,7 +38,7 @@ input:not([type="checkbox"]).filter-input { padding: 0 15px; &:not(:focus) { - background-image: url("../../../../../../assets/images/icon-search.svg"); + background-image: url("../../../../assets/images/icon-search.svg"); background-repeat: no-repeat; background-position: 10px center; text-indent: 20px; @@ -60,10 +61,10 @@ input:not([type="checkbox"]).filter-input { } } -.role-container { +.list-container { display: flex; flex-direction: row; justify-content: flex-start; flex-wrap: wrap; margin-right: 24px; -} +} \ No newline at end of file diff --git a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx index 492fd2b7..18940725 100644 --- a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx @@ -9,7 +9,7 @@ import IconEarthX from "../../../../assets/images/icon-earth-x.svg"; import Curve from "../../../../assets/images/curve.svg"; import Button from "components/Button"; -function NoMatchingProfilesResultCard({ prevSearchId, addedRoles }) { +function NoMatchingProfilesResultCard() { return (
@@ -28,10 +28,7 @@ function NoMatchingProfilesResultCard({ prevSearchId, addedRoles }) {

$1,200

/Week

- + diff --git a/src/routes/CreateNewTeam/components/ResultCard/index.jsx b/src/routes/CreateNewTeam/components/ResultCard/index.jsx index ac88dcfe..c6920c22 100644 --- a/src/routes/CreateNewTeam/components/ResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/ResultCard/index.jsx @@ -29,12 +29,12 @@ function ResultCard({ role }) { isExternalMember, rates: [rates], } = role; - const [userHandle, setUserHandle] = useState("handle"); + const [userHandle, setUserHandle] = useState(null); const [showRates, setShowRates] = useState(false); useEffect(() => { getAuthUserProfile().then((res) => { - setUserHandle(res.handle || "handle"); + setUserHandle(res.handle || null); }); }, []); @@ -68,30 +68,32 @@ function ResultCard({ role }) {
{showRates && !isExternalMember && (
-

- Hi {userHandle}, we have special rates for you as a Xeno User! -

+ {userHandle && ( +

+ Hi {userHandle}, we have special rates for you as a Xeno User! +

+ )}

Full-Time

(40h / week)

-
+

Global Rate

{formatRate(rates.global)}

/Week

-
+

In-Country Rate

{formatRate(rates.inCountry)}

/Week

-
+

Offshore Rate

{formatRate(rates.offShore)}

@@ -104,21 +106,21 @@ function ResultCard({ role }) {

Part-Time

(30h / week)

-
+

Global Rate

{formatRate(rates.rate30Global)}

/Week

-
+

In-Country Rate

{formatRate(rates.rate30InCountry)}

/Week

-
+

Offshore Rate

{formatRate(rates.rate30OffShore)}

@@ -131,21 +133,21 @@ function ResultCard({ role }) {

Part-Time

(20h / week)

-
+

Global Rate

{formatRate(rates.rate20Global)}

/Week

-
+

In-Country Rate

{formatRate(rates.rate20InCountry)}

/Week

-
+

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. +

+ + + +
+
+ ); +} + +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 ( +
+ {({ handleSubmit, hasValidationErrors }) => { + return ( + + Submit + + } + > +
+ + {showDescription && ( + + )} + + + + + + + + + {addedRoles.map(({ searchId: id, name }) => ( + + + + + + + ))} +
+ {formatPlural(addedRoles.length, "Role")} Added + # of resourcesDuration (weeks)Start month
{name} + + + + + + + {startMonthVisible[id] ? ( + <> + + + + ) : ( + + )} +
+
+
+ ); + }} +
+ ); +} + +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 ( -
- - - {filter && ( - setFilter("")} - > - X - - )} - - } - /> -
- {filteredRoles.map(({ id, name, imageUrl }) => ( - - ))} -
-
+ + {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); +};