diff --git a/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap index 3d56855d05..94a5da518a 100644 --- a/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap +++ b/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap @@ -15,9 +15,10 @@ exports[`Default render 1`] = ` diff --git a/src/assets/images/check-mark.svg b/src/assets/images/check-mark.svg new file mode 100644 index 0000000000..b4e1f7b3a6 --- /dev/null +++ b/src/assets/images/check-mark.svg @@ -0,0 +1,24 @@ + + + + 21742205-DC86-42C6-AD5E-D5ADCDBB3E3F + Created with sketchtool. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/l4.png b/src/assets/images/l4.png deleted file mode 100644 index 2e066b7f5d..0000000000 Binary files a/src/assets/images/l4.png and /dev/null differ diff --git a/src/shared/components/Contentful/AppComponent/index.jsx b/src/shared/components/Contentful/AppComponent/index.jsx index 5238ae7638..bc9971fa63 100644 --- a/src/shared/components/Contentful/AppComponent/index.jsx +++ b/src/shared/components/Contentful/AppComponent/index.jsx @@ -32,7 +32,7 @@ export function AppComponentSwitch(appComponent) { ); } if (appComponent.fields.type === 'RecruitCRM-Jobs') { - return ; + return ; } fireErrorMessage('Unsupported app component type from contentful', ''); return null; diff --git a/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss b/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss index b8c20ed703..d6d93533d2 100644 --- a/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss +++ b/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss @@ -2,6 +2,7 @@ $gui-kit-gray-30: #aaa; $gui-kit-gray-90: #2a2a2a; $gui-kit-level-2: #0ab88a; +$gui-kit-level-5: #ef476f; @mixin textInputLabel { font-size: 12px; @@ -22,6 +23,7 @@ $gui-kit-level-2: #0ab88a; padding: 15px; margin-bottom: 0; margin-top: 12px; + color: $gui-kit-gray-90; &::-webkit-input-placeholder { /* Edge */ @@ -50,6 +52,12 @@ $gui-kit-level-2: #0ab88a; border: 1px solid $gui-kit-gray-30; box-shadow: none; } + + &:focus-within { + label { + color: #229174; + } + } } @mixin textInputXs { @@ -75,8 +83,10 @@ $gui-kit-level-2: #0ab88a; line-height: 20px; margin-top: 10px; margin-left: 15px; + color: $gui-kit-gray-90; } @mixin errorMessageXs { margin-top: 8px; + color: $gui-kit-gray-90; } diff --git a/src/shared/components/GUIKit/Checkbox/index.jsx b/src/shared/components/GUIKit/Checkbox/index.jsx index 0576ea93c7..be44e9fcde 100644 --- a/src/shared/components/GUIKit/Checkbox/index.jsx +++ b/src/shared/components/GUIKit/Checkbox/index.jsx @@ -5,7 +5,7 @@ */ import React, { useRef, useState } from 'react'; import PT from 'prop-types'; -import IconCheckSolid from 'assets/images/dashboard/ico-checkmark.svg'; +import IconCheckSolid from 'assets/images/check-mark.svg'; import _ from 'lodash'; import './style.scss'; diff --git a/src/shared/components/GUIKit/Datepicker/style.scss b/src/shared/components/GUIKit/Datepicker/style.scss index 8f27b70db5..25a0f7a9e5 100644 --- a/src/shared/components/GUIKit/Datepicker/style.scss +++ b/src/shared/components/GUIKit/Datepicker/style.scss @@ -20,7 +20,7 @@ &.haveError .label, &.haveError.isFocused .label { - color: $tc-level-5; + color: $gui-kit-level-5; } :global { @@ -153,8 +153,17 @@ border-radius: 50%; } + &.CalendarDay__default { + color: #2a2a2a; + } + + &.CalendarDay__blocked_out_of_range { + color: #aaa; + } + &.CalendarDay__selected { background: transparent !important; + color: #fff; div { background: $gui-kit-level-2; @@ -190,7 +199,7 @@ .SingleDatePickerInput { .DateInput { input { - border: 2px solid $tc-level-5; + border: 2px solid $gui-kit-level-5; } } } diff --git a/src/shared/components/GUIKit/Dropdown/style.scss b/src/shared/components/GUIKit/Dropdown/style.scss index 837b2fe5e5..7ca3bf762b 100644 --- a/src/shared/components/GUIKit/Dropdown/style.scss +++ b/src/shared/components/GUIKit/Dropdown/style.scss @@ -43,7 +43,7 @@ &.haveError .label, &.haveError.isFocused .label { - color: $tc-level-5; + color: $gui-kit-level-5; } :global { @@ -80,6 +80,7 @@ height: 22px; line-height: 22px; font-size: 16px; + color: $gui-kit-gray-90 !important; } } @@ -163,7 +164,7 @@ &.haveError { :global { .Select-control { - border: 2px solid $tc-level-5 !important; + border: 2px solid $gui-kit-level-5 !important; } } } @@ -200,6 +201,7 @@ .Select-value { .Select-value-label { font-size: 14px; + color: $gui-kit-gray-90 !important; } } diff --git a/src/shared/components/GUIKit/TextInput/style.scss b/src/shared/components/GUIKit/TextInput/style.scss index b71c2b8184..453370fbb8 100644 --- a/src/shared/components/GUIKit/TextInput/style.scss +++ b/src/shared/components/GUIKit/TextInput/style.scss @@ -51,13 +51,13 @@ input:not([type='checkbox']).haveError + label, input:not([type='checkbox']).haveError:focus + label { - color: $tc-level-5; + color: $gui-kit-level-5; } input:not([type='checkbox']).haveError, input:not([type='checkbox']).haveError:active, input:not([type='checkbox']).haveError:focus, input:not([type='checkbox']).haveError:hover { - border: 2px solid $tc-level-5; + border: 2px solid $gui-kit-level-5; } } diff --git a/src/shared/components/GUIKit/Textarea/style.scss b/src/shared/components/GUIKit/Textarea/style.scss index 57f67b42a6..9346db5130 100644 --- a/src/shared/components/GUIKit/Textarea/style.scss +++ b/src/shared/components/GUIKit/Textarea/style.scss @@ -30,7 +30,7 @@ &.haveError ~ label, &.haveError:focus ~ label { - color: $tc-level-5; + color: $gui-kit-level-5; } } @@ -52,7 +52,7 @@ textarea.haveError:active, textarea.haveError:focus, textarea.haveError:hover { - border: 2px solid $tc-level-5; + border: 2px solid $gui-kit-level-5; } textarea.haveError ~ .labelMask { diff --git a/src/shared/components/Gigs/GigDetails.jsx b/src/shared/components/Gigs/GigDetails.jsx index 0181be5132..df0b06f343 100644 --- a/src/shared/components/Gigs/GigDetails.jsx +++ b/src/shared/components/Gigs/GigDetails.jsx @@ -8,6 +8,7 @@ import PT from 'prop-types'; import { isomorphy, Link, config } from 'topcoder-react-utils'; import ReactHtmlParser from 'react-html-parser'; import { getSalaryType, getCustomField } from 'utils/gigs'; +import SubscribeMailChimpTag from 'containers/SubscribeMailChimpTag'; import './style.scss'; import IconFacebook from 'assets/images/icon-facebook.svg'; import IconTwitter from 'assets/images/icon-twitter.svg'; @@ -21,7 +22,6 @@ import iconSkills from 'assets/images/icon-skills-blue.png'; import iconLabel1 from 'assets/images/l1.png'; import iconLabel2 from 'assets/images/l2.png'; import iconLabel3 from 'assets/images/l3.png'; -import iconLabel4 from 'assets/images/l4.png'; // Cleanup HTML from style tags // so it won't affect other parts of the UI @@ -127,8 +127,12 @@ export default function GigDetails(props) { +
+
SUBSCRIBE TO WEEKLY UPDATES
+

Not ready to apply? Want to stay tuned for any new gigs that may be upcoming? Join our weekly Gig Work list.

+ +
-

Thank you for checking out our latest gig at Topcoder. Gig work through us is simple and effective for those that would like traditional freelance work. To learn more about how Gigs work with us, go here.

At Topcoder, we pride ourselves in bringing our customers the very best candidates to help fill their needs. Want to improve your chances? You can do a few things:

  • @@ -137,14 +141,10 @@ export default function GigDetails(props) {
  • label 2 -
    Subscribe to our Gig notifications email. We’ll send you a weekly update on gigs available so you don’t miss a beat.
    +
    Let us know you’re here! Check in on our Gig Work forum and tell us you’re looking for a gig. It’s great visibility for the Gig team.
  • label 3 -
    Let us know you’re here! Check in on our Gig Work forum and tell us you’re looking for a gig. It’s great visibility for the Gig team.
    -
  • -
  • - label 4
    Check out our Topcoder challenges and participate. Challenges showing your technology skills make you a “qualified” candidate so we know you’re good. The proof is in the pudding!
diff --git a/src/shared/components/Gigs/style.scss b/src/shared/components/Gigs/style.scss index 9859cdc418..74c0cf3502 100644 --- a/src/shared/components/Gigs/style.scss +++ b/src/shared/components/Gigs/style.scss @@ -101,6 +101,7 @@ .shareButtons { display: flex; align-items: center; + margin-bottom: 12px; a { margin-right: 5px; @@ -111,11 +112,27 @@ } } + .subscribe-area { + background-image: linear-gradient(135.29deg, #2c95d7 0%, #06d6a0 100%); + border-radius: 10px; + padding: 25px 32px 30px 20px; + + h6 { + margin: 0 0 6px; + color: #fff; + } + + p { + color: #fff; + margin-bottom: 15px; + } + } + .info-area { background-color: #f4f4f4; padding: 20px; border-radius: 10px; - margin-top: 18px; + margin-top: 20px; /* stylelint-disable */ p, diff --git a/src/shared/components/examples/GUIKit/TextInput/index.jsx b/src/shared/components/examples/GUIKit/TextInput/index.jsx index 527108f6f2..d8b932b049 100644 --- a/src/shared/components/examples/GUIKit/TextInput/index.jsx +++ b/src/shared/components/examples/GUIKit/TextInput/index.jsx @@ -40,6 +40,7 @@ function TextInputExample({ size }) { value: 'Adam Morehead', required: true, errorMsg: '', + sectionTitle: 'Required', }, ]; diff --git a/src/shared/containers/Gigs/RecruitCRMJobs.jsx b/src/shared/containers/Gigs/RecruitCRMJobs.jsx index 46b5b50d6e..59b2973ba8 100644 --- a/src/shared/containers/Gigs/RecruitCRMJobs.jsx +++ b/src/shared/containers/Gigs/RecruitCRMJobs.jsx @@ -21,9 +21,7 @@ const sortByOptions = [ { label: 'Latest Updated Descending', selected: false }, ]; // Locations -const locations = [{ - label: 'Anywhere', selected: true, -}]; +let locations = []; class RecruitCRMJobsContainer extends React.Component { constructor(props) { @@ -33,7 +31,7 @@ class RecruitCRMJobsContainer extends React.Component { term: '', page: 0, sortBy: 'created_on', - location: 'Anywhere', + location: 'Any Location', }; // binds this.onSearch = this.onSearch.bind(this); @@ -127,6 +125,8 @@ class RecruitCRMJobsContainer extends React.Component { if (location === 'Anywhere' || location === 'Any' || location === 'Any Location') return true; return location.toLowerCase() === job.country.toLowerCase(); }); + // sort location dropdown + locations = _.sortBy(locations, ['label']); // Filter by term if (term) { jobsToDisplay = _.filter(jobsToDisplay, (job) => { @@ -178,11 +178,12 @@ class RecruitCRMJobsContainer extends React.Component { RecruitCRMJobsContainer.defaultProps = { jobs: [], + loading: true, }; RecruitCRMJobsContainer.propTypes = { getJobs: PT.func.isRequired, - loading: PT.bool.isRequired, + loading: PT.bool, jobs: PT.arrayOf(PT.shape), }; diff --git a/src/shared/containers/SubscribeMailChimpTag/index.jsx b/src/shared/containers/SubscribeMailChimpTag/index.jsx new file mode 100644 index 0000000000..d698bcf8a4 --- /dev/null +++ b/src/shared/containers/SubscribeMailChimpTag/index.jsx @@ -0,0 +1,129 @@ +/** + * Genetic subscribe for MailChimp tags component + */ +import React from 'react'; +import PT from 'prop-types'; +import { isValidEmail } from 'utils/tc'; +import { config } from 'topcoder-react-utils'; +import { Modal } from 'topcoder-react-ui-kit'; +import modalStyle from 'components/NewsletterSignupForMembers/modal.scss'; +import defaulTheme from './style.scss'; + +/* Holds the base URL of Community App endpoints that proxy HTTP request to + * mailchimp APIs. */ +const PROXY_ENDPOINT = `${config.URL.COMMUNITY_APP}/api/mailchimp`; + +class SubscribeMailChimpTagContainer extends React.Component { + constructor(props) { + super(props); + this.state = { + error: '', + subsribed: false, + disabled: true, + inputVal: '', + }; + this.onSubscribeClick = this.onSubscribeClick.bind(this); + this.onInputChange = this.onInputChange.bind(this); + } + + onSubscribeClick() { + const { inputVal } = this.state; + const { listId, tags } = this.props; + const fetchUrl = `${PROXY_ENDPOINT}/${listId}/members/${inputVal}/tags`; + const data = { + email_address: inputVal, + status: 'subscribed', + tags: tags.map(t => ({ name: t, status: 'active' })), + }; + const formData = JSON.stringify(data); + fetch(fetchUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: formData, + }).then(result => result.json()).then((dataResponse) => { + if (dataResponse.status === 204) { + // regist success + return this.setState({ + subsribed: true, + error: '', + }); + } + if (dataResponse.status === 404) { + // new email register it for list and add tags + return fetch(`${PROXY_ENDPOINT}/${listId}/members`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email_address: inputVal, + status: 'subscribed', + tags, + }), + }) + .then(result => result.json()).then(() => { + this.setState({ + subsribed: true, + error: '', + }); + }); + } + return this.setState({ + subsribed: false, + error: `Error ${dataResponse.status} when assign to tags`, + }); + }) + .catch((e) => { + this.setState({ + subsribed: false, + error: e.message, + }); + }); + } + + onInputChange(event) { + this.setState({ + inputVal: event.target.value, + disabled: !isValidEmail(event.target.value), + }); + } + + render() { + const { + disabled, inputVal, error, subsribed, + } = this.state; + return ( +
+ { + subsribed || error ? ( + this.setState({ + subsribed: false, + error: false, + inputVal: '', + disabled: true, + })} + > +
+

{subsribed ? 'Congratulations!' : 'Ops :('}

+

{error || 'You are now subscribed.'}

+
+
+ ) : null + } + + +
+ ); + } +} + +SubscribeMailChimpTagContainer.propTypes = { + listId: PT.string.isRequired, + tags: PT.arrayOf(PT.string).isRequired, +}; + +export default SubscribeMailChimpTagContainer; diff --git a/src/shared/containers/SubscribeMailChimpTag/style.scss b/src/shared/containers/SubscribeMailChimpTag/style.scss new file mode 100644 index 0000000000..4bf7d215ec --- /dev/null +++ b/src/shared/containers/SubscribeMailChimpTag/style.scss @@ -0,0 +1,57 @@ +.wrapper { + display: flex; + + input { + background-color: #fff !important; + border: 1px solid #aaa !important; + border-radius: 6px !important; + margin-right: 8px; + color: #2a2a2a; + font-family: Roboto, sans-serif; + font-size: 14px; + line-height: 22px; + + &::placeholder, + &::-moz-placeholder, + &::-webkit-input-placeholder, + &::-webkit-placeholder { + color: #aaa !important; + font-family: Roboto, sans-serif !important; + font-size: 14px !important; + line-height: 40px !important; + text-transform: none; + } + + &:focus { + box-shadow: none !important; + } + } + + .button { + color: #229174; + font-family: Roboto, sans-serif; + font-size: 14px; + font-weight: bold; + letter-spacing: 0.8px; + line-height: 40px; + background-color: #fff; + border-radius: 20px; + border: none; + text-transform: uppercase; + max-height: 40px; + padding: 0 20px; + cursor: pointer; + outline: none; + + &:disabled { + background-color: #e9e9e9; + color: #fff; + } + + &:hover:not(:disabled) { + color: #229174; + background-color: #fff !important; + box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2); + } + } +} diff --git a/src/shared/utils/tc.js b/src/shared/utils/tc.js index 92c93c9ca6..776729a973 100644 --- a/src/shared/utils/tc.js +++ b/src/shared/utils/tc.js @@ -287,4 +287,13 @@ export function formatDate(date, abbreviate, showDay) { return `${month} ${y}`; } +/** + * Test if a string is valid email + * @param {String} email The string to test + */ +export function isValidEmail(email) { + const pattern = new RegExp(/^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i); + return pattern.test(email); +} + export default undefined;