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 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 @@ + + \ No newline at end of file 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 1b9b127f25..cc82b1446d 100644 --- a/src/shared/actions/recruitCRM.js +++ b/src/shared/actions/recruitCRM.js @@ -58,47 +58,45 @@ function normalizeRecruitPayload(job, payload) { const perJob = [ `${job.name} ->`, `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}`, + `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, 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: [ - { - 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, value: payload.handle || '', }, - { - field_id: 3, - value: payload.whyFit || '', - }, { field_id: 14, value: perJob.join(','), }, ], - resume: payload.fileCV, }; + if (referral) { + normalized.custom_fields.push({ + field_id: 13, + value: referral.label, + }); + } + if (payload.fileCV) { + normalized.resume = payload.fileCV; + } + + return normalized; } /** @@ -124,6 +122,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 +161,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/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 ( -
We have most of your information. Is there anything you would like to update to your Gig Work Profile?
+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.
}Your Professional Work History
+ )} +Upload Your Resume or CV
+ { + recruitProfile.resume ? ( +Upload Your Resume or CV, {recruitProfile.resume.filename}
+ ) : ( +Upload Your Resume or CV
+ ) + }Please Complete the Following Questions
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')}`}) *
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}) *
+