Passed
Push — feature/timeline-progress-bar ( a5e95d )
by Yonathan
06:11 queued 11s
created

resources/assets/js/components/Application/ProgressBar/ProgressBar.tsx   A

Complexity

Total Complexity 3
Complexity/F 0

Size

Lines of Code 234
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 3
eloc 186
mnd 3
bc 3
fnc 0
dl 0
loc 234
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
import * as React from "react";
2
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
3
import dayjs from "dayjs";
4
import relativeTime from "dayjs/plugin/relativeTime";
5
import utc from "dayjs/plugin/utc";
6
import { Link } from "../../../models/app";
7
import { getLocale } from "../../../helpers/localize";
8
9
const stepNames = defineMessages({
10
  welcome: {
11
    id: "applicationTimeline.progressBar.welcome",
12
    defaultMessage: "Welcome",
13
  },
14
  step01: {
15
    id: "applicationTimeline.progressBar.step01",
16
    defaultMessage: "Step 1/6",
17
  },
18
  step02: {
19
    id: "applicationTimeline.progressBar.step02",
20
    defaultMessage: "Step 2/6",
21
  },
22
  step03: {
23
    id: "applicationTimeline.progressBar.step03",
24
    defaultMessage: "Step 3/6",
25
  },
26
  step04: {
27
    id: "applicationTimeline.progressBar.step04",
28
    defaultMessage: "Step 4/6",
29
  },
30
  step05: {
31
    id: "applicationTimeline.progressBar.step05",
32
    defaultMessage: "Step 5/6",
33
  },
34
  step06: {
35
    id: "applicationTimeline.progressBar.step06",
36
    defaultMessage: "Step 6/6",
37
  },
38
});
39
40
// Returns the list item element that corresponds to the steps status.
41
const createStep = (
42
  link: Link,
43
  status: ProgressBarStepStatus,
44
  icons: {
45
    [key in ProgressBarStepStatus]: { className: string; color: string };
46
  },
47
): React.ReactElement => {
48
  const isComplete: boolean = status === "complete";
49
  const isCurrent: boolean = status === "current";
50
  const hasError: boolean = status === "error";
51
52
  if (isComplete) {
53
    return (
54
      <li key={link.title}>
55
        <span data-c-visibility="invisible">
56
          <FormattedMessage
57
            id="applicationTimeline.progressbar.completedStepLabel"
58
            defaultMessage="Completed: "
59
            description="Visually hidden text used to indicate the completed steps."
60
          />
61
        </span>
62
        <a href={link.url} title={link.title}>
63
          <span data-c-visibility="invisible">{link.text}</span>
64
          <i
65
            className={icons[status].className}
66
            data-c-color={icons[status].color}
67
          />
68
        </a>
69
      </li>
70
    );
71
  }
72
  if (isCurrent) {
73
    return (
74
      <li key={link.title}>
75
        <span data-c-visibility="invisible">
76
          <FormattedMessage
77
            id="applicationTimeline.progressbar.currentStepLabel"
78
            defaultMessage="Current: "
79
            description="Visually hidden text used to indicate the current steps."
80
          />
81
        </span>
82
        <span data-c-visibility="invisible">{link.text}</span>
83
        <i
84
          className={icons[status].className}
85
          data-c-color={icons[status].color}
86
        />
87
      </li>
88
    );
89
  }
90
  if (hasError) {
91
    return (
92
      <li key={link.title}>
93
        <span data-c-visibility="invisible">
94
          <FormattedMessage
95
            id="applicationTimeline.progressbar.errorStepLabel"
96
            defaultMessage="Error: "
97
            description="Visually hidden text used to indicate the steps with errors."
98
          />
99
        </span>
100
        <a href={link.url} title={link.title}>
101
          <span data-c-visibility="invisible">{link.text}</span>
102
          <i
103
            className={icons[status].className}
104
            data-c-color={icons[status].color}
105
          />
106
        </a>
107
      </li>
108
    );
109
  }
110
  return (
111
    <li key={link.title} title={link.title}>
112
      <span data-c-visibility="invisible">{link.text}</span>
113
      <i
114
        className={icons[status].className}
115
        data-c-color={icons[status].color}
116
      />
117
    </li>
118
  );
119
};
120
121
export type ProgressBarStepStatus =
122
  | "default"
123
  | "complete"
124
  | "error"
125
  | "current";
126
127
interface ProgressBarProps {
128
  /** The closing date of the job poster. */
129
  closeDateTime: Date;
130
  /** The current step number. This is required for the informational steps, since they do not use a list item. */
131
  stepNumber: number;
132
  /** List of the steps. */
133
  steps: { link: Link; status: ProgressBarStepStatus }[];
134
}
135
136
const ProgressBar: React.FunctionComponent<ProgressBarProps> = ({
137
  steps,
138
  closeDateTime,
139
  stepNumber,
140
}) => {
141
  const intl = useIntl();
142
  const locale = getLocale(intl.locale);
143
144
  // dayjs() relativeTime API plugin configuration https://day.js.org/docs/en/display/from-now.
145
  const config = {
146
    thresholds: [
147
      { l: "s", r: 1 },
148
      { l: "ss", r: 59, d: "second" },
149
      { l: "m", r: 1 },
150
      { l: "mm", r: 59, d: "minute" },
151
      { l: "h", r: 1 },
152
      { l: "hh", r: 23, d: "hour" },
153
      { l: "d", r: 1 },
154
      { l: "dd", r: 29, d: "day" },
155
      { l: "M", r: 1 },
156
      { l: "MM", r: 11, d: "month" },
157
      { l: "y" },
158
      { l: "yy", d: "year" },
159
    ],
160
    rounding: Math.floor,
161
  };
162
  dayjs.extend(relativeTime, config);
163
  dayjs.extend(utc);
164
165
  // There are nine steps throughout the timeline, some steps using the same title (informational steps). This maps the step number to its corresponding title.
166
  const getStepName = {
167
    1: intl.formatMessage(stepNames.welcome),
168
    2: intl.formatMessage(stepNames.step01),
169
    3: intl.formatMessage(stepNames.step02),
170
    4: intl.formatMessage(stepNames.step02),
171
    5: intl.formatMessage(stepNames.step03),
172
    6: intl.formatMessage(stepNames.step03),
173
    7: intl.formatMessage(stepNames.step04),
174
    8: intl.formatMessage(stepNames.step05),
175
    9: intl.formatMessage(stepNames.step06),
176
  };
177
178
  const icons: {
179
    [key in ProgressBarStepStatus]: { className: string; color: string };
180
  } = {
181
    default: {
182
      className: "fas fa-circle",
183
      color: "grey",
184
    },
185
    complete: {
186
      className: "fas fa-check-circle",
187
      color: "go",
188
    },
189
    error: {
190
      className: "fas fa-exclamation-circle",
191
      color: "stop",
192
    },
193
    current: {
194
      className: "far fa-circle",
195
      color: "white",
196
    },
197
  };
198
199
  return (
200
    <div data-c-background="black(100)" data-c-padding="tb(1)">
201
      <div data-c-container="large">
202
        <div data-c-grid="gutter(all, 1)">
203
          <div data-c-grid-item="tl(1of2)" data-c-align="base(center) tl(left)">
204
            <span data-c-color="white">{getStepName[stepNumber]}</span>
205
            <ol className="applicant-application-progress-bar">
206
              {steps.map(
207
                ({ link, status }): React.ReactElement =>
208
                  createStep(link, status, icons),
209
              )}
210
            </ol>
211
          </div>
212
          <div
213
            data-c-grid-item="tl(1of2)"
214
            data-c-align="base(center) tl(right)"
215
          >
216
            <span data-c-color="white">
217
              <FormattedMessage
218
                id="applicationTimeline.progressbar.applicationDeadline"
219
                defaultMessage="Application Deadline: {timeLeft}"
220
                description="Label for the application deadline"
221
                values={{
222
                  timeLeft: dayjs(closeDateTime).utc().locale(locale).fromNow(),
223
                }}
224
              />
225
            </span>
226
          </div>
227
        </div>
228
      </div>
229
    </div>
230
  );
231
};
232
233
export default ProgressBar;
234