diff --git a/src/assets/images/customer-logos.svg b/src/assets/images/customer-logos.svg index 0719e61e..6748a16c 100644 --- a/src/assets/images/customer-logos.svg +++ b/src/assets/images/customer-logos.svg @@ -1,139 +1,192 @@ - - - Homepage-Logos - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + diff --git a/src/components/Checkbox/index.jsx b/src/components/Checkbox/index.jsx index b4cbb52d..0431fa0f 100644 --- a/src/components/Checkbox/index.jsx +++ b/src/components/Checkbox/index.jsx @@ -1,19 +1,27 @@ import React from "react"; import PT from "prop-types"; +import cn from "classnames"; import "./styles.module.scss"; -function Checkbox({ label, checked, onClick }) { +function Checkbox({ label, disabled, checkmarkFloat, checked, onClick }) { return ( - + {label} - - + + ); } Checkbox.propTypes = { label: PT.string, + checkmarkFloat: PT.oneOf(["right", "left"]), + disabled: PT.bool, checked: PT.bool, onClick: PT.func, }; diff --git a/src/components/Checkbox/styles.module.scss b/src/components/Checkbox/styles.module.scss index b80c99bd..69217b04 100644 --- a/src/components/Checkbox/styles.module.scss +++ b/src/components/Checkbox/styles.module.scss @@ -14,6 +14,10 @@ -moz-user-select: none; -ms-user-select: none; user-select: none; + &.disabled { + pointer-events: none; + color: #a89e9e; + } } /* Hide the browser's default checkbox */ @@ -29,12 +33,17 @@ .checkmark { position: absolute; top: 0; - left: 0; height: 20px; width: 20px; background-color: #fff; border: 1px solid #AAA; border-radius: 3px; + &.float-left { + left: 0; + } + &.float-right { + right: 0; + } } /* On mouse-over, add a grey background color */ @@ -48,6 +57,11 @@ box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.29); } +/* When the checkbox is disabled, grey out the background */ +.container input:disabled ~ .checkmark { + background-color: #cccccc; +} + /* Create the checkmark/indicator (hidden when not checked) */ .checkmark:after { content: ""; diff --git a/src/components/CustomerScroll/index.jsx b/src/components/CustomerScroll/index.jsx index bc89cecf..dba1e229 100644 --- a/src/components/CustomerScroll/index.jsx +++ b/src/components/CustomerScroll/index.jsx @@ -5,7 +5,7 @@ function CustomerScroll() { return ( Trusted By - + ); } diff --git a/src/components/CustomerScroll/styles.module.scss b/src/components/CustomerScroll/styles.module.scss index 60cfdf55..0aa7490e 100644 --- a/src/components/CustomerScroll/styles.module.scss +++ b/src/components/CustomerScroll/styles.module.scss @@ -10,45 +10,15 @@ margin-bottom: 30px; } -@keyframes scroll { - from {background-position: 0 0;} - to {background-position: -7701px 0;} -} - -.scrolling-logos { +.customer-logos { background-image: url("../../assets/images/customer-logos.svg"); - background-size: cover; - height: 60px; + background-size: contain; + background-repeat: no-repeat; + height: 56px; width: 100%; - animation: scroll 300s linear infinite; - position: relative; - - &:before { - background: linear-gradient(to right, #F4F5F6 0%, rgba(255, 255, 255, 0) 100%); - content: ''; - height: 60px; - left: 0; - position: absolute; - top: 0; - width: 60px; - z-index: 2; - } - &:after { - background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, #F4F5F6 100%); - content: ''; - height: 60px; - position: absolute; - right: 0; - top: 0; - width: 60px; - z-index: 2; - } } @media only screen and (max-height: 859px) { - .scrolling-logos { - height: 30px; - } .title { font-size: 16px; margin-bottom: 15px; diff --git a/src/constants/index.js b/src/constants/index.js index 235a67e7..588c2084 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -185,6 +185,15 @@ export const CANDIDATES_SORT_OPTIONS = [ { label: "Handle", value: CANDIDATES_SORT_BY.HANDLE }, ]; +/** + * "Full or Part Time" select options + */ +export const FULL_OR_PART_TIME_OPTIONS = [ + { label: "Full Time - 40hr/wk", value: "40" }, + { label: "Part Time - 30hr/wk", value: "30" }, + { label: "Part Time - 20hr/wk", value: "20" }, +]; + /** * All action types */ @@ -271,6 +280,11 @@ export const ACTION_TYPE = { ADD_MATCHING_ROLE: "ADD_MATCHING_ROLE", DELETE_MATCHING_ROLE: "DELETE_MATCHING_ROLE", EDIT_MATCHING_ROLE: "EDIT_MATCHING_ROLE", + + /* + * global loading state + */ + SET_IS_LOADING: "SET_IS_LOADING", }; /** @@ -378,3 +392,8 @@ export const CUSTOM_ROLE_NAMES = ["custom", "niche"]; * Minimal Resource Booking duration (weeks) */ export const MIN_DURATION = 4; + +/** + * Maximum allowed numbers of selecter skills for search. + */ +export const MAX_SELECTED_SKILLS = 3; diff --git a/src/hoc/withAuthentication/index.js b/src/hoc/withAuthentication/index.js index 62b66a85..0708f0c9 100644 --- a/src/hoc/withAuthentication/index.js +++ b/src/hoc/withAuthentication/index.js @@ -13,7 +13,11 @@ */ import React, { useEffect } from "react"; import _ from "lodash"; -import { getAuthUserTokens, login } from "@topcoder/micro-frontends-navbar-app"; +import { + getAuthUserTokens, + login, + businessLogin, +} from "@topcoder/micro-frontends-navbar-app"; import LoadingIndicator from "../../components/LoadingIndicator"; import { authUserSuccess, @@ -22,11 +26,12 @@ import { authLoadV5UserProfile, authClearTeamMembers, } from "./actions"; +import { setIsLoading } from "../../routes/CreateNewTeam/actions"; import { decodeToken } from "tc-auth-lib"; import { useDispatch, useSelector } from "react-redux"; import { useParams } from "@reach/router"; -export default function withAuthentication(Component) { +export default function withAuthentication(Component, businessAuth = false) { const AuthenticatedComponent = (props) => { const dispatch = useDispatch(); const { @@ -49,6 +54,7 @@ export default function withAuthentication(Component) { let isUnmount = false; if (!isLoggedIn) { + dispatch(setIsLoading(true)); getAuthUserTokens() .then(({ tokenV3 }) => { if (!!tokenV3) { @@ -59,7 +65,7 @@ export default function withAuthentication(Component) { ) ); } else if (!isUnmount) { - login(); + businessAuth ? businessLogin() : login(); } }) .catch((error) => dispatch(authUserError(error))); @@ -81,6 +87,7 @@ export default function withAuthentication(Component) { params.teamId && (!teamId || params.teamId !== teamId) ) { + dispatch(setIsLoading(true)); dispatch(authLoadTeamMembers(params.teamId)); // if we are going to some page without `teamId` then we have to clear team members @@ -96,6 +103,7 @@ export default function withAuthentication(Component) { useEffect(() => { // is user is logged-in, but V5 user profile is not loaded yet, then load it if (isLoggedIn && !v5UserProfileLoading && !v5UserProfile) { + dispatch(setIsLoading(true)); dispatch(authLoadV5UserProfile()); } }, [dispatch, isLoggedIn, v5UserProfileLoading, v5UserProfile]); @@ -129,3 +137,7 @@ export default function withAuthentication(Component) { return AuthenticatedComponent; } + +export function withBusinessAuthentication(Component) { + return withAuthentication(Component, true); +} diff --git a/src/routes/CreateNewTeam/actions/index.js b/src/routes/CreateNewTeam/actions/index.js index 62d6e04a..835cff1e 100644 --- a/src/routes/CreateNewTeam/actions/index.js +++ b/src/routes/CreateNewTeam/actions/index.js @@ -75,3 +75,8 @@ export const clearMatchingRole = () => (dispatch, getState) => { dispatch(deleteMatchingRole()); updateLocalStorage(getState().searchedRoles); }; + +export const setIsLoading = (isLoading) => ({ + type: ACTION_TYPE.SET_IS_LOADING, + payload: isLoading, +}); diff --git a/src/routes/CreateNewTeam/components/EditRoleForm/index.jsx b/src/routes/CreateNewTeam/components/EditRoleForm/index.jsx index e816f5aa..9402e51d 100644 --- a/src/routes/CreateNewTeam/components/EditRoleForm/index.jsx +++ b/src/routes/CreateNewTeam/components/EditRoleForm/index.jsx @@ -13,13 +13,14 @@ import InformationTooltip from "components/InformationTooltip"; import IconCrossLight from "../../../../assets/images/icon-cross-light.svg"; import "./styles.module.scss"; import NumberInput from "components/NumberInput"; +import Select from "components/Select"; import { validator, validateExists, validateMin, composeValidators, } from "./utils/validator"; -import { MIN_DURATION } from "constants"; +import { MIN_DURATION, FULL_OR_PART_TIME_OPTIONS } from "constants"; const Error = ({ name }) => { const { @@ -33,7 +34,7 @@ function EditRoleForm({ onChange, role }) { const onRoleChange = (state) => { if (state.hasValidationErrors) { onChange(false); - }else { + } else { onChange(true, state.values); } }; @@ -61,11 +62,15 @@ function EditRoleForm({ onChange, role }) { # of resources Duration (weeks) Start month + Full or Part Time @@ -88,7 +93,13 @@ function EditRoleForm({ onChange, role }) { @@ -143,6 +154,22 @@ function EditRoleForm({ onChange, role }) { )} + + + {(props) => ( + { + props.input.onChange(v); + onRoleChange(getState()); + }} + options={FULL_OR_PART_TIME_OPTIONS} + styleName="select" + /> + )} + + diff --git a/src/routes/CreateNewTeam/components/EditRoleForm/styles.module.scss b/src/routes/CreateNewTeam/components/EditRoleForm/styles.module.scss index d9f635f9..d6b20d29 100644 --- a/src/routes/CreateNewTeam/components/EditRoleForm/styles.module.scss +++ b/src/routes/CreateNewTeam/components/EditRoleForm/styles.module.scss @@ -41,7 +41,14 @@ margin-right: auto; } - padding: 18px 18px 18px 0; + .select { + border-radius: 4px; + width: 160px; + height: 34px; + font-size: 14px; + } + + padding: 18px 9px; width: 30%; vertical-align: top; @include font-barlow; diff --git a/src/routes/CreateNewTeam/components/InputContainer/index.jsx b/src/routes/CreateNewTeam/components/InputContainer/index.jsx index f87a671c..6ded40ff 100644 --- a/src/routes/CreateNewTeam/components/InputContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/InputContainer/index.jsx @@ -27,7 +27,7 @@ function InputContainer({ { - setButtonClickable(isValid) + setButtonClickable(isValid); if (isValid) { dispatch(editRoleAction({ ...role, searchId: previousSearchId })); } }, - [addedRoles, previousSearchId] + [dispatch, previousSearchId] ); const onSubmit = useCallback(() => { diff --git a/src/routes/CreateNewTeam/components/SkillListPopup/index.jsx b/src/routes/CreateNewTeam/components/SkillListPopup/index.jsx new file mode 100644 index 00000000..e3b43d1d --- /dev/null +++ b/src/routes/CreateNewTeam/components/SkillListPopup/index.jsx @@ -0,0 +1,110 @@ +/** + * Temporary Popup for skill list + * show skill list + */ +import React, { useCallback } from "react"; +import { MAX_SELECTED_SKILLS } from "constants"; +import _ from "lodash"; +import PT from "prop-types"; +import Button from "components/Button"; +import Checkbox from "components/Checkbox"; +import IconSingleManAdd from "../../../../assets/images/icon-single-man-add.svg"; +import "./styles.module.scss"; +import BaseCreateModal from "../BaseCreateModal"; + +function SkillListPopup({ + page, + open, + skills, + selectedSkills, + setSelectedSkills, + isLoading, + loadingTxt, + onClose, + onContinueClick, +}) { + const subTitle = + page === "jd" + ? `Topcoder has identified the following skills referenced in your job description. Select your the ${MAX_SELECTED_SKILLS} skills most important to be successful in the job. These skills will be weighted more heavily in matching.` + : `You have chosen the following skills in your request. Please select your the ${MAX_SELECTED_SKILLS} skills most important to be successful in the job. These skills will be weighted more heavily in matching.`; + const skillsNotFoundTxt = "No skills are found in your Job Description"; + + const toggleSkill = useCallback( + (skillId) => { + const skillIdx = selectedSkills.indexOf(skillId); + setSelectedSkills((skills) => + skillIdx > -1 + ? [...skills.slice(0, skillIdx), ...skills.slice(skillIdx + 1)] + : [...skills, skillId] + ); + }, + [selectedSkills, setSelectedSkills] + ); + const Buttons = ( + <> + + {page === "jd" ? "Edit Job Description" : "Edit Skill Selections"} + + + Continue + + > + ); + + return ( + } + title="Identified Skills" + subtitle={skills.length ? subTitle : skillsNotFoundTxt} + isLoading={isLoading} + loadingMessage={loadingTxt || "Loading..."} + maxWidth="560px" + buttons={Buttons} + > + + {_.map(skills, (s) => { + return ( + + toggleSkill(s.id)} + label={s.tag || s.name} + checkmarkFloat="right" + /> + + ); + })} + + {selectedSkills.length === MAX_SELECTED_SKILLS && + selectedSkills.length !== skills.length && ( + + You can select up to {MAX_SELECTED_SKILLS} skills. + + )} + + ); +} + +SkillListPopup.propTypes = { + page: PT.oneOf(["jd", "skills"]), + open: PT.bool, + onClose: PT.func, + isLoading: PT.bool, + loadingTxt: PT.string, + onContinueClick: PT.func, + skills: PT.arrayOf(PT.shape()), +}; + +export default SkillListPopup; diff --git a/src/routes/CreateNewTeam/components/SkillListPopup/styles.module.scss b/src/routes/CreateNewTeam/components/SkillListPopup/styles.module.scss new file mode 100644 index 00000000..9d42a0e7 --- /dev/null +++ b/src/routes/CreateNewTeam/components/SkillListPopup/styles.module.scss @@ -0,0 +1,32 @@ +@import "styles/include"; + +.list { + @include font-roboto; + font-size: 18px; + line-height: normal; + color: #2a2a2a; + align-self: center; + text-align: left; + li { + padding-right: 30px; + &:not(:last-child) { + margin-bottom: 5px; + } + label:hover { + color: rgba(#2a2a2a, 0.8); + font-weight: 700; + } + } +} + +.max-selection { + @include font-roboto; + font-size: 14px; + color: #da0e0e; + padding-left: 30px; + margin-top: 15px; +} + +.continue-button:disabled { + background-color: #997b7be6; +} diff --git a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx index 5a523cfd..f00a6bc6 100644 --- a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx @@ -21,7 +21,7 @@ import Progress from "../Progress"; import AddAnotherModal from "../AddAnotherModal"; import TeamDetailsModal from "../TeamDetailsModal"; import ConfirmationModal from "../ConfirmationModal"; -import withAuthentication from "../../../../hoc/withAuthentication"; +import { withBusinessAuthentication } from "../../../../hoc/withAuthentication"; import "./styles.module.scss"; import { isCustomRole, isUuid, setCurrentStage } from "utils/helpers"; import { clearSearchedRoles } from "../../actions"; @@ -159,4 +159,4 @@ SubmitContainer.propTypes = { matchingRole: PT.object, }; -export default withAuthentication(SubmitContainer); +export default withBusinessAuthentication(SubmitContainer); diff --git a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx index fde71b11..2fd2b479 100644 --- a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx +++ b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx @@ -9,10 +9,11 @@ import { Form, Field, useField } from "react-final-form"; import { useDispatch } from "react-redux"; import FormField from "components/FormField"; import BaseCreateModal from "../BaseCreateModal"; -import { FORM_FIELD_TYPE } from "constants/"; +import { FORM_FIELD_TYPE, FULL_OR_PART_TIME_OPTIONS } from "constants/"; import { formatPlural } from "utils/format"; import { isUuid } from "utils/helpers"; import Button from "components/Button"; +import Select from "components/Select"; import MonthPicker from "components/MonthPicker"; import InformationTooltip from "components/InformationTooltip"; import { deleteSearchedRole } from "../../actions"; @@ -78,6 +79,7 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { # of resources Duration (weeks) Start month + Full or Part Time {addedRoles.map( @@ -153,6 +156,7 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { numberOfResources, durationWeeks, startMonth, + hoursPerWeek, }) => ( {name} @@ -235,6 +239,22 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { )} + + + {(props) => ( + + )} + + ( - - {props.children} - - +const CreateNewTeam = (props) => { + const { isLoading } = useSelector((state) => state.searchedRoles); + + return ( + + {props.children} + {!isLoading && ( + + + + )} - -); + ); +}; export default CreateNewTeam; diff --git a/src/routes/CreateNewTeam/pages/CreateTeamLanding/index.jsx b/src/routes/CreateNewTeam/pages/CreateTeamLanding/index.jsx index a00bf427..8cd612be 100644 --- a/src/routes/CreateNewTeam/pages/CreateTeamLanding/index.jsx +++ b/src/routes/CreateNewTeam/pages/CreateTeamLanding/index.jsx @@ -25,10 +25,11 @@ function CreateNewTeam() { }; return ( - - Create New Team} /> + + Find your Talent} /> - Please select how you want to find members that match your requirements. + Please select how you'd like to search the Topcoder community for a + perfect match. - - Edit Job Description - - - Continue - - > - ); - - return ( - } - title="Identified Skills" - subtitle={ - skills.length - ? "Topcoder has identified the following skills referenced in your Job Description." - : "No skills are found in your Job Description" - } - isLoading={isLoading} - loadingMessage="Loading skills..." - maxWidth="460px" - buttons={Buttons} - > - - {_.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 ee0c59e2..00000000 --- a/src/routes/CreateNewTeam/pages/InputJobDescription/components/SkillListPopup/styles.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -@import "styles/include"; - -.list { - @include font-roboto; - font-size: 18px; - line-height: normal; - color: #2a2a2a; - text-align: center; -} \ No newline at end of file diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx index 1a961fa5..53244633 100644 --- a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx @@ -11,7 +11,7 @@ import "./styles.module.scss"; import SearchAndSubmit from "../../components/SearchAndSubmit"; import TextInput from "components/TextInput"; import { getSkillsByJobDescription } from "services/teams"; -import SkillListPopup from "./components/SkillListPopup"; +import SkillListPopup from "../../components/SkillListPopup"; function InputJobDescription() { const [stages, setStages] = useState([ @@ -23,6 +23,7 @@ function InputJobDescription() { const [jobTitle, setJobTitle] = useState(""); const [skills, setSkills] = useState([]); const [loadingSkills, setLoadingSkills] = useState(true); + const [selectedSkills, setSelectedSkills] = useState([]); const [popupOpen, setPopupOpen] = useState(false); const onEditChange = useCallback((value) => { @@ -30,15 +31,13 @@ function InputJobDescription() { }, []); const searchObject = useMemo(() => { - if (jobTitle && jobTitle.length) { - return { jobDescription: jdString, jobTitle }; - } - return { jobDescription: jdString }; - }, [jobTitle, jdString]); + return { skills: selectedSkills }; + }, [selectedSkills]); const onClick = useCallback(() => { setLoadingSkills(true); setSkills([]); + setSelectedSkills([]); setPopupOpen(true); getSkillsByJobDescription(jdString) .then((res) => { @@ -52,6 +51,11 @@ function InputJobDescription() { }); }, [jdString]); + const onContinueClick = useCallback((searchFunc) => { + setLoadingSkills(true); + setTimeout(searchFunc, 100); + }, []); + return ( setPopupOpen(false)} skills={skills} + selectedSkills={selectedSkills} + setSelectedSkills={setSelectedSkills} isLoading={loadingSkills} - onContinueClick={searchFunc} + loadingTxt={selectedSkills.length === 0 && "Loading skills..."} + onContinueClick={() => onContinueClick(searchFunc)} /> )} diff --git a/src/routes/CreateNewTeam/pages/InputSkills/components/SkillItem/index.jsx b/src/routes/CreateNewTeam/pages/InputSkills/components/SkillItem/index.jsx index 66a819e7..6db25783 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/components/SkillItem/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputSkills/components/SkillItem/index.jsx @@ -18,8 +18,10 @@ const assets = require.context( function SkillItem({ id, name, onClick, isSelected }) { return ( onClick(id)} + onClick={() => onClick({ id, name })} > {assets && assets.keys().includes(`./id-${id}.svg`) ? ( s.id === id) > -1} /> ))} diff --git a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx index 52a2b6cf..516c057b 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx @@ -6,42 +6,60 @@ * with those skills, and submitting a job requiring the skills. */ import React, { useCallback, useState } from "react"; +import { useDispatch } from "react-redux"; import { useData } from "hooks/useData"; +import { setIsLoading } from "../../actions"; import SkillsList from "./components/SkillsList"; import { getSkills } from "services/skills"; import LoadingIndicator from "components/LoadingIndicator"; -import SearchContainer from "../../components/SearchContainer"; +import SkillListPopup from "../../components/SkillListPopup"; import SearchAndSubmit from "../../components/SearchAndSubmit"; function InputSkills() { + const dispatch = useDispatch(); const [stages, setStages] = useState([ { name: "Input Skills", isCurrent: true }, { name: "Search Member" }, { name: "Overview of the Results" }, ]); const [selectedSkills, setSelectedSkills] = useState([]); - + const [popupSelectedSkills, setPopupSelectedSkills] = useState([]); + const [popupOpen, setPopupOpen] = useState(false); + const [isPopupLoading, setIsPopupLoading] = useState(false); const [skills, loadingError] = useData(getSkills); const toggleSkill = useCallback( - (id) => { - if (selectedSkills.includes(id)) { - setSelectedSkills(selectedSkills.filter((skill) => skill !== id)); + (skill) => { + const isSelected = + selectedSkills.findIndex((s) => s.id === skill.id) > -1; + if (isSelected) { + setSelectedSkills(selectedSkills.filter((s) => s.id !== skill.id)); } else { setSelectedSkills(() => { - return [...selectedSkills, id]; + return [...selectedSkills, skill]; }); } }, [selectedSkills] ); + const onClick = useCallback(() => { + setPopupSelectedSkills([]); + setPopupOpen(true); + }, []); + + const onContinueClick = useCallback((searchFunc) => { + setIsPopupLoading(true); + setTimeout(searchFunc, 100); + }, []); + if (!Array.isArray(skills)) { + dispatch(setIsLoading(true)); return ; - } - - if (skills.length === 0) { + } else if (skills.length === 0) { return Failed to load skills; + } else { + dispatch(setIsLoading(false)); } return ( @@ -49,15 +67,28 @@ function InputSkills() { stages={stages} setStages={setStages} isProgressDisabled={selectedSkills.length < 1} - searchObject={{ skills: selectedSkills }} + searchObject={{ skills: popupSelectedSkills }} page="skills" progressStyle="input-skills" - toRender={() => ( - + onClick={onClick} + toRender={(searchFunc) => ( + <> + + setPopupOpen(false)} + skills={selectedSkills} + selectedSkills={popupSelectedSkills} + setSelectedSkills={setPopupSelectedSkills} + isLoading={isPopupLoading} + onContinueClick={() => onContinueClick(searchFunc)} + /> + > )} /> ); diff --git a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx index 057bb02e..88dcc6ff 100644 --- a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx +++ b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx @@ -5,7 +5,9 @@ * with that role, and submitting a job requiring the roles. */ import React, { useCallback, useState } from "react"; +import { useDispatch } from "react-redux"; import { useData } from "hooks/useData"; +import { setIsLoading } from "../../actions"; import RolesList from "./components/RolesList"; import { getRoles } from "services/roles"; import LoadingIndicator from "components/LoadingIndicator"; @@ -18,6 +20,7 @@ const removeCustomRoles = (roles) => roles.filter((role) => !isCustomRole(role)); function SelectRole() { + const dispatch = useDispatch(); const [stages, setStages] = useState([ { name: "Select a Role", isCurrent: true }, { name: "Search Member" }, @@ -39,7 +42,10 @@ function SelectRole() { }, []); if (!roles) { + dispatch(setIsLoading(true)); return ; + } else { + dispatch(setIsLoading(false)); } return ( diff --git a/src/routes/CreateNewTeam/reducers/index.js b/src/routes/CreateNewTeam/reducers/index.js index 4c16c849..3e14ceac 100644 --- a/src/routes/CreateNewTeam/reducers/index.js +++ b/src/routes/CreateNewTeam/reducers/index.js @@ -8,6 +8,7 @@ const loadState = () => { previousSearchId: undefined, addedRoles: [], matchingRole: undefined, + isLoading: false, }; try { const state = localStorage.getItem("rolesState"); @@ -81,6 +82,12 @@ const reducer = (state = initialState, action) => { case ACTION_TYPE.DELETE_SEARCHED_ROLE: return deleteRoleInState(state, action.payload); + case ACTION_TYPE.SET_IS_LOADING: + return { + ...state, + isLoading: action.payload, + }; + default: return state; } diff --git a/src/routes/CreateNewTeam/styles.module.scss b/src/routes/CreateNewTeam/styles.module.scss index ba56c3ed..ffd6ec2f 100644 --- a/src/routes/CreateNewTeam/styles.module.scss +++ b/src/routes/CreateNewTeam/styles.module.scss @@ -1,25 +1,4 @@ .logos { - position: fixed; - bottom: 15px; - width: calc(100vw - 270px); - z-index: -1; -} - -@media only screen and (max-width: 1023px) { - .logos { - width: 100vw; - } -} - -@media only screen and (max-height: 859px) { - .logos { - position: relative; - width: 100vw; - } -} - -@media only screen and (max-height: 859px) and (min-width: 1024px) { - .logos { - left: -270px; - } + padding: 0 35px 15px; + width: 100%; }
+ You can select up to {MAX_SELECTED_SKILLS} skills. +
- Please select how you want to find members that match your requirements. + Please select how you'd like to search the Topcoder community for a + perfect match.
Failed to load skills