diff --git a/local/login-locally/package-lock.json b/local/login-locally/package-lock.json new file mode 100644 index 00000000..35fdb913 --- /dev/null +++ b/local/login-locally/package-lock.json @@ -0,0 +1,5 @@ +{ + "name": "login-locally", + "version": "1.0.0", + "lockfileVersion": 1 +} diff --git a/package-lock.json b/package-lock.json index d0d5c0df..4239388b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4819,9 +4819,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001146", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001146.tgz", - "integrity": "sha512-VAy5RHDfTJhpxnDdp2n40GPPLp3KqNrXz1QqFv4J64HvArKs8nuNMOWkB3ICOaBTU/Aj4rYAo/ytdQDDFF/Pug==", + "version": "1.0.30001257", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz", + "integrity": "sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA==", "dev": true }, "capture-exit": { diff --git a/src/assets/images/icon-earth-x.svg b/src/assets/images/icon-earth-x.svg deleted file mode 100644 index 40037611..00000000 --- a/src/assets/images/icon-earth-x.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - F087A2AF-1A0A-4B0C-9EFD-323859E6F7A9 - - - - - - - - - - \ No newline at end of file diff --git a/src/assets/images/icon-person.svg b/src/assets/images/icon-person.svg new file mode 100644 index 00000000..9f3fa5a5 --- /dev/null +++ b/src/assets/images/icon-person.svg @@ -0,0 +1,11 @@ + + + 6689786D-49B2-4268-925B-3437502831F5 + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/trusted-logos.svg b/src/assets/images/trusted-logos.svg new file mode 100644 index 00000000..7fd53faa --- /dev/null +++ b/src/assets/images/trusted-logos.svg @@ -0,0 +1,3 @@ + + + \ 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 7a834283..20b6ee5f 100644 --- a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx @@ -8,12 +8,12 @@ import PT from "prop-types"; import { useDispatch, useSelector } from "react-redux"; import { addSearchedRole } from "../../actions"; import "./styles.module.scss"; -import IconEarthX from "../../../../assets/images/icon-earth-x.svg"; +import IconPerson from "../../../../assets/images/icon-person.svg"; import Curve from "../../../../assets/images/curve.svg"; import Button from "components/Button"; import { formatMoney } from "utils/format"; -function NoMatchingProfilesResultCard({ role }) { +function NoMatchingProfilesResultCard({ role, onSubmit }) { const { addedRoles } = useSelector((state) => state.searchedRoles); const alreadyAdded = useMemo(() => { @@ -35,62 +35,48 @@ function NoMatchingProfilesResultCard({ role }) { if (role.jobTitle && role.jobTitle.length) { name = role.jobTitle; } - dispatch( - addSearchedRole({ - searchId, - name, - rates: role.rates, - imageUrl: role.imageUrl, - }) - ); - }, [dispatch, role]); + if (!alreadyAdded) { + dispatch( + addSearchedRole({ + searchId, + isCustomRole: true, + name, + rates: role.rates, + imageUrl: role.imageUrl, + }) + ); + } + onSubmit(); + }, [dispatch, role, alreadyAdded]); return (
- -

Dang. No matching talent (yet)

+ +

locating available custom talent

-
-

- {role.jobTitle && role.jobTitle.length - ? role.jobTitle - : "What happens next"} -

+

What happens next

- We did not find a perfect match to your requirements, but we'd like to - dig a little deeper into our community. We’ll start right away, and - this may take up to two weeks. You can modify your criteria, or - continue this search. If you choose to continue, we will reach out - soon with next steps. + We routinely place great people with the skills you’ve asked for. + Right now, we don’t have anyone available. However, our database is + dynamic and updated often. Please continue below so we can finalize + your talent request and alert you when a great candidate becomes + available.

- {role.rates && role.name ? ( -
-

Estimate for this role

-

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

-

/Week

-
- ) : ( -
-

Estimate for this role

-

$1,200

-

/Week

-
- )}
@@ -100,6 +86,7 @@ function NoMatchingProfilesResultCard({ role }) { NoMatchingProfilesResultCard.propTypes = { role: PT.object, + onSubmit: PT.func, }; export default NoMatchingProfilesResultCard; diff --git a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/styles.module.scss b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/styles.module.scss index 8c4765b7..008b04f7 100644 --- a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/styles.module.scss +++ b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/styles.module.scss @@ -15,7 +15,7 @@ padding: 30px 0 60px 0; margin-bottom: 14px; color: #fff; - background-image: linear-gradient(225deg, #555555 0%, #2a2a2a 100%); + background-image: linear-gradient(90deg, #F45500 0%, #FF940F 100%); position: relative; text-align: center; border-radius: 8px 8px 0 0; @@ -59,38 +59,6 @@ width: 80%; text-align: center; } - .niche-rate-box { - margin-top: 32px; - background-color: #fbfbfb; - padding: 30px; - border: 1px solid #f4f4f4; - border-radius: 6px; - width: 196px; - p:first-child { - @include font-barlow; - margin-top: 4px; - font-size: 16px; - line-height: 20px; - font-weight: 600; - text-align: center; - text-transform: uppercase; - } - p:last-child { - @include font-roboto; - color: #555555; - font-size: 14px; - line-height: 22px; - text-align: center; - } - .cost { - @include font-barlow-condensed; - margin-top: 8px; - font-size: 48px; - line-height: 50px; - font-weight: 500; - text-align: center; - } - } .button-group { margin-top: 62px; @@ -110,12 +78,3 @@ bottom: -70px; width: 100%; } - -.transparent-icon { - position: absolute; - top: -40px; - right: 10px; - opacity: 12%; - height: 142px; - width: 142px; -} diff --git a/src/routes/CreateNewTeam/components/ResultCard/index.jsx b/src/routes/CreateNewTeam/components/ResultCard/index.jsx index 50ebef44..64cb8cb8 100644 --- a/src/routes/CreateNewTeam/components/ResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/ResultCard/index.jsx @@ -245,10 +245,7 @@ function ResultCard({
-
-

Interviews can start within

-
{timeToInterview}h
diff --git a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx index 291de894..bd481eb3 100644 --- a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx @@ -77,7 +77,7 @@ function SearchContainer({ currentRole={currentRole} /> ); - return ; + return ; }; const progressBarPercentage = useMemo( diff --git a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx index 934275b6..0d9835ae 100644 --- a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx @@ -109,7 +109,9 @@ function SubmitContainer({ position.roleSearchRequestId = key; position.roleName = addedRoles.find((role) => role.searchId === key).name; - + const role = addedRoles.find((role) => role.searchId === key); + position.roleName = role.name; + position.isCustomRole = role.isCustomRole; positions.push(position); } teamObject.positions = positions; @@ -143,6 +145,9 @@ function SubmitContainer({ navigate("/taas/myteams/createnewteam/create-taas-payment"); } else { setMsg(true); + teamObject.positions = _.map(teamObject.positions, (p) => + _.omit(p, "isCustomRole") + ); postTeamRequest(teamObject) .then(() => { setTimeout(() => { @@ -176,7 +181,10 @@ function SubmitContainer({ currentRole={currentRole} /> ) : ( - + setAddAnotherOpen(true)} + /> )}
diff --git a/src/routes/CreateNewTeam/hooks/useLoadSkills.js b/src/routes/CreateNewTeam/hooks/useLoadSkills.js new file mode 100644 index 00000000..86b0f695 --- /dev/null +++ b/src/routes/CreateNewTeam/hooks/useLoadSkills.js @@ -0,0 +1,36 @@ +/** + * useLoadSkills hook + */ +import { useEffect, useState } from "react"; +import { flatten, partition } from "lodash"; +import { useData } from "hooks/useData"; +import { getSkills } from "services/skills"; +import { getRoles } from "services/roles"; + +/** + * Hook which loads all skills and roles, then partitions skills based + * on whether any role requires the given skill. + * + * @returns [skills, error] tuple with `skills` array and `error` object + */ +export const useLoadSkills = () => { + const [skills, skillsError] = useData(getSkills); + const [roles, rolesError] = useData(getRoles); + const [partedSkills, setPartedSkills] = useState(); + + useEffect(() => { + if (skills && roles) { + const requiredSkills = new Set(); + roles.forEach((role) => { + role.listOfSkills.forEach((skill) => { + requiredSkills.add(skill); + }); + }); + setPartedSkills(() => + flatten(partition(skills, (skill) => requiredSkills.has(skill.name))) + ); + } + }, [skills, roles]); + + return [partedSkills, skillsError || rolesError]; +}; diff --git a/src/routes/CreateNewTeam/pages/CreateTaasPayment/PaymentForm/index.jsx b/src/routes/CreateNewTeam/pages/CreateTaasPayment/PaymentForm/index.jsx index c34c7b1c..17c17188 100644 --- a/src/routes/CreateNewTeam/pages/CreateTaasPayment/PaymentForm/index.jsx +++ b/src/routes/CreateNewTeam/pages/CreateTaasPayment/PaymentForm/index.jsx @@ -142,6 +142,7 @@ const PaymentForm = ({ calculatedAmount }) => { } else if (payload.paymentIntent.status === "succeeded") { toastr.success("Payment is successful"); // setRequestLoading(true); + teamObject.positions = _.map(teamObject.positions, p=> _.omit(p, 'isCustomRole')) postTeamRequest(teamObject) .then((res) => { setProjectId(_.get(res, "data.projectId")); diff --git a/src/routes/CreateNewTeam/pages/CreateTaasPayment/PaymentRule/index.jsx b/src/routes/CreateNewTeam/pages/CreateTaasPayment/PaymentRule/index.jsx new file mode 100644 index 00000000..bf4268e0 --- /dev/null +++ b/src/routes/CreateNewTeam/pages/CreateTaasPayment/PaymentRule/index.jsx @@ -0,0 +1,84 @@ +import React, { useState } from "react"; +import cn from "classnames"; +import { useDispatch, useSelector } from "react-redux"; +import { navigate } from "@reach/router"; +import _ from "lodash"; +import { toastr } from "react-redux-toastr"; +import { postTeamRequest } from "services/teams"; +import Button from "components/Button"; +import Checkbox from "components/Checkbox"; +import Spinner from "components/CenteredSpinner"; +import { clearSearchedRoles } from "../../../actions"; + +import "./styles.module.scss"; + +const PaymentRule = () => { + const [processing, setProcessing] = useState(false); + const [isChecked, setIsChecked] = useState(false); + const [clicked, setClicked] = useState(true); + const [projectId, setProjectId] = useState(null); + const dispatch = useDispatch(); + const { teamObject } = useSelector((state) => state.searchedRoles); + + const handlePostTeam = async (e) => { + setProcessing(true); + teamObject.positions = _.map(teamObject.positions, p=> _.omit(p, 'isCustomRole')) + postTeamRequest(teamObject) + .then((res) => { + const projectId = _.get(res, "data.projectId"); + dispatch(clearSearchedRoles()); + navigate(`/taas/myteams/${projectId}`); + }) + .catch((err) => { + toastr.error("Error Requesting Team", err.message); + }) + .finally(() => { + setProcessing(false); + }); + }; + + 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. +
+
+ setIsChecked(!isChecked)} + /> +
+ +
+ ); +}; + +export default PaymentRule; diff --git a/src/routes/CreateNewTeam/pages/CreateTaasPayment/PaymentRule/styles.module.scss b/src/routes/CreateNewTeam/pages/CreateTaasPayment/PaymentRule/styles.module.scss new file mode 100644 index 00000000..bd21f64d --- /dev/null +++ b/src/routes/CreateNewTeam/pages/CreateTaasPayment/PaymentRule/styles.module.scss @@ -0,0 +1,64 @@ +@import "styles/include"; + +.commitment-container { + display: flex; + flex-direction: column; + margin: 0 10px 20px;; + + label { + margin-top: 10px; + font-size: 12px; + line-height: 15px; + span:last-child { + width: 15px; + height: 15px; + &:after { + left: 5px; + top: 2px ; + width: 4px ; + height: 9px ; + border-width: 0 2px 2px 0; + } + } + } + + + button[disabled] { + background-color: #e9e9e9; + color: #fff; + opacity: 1; + filter: none; + } + button { + margin-top: 10px; + display: flex; + width: 100%; + justify-content: center; + &.processing { + pointer-events: none; + } + .spinner { + margin-top: 3px; + margin-right: 5px; + } + } +} +.commitment-title { + @include font-barlow; + + margin-bottom: 2px; + font-weight: 600; + color: #2A2A2A; + font-size: 16px; + line-height: 20px; + text-transform: uppercase; +} + +.commitment-content { + @include font-roboto; + + color: #2A2A2A; + font-size: 14px; + line-height: 22px; + margin-bottom: 10px; +} diff --git a/src/routes/CreateNewTeam/pages/CreateTaasPayment/index.jsx b/src/routes/CreateNewTeam/pages/CreateTaasPayment/index.jsx index bca64103..4f4e7d26 100644 --- a/src/routes/CreateNewTeam/pages/CreateTaasPayment/index.jsx +++ b/src/routes/CreateNewTeam/pages/CreateTaasPayment/index.jsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useCallback } from "react"; import _ from "lodash"; +import cn from "classnames"; import { useSelector } from "react-redux"; import { Elements } from "@stripe/react-stripe-js"; import { loadStripe } from "@stripe/stripe-js"; @@ -7,11 +8,13 @@ import { ThemeProvider } from "@material-ui/styles"; import { toastr } from "react-redux-toastr"; import PaymentForm from "./PaymentForm"; +import PaymentRule from "./PaymentRule"; import PageHeader from "components/PageHeader"; import { calculateAmount } from "services/teams"; import Progress from "../../components/Progress"; import theme from "./theme"; import FallbackIcon from "../../../../assets/images/icon-role-fallback.svg"; +import TrustedLogos from "../../../../assets/images/trusted-logos.svg"; import "./styles.module.scss"; const stripePromise = loadStripe(process.env.STRIPE_PUBLIC_KEY); @@ -54,11 +57,14 @@ const CreateTassPayment = () => { imageUrl, name, rate, + isCustomRole: role.isCustomRole, numberOfResources, durationWeeks, availability, }); - amount.push({ rate, numberOfResources }); + if (!role.isCustomRole) { + amount.push({ rate, numberOfResources }); + } }); setValue(temp); @@ -109,15 +115,20 @@ const CreateTassPayment = () => {

{data.name}

    -
  • - {data.numberOfResources} x ${data.rate}/ Week -
  • + {!data.isCustomRole && ( +
  • + {data.numberOfResources} x ${data.rate}/ Week +
  • + )}
  • {data.durationWeeks} Week Duration
  • {data.availability}

- ${data.numberOfResources * data.rate} + $ + {data.isCustomRole + ? "0" + : data.numberOfResources * data.rate}


@@ -125,34 +136,44 @@ const CreateTassPayment = () => { ))} -

Deposit & Refund Terms

-
    -
  • This is a refundable deposit payment.
  • -
  • - Topcoder will find you qualified candidates within 2 weeks, or - your money back. -
  • -
  • - If we find you talent that meets your needs, this deposit will - be credited towards your payment. -
  • -
  • - If we are only able to partially fill your talent order, we - will refund any portion we cannot fulfill. -
  • -
  • - Future payments can be processed on this credit card or you - can arrange invoicing. -
  • -
+ {calculatedAmount ? ( + <> +

Deposit & Refund Terms

+
    +
  • This is a refundable deposit payment.
  • +
  • + Topcoder will find you qualified candidates within 2 + weeks, or your money back. +
  • +
  • + If we find you talent that meets your needs, this deposit + will be credited towards your payment. +
  • +
  • + If we are only able to partially fill your talent order, + we will refund any portion we cannot fulfill. +
  • +
  • + Future payments can be processed on this credit card or + you can arrange invoicing. +
  • +
+ + ) : null} -
+

${calculatedAmount}

Total Deposit


- + {calculatedAmount ? ( + + ) : ( + + )}
@@ -160,12 +181,18 @@ const CreateTassPayment = () => {
- +
+ +
+
Trusted By
+ +
+
); }; diff --git a/src/routes/CreateNewTeam/pages/CreateTaasPayment/styles.module.scss b/src/routes/CreateNewTeam/pages/CreateTaasPayment/styles.module.scss index 23516d0d..26a76d13 100644 --- a/src/routes/CreateNewTeam/pages/CreateTaasPayment/styles.module.scss +++ b/src/routes/CreateNewTeam/pages/CreateTaasPayment/styles.module.scss @@ -138,6 +138,10 @@ min-height: 570px; margin-left: 15px; + &.show-rule { + min-height: auto; + } + hr { background-color: #aaaaaa; height: 1px; @@ -167,3 +171,20 @@ } } } + +.trusted { + background-color: #FFF; + border-radius: 8px; + padding: 12px 10px 15px 10px; + width: 250px; + + h6 { + @include font-barlow; + font-weight: 600; + text-align: center; + color: #9D41C9; + font-size: 16px; + margin-bottom: 10px; + text-transform: uppercase; + } +} diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx index 0307083d..f6a49dad 100644 --- a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx @@ -31,11 +31,11 @@ function InputJobDescription() { }, []); const searchObject = useMemo(() => { - if (jobTitle && jobTitle.length) { - return { jobTitle, skills: selectedSkills }; + if (jobTitle && jobTitle.length && jdString) { + return { jobTitle, skills: selectedSkills, jobDescription: jdString }; } - return { skills: selectedSkills }; - }, [jobTitle, selectedSkills]); + return { skills: selectedSkills, jobDescription: jdString }; + }, [jobTitle, selectedSkills, jdString]); const onClick = useCallback(() => { setLoadingSkills(true); diff --git a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx index 516c057b..83ac07a9 100644 --- a/src/routes/CreateNewTeam/pages/InputSkills/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputSkills/index.jsx @@ -7,13 +7,12 @@ */ 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 SkillListPopup from "../../components/SkillListPopup"; import SearchAndSubmit from "../../components/SearchAndSubmit"; +import { useLoadSkills } from "../../hooks/useLoadSkills"; function InputSkills() { const dispatch = useDispatch(); @@ -26,7 +25,7 @@ function InputSkills() { const [popupSelectedSkills, setPopupSelectedSkills] = useState([]); const [popupOpen, setPopupOpen] = useState(false); const [isPopupLoading, setIsPopupLoading] = useState(false); - const [skills, loadingError] = useData(getSkills); + const [skills, loadingError] = useLoadSkills(); const toggleSkill = useCallback( (skill) => {