Cancelled
Push — feature/application-basic-info... ( 98c9e5...88f9a8 )
by Tristan
05:35 queued 01:32
created

resources/assets/js/components/Application/Skills/SkillsIntroPage.tsx   A

Complexity

Total Complexity 16
Complexity/F 0

Size

Lines of Code 241
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 16
eloc 199
mnd 16
bc 16
fnc 0
dl 0
loc 241
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
rs 10
1
/* eslint-disable camelcase */
2
import React, { useEffect, useCallback, useState } from "react";
3
import { useIntl, FormattedMessage } from "react-intl";
4
import { useDispatch, useSelector } from "react-redux";
5
import makeProgressBarSteps from "../ProgressBar/progressHelpers";
6
import ProgressBar, { stepNames } from "../ProgressBar/ProgressBar";
7
import { Experience } from "../../../models/types";
8
import { navigate } from "../../../helpers/router";
9
import { applicationSkills } from "../../../helpers/routes";
10
import { getLocale } from "../../../helpers/localize";
11
import { DispatchType } from "../../../configureStore";
12
import { RootState } from "../../../store/store";
13
import {
14
  getApplicationById,
15
  getApplicationIsUpdating,
16
} from "../../../store/Application/applicationSelector";
17
import { fetchApplication } from "../../../store/Application/applicationActions";
18
import { getJob, getJobIsUpdating } from "../../../store/Job/jobSelector";
19
import { fetchJob } from "../../../store/Job/jobActions";
20
import {
21
  getExperienceByApplicant,
22
  getExperienceByApplication,
23
  getUpdatingByApplicant,
24
  getUpdatingByApplication,
25
} from "../../../store/Experience/experienceSelector";
26
import {
27
  fetchExperienceByApplicant,
28
  fetchExperienceByApplication,
29
} from "../../../store/Experience/experienceActions";
30
import { ApplicationStatusId } from "../../../models/lookupConstants";
31
import {
32
  getSkills,
33
  getSkillsUpdating,
34
} from "../../../store/Skill/skillSelector";
35
import { fetchSkills } from "../../../store/Skill/skillActions";
36
import { loadingMessages } from "../applicationMessages";
37
38
interface SkillsIntroPageProps {
39
  applicationId: number;
40
}
41
42
/**
43
 * This page displays some instructions for the Skills step, and prefetches the data that will be used there.
44
 * @param applicationId
45
 */
46
export const SkillsIntroPage: React.FunctionComponent<SkillsIntroPageProps> = ({
47
  applicationId,
48
}) => {
49
  const intl = useIntl();
50
  const locale = getLocale(intl.locale);
51
  const dispatch = useDispatch<DispatchType>();
52
53
  const applicationSelector = (state: RootState) =>
54
    getApplicationById(state, { id: applicationId });
55
  const application = useSelector(applicationSelector);
56
  const applicationIsUpdating = useSelector((state: RootState) =>
57
    getApplicationIsUpdating(state, { applicationId }),
58
  );
59
  useEffect(() => {
60
    if (application === null && !applicationIsUpdating) {
61
      dispatch(fetchApplication(applicationId));
62
    }
63
  }, [application, applicationId, applicationIsUpdating, dispatch]);
64
65
  const jobId = application?.job_poster_id;
66
  const jobSelector = (state: RootState) =>
67
    jobId ? getJob(state, { jobId }) : null;
68
  const job = useSelector(jobSelector);
69
  const jobUpdatingSelector = (state: RootState) =>
70
    jobId ? getJobIsUpdating(state, jobId) : false;
71
  const jobIsUpdating = useSelector(jobUpdatingSelector);
72
  useEffect(() => {
73
    // If job is null and not already updating, fetch it.
74
    if (jobId && job === null && !jobIsUpdating) {
75
      dispatch(fetchJob(jobId));
76
    }
77
  }, [jobId, job, jobIsUpdating, dispatch]);
78
79
  const applicantId = application?.applicant_id ?? 0;
80
81
  // When an Application is still a draft, use Experiences associated with the applicant profile.
82
  // When an Application has been submitted and is no longer a draft, display Experience associated with the Application directly.
83
  const applicationLoaded = application !== null;
84
  const useProfileExperience =
85
    application === null ||
86
    application.application_status_id === ApplicationStatusId.draft;
87
88
  // This selector must be memoized because getExperienceByApplicant/Application uses reselect, and not re-reselect.
89
  const experienceSelector = useCallback(
90
    (state: RootState) =>
91
      useProfileExperience
92
        ? getExperienceByApplicant(state, { applicantId })
93
        : getExperienceByApplication(state, { applicationId }),
94
    [applicationId, applicantId, useProfileExperience],
95
  );
96
  const experiencesByType = useSelector(experienceSelector);
97
  const experiences: Experience[] = [
98
    ...experiencesByType.award,
99
    ...experiencesByType.community,
100
    ...experiencesByType.education,
101
    ...experiencesByType.personal,
102
    ...experiencesByType.work,
103
  ];
104
  const experiencesUpdating = useSelector((state: RootState) =>
105
    useProfileExperience
106
      ? getUpdatingByApplicant(state, { applicantId })
107
      : getUpdatingByApplication(state, { applicationId }),
108
  );
109
  const [experiencesFetched, setExperiencesFetched] = useState(false);
110
  useEffect(() => {
111
    // Only load experiences if they have never been fetched by this component (!experiencesFetched),
112
    //  have never been fetched by another component (length === 0),
113
    //  and are not currently being fetched (!experiencesUpdating).
114
    // Also, wait until application has been loaded so the correct source can be determined.
115
    if (
116
      applicationLoaded &&
117
      !experiencesFetched &&
118
      !experiencesUpdating &&
119
      experiences.length === 0
120
    ) {
121
      setExperiencesFetched(true);
122
      if (useProfileExperience) {
123
        dispatch(fetchExperienceByApplicant(applicantId));
124
      } else {
125
        dispatch(fetchExperienceByApplication(applicationId));
126
      }
127
    }
128
  }, [
129
    applicantId,
130
    applicationId,
131
    applicationLoaded,
132
    dispatch,
133
    experiences.length,
134
    experiencesFetched,
135
    experiencesUpdating,
136
    useProfileExperience,
137
  ]);
138
139
  const skills = useSelector(getSkills);
140
  const skillsUpdating = useSelector(getSkillsUpdating);
141
  useEffect(() => {
142
    if (skills.length === 0 && !skillsUpdating) {
143
      dispatch(fetchSkills());
144
    }
145
  }, [skills.length, skillsUpdating, dispatch]);
146
147
  const closeDate = job?.close_date_time ?? null;
148
149
  const handleContinue = (): void => {
150
    navigate(applicationSkills(locale, applicationId));
151
  };
152
153
  return (
154
    <>
155
      {application && (
156
        <ProgressBar
157
          closeDateTime={closeDate}
158
          currentTitle={intl.formatMessage(stepNames.step01)}
159
          steps={makeProgressBarSteps(
160
            applicationId,
161
            application,
162
            intl,
163
            "skills",
164
          )}
165
        />
166
      )}
167
      {!application && (
168
        <h2
169
          data-c-heading="h2"
170
          data-c-align="center"
171
          data-c-padding="top(2) bottom(2)"
172
        >
173
          {intl.formatMessage(loadingMessages.loading)}
174
        </h2>
175
      )}
176
      {application && (
177
        <div data-c-border="bottom(thin, solid, gray)">
178
          <div data-c-container="medium">
179
            <h2 data-c-heading="h2" data-c-margin="top(3) bottom(1)">
180
              <FormattedMessage
181
                id="application.skills.intro.header"
182
                defaultMessage="How You Used Each Skill"
183
                description="Header for the Skills Intro step."
184
              />
185
            </h2>
186
            <p data-c-margin="bottom(1)">
187
              <FormattedMessage
188
                id="application.experience.intro.opening"
189
                defaultMessage="Now that you've shared your experiences, tell us how they connect to the skills required for the job."
190
                description="Opening sentence describing the Skills step."
191
              />
192
            </p>
193
            <p data-c-margin="bottom(1)">
194
              <FormattedMessage
195
                id="application.experience.intro.explanation"
196
                defaultMessage="For each experience <b>add a short explanation that demonstrates how you used the skill</b>. These explanations are what the manager will use to decide how strong your application is, so <b>it's important that you share your best examples</b>."
197
                description="Paragraphs explaining what to expect on the Skills step."
198
                values={{
199
                  b: (...chunks) => (
200
                    <span data-c-font-weight="bold">{chunks}</span>
201
                  ),
202
                }}
203
              />
204
            </p>
205
            <p data-c-margin="bottom(1)">
206
              <FormattedMessage
207
                id="application.experience.intro.savedToProfile"
208
                defaultMessage="Just like experience, this information is saved to your profile so that you can reuse it on other applications!"
209
                description="Paragraph explaining that changes to Skills will be saved to profile."
210
              />
211
            </p>
212
          </div>
213
          <div data-c-container="medium" data-c-padding="tb(2)">
214
            <hr data-c-hr="thin(c1)" data-c-margin="bottom(2)" />
215
            <div data-c-grid="gutter(all, 1)">
216
              <div data-c-grid-item="tl(1of1)" data-c-align="base(center)">
217
                <button
218
                  data-c-button="solid(c1)"
219
                  data-c-radius="rounded"
220
                  type="button"
221
                  onClick={handleContinue}
222
                >
223
                  <span>
224
                    <FormattedMessage
225
                      id="application.skills.intro.letsGo"
226
                      defaultMessage="Let's Go"
227
                      description="Button text for continuing to next step in Application Form."
228
                    />
229
                  </span>
230
                </button>
231
              </div>
232
            </div>
233
          </div>
234
        </div>
235
      )}
236
    </>
237
  );
238
};
239
240
export default SkillsIntroPage;
241