diff --git a/package-lock.json b/package-lock.json index c5832bbb..da18caae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1249,6 +1249,25 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, + "@hypnosphi/create-react-context": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", + "integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==", + "requires": { + "gud": "^1.0.0", + "warning": "^4.0.3" + }, + "dependencies": { + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -14434,24 +14453,24 @@ } }, "react-datepicker": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.4.1.tgz", - "integrity": "sha512-ASyVb7UmVx1vzeITidD7Cr/EXRXhKyjjbSkBndPc1MipYq4rqQ3eMFgvRQzpsXc3JmIMFgICm7nmN6Otc1GE/Q==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.8.0.tgz", + "integrity": "sha512-iFVNEp8DJoX5yEvEiciM7sJKmLGrvE70U38KhpG13XrulNSijeHw1RZkhd/0UmuXR71dcZB/kdfjiidifstZjw==", "requires": { "classnames": "^2.2.6", "date-fns": "^2.0.1", "prop-types": "^15.7.2", - "react-onclickoutside": "^6.9.0", - "react-popper": "^1.3.4" + "react-onclickoutside": "^6.10.0", + "react-popper": "^1.3.8" }, "dependencies": { "react-popper": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz", - "integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==", + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.11.tgz", + "integrity": "sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg==", "requires": { "@babel/runtime": "^7.1.2", - "create-react-context": "^0.3.0", + "@hypnosphi/create-react-context": "^0.3.1", "deep-equal": "^1.1.1", "popper.js": "^1.14.4", "prop-types": "^15.6.1", @@ -14548,9 +14567,9 @@ } }, "react-onclickoutside": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.10.0.tgz", - "integrity": "sha512-7i2L3ef+0ILXpL6P+Hg304eCQswh4jl3ynwR71BSlMU49PE2uk31k8B2GkP6yE9s2D4jTGKnzuSpzWxu4YxfQQ==" + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.11.2.tgz", + "integrity": "sha512-640486eSwU/t5iD6yeTlefma8dI3bxPXD93hM9JGKyYITAd0P1JFkkcDeyHZRqNpY/fv1YW0Fad9BXr44OY8wQ==" }, "react-outside-click-handler": { "version": "1.3.0", diff --git a/package.json b/package.json index 4215d91d..61cd6967 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "prop-types": "^15.7.2", "react": "^16.12.0", "react-avatar": "^3.9.7", - "react-datepicker": "^3.4.1", + "react-datepicker": "^3.8.0", "react-dom": "^16.12.0", "react-final-form": "^6.5.2", "react-final-form-arrays": "^3.1.3", diff --git a/src/assets/images/icon-information-circle.svg b/src/assets/images/icon-information-circle.svg new file mode 100644 index 00000000..2047ab02 --- /dev/null +++ b/src/assets/images/icon-information-circle.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/src/assets/images/icon-thick-calendar.svg b/src/assets/images/icon-thick-calendar.svg new file mode 100644 index 00000000..222202ca --- /dev/null +++ b/src/assets/images/icon-thick-calendar.svg @@ -0,0 +1,23 @@ + + + 89C4F8D6-2320-499E-BC02-40DE84B9F35C + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/FormField/index.jsx b/src/components/FormField/index.jsx index f43d48fc..c7de9520 100644 --- a/src/components/FormField/index.jsx +++ b/src/components/FormField/index.jsx @@ -82,6 +82,7 @@ const FormField = ({ field }) => { onBlur={input.onBlur} onFocus={input.onFocus} className={meta.error && meta.touched ? "error" : ""} + maxLength={field.maxLength} /> )} {field.type === FORM_FIELD_TYPE.DATE && ( diff --git a/src/components/InformationTooltip/index.jsx b/src/components/InformationTooltip/index.jsx new file mode 100644 index 00000000..996168a2 --- /dev/null +++ b/src/components/InformationTooltip/index.jsx @@ -0,0 +1,87 @@ +/** + * Information Tooltip + * Icon which reveals a tooltip on mouse hover + */ +import React, { useCallback, useState } from "react"; +import PT from "prop-types"; +import { usePopper } from "react-popper"; +import IconInformationCircle from "../../assets/images/icon-information-circle.svg"; +import "./styles.module.scss"; + +function InformationTooltip({ text, iconSize = "16px" }) { + const [isTooltipShown, setIsTooltipShown] = useState(false); + const [referenceElement, setReferenceElement] = useState(null); + const [popperElement, setPopperElement] = useState(null); + const [arrowElement, setArrowElement] = useState(null); + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: "top", + modifiers: [ + { + name: "flip", + options: { + fallbackPlacements: ["top"], + }, + }, + { + name: "offset", + options: { + // use offset to move the tooltip slightly up + offset: [0, 12], + }, + }, + { + name: "arrow", + // padding should be equal to border-radius of the tooltip + options: { element: arrowElement, padding: 5 }, + }, + ], + }); + + const showTooltip = useCallback(() => { + setIsTooltipShown(true); + }, []); + + const hideTooltip = useCallback(() => { + setIsTooltipShown(false); + }, []); + + const iconStyle = { + width: iconSize, + height: iconSize, + }; + + return ( +
+
+ +
+ {isTooltipShown && ( +
+
{text}
+
+
+ )} +
+ ); +} + +InformationTooltip.propTypes = { + iconSize: PT.string, + text: PT.string, +}; + +export default InformationTooltip; diff --git a/src/components/InformationTooltip/styles.module.scss b/src/components/InformationTooltip/styles.module.scss new file mode 100644 index 00000000..bf1084c1 --- /dev/null +++ b/src/components/InformationTooltip/styles.module.scss @@ -0,0 +1,34 @@ +@import "styles/include"; + +.circle { + * { + fill: #aaa; + } +} + +.tooltip { + border-radius: 5px; + box-shadow: 0px 5px 25px #c6c6c6; + width: 165px; + z-index: 1000; +} + +.tooltip-arrow { + bottom: -9px; + border-style: solid; + border-width: 10px 8px 0 8px; + border-color: #2a2a2a transparent transparent transparent; + height: 0; + width: 0; +} + +.tooltip-content { + border-radius: 5px; + @include font-roboto; + font-size: 12px; + line-height: 16px; + font-weight: 400; + color: #fff; + background-color: #2a2a2a; + padding: 10px; +} \ No newline at end of file diff --git a/src/components/MarkdownEditor/index.jsx b/src/components/MarkdownEditor/index.jsx index 6c38b851..83939222 100644 --- a/src/components/MarkdownEditor/index.jsx +++ b/src/components/MarkdownEditor/index.jsx @@ -53,6 +53,9 @@ const MarkdownEditor = (props) => { ]} plugins={[]} /> + {props.errorMessage && ( +
{props.errorMessage}
+ )}
); }; diff --git a/src/components/MarkdownEditor/styles.module.scss b/src/components/MarkdownEditor/styles.module.scss index d444b248..13c0aa00 100644 --- a/src/components/MarkdownEditor/styles.module.scss +++ b/src/components/MarkdownEditor/styles.module.scss @@ -12,21 +12,6 @@ overflow-y: auto; background: #fafafb; } - .message { - @include font-roboto; - - width: 100%; - text-align: center; - min-height: 40px; - line-height: 20px; - padding: 9px 10px; - margin: 10px 0 5px; - font-size: 14px; - color: #ff5b52; - border: 1px solid #ffd5d1; - cursor: auto; - outline: none; - } } .editor-container { :global { @@ -72,3 +57,19 @@ } } } + +.message { + @include font-roboto; + + width: 100%; + text-align: center; + min-height: 40px; + line-height: 20px; + padding: 9px 10px; + margin: 10px 0 5px; + font-size: 14px; + color: #ff5b52; + border: 1px solid #ffd5d1; + cursor: auto; + outline: none; +} \ No newline at end of file diff --git a/src/components/MonthPicker/index.jsx b/src/components/MonthPicker/index.jsx new file mode 100644 index 00000000..0da605f8 --- /dev/null +++ b/src/components/MonthPicker/index.jsx @@ -0,0 +1,44 @@ +/** + * Month Picker + * An styled input component for selecting date by month. + * Compatible with react-final-form + */ +import React from "react"; +import PT from "prop-types"; +import DatePicker from "react-datepicker"; +import "./styles.module.scss"; + +function getCurrMonthYear() { + const now = new Date(); + const year = now.getFullYear(); + const month = now.getMonth(); + return new Date(`${year}-${month + 1}`); +} + +function MonthPicker({ name, value, onChange, onBlur }) { + return ( +
+ +
+ ); +} + +MonthPicker.propTypes = { + name: PT.string, + value: PT.any, + onChange: PT.func, + onBlur: PT.func, +}; + +export default MonthPicker; diff --git a/src/components/MonthPicker/styles.module.scss b/src/components/MonthPicker/styles.module.scss new file mode 100644 index 00000000..ba93e757 --- /dev/null +++ b/src/components/MonthPicker/styles.module.scss @@ -0,0 +1,60 @@ +@import "styles/include"; + +.month-picker { + input { + width: 118px; + background-image: url("../../assets/images/icon-thick-calendar.svg"); + background-repeat: no-repeat; + background-position: 95px 7px; + } + + :global { + .react-datepicker__header { + @include font-barlow; + font-size: 20px; + color: #2a2a2a; + font-weight: 600; + padding-top: 14px; + padding-bottom: 14px; + background-color: transparent; + border-bottom: 1px solid #d4d4d4; + margin: 0 16px; + } + + .react-datepicker__month-text { + @include font-roboto; + font-size: 14px; + font-weight: 400; + line-height: 40px; + width: 40px; + height: 40px; + margin: 8px; + border-radius: 100%; + } + .react-datepicker__month--selected { + border-radius: 100%; + background-color: #0AB88A; + } + + .react-datepicker__navigation { + top: 18px; + border: 2px solid transparent; + height: 12px; + width: 12px; + } + .react-datepicker__navigation--next, .react-datepicker__navigation--previous { + border-right-color: transparent; + border-bottom-color: transparent; + border-left-color: #137D60; + border-top-color: #137D60; + } + .react-datepicker__navigation--next { + right: 24px; + transform: rotate(135deg); + } + .react-datepicker__navigation--previous { + left: 24px; + transform: rotate(-45deg); + } + } +} \ No newline at end of file diff --git a/src/components/TextArea/index.jsx b/src/components/TextArea/index.jsx index 8ec9e7e5..fc247056 100644 --- a/src/components/TextArea/index.jsx +++ b/src/components/TextArea/index.jsx @@ -18,6 +18,7 @@ function TextArea(props) { autoFocus={props.autoFocus} onBlur={props.onBlur} onFocus={props.onFocus} + maxLength={props.maxLength} /> ); } diff --git a/src/constants/index.js b/src/constants/index.js index 5bc6228f..45507f02 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -357,6 +357,6 @@ export const INTERVIEW_POPUP_MEDIA_URL = export const MAX_ALLOWED_INTERVIEWS = 3; /** - * Matching rate to show in CreateNewTeam ResultCard + * Custom role names to remove from RoleList component */ -export const MATCHING_RATE = "80"; +export const CUSTOM_ROLE_NAMES = ["custom", "niche"]; diff --git a/src/routes/CreateNewTeam/components/CircularProgressBar/index.jsx b/src/routes/CreateNewTeam/components/CircularProgressBar/index.jsx index 7a19f03e..06ea736c 100644 --- a/src/routes/CreateNewTeam/components/CircularProgressBar/index.jsx +++ b/src/routes/CreateNewTeam/components/CircularProgressBar/index.jsx @@ -14,7 +14,7 @@ const CircularProgressBar = ({ size, progress, children, strokeWidth }) => { const radius = size / 2 - strokeWidth / 2; const circumference = 2 * Math.PI * radius; useEffect(() => { - const progressOffset = ((100 - progress) / 100) * circumference; + const progressOffset = (1 - progress) * circumference; setOffset(progressOffset); circleRef.current.style = "transition: stroke-dashoffset 850ms ease-in-out"; }, [setOffset, progress, circumference, offset]); diff --git a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx index 2b1fd779..e612d1e1 100644 --- a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx @@ -4,12 +4,14 @@ */ import React from "react"; import { Link } from "@reach/router"; +import PT from "prop-types"; import "./styles.module.scss"; import IconEarthX from "../../../../assets/images/icon-earth-x.svg"; import Curve from "../../../../assets/images/curve.svg"; import Button from "components/Button"; +import { formatMoney } from "utils/format"; -function NoMatchingProfilesResultCard() { +function NoMatchingProfilesResultCard({ role }) { return (
@@ -23,11 +25,13 @@ function NoMatchingProfilesResultCard() { We will be looking internally for members matching your requirements and be back at them in about 2 weeks.

-
-

Niche Rate

-

$1,200

-

/Week

-
+ {role && ( +
+

{role.name} Rate

+

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

+

/Week

+
+ )} +
+ + +
)} diff --git a/src/routes/CreateNewTeam/components/TeamDetailsModal/styles.module.scss b/src/routes/CreateNewTeam/components/TeamDetailsModal/styles.module.scss index eb6080c6..a3f5675a 100644 --- a/src/routes/CreateNewTeam/components/TeamDetailsModal/styles.module.scss +++ b/src/routes/CreateNewTeam/components/TeamDetailsModal/styles.module.scss @@ -42,20 +42,30 @@ color: #2a2a2a; border-bottom: 1px solid #e9e9e9; + &.start-month { + padding-right: 0; + } + input { @include font-roboto; font-size: 14px; line-height: 22px; height: 34px; - width: 98px; - &[type="month"] { - width: 170px; + &[type="number"] { + width: 98px; } } } } } +.flex-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; +} + .error { font-size: 14px; font-weight: 400; @@ -67,4 +77,10 @@ textarea { height: 95px; } +} + +@media only screen and (max-width: 550px) { + .modal-body { + overflow-x: scroll; + } } \ 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 776267b3..9bc211e1 100644 --- a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx @@ -26,7 +26,7 @@ function InputJobDescription() { 255} completenessStyle="input-job-description" searchObject={{ jobDescription: jdString }} toRender={ @@ -40,6 +40,11 @@ function InputJobDescription() { height="482px" placeholder="input job description" onChange={onEditChange} + errorMessage={ + jdString.length > 255 + ? "Maximum of 255 characters. Please reduce job description length." + : "" + } />
diff --git a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx index 7486b59e..88c84e07 100644 --- a/src/routes/CreateNewTeam/pages/SelectRole/index.jsx +++ b/src/routes/CreateNewTeam/pages/SelectRole/index.jsx @@ -11,6 +11,11 @@ import { getRoles } from "services/roles"; import LoadingIndicator from "components/LoadingIndicator"; import RoleDetailsModal from "../../components/RoleDetailsModal"; import SearchAndSubmit from "../../components/SearchAndSubmit"; +import { isCustomRole } from "utils/helpers"; + +// Remove custom roles from role list +const removeCustomRoles = (roles) => + roles.filter((role) => !isCustomRole(role)); function SelectRole() { const [stages, setStages] = useState([ @@ -47,7 +52,7 @@ function SelectRole() { toRender={ <> { .map((s) => ({ ...s, completed: false, isCurrent: false })), ]); }; + +/** + * Checks if role is custom/niche + * @param {Object} role role to check + * @returns {boolean} whether the role is custom/niche + */ +export const isCustomRole = (role) => + !role.name || CUSTOM_ROLE_NAMES.includes(role.name.toLowerCase());