From bd15106a65be1e5b54eff039cfc2c79f2abfa06c Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Tue, 16 Mar 2021 09:16:44 +0200 Subject: [PATCH 1/8] step 1 - gig apply v2 --- src/shared/actions/recruitCRM.js | 40 +++++++++++++--- src/shared/components/Gigs/GigApply/index.jsx | 46 ++++++------------- .../containers/Gigs/RecruitCRMJobApply.jsx | 43 +++++++++++++---- src/shared/reducers/recruitCRM.js | 28 +++++++++++ src/shared/services/recruitCRM.js | 13 ++++++ 5 files changed, 123 insertions(+), 47 deletions(-) diff --git a/src/shared/actions/recruitCRM.js b/src/shared/actions/recruitCRM.js index 1b9b127f25..54b9d31146 100644 --- a/src/shared/actions/recruitCRM.js +++ b/src/shared/actions/recruitCRM.js @@ -60,10 +60,8 @@ function normalizeRecruitPayload(job, payload) { `Pay Expectation: ${payload.payExpectation}`, `Date Available: ${new Date(payload.availFrom).toDateString()}`, `Heard About Gig: ${payload.reffereal}`, - `Why fit: ${payload.whyFit}`, `Able to work during timezone? ${payload.timezoneConfirm.filter(s => s.value).map(() => getCustomField(job.custom_fields, 'Timezone')).join(',')}`, `Am I ok to work the duration? ${payload.durationConfirm.filter(s => s.value).map(s => s.label).join(',')}`, - `Notes: ${payload.notes}`, ]; return { last_name: payload.lname, @@ -72,7 +70,6 @@ function normalizeRecruitPayload(job, payload) { contact_number: payload.phone, city: payload.city, locality: _.find(payload.country, { selected: true }).label, - available_from: payload.availFrom, salary_expectation: payload.payExpectation, skill: payload.skills.filter(s => s.selected).map(s => s.label).join(','), custom_fields: [ @@ -88,10 +85,6 @@ function normalizeRecruitPayload(job, payload) { field_id: 2, value: payload.handle || '', }, - { - field_id: 3, - value: payload.whyFit || '', - }, { field_id: 14, value: perJob.join(','), @@ -124,6 +117,37 @@ async function applyForJobDone(job, payload) { } } +/** + * Search for cnadidate in recruit + */ +function searchCandidatesInit(email) { + return { email }; +} + +/** + * Search for cnadidate in recruit and get profile if available + * @param {string} email the email to search + */ +async function searchCandidatesDone(email) { + const ss = new Service(); + try { + const res = await ss.searchCandidates(email); + + return { + email, + data: res, + }; + } catch (error) { + return { + email, + data: { + error: true, + errorObj: error, + }, + }; + } +} + export default redux.createActions({ RECRUIT: { GET_JOBS_INIT: getJobsInit, @@ -132,5 +156,7 @@ export default redux.createActions({ GET_JOB_DONE: getJobDone, APPLY_FOR_JOB_INIT: applyForJobInit, APPLY_FOR_JOB_DONE: applyForJobDone, + SEARCH_CANDIDATES_INIT: searchCandidatesInit, + SEARCH_CANDIDATES_DONE: searchCandidatesDone, }, }); diff --git a/src/shared/components/Gigs/GigApply/index.jsx b/src/shared/components/Gigs/GigApply/index.jsx index 9d3cc63dfa..ec28c3fef4 100644 --- a/src/shared/components/Gigs/GigApply/index.jsx +++ b/src/shared/components/Gigs/GigApply/index.jsx @@ -7,7 +7,6 @@ import React from 'react'; import PT from 'prop-types'; import { Link, config } from 'topcoder-react-utils'; import TextInput from 'components/GUIKit/TextInput'; -import Datepicker from 'components/GUIKit/Datepicker'; import DropdownTerms from 'components/GUIKit/DropdownTerms'; import RadioButton from 'components/GUIKit/RadioButton'; import Checkbox from 'components/GUIKit/Checkbox'; @@ -24,9 +23,12 @@ import BackArrowGig from 'assets/images/back-arrow-gig-apply.svg'; export default function GigApply(props) { const { job, onFormInputChange, formData, formErrors, onApplyClick, applying, application, user, + recruitProfile, } = props; const retUrl = window.location.href; + console.log('GigApply', recruitProfile) + return user ? (
{ @@ -182,13 +184,7 @@ export default function GigApply(props) { onChange={val => onFormInputChange('payExpectation', val)} errorMsg={formErrors.payExpectation} value={formData.payExpectation} - /> - onFormInputChange('availFrom', val ? val.toISOString() : null)} - errorMsg={formErrors.availFrom} - value={formData.availFrom} + required />
@@ -218,22 +214,15 @@ export default function GigApply(props) {

FINAL QUESTIONS

Please Complete the Following Questions

- onFormInputChange('reffereal', val)} errorMsg={formErrors.reffereal} - value={formData.reffereal} + options={formData.reffereal} required />
- onFormInputChange('whyFit', val)} - errorMsg={formErrors.whyFit} - value={formData.whyFit} - />

Are you able to work during the specified timezone? ({`${getCustomField(job.custom_fields, 'Timezone')}`})

onFormInputChange('timezoneConfirm', val)} @@ -241,19 +230,13 @@ export default function GigApply(props) { options={formData.timezoneConfirm} size="lg" /> -

Are you ok to work with the duration of the gig? ({`${getCustomField(job.custom_fields, 'Duration')}`})

- onFormInputChange('durationConfirm', val)} - errorMsg={formErrors.durationConfirm} - options={formData.durationConfirm} - size="lg" - />
- onFormInputChange('notes', val)} - errorMsg={formErrors.notes} +

Are you ok to work with the duration of the gig? ({`${getCustomField(job.custom_fields, 'Duration')}`})

+ onFormInputChange('durationConfirm', val)} + errorMsg={formErrors.durationConfirm} + options={formData.durationConfirm} + size="lg" />
@@ -313,4 +296,5 @@ GigApply.propTypes = { applying: PT.bool, application: PT.shape(), user: PT.shape(), + recruitProfile: PT.shape().isRequired, }; diff --git a/src/shared/containers/Gigs/RecruitCRMJobApply.jsx b/src/shared/containers/Gigs/RecruitCRMJobApply.jsx index c0b847e339..244a2990d4 100644 --- a/src/shared/containers/Gigs/RecruitCRMJobApply.jsx +++ b/src/shared/containers/Gigs/RecruitCRMJobApply.jsx @@ -5,6 +5,7 @@ import _ from 'lodash'; import actions from 'actions/recruitCRM'; import GigApply from 'components/Gigs/GigApply'; +import LoadingIndicator from 'components/LoadingIndicator'; import PT from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; @@ -21,12 +22,14 @@ class RecruitCRMJobApplyContainer extends React.Component { this.state = { formErrors: {}, formData: { - availFrom: new Date().toISOString(), skills: _.map(techSkills, label => ({ label, selected: false })), durationConfirm: [{ label: 'Yes', value: false }, { label: 'No', value: false }], timezoneConfirm: [{ label: 'Yes', value: false }, { label: 'No', value: false }], agreedTerms: false, country: _.map(countries.getNames('en'), val => ({ label: val, selected: false })), + reffereal: [ + { label: 'Google', selected: false }, + ], // eslint-disable-next-line react/destructuring-assignment }, }; @@ -39,10 +42,13 @@ class RecruitCRMJobApplyContainer extends React.Component { componentDidMount() { const { formData } = this.state; - const { user } = this.props; + const { user, recruitProfile, searchCandidates } = this.props; this.setState({ formData: _.merge(formData, user), }); + if (user && !recruitProfile) { + searchCandidates(user.email); + } } onFormInputChange(key, value) { @@ -73,7 +79,7 @@ class RecruitCRMJobApplyContainer extends React.Component { const { formData, formErrors } = state; // Form validation happens here const requiredTextFields = [ - 'fname', 'lname', 'city', 'reffereal', 'phone', 'email', + 'fname', 'lname', 'city', 'phone', 'email', ]; // check required text fields for value // check min/max lengths @@ -85,10 +91,6 @@ class RecruitCRMJobApplyContainer extends React.Component { else if (formData[key] && _.trim(formData[key]).length < 2) formErrors[key] = 'Must be at least 2 characters'; else if (formData[key] && _.trim(formData[key]).length > 2) { switch (key) { - case 'reffereal': - if (_.trim(formData[key]).length > 2000) formErrors[key] = 'Must be max 2000 characters'; - else delete formErrors[key]; - break; case 'city': case 'phone': if (_.trim(formData[key]).length > 50) formErrors[key] = 'Must be max 50 characters'; @@ -106,12 +108,17 @@ class RecruitCRMJobApplyContainer extends React.Component { if (!_.find(formData.country, { selected: true })) formErrors.country = 'Please, select your country'; else delete formErrors.country; } + // check for selected reffereal + if (!prop || prop === 'reffereal') { + if (!_.find(formData.reffereal, { selected: true })) formErrors.reffereal = 'Please, select your reffereal'; + else delete formErrors.reffereal; + } // check payExpectation to be a number if (!prop || prop === 'payExpectation') { if (formData.payExpectation && _.trim(formData.payExpectation)) { if (!_.isInteger(_.toNumber(formData.payExpectation))) formErrors.payExpectation = 'Must be integer value in $'; else delete formErrors.payExpectation; - } else delete formErrors.payExpectation; + } else formErrors.payExpectation = 'Required field'; } // check for valid email if (!prop || prop === 'email') { @@ -147,6 +154,14 @@ class RecruitCRMJobApplyContainer extends React.Component { } } } + // timezone & duration + if (!prop || prop === 'timezoneConfirm' || prop === 'durationConfirm') { + const a = _.find(formData[prop], { value: true }); + if (a) { + if (a.label === 'No') formErrors[prop] = 'Sorry, we are only looking for candidates that can work the hours and duration listed'; + else delete formErrors[prop]; + } + } // updated state return { ...state, @@ -157,7 +172,8 @@ class RecruitCRMJobApplyContainer extends React.Component { render() { const { formErrors, formData } = this.state; - return ( + const { recruitProfile } = this.props; + return !recruitProfile ? : ( { + dispatch(a.searchCandidatesInit(email)); + dispatch(a.searchCandidatesDone(email)); + }, }; } diff --git a/src/shared/reducers/recruitCRM.js b/src/shared/reducers/recruitCRM.js index 786caef018..0ff13090e3 100644 --- a/src/shared/reducers/recruitCRM.js +++ b/src/shared/reducers/recruitCRM.js @@ -84,6 +84,32 @@ function onApplyForJobDone(state, { payload }) { return r; } +/** + * Handles recruit.applyForJobInit action. + * @param {Object} state Previous state. + */ +function onSearchCandidatesInit(state, { payload }) { + const r = { + ...state, + }; + r[payload.email] = {}; + return r; +} + +/** + * Handles recruit.applyForJobDone action. + * @param {Object} state Previous state. + * @param {Object} action The action. + */ +function onSearchCandidatesDone(state, { payload }) { + const r = { + ...state, + }; + const profile = _.isArray(payload.data) ? {} : payload.data.data[0]; + r[payload.email].profile = profile; + return r; +} + /** * Creates recruitCRM reducer with the specified initial state. * @param {Object} state Optional. If not given, the default one is @@ -98,6 +124,8 @@ function create(state = {}) { [actions.recruit.getJobDone]: onJobDone, [actions.recruit.applyForJobInit]: onApplyForJobInit, [actions.recruit.applyForJobDone]: onApplyForJobDone, + [actions.recruit.searchCandidatesInit]: onSearchCandidatesInit, + [actions.recruit.searchCandidatesDone]: onSearchCandidatesDone, }, state); } diff --git a/src/shared/services/recruitCRM.js b/src/shared/services/recruitCRM.js index e20cccb5f4..c71bae877b 100644 --- a/src/shared/services/recruitCRM.js +++ b/src/shared/services/recruitCRM.js @@ -67,4 +67,17 @@ export default class Service { } return res.json(); } + + /** + * Search for candidate + * @param {object} email The email to search with + */ + async searchCandidates(email) { + const res = await fetch(`${this.baseUrl}/candidates/search?email=${email}`); + if (!res.ok) { + const error = new Error('Failed to search for candidates'); + logger.error(error, res); + } + return res.json(); + } } From 4c204cd3246899da69eb2e2883d54b2488ac6b68 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Tue, 16 Mar 2021 11:59:48 +0200 Subject: [PATCH 2/8] v1 for #5429 --- src/shared/actions/recruitCRM.js | 21 +++++++++------- src/shared/components/Gigs/GigApply/index.jsx | 24 ++++++++++++++----- .../components/Gigs/GigApply/style.scss | 5 ++++ .../containers/Gigs/RecruitCRMJobApply.jsx | 23 ++++++++++++++---- 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/src/shared/actions/recruitCRM.js b/src/shared/actions/recruitCRM.js index 54b9d31146..c4e7ed649b 100644 --- a/src/shared/actions/recruitCRM.js +++ b/src/shared/actions/recruitCRM.js @@ -58,12 +58,11 @@ function normalizeRecruitPayload(job, payload) { const perJob = [ `${job.name} ->`, `Pay Expectation: ${payload.payExpectation}`, - `Date Available: ${new Date(payload.availFrom).toDateString()}`, - `Heard About Gig: ${payload.reffereal}`, `Able to work during timezone? ${payload.timezoneConfirm.filter(s => s.value).map(() => getCustomField(job.custom_fields, 'Timezone')).join(',')}`, - `Am I ok to work the duration? ${payload.durationConfirm.filter(s => s.value).map(s => s.label).join(',')}`, + `Am I ok to work the duration? ${payload.durationConfirm.filter(s => s.value).map(() => getCustomField(job.custom_fields, 'Duration')).join(',')}`, ]; - return { + const referral = _.find(payload.reffereal, { selected: true }); + const normalized = { last_name: payload.lname, first_name: payload.fname, email: payload.email, @@ -73,13 +72,9 @@ function normalizeRecruitPayload(job, payload) { salary_expectation: payload.payExpectation, skill: payload.skills.filter(s => s.selected).map(s => s.label).join(','), custom_fields: [ - { - field_id: 13, - value: payload.reffereal || '', - }, { field_id: 1, - value: payload.tcProfileLink || (payload.handle ? `topcoder.com/members/${payload.handle}` : ''), + value: payload.tcProfileLink || (payload.handle ? `https://topcoder.com/members/${payload.handle}` : ''), }, { field_id: 2, @@ -92,6 +87,14 @@ function normalizeRecruitPayload(job, payload) { ], resume: payload.fileCV, }; + if (referral) { + normalized.custom_fields.push({ + field_id: 13, + value: referral.label, + }); + } + + return normalized; } /** diff --git a/src/shared/components/Gigs/GigApply/index.jsx b/src/shared/components/Gigs/GigApply/index.jsx index ec28c3fef4..df3d55c6f7 100644 --- a/src/shared/components/Gigs/GigApply/index.jsx +++ b/src/shared/components/Gigs/GigApply/index.jsx @@ -27,7 +27,7 @@ export default function GigApply(props) { } = props; const retUrl = window.location.href; - console.log('GigApply', recruitProfile) + console.log('GigApply', recruitProfile, formErrors); return user ? (
@@ -94,8 +94,16 @@ export default function GigApply(props) { { !application && !applying ? (
+ {!_.isEmpty(recruitProfile) + && ( +

It looks like you have applied to a gig previously. + Perfect! We have most of your information. + Is there anything you would like to update to your Gig Work Profile? +

+ )}

PERSONAL INFORMATION

-

Welcome to Topcoder Gigs! We’d like to get to know you.

+ {_.isEmpty(recruitProfile) + &&

Welcome to Topcoder Gigs! We’d like to get to know you.

}
-

TOPCODER INFORMATION

+ {_.isEmpty(recruitProfile) &&

TOPCODER INFORMATION

} + {_.isEmpty(recruitProfile) && (
+ )}

SHARE YOUR EXPECTATIONS

-

Your Professional Work History

+ {_.isEmpty(recruitProfile) &&

Your Professional Work History

}
FINAL QUESTIONS

Please Complete the Following Questions

+ {_.isEmpty(recruitProfile) && ( + )}
-

Are you able to work during the specified timezone? ({`${getCustomField(job.custom_fields, 'Timezone')}`})

+

Are you able to work during the specified timezone? ({`${getCustomField(job.custom_fields, 'Timezone')}`}) *

onFormInputChange('timezoneConfirm', val)} errorMsg={formErrors.timezoneConfirm} @@ -231,7 +243,7 @@ export default function GigApply(props) { size="lg" />
-

Are you ok to work with the duration of the gig? ({`${getCustomField(job.custom_fields, 'Duration')}`})

+

Are you ok to work with the duration of the gig? ({`${getCustomField(job.custom_fields, 'Duration')}`}) *

onFormInputChange('durationConfirm', val)} errorMsg={formErrors.durationConfirm} diff --git a/src/shared/components/Gigs/GigApply/style.scss b/src/shared/components/Gigs/GigApply/style.scss index b807f0b8ab..01718f94c2 100644 --- a/src/shared/components/Gigs/GigApply/style.scss +++ b/src/shared/components/Gigs/GigApply/style.scss @@ -148,6 +148,11 @@ } } + .info-text { + font-size: 16px; + margin-top: 35px; + } + .form-section { margin: 13px 0 50px; diff --git a/src/shared/containers/Gigs/RecruitCRMJobApply.jsx b/src/shared/containers/Gigs/RecruitCRMJobApply.jsx index 244a2990d4..cbea7c06c2 100644 --- a/src/shared/containers/Gigs/RecruitCRMJobApply.jsx +++ b/src/shared/containers/Gigs/RecruitCRMJobApply.jsx @@ -77,6 +77,7 @@ class RecruitCRMJobApplyContainer extends React.Component { validateForm(prop) { this.setState((state) => { const { formData, formErrors } = state; + const { recruitProfile } = this.props; // Form validation happens here const requiredTextFields = [ 'fname', 'lname', 'city', 'phone', 'email', @@ -110,8 +111,10 @@ class RecruitCRMJobApplyContainer extends React.Component { } // check for selected reffereal if (!prop || prop === 'reffereal') { - if (!_.find(formData.reffereal, { selected: true })) formErrors.reffereal = 'Please, select your reffereal'; - else delete formErrors.reffereal; + if (!recruitProfile) { + if (!_.find(formData.reffereal, { selected: true })) formErrors.reffereal = 'Please, select your reffereal'; + else delete formErrors.reffereal; + } } // check payExpectation to be a number if (!prop || prop === 'payExpectation') { @@ -154,13 +157,23 @@ class RecruitCRMJobApplyContainer extends React.Component { } } } - // timezone & duration - if (!prop || prop === 'timezoneConfirm' || prop === 'durationConfirm') { + // timezone + if (!prop || prop === 'timezoneConfirm') { const a = _.find(formData[prop], { value: true }); if (a) { if (a.label === 'No') formErrors[prop] = 'Sorry, we are only looking for candidates that can work the hours and duration listed'; else delete formErrors[prop]; - } + } else if (prop) formErrors[prop] = 'Required field'; + else if (!prop && !_.find(formData.timezoneConfirm, { value: true })) formErrors.timezoneConfirm = 'Required field'; + } + // duration + if (!prop || prop === 'durationConfirm') { + const a = _.find(formData[prop], { value: true }); + if (a) { + if (a.label === 'No') formErrors[prop] = 'Sorry, we are only looking for candidates that can work the hours and duration listed'; + else delete formErrors[prop]; + } else if (prop) formErrors[prop] = 'Required field'; + else if (!prop && !_.find(formData.durationConfirm, { value: true })) formErrors.durationConfirm = 'Required field'; } // updated state return { From 5f29c1e0cc8d697361640aea7733850b00b0f62b Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Tue, 16 Mar 2021 12:00:54 +0200 Subject: [PATCH 3/8] ci: on test --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3b0049b420..817bd695c4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -283,7 +283,7 @@ workflows: filters: branches: only: - - route-redirects + - gig-application-update # This is alternate dev env for parallel testing - "build-qa": context : org-global From 18b5533c0188c78933f05d93544caac9dc61ec55 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Wed, 17 Mar 2021 12:36:20 +0200 Subject: [PATCH 4/8] Final tweaks #5429 --- src/server/services/recruitCRM.js | 48 ++++++++++--------- src/shared/actions/recruitCRM.js | 4 +- .../GUIKit/Assets/Styles/Includes/_mixin.scss | 1 + .../components/GUIKit/DropdownTerms/index.jsx | 3 ++ .../components/GUIKit/TextInput/index.jsx | 2 +- .../components/GUIKit/TextInput/style.scss | 8 ++++ src/shared/components/Gigs/GigApply/index.jsx | 18 ++++--- .../components/Gigs/LoginModal/modal.scss | 2 +- .../containers/Gigs/RecruitCRMJobApply.jsx | 47 +++++++++++++++--- 9 files changed, 95 insertions(+), 38 deletions(-) diff --git a/src/server/services/recruitCRM.js b/src/server/services/recruitCRM.js index 1320f95467..4f8ee4e2e1 100644 --- a/src/server/services/recruitCRM.js +++ b/src/server/services/recruitCRM.js @@ -187,7 +187,9 @@ export default class RecruitCRMService { const { body, file } = req; const form = JSON.parse(body.form); const fileData = new FormData(); - fileData.append('resume', file.buffer, file.originalname); + if (file) { + fileData.append('resume', file.buffer, file.originalname); + } let candidateSlug; let referralCookie = req.cookies[config.GROWSURF_COOKIE]; if (referralCookie) referralCookie = JSON.parse(referralCookie); @@ -272,29 +274,31 @@ export default class RecruitCRMService { }); } candidateData = await workCandidateResponse.json(); - // Attach resume to candidate - const formHeaders = fileData.getHeaders(); - const fileCandidateResponse = await fetch(`${this.private.baseUrl}/v1/candidates/${candidateData.slug}`, { - method: 'POST', - headers: { - Authorization: this.private.authorization, - ...formHeaders, - }, - body: fileData, - }); - if (fileCandidateResponse.status >= 300) { - return res.send({ - error: true, - status: fileCandidateResponse.status, - url: `${this.private.baseUrl}/v1/candidates/${candidateData.slug}`, - form, - fileData, - file, - formHeaders, - errObj: await fileCandidateResponse.json(), + // Attach resume to candidate if uploaded + if (file) { + const formHeaders = fileData.getHeaders(); + const fileCandidateResponse = await fetch(`${this.private.baseUrl}/v1/candidates/${candidateData.slug}`, { + method: 'POST', + headers: { + Authorization: this.private.authorization, + ...formHeaders, + }, + body: fileData, }); + if (fileCandidateResponse.status >= 300) { + return res.send({ + error: true, + status: fileCandidateResponse.status, + url: `${this.private.baseUrl}/v1/candidates/${candidateData.slug}`, + form, + fileData, + file, + formHeaders, + errObj: await fileCandidateResponse.json(), + }); + } + candidateData = await fileCandidateResponse.json(); } - candidateData = await fileCandidateResponse.json(); // Candidate ready to apply for job const applyResponse = await fetch(`${this.private.baseUrl}/v1/candidates/${candidateData.slug}/assign?job_slug=${id}`, { method: 'POST', diff --git a/src/shared/actions/recruitCRM.js b/src/shared/actions/recruitCRM.js index c4e7ed649b..cc82b1446d 100644 --- a/src/shared/actions/recruitCRM.js +++ b/src/shared/actions/recruitCRM.js @@ -85,7 +85,6 @@ function normalizeRecruitPayload(job, payload) { value: perJob.join(','), }, ], - resume: payload.fileCV, }; if (referral) { normalized.custom_fields.push({ @@ -93,6 +92,9 @@ function normalizeRecruitPayload(job, payload) { value: referral.label, }); } + if (payload.fileCV) { + normalized.resume = payload.fileCV; + } return normalized; } diff --git a/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss b/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss index 5589e9dc31..ef6b4576f7 100644 --- a/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss +++ b/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss @@ -4,6 +4,7 @@ $gui-kit-gray-90: #2a2a2a; $gui-kit-level-2: #0ab88a; $gui-kit-level-5: #ef476f; $gui-kit-active-label: #229174; +$gui-kit-readonly: #d4d4d4; @mixin textInputLabel { font-size: 12px; diff --git a/src/shared/components/GUIKit/DropdownTerms/index.jsx b/src/shared/components/GUIKit/DropdownTerms/index.jsx index 1ef2c69792..74001e88bd 100644 --- a/src/shared/components/GUIKit/DropdownTerms/index.jsx +++ b/src/shared/components/GUIKit/DropdownTerms/index.jsx @@ -41,6 +41,9 @@ function DropdownTerms({ selectInput[0].style.borderTop = 'none'; } }, [focused, selectedOption]); + useEffect(() => { + setInternalTerms(terms); + }, [terms]); const CustomReactSelectRow = React.forwardRef(({ className, diff --git a/src/shared/components/GUIKit/TextInput/index.jsx b/src/shared/components/GUIKit/TextInput/index.jsx index d4ef9938c9..43d910a76a 100644 --- a/src/shared/components/GUIKit/TextInput/index.jsx +++ b/src/shared/components/GUIKit/TextInput/index.jsx @@ -28,7 +28,7 @@ function TextInput({ const sizeStyle = size === 'lg' ? 'lgSize' : 'xsSize'; return ( -
+
{ @@ -76,7 +74,7 @@ export default function GigApply(props) { VIEW OTHER GIGS ) : ( - GO TO GIG LIST + GO TO GIGS LIST ) }
@@ -113,6 +111,7 @@ export default function GigApply(props) { errorMsg={formErrors.fname} value={formData.fname} required + readonly />
@@ -184,8 +184,7 @@ export default function GigApply(props) {
)} -

SHARE YOUR EXPECTATIONS

- {_.isEmpty(recruitProfile) &&

Your Professional Work History

} +

SHARE YOUR WEEKLY PAY EXPECTATIONS

RESUME & SKILLS

-

Upload Your Resume or CV

+ { + recruitProfile.resume ? ( +

Upload Your Resume or CV, {recruitProfile.resume.filename}

+ ) : ( +

Upload Your Resume or CV

+ ) + }

FINAL QUESTIONS

-

Please Complete the Following Questions

{_.isEmpty(recruitProfile) && ( ({ label: val, selected: false })), reffereal: [ { label: 'Google', selected: false }, + { label: 'LinkedIn', selected: false }, + { label: 'Other Ad or Promotion', selected: false }, + { label: 'Quora', selected: false }, + { label: 'Uprisor Podcast', selected: false }, + { label: 'YouTube or Video Ad', selected: false }, ], // eslint-disable-next-line react/destructuring-assignment }, @@ -51,6 +56,36 @@ class RecruitCRMJobApplyContainer extends React.Component { } } + componentDidUpdate(prevProps) { + const { recruitProfile } = this.props; + if (recruitProfile !== prevProps.recruitProfile && !_.isEmpty(recruitProfile)) { + // when recruit profile loaded + const { formData } = this.state; + const { country, skills } = formData; + const recruitSkills = recruitProfile.skill.split(',').map(s => s.toLowerCase()); + const updatedForm = { + formData: { + ...formData, + phone: recruitProfile.contact_number, + country: _.map( + country, + c => ({ + label: c.label, + selected: c.label.toLowerCase() === recruitProfile.locality.toLowerCase(), + }), + ), + skills: skills.map(s => ({ + label: s.label, + selected: recruitSkills.includes(s.label.toLowerCase()), + })), + payExpectation: recruitProfile.salary_expectation, + }, + }; + // eslint-disable-next-line react/no-did-update-set-state + this.setState(updatedForm); + } + } + onFormInputChange(key, value) { // update the state this.setState(state => ({ @@ -111,7 +146,7 @@ class RecruitCRMJobApplyContainer extends React.Component { } // check for selected reffereal if (!prop || prop === 'reffereal') { - if (!recruitProfile) { + if (_.isEmpty(recruitProfile)) { if (!_.find(formData.reffereal, { selected: true })) formErrors.reffereal = 'Please, select your reffereal'; else delete formErrors.reffereal; } @@ -144,8 +179,8 @@ class RecruitCRMJobApplyContainer extends React.Component { } // has CV file ready for upload if (!prop || prop === 'fileCV') { - if (!formData.fileCV) formErrors.fileCV = 'Please, pick your CV file for uploading'; - else { + if (!formData.fileCV && _.isEmpty(recruitProfile)) formErrors.fileCV = 'Please, pick your CV file for uploading'; + else if (formData.fileCV) { const sizeInMB = (formData.fileCV.size / (1024 * 1024)).toFixed(2); if (sizeInMB > 8) { formErrors.fileCV = 'Max file size is limited to 8 MB'; @@ -185,8 +220,8 @@ class RecruitCRMJobApplyContainer extends React.Component { render() { const { formErrors, formData } = this.state; - const { recruitProfile } = this.props; - return !recruitProfile ? : ( + const { recruitProfile, user } = this.props; + return !recruitProfile && user ? : ( Date: Wed, 17 Mar 2021 14:40:49 +0200 Subject: [PATCH 5/8] text updated to design --- src/shared/components/Gigs/GigApply/index.jsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/shared/components/Gigs/GigApply/index.jsx b/src/shared/components/Gigs/GigApply/index.jsx index cd2597b950..0c30e6b231 100644 --- a/src/shared/components/Gigs/GigApply/index.jsx +++ b/src/shared/components/Gigs/GigApply/index.jsx @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ /** * The Gig apply page. */ @@ -94,9 +95,9 @@ export default function GigApply(props) {
{!_.isEmpty(recruitProfile) && ( -

It looks like you have applied to a gig previously. - Perfect! We have most of your information. - Is there anything you would like to update to your Gig Work Profile? +

+ It looks like you have applied to a gig previously. Perfect!✓
+ We have most of your information. Is there anything you would like to update to your Gig Work Profile?

)}

PERSONAL INFORMATION

From 69cedb5fb1415d205f933d5ff7abaafd0e5f9f1f Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Wed, 17 Mar 2021 14:48:14 +0200 Subject: [PATCH 6/8] add weeks to duration --- src/shared/components/Gigs/GigApply/index.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shared/components/Gigs/GigApply/index.jsx b/src/shared/components/Gigs/GigApply/index.jsx index 0c30e6b231..e2b71b62d7 100644 --- a/src/shared/components/Gigs/GigApply/index.jsx +++ b/src/shared/components/Gigs/GigApply/index.jsx @@ -27,6 +27,7 @@ export default function GigApply(props) { recruitProfile, } = props; const retUrl = window.location.href; + const duration = getCustomField(job.custom_fields, 'Duration'); return user ? (
@@ -248,7 +249,7 @@ export default function GigApply(props) { size="lg" />
-

Are you ok to work with the duration of the gig? ({`${getCustomField(job.custom_fields, 'Duration')}`}) *

+

Are you ok to work with the duration of the gig? ({/^\d+$/.test(duration) ? `${duration} Weeks` : duration}) *

onFormInputChange('durationConfirm', val)} errorMsg={formErrors.durationConfirm} From 622586b4486dddf4e1d17220725cf3f8d170d4a5 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Thu, 18 Mar 2021 11:44:14 +0200 Subject: [PATCH 7/8] qa fixes #5429 --- src/assets/images/checkmark-green.svg | 12 ++++++ src/shared/components/Gigs/GigApply/index.jsx | 9 +++-- .../components/Gigs/GigApply/style.scss | 16 +++++++- .../containers/Gigs/RecruitCRMJobApply.jsx | 39 ++++++++++++++----- 4 files changed, 62 insertions(+), 14 deletions(-) create mode 100644 src/assets/images/checkmark-green.svg diff --git a/src/assets/images/checkmark-green.svg b/src/assets/images/checkmark-green.svg new file mode 100644 index 0000000000..a31e6590cf --- /dev/null +++ b/src/assets/images/checkmark-green.svg @@ -0,0 +1,12 @@ + + + CA804B2A-E950-4496-A96D-0236E1B4CB62@2x + + + + + + + + + \ No newline at end of file diff --git a/src/shared/components/Gigs/GigApply/index.jsx b/src/shared/components/Gigs/GigApply/index.jsx index e2b71b62d7..6351d65616 100644 --- a/src/shared/components/Gigs/GigApply/index.jsx +++ b/src/shared/components/Gigs/GigApply/index.jsx @@ -20,6 +20,7 @@ import './style.scss'; import bigCheckmark from 'assets/images/big-checkmark.png'; import SadFace from 'assets/images/sad-face-icon.svg'; import BackArrowGig from 'assets/images/back-arrow-gig-apply.svg'; +import CheckmarkGreen from 'assets/images/checkmark-green.svg'; export default function GigApply(props) { const { @@ -96,10 +97,10 @@ export default function GigApply(props) {
{!_.isEmpty(recruitProfile) && ( -

- It looks like you have applied to a gig previously. Perfect!✓
- We have most of your information. Is there anything you would like to update to your Gig Work Profile? -

+
+
It looks like you have applied to a gig previously. Perfect!
+

We have most of your information. Is there anything you would like to update to your Gig Work Profile?

+
)}

PERSONAL INFORMATION

{_.isEmpty(recruitProfile) diff --git a/src/shared/components/Gigs/GigApply/style.scss b/src/shared/components/Gigs/GigApply/style.scss index 01718f94c2..ec860228fc 100644 --- a/src/shared/components/Gigs/GigApply/style.scss +++ b/src/shared/components/Gigs/GigApply/style.scss @@ -150,7 +150,21 @@ .info-text { font-size: 16px; - margin-top: 35px; + margin-top: 55px; + + h6 { + font-family: Barlow, sans-serif; + font-size: 16px; + line-height: 20px; + font-weight: 600; + margin-bottom: 8px; + display: flex; + align-items: center; + + svg { + margin-left: 5px; + } + } } .form-section { diff --git a/src/shared/containers/Gigs/RecruitCRMJobApply.jsx b/src/shared/containers/Gigs/RecruitCRMJobApply.jsx index e4754c44d8..f418265630 100644 --- a/src/shared/containers/Gigs/RecruitCRMJobApply.jsx +++ b/src/shared/containers/Gigs/RecruitCRMJobApply.jsx @@ -45,27 +45,48 @@ class RecruitCRMJobApplyContainer extends React.Component { this.validateForm = this.validateForm.bind(this); } + // eslint-disable-next-line consistent-return componentDidMount() { const { formData } = this.state; const { user, recruitProfile, searchCandidates } = this.props; - this.setState({ - formData: _.merge(formData, user), - }); - if (user && !recruitProfile) { - searchCandidates(user.email); + if (user) { + if (!recruitProfile) searchCandidates(user.email); + else { + const { country, skills } = formData; + const recruitSkills = recruitProfile.skill.split(',').map(s => s.toLowerCase()); + return this.setState({ + formData: _.merge(formData, user, { + phone: recruitProfile.contact_number, + country: _.map( + country, + c => ({ + label: c.label, + selected: c.label.toLowerCase() === recruitProfile.locality.toLowerCase(), + }), + ), + skills: skills.map(s => ({ + label: s.label, + selected: recruitSkills.includes(s.label.toLowerCase()), + })), + payExpectation: recruitProfile.salary_expectation, + }), + }); + } + this.setState({ + formData: _.merge(formData, user), + }); } } componentDidUpdate(prevProps) { - const { recruitProfile } = this.props; + const { recruitProfile, user } = this.props; if (recruitProfile !== prevProps.recruitProfile && !_.isEmpty(recruitProfile)) { // when recruit profile loaded const { formData } = this.state; const { country, skills } = formData; const recruitSkills = recruitProfile.skill.split(',').map(s => s.toLowerCase()); const updatedForm = { - formData: { - ...formData, + formData: _.merge(formData, user, { phone: recruitProfile.contact_number, country: _.map( country, @@ -79,7 +100,7 @@ class RecruitCRMJobApplyContainer extends React.Component { selected: recruitSkills.includes(s.label.toLowerCase()), })), payExpectation: recruitProfile.salary_expectation, - }, + }), }; // eslint-disable-next-line react/no-did-update-set-state this.setState(updatedForm); From 8fde79b19567a55c6c1ecc60d1c6892a87acc800 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Thu, 18 Mar 2021 12:44:20 +0200 Subject: [PATCH 8/8] refresh page after gig apply --- src/shared/components/Gigs/GigApply/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/components/Gigs/GigApply/index.jsx b/src/shared/components/Gigs/GigApply/index.jsx index 6351d65616..d48f9ec819 100644 --- a/src/shared/components/Gigs/GigApply/index.jsx +++ b/src/shared/components/Gigs/GigApply/index.jsx @@ -77,7 +77,7 @@ export default function GigApply(props) { VIEW OTHER GIGS ) : ( - GO TO GIGS LIST + GO TO GIGS LIST ) }