Passed
Push — feature/connect-application-st... ( a6f23b...9d3dc6 )
by Chris
04:02
created

resources/assets/js/components/JobBuilder/Intro/JobIntro.tsx   A

Complexity

Total Complexity 7
Complexity/F 0

Size

Lines of Code 500
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 7
eloc 387
mnd 7
bc 7
fnc 0
dl 0
loc 500
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
/* eslint-disable @typescript-eslint/camelcase */
2
import React, { useState } from "react";
3
import { FormattedMessage, defineMessages, useIntl } from "react-intl";
4
import { Form, Formik, FastField } from "formik";
5
import * as Yup from "yup";
6
import nprogress from "nprogress";
7
import get from "lodash/get";
8
import { validationMessages } from "../../Form/Messages";
9
import { Job, Department, Manager, User } from "../../../models/types";
10
import { emptyJob } from "../../../models/jobUtil";
11
import TextInput from "../../Form/TextInput";
12
import { accountSettings } from "../../../helpers/routes";
13
import { localizeFieldNonNull, getLocale } from "../../../helpers/localize";
14
15
const pageMessages = defineMessages({
16
  explanationBoldText: {
17
    id: "jobBuilder.intro.explanation.boldText",
18
    defaultMessage: "confirm that your personal information below is correct.",
19
    description: "Bold text portion of the JPB Intro explanation.",
20
  },
21
  emailLinkTitle: {
22
    id: "jobBuilder.intro.emailLinkTitle",
23
    defaultMessage: "Email Talent Cloud",
24
    description: "Title of the Talent Cloud email link.",
25
  },
26
  emailLinkText: {
27
    id: "jobBuilder.intro.emailLinkText",
28
    defaultMessage: "Talent Cloud",
29
    description: "Visible text of the Talent Cloud email link.",
30
  },
31
});
32
33
const formMessages = defineMessages({
34
  jobTitleLabelEN: {
35
    id: "jobBuilder.intro.jobTitleLabelEN",
36
    defaultMessage: "{name}'s Position (English)",
37
    description: "The label displayed on the manager position input.",
38
  },
39
  jobTitlePlaceholderEN: {
40
    id: "jobBuilder.intro.jobTitlePlaceholderEN",
41
    defaultMessage: "e.g. Design Manager",
42
    description: "The placeholder displayed on the Manager Position input.",
43
  },
44
  jobTitleLabelFR: {
45
    id: "jobBuilder.intro.jobTitleLabelFR",
46
    defaultMessage: "{name}'s Position (Français)",
47
    description: "The label displayed on the Manager Position input.",
48
  },
49
  jobTitlePlaceholderFR: {
50
    id: "jobBuilder.intro.jobTitlePlaceholderFR",
51
    defaultMessage: "e.g. Gestionnaire de la conception",
52
    description: "The placeholder displayed on the Manager Position input.",
53
  },
54
  departmentNullSelection: {
55
    id: "jobBuilder.intro.departmentNullSelection",
56
    defaultMessage: "Select a department...",
57
    description:
58
      "The default selection option displayed on the department select box.",
59
  },
60
  accountSettingsLinkTitle: {
61
    id: "jobBuilder.intro.accountSettingsLinkTitle",
62
    defaultMessage: "Visit Account Settings page.",
63
    description: "Title of the account settings page link.",
64
  },
65
  accountSettingsLinkText: {
66
    id: "jobBuilder.intro.accountSettingsLinkText",
67
    defaultMessage: "account settings",
68
    description: "Visible text of the account settings link.",
69
  },
70
  divisionLabelEN: {
71
    id: "jobBuilder.intro.divisionLabelEN",
72
    defaultMessage: "{name}'s Division (English)",
73
    description: "The label displayed on the division input.",
74
  },
75
  divisionPlaceholderEN: {
76
    id: "jobBuilder.intro.divisionPlaceholderEN",
77
    defaultMessage: "e.g. Digital Change",
78
    description: "The placeholder displayed on the division input.",
79
  },
80
  divisionLabelFR: {
81
    id: "jobBuilder.intro.divisionLabelFR",
82
    defaultMessage: "{name}'s Division (Français)",
83
    description: "The label displayed on the division input.",
84
  },
85
  divisionPlaceholderFR: {
86
    id: "jobBuilder.intro.divisionPlaceholderFR",
87
    defaultMessage: "e.g. Changement numérique",
88
    description: "The placeholder displayed on the division input.",
89
  },
90
});
91
92
// shape of values used in Form
93
interface JobIntroValues {
94
  managerPositionEn: string;
95
  managerPositionFr: string;
96
  department: number | "";
97
  divisionEN: string;
98
  divisionFR: string;
99
}
100
101
interface JobIntroProps {
102
  // If not null, used to prepopulate form values
103
  job: Job | null;
104
  // The manager of this job. Used to prepopulate values.
105
  manager: Manager;
106
  // List of known department options.
107
  departments: Department[];
108
  // The user related to the manager of this job.
109
  user: User;
110
  // Runs after successful validation.
111
  // It must (asynchronously) return the resulting job, if successful.
112
  handleSubmit: (job: Job, manager: Manager) => Promise<Job>;
113
  // Continues to next step in JobBuilder.
114
  handleContinue: (chosenLang: "en" | "fr", job: Job) => void;
115
  // // Continues the JobBuilder in French.
116
  // handleContinueFr: (job: Job) => void;
117
}
118
119
const initializeValues = (
120
  job: Job | null,
121
  manager: Manager,
122
  user: User,
123
): JobIntroValues => {
124
  let department: number | "" = "";
125
  if (user.department_id !== null) {
126
    department = user.department_id;
127
  }
128
129
  const managerDivision = {
130
    en: get(manager, "division.en", ""),
131
    fr: get(manager, "division.fr", ""),
132
  };
133
134
  let divisionEN = "";
135
  if (job !== null && job.division.en) {
136
    divisionEN = job.division.en;
137
  } else if (managerDivision.en) {
138
    divisionEN = managerDivision.en;
139
  }
140
141
  let divisionFR = "";
142
  if (job !== null && job.division.fr) {
143
    divisionFR = job.division.fr;
144
  } else if (managerDivision.fr) {
145
    divisionFR = managerDivision.fr;
146
  }
147
148
  return {
149
    managerPositionEn: get(manager, "position.en", ""),
150
    managerPositionFr: get(manager, "position.fr", ""),
151
    department,
152
    divisionEN,
153
    divisionFR,
154
  };
155
};
156
157
const updateJobWithValues = (
158
  job: Job,
159
  values: JobIntroValues,
160
  locale: string,
161
  departmentId: number | null,
162
): Job => ({
163
  ...job,
164
  chosen_lang: locale,
165
  department_id: departmentId,
166
  division: {
167
    ...job.division,
168
    en: values.divisionEN || null,
169
    fr: values.divisionFR || null,
170
  },
171
});
172
173
const updateManagerWithValues = (
174
  manager: Manager,
175
  values: JobIntroValues,
176
): Manager => ({
177
  ...manager,
178
  division: {
179
    ...manager.division,
180
    en: values.divisionEN || null,
181
    fr: values.divisionFR || null,
182
  },
183
  position: {
184
    ...manager.position,
185
    en: values.managerPositionEn || null,
186
    fr: values.managerPositionFr || null,
187
  },
188
});
189
190
const JobIntro: React.FunctionComponent<JobIntroProps> = ({
191
  job,
192
  manager,
193
  user,
194
  departments,
195
  handleSubmit,
196
  handleContinue,
197
}: JobIntroProps): React.ReactElement => {
198
  const intl = useIntl();
199
  const locale = getLocale(intl.locale);
200
  const initialValues: JobIntroValues = initializeValues(job, manager, user);
201
  const [languageSelection, setLanguageSelection] = useState(locale);
202
203
  const getDepartmentName = (): string | undefined => {
204
    const departmentName = departments.find(
205
      department => department.id === user.department_id,
206
    );
207
    return departmentName
208
      ? localizeFieldNonNull(locale, departmentName, "name")
209
      : undefined;
210
  };
211
212
  const introSchema = Yup.object().shape({
213
    managerPositionEn: Yup.string().required(
214
      intl.formatMessage(validationMessages.required),
215
    ),
216
    managerPositionFr: Yup.string().required(
217
      intl.formatMessage(validationMessages.required),
218
    ),
219
    divisionEN: Yup.string().required(
220
      intl.formatMessage(validationMessages.required),
221
    ),
222
    divisionFR: Yup.string().required(
223
      intl.formatMessage(validationMessages.required),
224
    ),
225
  });
226
227
  return (
228
    <>
229
      <div data-c-container="form" data-c-padding="top(triple) bottom(triple)">
230
        <h3
231
          data-c-font-size="h3"
232
          data-c-font-weight="bold"
233
          data-c-margin="bottom(normal)"
234
        >
235
          <FormattedMessage
236
            id="jobBuilder.intro.welcome"
237
            defaultMessage="Welcome to the Job Poster Builder"
238
            description="Header of Job Poster Builder Intro Step"
239
          />
240
        </h3>
241
        <p data-c-margin="bottom(double)">
242
          <FormattedMessage
243
            id="jobBuilder.intro.explanation"
244
            defaultMessage="This tool will help you create a job poster that attracts the right talent. Before we get started on your job poster, take some time to {boldText}"
245
            description="Explanation of Job Poster Builder Intro Step"
246
            values={{
247
              boldText: (
248
                <span data-c-font-weight="bold">
249
                  {intl.formatMessage(pageMessages.explanationBoldText)}
250
                </span>
251
              ),
252
            }}
253
          />
254
        </p>
255
        <Formik
256
          enableReinitialize
257
          initialValues={initialValues}
258
          validationSchema={introSchema}
259
          onSubmit={(values, { setSubmitting }): void => {
260
            const updatedJob = updateJobWithValues(
261
              job || emptyJob(),
262
              values,
263
              languageSelection,
264
              user.department_id,
265
            );
266
            const updatedManager = updateManagerWithValues(manager, values);
267
            nprogress.start();
268
            handleSubmit(updatedJob, updatedManager)
269
              .then((newJob: Job): void => {
270
                if (languageSelection === "fr") {
271
                  handleContinue("fr", newJob);
272
                } else {
273
                  handleContinue("en", newJob);
274
                }
275
              })
276
              .finally((): void => {
277
                nprogress.done();
278
                setSubmitting(false); // Required by Formik to finish the submission cycle
279
              });
280
          }}
281
        >
282
          {({ isSubmitting, submitForm }): React.ReactElement => (
283
            <>
284
              <Form id="form" data-c-margin="bottom(normal)">
285
                <div data-c-grid="gutter">
286
                  <h4 data-c-font-size="h4" data-c-grid-item="base(1of1)">
287
                    <FormattedMessage
288
                      id="jobBuilder.intro.formTitle"
289
                      defaultMessage="{name}'s Profile Information"
290
                      description="The title of the profile information form."
291
                      values={{
292
                        name: manager.first_name,
293
                      }}
294
                    />
295
                  </h4>
296
                  <p data-c-grid-item="base(1of1)">
297
                    <FormattedMessage
298
                      id="jobBuilder.intro.formDescription"
299
                      defaultMessage="This information is used on the Job Poster to help applicants learn more about who they'll be working with."
300
                      description="Explanation of why the profile information is collected."
301
                    />
302
                  </p>
303
                  <FastField
304
                    type="text"
305
                    id="managerPositionEn"
306
                    name="managerPositionEn"
307
                    label={intl.formatMessage(formMessages.jobTitleLabelEN, {
308
                      name: manager.first_name,
309
                    })}
310
                    placeholder={intl.formatMessage(
311
                      formMessages.jobTitlePlaceholderEN,
312
                    )}
313
                    required
314
                    grid="tl(1of2)"
315
                    component={TextInput}
316
                  />
317
                  <FastField
318
                    type="text"
319
                    id="managerPositionFr"
320
                    name="managerPositionFr"
321
                    label={intl.formatMessage(formMessages.jobTitleLabelFR, {
322
                      name: manager.first_name,
323
                    })}
324
                    placeholder={intl.formatMessage(
325
                      formMessages.jobTitlePlaceholderFR,
326
                    )}
327
                    required
328
                    grid="tl(1of2)"
329
                    component={TextInput}
330
                  />
331
                  <h4 data-c-font-size="h4" data-c-grid-item="base(1of1)">
332
                    <FormattedMessage
333
                      id="jobBuilder.intro.departmentHeader"
334
                      defaultMessage="{name}'s Department Information"
335
                      description="The label displayed on the department select box."
336
                      values={{ name: manager.first_name }}
337
                    />
338
                  </h4>
339
                  <p data-c-grid-item="base(1of1)">
340
                    <FormattedMessage
341
                      id="jobBuilder.intro.departmentLabel"
342
                      defaultMessage="Department"
343
                      description="The label displayed on the department select box."
344
                    />
345
                    :{" "}
346
                    {getDepartmentName && (
347
                      <span id="department" data-c-font-weight="bold">
348
                        {getDepartmentName()}
349
                      </span>
350
                    )}
351
                    <span
352
                      data-c-margin="top(quarter)"
353
                      data-c-font-size="small"
354
                      style={{ display: "block" }}
355
                    >
356
                      <FormattedMessage
357
                        id="jobBuilder.intro.changeDepartment"
358
                        defaultMessage="To change your department, please contact {email}. To learn more visit your {accountSettings}."
359
                        values={{
360
                          email: (
361
                            <a
362
                              href="mailto:[email protected]"
363
                              title={intl.formatMessage(
364
                                pageMessages.emailLinkTitle,
365
                              )}
366
                            >
367
                              {intl.formatMessage(pageMessages.emailLinkText)}
368
                            </a>
369
                          ),
370
                          accountSettings: (
371
                            <a
372
                              href={accountSettings(locale)}
373
                              title={intl.formatMessage(
374
                                formMessages.accountSettingsLinkTitle,
375
                              )}
376
                              target="_blank"
377
                              rel="noopener noreferrer"
378
                            >
379
                              {intl.formatMessage(
380
                                formMessages.accountSettingsLinkText,
381
                              )}
382
                            </a>
383
                          ),
384
                        }}
385
                      />
386
                    </span>
387
                  </p>
388
                  <FastField
389
                    type="text"
390
                    id="divisionEN"
391
                    name="divisionEN"
392
                    label={intl.formatMessage(formMessages.divisionLabelEN, {
393
                      name: manager.first_name,
394
                    })}
395
                    placeholder={intl.formatMessage(
396
                      formMessages.divisionPlaceholderEN,
397
                    )}
398
                    required
399
                    grid="tl(1of2)"
400
                    component={TextInput}
401
                  />
402
                  <FastField
403
                    type="text"
404
                    id="divisionFR"
405
                    name="divisionFR"
406
                    label={intl.formatMessage(formMessages.divisionLabelFR, {
407
                      name: manager.first_name,
408
                    })}
409
                    placeholder={intl.formatMessage(
410
                      formMessages.divisionPlaceholderFR,
411
                    )}
412
                    required
413
                    grid="tl(1of2)"
414
                    component={TextInput}
415
                  />
416
                </div>
417
              </Form>
418
              <p data-c-margin="bottom(double)">
419
                <FormattedMessage
420
                  id="jobBuilder.intro.completeInLanguage"
421
                  defaultMessage="Complete the job poster in the language of your choice. We will handle translation."
422
                  description="Instructions at bottom of form on language choice for job poster builder."
423
                />{" "}
424
                <FormattedMessage
425
                  id="jobBuilder.intro.contactUs"
426
                  defaultMessage="We’ve also provided instructions and examples to help guide you through the process but if you still have questions, contact {link}."
427
                  description="Subtitle 2 of Job Poster Builder Intro Step"
428
                  values={{
429
                    link: (
430
                      <a
431
                        href="mailto:[email protected]"
432
                        title={intl.formatMessage(pageMessages.emailLinkTitle)}
433
                      >
434
                        {intl.formatMessage(pageMessages.emailLinkText)}
435
                      </a>
436
                    ),
437
                  }}
438
                />
439
              </p>
440
              {/* This button continues the user in English. */}
441
              <button
442
                form="form"
443
                data-c-button="solid(c1)"
444
                data-c-radius="rounded"
445
                data-c-margin="right(normal) bottom(normal)"
446
                type="button"
447
                disabled={isSubmitting}
448
                onClick={(): void => {
449
                  /** FIXME:
450
                   * This is a race condition, since setLanguageSelection is asynchronous.
451
                   * I have to find a way to handle 2 submit buttons in formik without a race condition somewhere :(
452
                   * For now, the setState always happens faster than the validation check, so it works.
453
                   * See https://github.com/jaredpalmer/formik/issues/214
454
                   * -- Tristan
455
                   */
456
                  setLanguageSelection("en");
457
                  submitForm();
458
                }}
459
              >
460
                <FormattedMessage
461
                  id="jobBuilder.intro.continueButtonLabelEN"
462
                  defaultMessage="Continue in English"
463
                  description="Label displayed on submit button for continuation of job builder in english."
464
                />
465
              </button>
466
              {/* This button switches the user to French and continues the process in French.  */}
467
              <button
468
                form="form"
469
                data-c-button="solid(c1)"
470
                data-c-radius="rounded"
471
                type="button"
472
                disabled={isSubmitting}
473
                onClick={(): void => {
474
                  /** FIXME:
475
                   * This is a race condition, since setLanguageSelection is asynchronous.
476
                   * I have to find a way to handle 2 submit buttons in formik without a race condition somewhere :(
477
                   * For now, the setState always happens faster than the validation check, so it works.
478
                   * See https://github.com/jaredpalmer/formik/issues/214
479
                   * -- Tristan
480
                   */
481
                  setLanguageSelection("fr");
482
                  submitForm();
483
                }}
484
              >
485
                <FormattedMessage
486
                  id="jobBuilder.intro.continueButtonLabelFR"
487
                  defaultMessage="Continuer en Français"
488
                  description="Label displayed on submit button for continuation of job builder in french."
489
                />
490
              </button>
491
            </>
492
          )}
493
        </Formik>
494
      </div>
495
    </>
496
  );
497
};
498
499
export default JobIntro;
500