1
|
|
|
import React, { FunctionComponent } from "react"; |
2
|
|
|
import { |
3
|
|
|
FormattedMessage, |
4
|
|
|
useIntl, |
5
|
|
|
defineMessages, |
6
|
|
|
IntlShape, |
7
|
|
|
} from "react-intl"; |
8
|
|
|
import * as Yup from "yup"; |
9
|
|
|
import { Field, FieldArray } from "formik"; |
10
|
|
|
import { getLocale, localizeFieldNonNull } from "../../../helpers/localize"; |
11
|
|
|
import { validationMessages } from "../../Form/Messages"; |
12
|
|
|
import { Experience, Skill } from "../../../models/types"; |
13
|
|
|
import TextAreaInput from "../../Form/TextAreaInput"; |
14
|
|
|
import CheckboxInput from "../../Form/CheckboxInput"; |
15
|
|
|
import { countNumberOfWords } from "../../WordCounter/helpers"; |
16
|
|
|
import { ExperienceSubmitData } from "./ExperienceModalCommon"; |
17
|
|
|
import { |
18
|
|
|
deleteProperty, |
19
|
|
|
getId, |
20
|
|
|
mapToObjectTrans, |
21
|
|
|
objectMap, |
22
|
|
|
} from "../../../helpers/queries"; |
23
|
|
|
|
24
|
|
|
const messages = defineMessages({ |
25
|
|
|
justificationLabel: { |
26
|
|
|
id: "profile.experience.skillSubform.justificationLabel", |
27
|
|
|
defaultMessage: "Explanation", |
28
|
|
|
description: "Label for the 'How did you use this skill?' field.", |
29
|
|
|
}, |
30
|
|
|
justificationPlaceholder: { |
31
|
|
|
id: "profile.experience.skillSubform.justificationPlaceholder", |
32
|
|
|
defaultMessage: "How did you use this skill...", |
33
|
|
|
description: |
34
|
|
|
"Placeholder text for the 'How did you use this skill?' field.", |
35
|
|
|
}, |
36
|
|
|
}); |
37
|
|
|
|
38
|
|
|
export interface SkillFormValues { |
39
|
|
|
skills: { |
40
|
|
|
[key: number]: { |
41
|
|
|
selected: boolean; |
42
|
|
|
justification: string; |
43
|
|
|
}; |
44
|
|
|
}; |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
export const submitDataToFormSkills = ( |
48
|
|
|
data: ExperienceSubmitData<Experience>, |
49
|
|
|
allSkills: Skill[], |
50
|
|
|
): SkillFormValues => { |
51
|
|
|
const skillToData = (skill: Skill) => { |
52
|
|
|
const savedSkillData = data.savedSkills.find((x) => x.skillId === skill.id); |
53
|
|
|
return savedSkillData |
54
|
|
|
? { selected: true, justification: savedSkillData.justification } |
55
|
|
|
: { selected: false, justification: "" }; |
56
|
|
|
}; |
57
|
|
|
return { skills: mapToObjectTrans(allSkills, getId, skillToData) }; |
58
|
|
|
}; |
59
|
|
|
|
60
|
|
|
export const formSkillsToSubmitData = ( |
61
|
|
|
skillFormValues: SkillFormValues, |
62
|
|
|
): ExperienceSubmitData<Experience>["savedSkills"] => |
63
|
|
|
objectMap(skillFormValues.skills, (skillId, { selected, justification }) => ({ |
64
|
|
|
skillId: Number(skillId), |
65
|
|
|
selected, |
66
|
|
|
justification, |
67
|
|
|
})) |
68
|
|
|
.filter((x) => x.selected) |
69
|
|
|
.map((x) => deleteProperty(x, "selected")); |
70
|
|
|
|
71
|
|
|
const JUSTIFICATION_WORD_LIMIT = 100; |
72
|
|
|
|
73
|
|
|
export const validationShape = (intl: IntlShape) => { |
74
|
|
|
const requiredMsg = intl.formatMessage(validationMessages.required); |
75
|
|
|
const overWordLimit = intl.formatMessage(validationMessages.overMaxWords, { |
76
|
|
|
numberOfWords: JUSTIFICATION_WORD_LIMIT, |
77
|
|
|
}); |
78
|
|
|
return { |
79
|
|
|
skills: Yup.array().of( |
80
|
|
|
Yup.object().shape({ |
81
|
|
|
selected: Yup.boolean(), |
82
|
|
|
justification: Yup.string().when("selected", { |
83
|
|
|
is: true, |
84
|
|
|
then: Yup.string() |
85
|
|
|
.test( |
86
|
|
|
"wordCount", |
87
|
|
|
overWordLimit, |
88
|
|
|
(value: string) => |
89
|
|
|
countNumberOfWords(value) <= JUSTIFICATION_WORD_LIMIT, |
90
|
|
|
) |
91
|
|
|
.required(requiredMsg), |
92
|
|
|
otherwise: Yup.string().test( |
93
|
|
|
"wordCount", |
94
|
|
|
overWordLimit, |
95
|
|
|
(value: string) => |
96
|
|
|
countNumberOfWords(value) <= JUSTIFICATION_WORD_LIMIT, |
97
|
|
|
), // Enforce word limit even if justification isn't required. |
98
|
|
|
}), |
99
|
|
|
}), |
100
|
|
|
), |
101
|
|
|
}; |
102
|
|
|
}; |
103
|
|
|
|
104
|
|
|
export interface ProfileSkillSubformProps { |
105
|
|
|
keyPrefix: string; |
106
|
|
|
skills: Skill[]; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
export const ProfileSkillSubform: FunctionComponent<ProfileSkillSubformProps> = ({ |
110
|
|
|
keyPrefix, |
111
|
|
|
skills, |
112
|
|
|
}) => { |
113
|
|
|
const intl = useIntl(); |
114
|
|
|
const locale = getLocale(intl.locale); |
115
|
|
|
|
116
|
|
|
return ( |
117
|
|
|
<> |
118
|
|
|
<div data-c-container="medium"> |
119
|
|
|
<p |
120
|
|
|
data-c-margin="top(1) bottom(1)" |
121
|
|
|
data-c-font-size="h4" |
122
|
|
|
data-c-font-weight="bold" |
123
|
|
|
data-c-color="c3" |
124
|
|
|
> |
125
|
|
|
<FormattedMessage |
126
|
|
|
id="profile.experience.skillSubform.connectSubtitle" |
127
|
|
|
defaultMessage="Connect your Skills to this Experience" |
128
|
|
|
description="Subtitle of Connect-to-skills section." |
129
|
|
|
/> |
130
|
|
|
</p> |
131
|
|
|
<p data-c-margin="bottom(1)"> |
132
|
|
|
<FormattedMessage |
133
|
|
|
id="profile.experience.skillSubform.connectDescription" |
134
|
|
|
defaultMessage="Add any skills below that you learned or used in this experience. Hiring Managers see a lot of applicant profiles and you will need to set yourself apart if you want new job opportunities. You can do this by answering the following questions for each of the skills you add. This is the most important part of your profile if you're hoping a manager will find you." |
135
|
|
|
description="Explanation for Connect-to-skills section." |
136
|
|
|
/> |
137
|
|
|
</p> |
138
|
|
|
<ul data-c-font-weight="bold" data-c-margin="bottom(1)"> |
139
|
|
|
<li> |
140
|
|
|
<FormattedMessage |
141
|
|
|
id="profile.experience.skillSubform.question1" |
142
|
|
|
defaultMessage="What did you accomplish, create or deliver using this skill?" |
143
|
|
|
description="A question the user should answer when connecting a Skill to Experience." |
144
|
|
|
/> |
145
|
|
|
</li> |
146
|
|
|
<li> |
147
|
|
|
<FormattedMessage |
148
|
|
|
id="profile.experience.skillSubform.question2" |
149
|
|
|
defaultMessage="What tasks or activities did you do that relate to this skill?" |
150
|
|
|
description="A question the user should answer when connecting a Skill to Experience." |
151
|
|
|
/> |
152
|
|
|
</li> |
153
|
|
|
<li> |
154
|
|
|
<FormattedMessage |
155
|
|
|
id="profile.experience.skillSubform.question3" |
156
|
|
|
defaultMessage="Were there any special techniques or approaches that you used?" |
157
|
|
|
description="A question the user should answer when connecting a Skill to Experience." |
158
|
|
|
/> |
159
|
|
|
</li> |
160
|
|
|
<li> |
161
|
|
|
<FormattedMessage |
162
|
|
|
id="profile.experience.skillSubform.question4" |
163
|
|
|
defaultMessage="How much responsibility did you have in this role?" |
164
|
|
|
description="A question the user should answer when connecting a Skill to Experience." |
165
|
|
|
/> |
166
|
|
|
</li> |
167
|
|
|
</ul> |
168
|
|
|
</div> |
169
|
|
|
<div data-c-container="medium"> |
170
|
|
|
<div data-c-grid="gutter(all, 1) middle"> |
171
|
|
|
<FieldArray |
172
|
|
|
name="skills" |
173
|
|
|
render={(arrayHelpers) => ( |
174
|
|
|
<div data-c-grid> |
175
|
|
|
{skills.map((skill) => ( |
176
|
|
|
<div key={skill.id} data-c-grid-item="tl(1of1)"> |
177
|
|
|
<div data-c-grid="gutter(all, 1)"> |
178
|
|
|
<Field |
179
|
|
|
id={`${keyPrefix}-${skill.id}-selected`} |
180
|
|
|
component={CheckboxInput} |
181
|
|
|
name={`skills.${skill.id}.selected`} |
182
|
|
|
grid="tl(1of4)" |
183
|
|
|
label={localizeFieldNonNull(locale, skill, "name")} |
184
|
|
|
/> |
185
|
|
|
<Field |
186
|
|
|
id={`${keyPrefix}-${skill.id}-justification`} |
187
|
|
|
type="text" |
188
|
|
|
name={`skills.${skill.id}.justification`} |
189
|
|
|
component={TextAreaInput} |
190
|
|
|
required |
191
|
|
|
grid="tl(3of4)" |
192
|
|
|
label={intl.formatMessage(messages.justificationLabel)} |
193
|
|
|
placeholder={intl.formatMessage( |
194
|
|
|
messages.justificationPlaceholder, |
195
|
|
|
)} |
196
|
|
|
wordLimit={JUSTIFICATION_WORD_LIMIT} |
197
|
|
|
/> |
198
|
|
|
</div> |
199
|
|
|
</div> |
200
|
|
|
))} |
201
|
|
|
</div> |
202
|
|
|
)} |
203
|
|
|
/> |
204
|
|
|
</div> |
205
|
|
|
</div> |
206
|
|
|
</> |
207
|
|
|
); |
208
|
|
|
}; |
209
|
|
|
|