Skip to content

Commit bd15106

Browse files
committed
step 1 - gig apply v2
1 parent df549d5 commit bd15106

File tree

5 files changed

+123
-47
lines changed

5 files changed

+123
-47
lines changed

src/shared/actions/recruitCRM.js

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,8 @@ function normalizeRecruitPayload(job, payload) {
6060
`Pay Expectation: ${payload.payExpectation}`,
6161
`Date Available: ${new Date(payload.availFrom).toDateString()}`,
6262
`Heard About Gig: ${payload.reffereal}`,
63-
`Why fit: ${payload.whyFit}`,
6463
`Able to work during timezone? ${payload.timezoneConfirm.filter(s => s.value).map(() => getCustomField(job.custom_fields, 'Timezone')).join(',')}`,
6564
`Am I ok to work the duration? ${payload.durationConfirm.filter(s => s.value).map(s => s.label).join(',')}`,
66-
`Notes: ${payload.notes}`,
6765
];
6866
return {
6967
last_name: payload.lname,
@@ -72,7 +70,6 @@ function normalizeRecruitPayload(job, payload) {
7270
contact_number: payload.phone,
7371
city: payload.city,
7472
locality: _.find(payload.country, { selected: true }).label,
75-
available_from: payload.availFrom,
7673
salary_expectation: payload.payExpectation,
7774
skill: payload.skills.filter(s => s.selected).map(s => s.label).join(','),
7875
custom_fields: [
@@ -88,10 +85,6 @@ function normalizeRecruitPayload(job, payload) {
8885
field_id: 2,
8986
value: payload.handle || '',
9087
},
91-
{
92-
field_id: 3,
93-
value: payload.whyFit || '',
94-
},
9588
{
9689
field_id: 14,
9790
value: perJob.join(','),
@@ -124,6 +117,37 @@ async function applyForJobDone(job, payload) {
124117
}
125118
}
126119

120+
/**
121+
* Search for cnadidate in recruit
122+
*/
123+
function searchCandidatesInit(email) {
124+
return { email };
125+
}
126+
127+
/**
128+
* Search for cnadidate in recruit and get profile if available
129+
* @param {string} email the email to search
130+
*/
131+
async function searchCandidatesDone(email) {
132+
const ss = new Service();
133+
try {
134+
const res = await ss.searchCandidates(email);
135+
136+
return {
137+
email,
138+
data: res,
139+
};
140+
} catch (error) {
141+
return {
142+
email,
143+
data: {
144+
error: true,
145+
errorObj: error,
146+
},
147+
};
148+
}
149+
}
150+
127151
export default redux.createActions({
128152
RECRUIT: {
129153
GET_JOBS_INIT: getJobsInit,
@@ -132,5 +156,7 @@ export default redux.createActions({
132156
GET_JOB_DONE: getJobDone,
133157
APPLY_FOR_JOB_INIT: applyForJobInit,
134158
APPLY_FOR_JOB_DONE: applyForJobDone,
159+
SEARCH_CANDIDATES_INIT: searchCandidatesInit,
160+
SEARCH_CANDIDATES_DONE: searchCandidatesDone,
135161
},
136162
});

src/shared/components/Gigs/GigApply/index.jsx

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import React from 'react';
77
import PT from 'prop-types';
88
import { Link, config } from 'topcoder-react-utils';
99
import TextInput from 'components/GUIKit/TextInput';
10-
import Datepicker from 'components/GUIKit/Datepicker';
1110
import DropdownTerms from 'components/GUIKit/DropdownTerms';
1211
import RadioButton from 'components/GUIKit/RadioButton';
1312
import Checkbox from 'components/GUIKit/Checkbox';
@@ -24,9 +23,12 @@ import BackArrowGig from 'assets/images/back-arrow-gig-apply.svg';
2423
export default function GigApply(props) {
2524
const {
2625
job, onFormInputChange, formData, formErrors, onApplyClick, applying, application, user,
26+
recruitProfile,
2727
} = props;
2828
const retUrl = window.location.href;
2929

30+
console.log('GigApply', recruitProfile)
31+
3032
return user ? (
3133
<div styleName="container">
3234
{
@@ -182,13 +184,7 @@ export default function GigApply(props) {
182184
onChange={val => onFormInputChange('payExpectation', val)}
183185
errorMsg={formErrors.payExpectation}
184186
value={formData.payExpectation}
185-
/>
186-
<Datepicker
187-
placeholder="Available From"
188-
label="Available From"
189-
onChange={val => onFormInputChange('availFrom', val ? val.toISOString() : null)}
190-
errorMsg={formErrors.availFrom}
191-
value={formData.availFrom}
187+
required
192188
/>
193189
</div>
194190
</div>
@@ -218,42 +214,29 @@ export default function GigApply(props) {
218214
<h4>FINAL QUESTIONS</h4>
219215
<p>Please Complete the Following Questions</p>
220216
<div styleName="form-section">
221-
<TextInput
222-
placeholder="How did you hear about this gig?"
223-
label="How did you hear about this gig?"
217+
<Dropdown
218+
placeholder="How did you find out about Topcoder Gig Work?"
219+
label="How did you find out about Topcoder Gig Work?"
224220
onChange={val => onFormInputChange('reffereal', val)}
225221
errorMsg={formErrors.reffereal}
226-
value={formData.reffereal}
222+
options={formData.reffereal}
227223
required
228224
/>
229225
<div styleName="input-bot-margin" />
230-
<TextInput
231-
placeholder="Why do you think you're a good fit for this gig?"
232-
label="Why do you think you're a good fit for this gig?"
233-
onChange={val => onFormInputChange('whyFit', val)}
234-
errorMsg={formErrors.whyFit}
235-
value={formData.whyFit}
236-
/>
237226
<p>Are you able to work during the specified timezone? (<strong>{`${getCustomField(job.custom_fields, 'Timezone')}`}</strong>)</p>
238227
<RadioButton
239228
onChange={val => onFormInputChange('timezoneConfirm', val)}
240229
errorMsg={formErrors.timezoneConfirm}
241230
options={formData.timezoneConfirm}
242231
size="lg"
243232
/>
244-
<p>Are you ok to work with the duration of the gig? (<strong>{`${getCustomField(job.custom_fields, 'Duration')}`}</strong>)</p>
245-
<RadioButton
246-
onChange={val => onFormInputChange('durationConfirm', val)}
247-
errorMsg={formErrors.durationConfirm}
248-
options={formData.durationConfirm}
249-
size="lg"
250-
/>
251233
<div styleName="last-input">
252-
<TextInput
253-
placeholder="Add any other notes you might have"
254-
label="Notes"
255-
onChange={val => onFormInputChange('notes', val)}
256-
errorMsg={formErrors.notes}
234+
<p>Are you ok to work with the duration of the gig? (<strong>{`${getCustomField(job.custom_fields, 'Duration')}`}</strong>)</p>
235+
<RadioButton
236+
onChange={val => onFormInputChange('durationConfirm', val)}
237+
errorMsg={formErrors.durationConfirm}
238+
options={formData.durationConfirm}
239+
size="lg"
257240
/>
258241
</div>
259242
</div>
@@ -313,4 +296,5 @@ GigApply.propTypes = {
313296
applying: PT.bool,
314297
application: PT.shape(),
315298
user: PT.shape(),
299+
recruitProfile: PT.shape().isRequired,
316300
};

src/shared/containers/Gigs/RecruitCRMJobApply.jsx

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import _ from 'lodash';
66
import actions from 'actions/recruitCRM';
77
import GigApply from 'components/Gigs/GigApply';
8+
import LoadingIndicator from 'components/LoadingIndicator';
89
import PT from 'prop-types';
910
import React from 'react';
1011
import { connect } from 'react-redux';
@@ -21,12 +22,14 @@ class RecruitCRMJobApplyContainer extends React.Component {
2122
this.state = {
2223
formErrors: {},
2324
formData: {
24-
availFrom: new Date().toISOString(),
2525
skills: _.map(techSkills, label => ({ label, selected: false })),
2626
durationConfirm: [{ label: 'Yes', value: false }, { label: 'No', value: false }],
2727
timezoneConfirm: [{ label: 'Yes', value: false }, { label: 'No', value: false }],
2828
agreedTerms: false,
2929
country: _.map(countries.getNames('en'), val => ({ label: val, selected: false })),
30+
reffereal: [
31+
{ label: 'Google', selected: false },
32+
],
3033
// eslint-disable-next-line react/destructuring-assignment
3134
},
3235
};
@@ -39,10 +42,13 @@ class RecruitCRMJobApplyContainer extends React.Component {
3942

4043
componentDidMount() {
4144
const { formData } = this.state;
42-
const { user } = this.props;
45+
const { user, recruitProfile, searchCandidates } = this.props;
4346
this.setState({
4447
formData: _.merge(formData, user),
4548
});
49+
if (user && !recruitProfile) {
50+
searchCandidates(user.email);
51+
}
4652
}
4753

4854
onFormInputChange(key, value) {
@@ -73,7 +79,7 @@ class RecruitCRMJobApplyContainer extends React.Component {
7379
const { formData, formErrors } = state;
7480
// Form validation happens here
7581
const requiredTextFields = [
76-
'fname', 'lname', 'city', 'reffereal', 'phone', 'email',
82+
'fname', 'lname', 'city', 'phone', 'email',
7783
];
7884
// check required text fields for value
7985
// check min/max lengths
@@ -85,10 +91,6 @@ class RecruitCRMJobApplyContainer extends React.Component {
8591
else if (formData[key] && _.trim(formData[key]).length < 2) formErrors[key] = 'Must be at least 2 characters';
8692
else if (formData[key] && _.trim(formData[key]).length > 2) {
8793
switch (key) {
88-
case 'reffereal':
89-
if (_.trim(formData[key]).length > 2000) formErrors[key] = 'Must be max 2000 characters';
90-
else delete formErrors[key];
91-
break;
9294
case 'city':
9395
case 'phone':
9496
if (_.trim(formData[key]).length > 50) formErrors[key] = 'Must be max 50 characters';
@@ -106,12 +108,17 @@ class RecruitCRMJobApplyContainer extends React.Component {
106108
if (!_.find(formData.country, { selected: true })) formErrors.country = 'Please, select your country';
107109
else delete formErrors.country;
108110
}
111+
// check for selected reffereal
112+
if (!prop || prop === 'reffereal') {
113+
if (!_.find(formData.reffereal, { selected: true })) formErrors.reffereal = 'Please, select your reffereal';
114+
else delete formErrors.reffereal;
115+
}
109116
// check payExpectation to be a number
110117
if (!prop || prop === 'payExpectation') {
111118
if (formData.payExpectation && _.trim(formData.payExpectation)) {
112119
if (!_.isInteger(_.toNumber(formData.payExpectation))) formErrors.payExpectation = 'Must be integer value in $';
113120
else delete formErrors.payExpectation;
114-
} else delete formErrors.payExpectation;
121+
} else formErrors.payExpectation = 'Required field';
115122
}
116123
// check for valid email
117124
if (!prop || prop === 'email') {
@@ -147,6 +154,14 @@ class RecruitCRMJobApplyContainer extends React.Component {
147154
}
148155
}
149156
}
157+
// timezone & duration
158+
if (!prop || prop === 'timezoneConfirm' || prop === 'durationConfirm') {
159+
const a = _.find(formData[prop], { value: true });
160+
if (a) {
161+
if (a.label === 'No') formErrors[prop] = 'Sorry, we are only looking for candidates that can work the hours and duration listed';
162+
else delete formErrors[prop];
163+
}
164+
}
150165
// updated state
151166
return {
152167
...state,
@@ -157,7 +172,8 @@ class RecruitCRMJobApplyContainer extends React.Component {
157172

158173
render() {
159174
const { formErrors, formData } = this.state;
160-
return (
175+
const { recruitProfile } = this.props;
176+
return !recruitProfile ? <LoadingIndicator /> : (
161177
<GigApply
162178
{...this.props}
163179
onFormInputChange={this.onFormInputChange}
@@ -173,6 +189,7 @@ RecruitCRMJobApplyContainer.defaultProps = {
173189
user: null,
174190
applying: false,
175191
application: null,
192+
recruitProfile: null,
176193
};
177194

178195
RecruitCRMJobApplyContainer.propTypes = {
@@ -181,6 +198,8 @@ RecruitCRMJobApplyContainer.propTypes = {
181198
applyForJob: PT.func.isRequired,
182199
applying: PT.bool,
183200
application: PT.shape(),
201+
searchCandidates: PT.func.isRequired,
202+
recruitProfile: PT.shape(),
184203
};
185204

186205
function mapStateToProps(state, ownProps) {
@@ -202,6 +221,8 @@ function mapStateToProps(state, ownProps) {
202221
? state.recruitCRM[job.slug].applying : false,
203222
application: state.recruitCRM && state.recruitCRM[job.slug]
204223
? state.recruitCRM[job.slug].application : null,
224+
recruitProfile: state.recruitCRM && state.recruitCRM[profile.email]
225+
? state.recruitCRM[profile.email].profile : null,
205226
};
206227
}
207228

@@ -212,6 +233,10 @@ function mapDispatchToActions(dispatch) {
212233
dispatch(a.applyForJobInit(job, payload));
213234
dispatch(a.applyForJobDone(job, payload));
214235
},
236+
searchCandidates: (email) => {
237+
dispatch(a.searchCandidatesInit(email));
238+
dispatch(a.searchCandidatesDone(email));
239+
},
215240
};
216241
}
217242

src/shared/reducers/recruitCRM.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,32 @@ function onApplyForJobDone(state, { payload }) {
8484
return r;
8585
}
8686

87+
/**
88+
* Handles recruit.applyForJobInit action.
89+
* @param {Object} state Previous state.
90+
*/
91+
function onSearchCandidatesInit(state, { payload }) {
92+
const r = {
93+
...state,
94+
};
95+
r[payload.email] = {};
96+
return r;
97+
}
98+
99+
/**
100+
* Handles recruit.applyForJobDone action.
101+
* @param {Object} state Previous state.
102+
* @param {Object} action The action.
103+
*/
104+
function onSearchCandidatesDone(state, { payload }) {
105+
const r = {
106+
...state,
107+
};
108+
const profile = _.isArray(payload.data) ? {} : payload.data.data[0];
109+
r[payload.email].profile = profile;
110+
return r;
111+
}
112+
87113
/**
88114
* Creates recruitCRM reducer with the specified initial state.
89115
* @param {Object} state Optional. If not given, the default one is
@@ -98,6 +124,8 @@ function create(state = {}) {
98124
[actions.recruit.getJobDone]: onJobDone,
99125
[actions.recruit.applyForJobInit]: onApplyForJobInit,
100126
[actions.recruit.applyForJobDone]: onApplyForJobDone,
127+
[actions.recruit.searchCandidatesInit]: onSearchCandidatesInit,
128+
[actions.recruit.searchCandidatesDone]: onSearchCandidatesDone,
101129
}, state);
102130
}
103131

src/shared/services/recruitCRM.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,17 @@ export default class Service {
6767
}
6868
return res.json();
6969
}
70+
71+
/**
72+
* Search for candidate
73+
* @param {object} email The email to search with
74+
*/
75+
async searchCandidates(email) {
76+
const res = await fetch(`${this.baseUrl}/candidates/search?email=${email}`);
77+
if (!res.ok) {
78+
const error = new Error('Failed to search for candidates');
79+
logger.error(error, res);
80+
}
81+
return res.json();
82+
}
7083
}

0 commit comments

Comments
 (0)