• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CBIIT / crdc-datahub-ui / 18789341118

24 Oct 2025 06:57PM UTC coverage: 78.178% (+15.5%) from 62.703%
18789341118

push

github

web-flow
Merge pull request #888 from CBIIT/3.4.0

3.4.0 Release

4977 of 5488 branches covered (90.69%)

Branch coverage included in aggregate %.

8210 of 9264 new or added lines in 257 files covered. (88.62%)

6307 existing lines in 120 files now uncovered.

30203 of 39512 relevant lines covered (76.44%)

213.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.9
/src/components/Header/components/HeaderTabletAndMobile.tsx
1
import { styled } from "@mui/material";
1✔
2
import { flatMap } from "lodash";
1✔
3
import { useSnackbar } from "notistack";
1✔
4
import React, { HTMLProps, useEffect, useMemo, useState } from "react";
1✔
5
import { NavLink, Link, useNavigate, useLocation } from "react-router-dom";
1✔
6

7
import leftArrowIcon from "../../../assets/header/Left_Arrow.svg?url";
1✔
8
import menuClearIcon from "../../../assets/header/Menu_Cancel_Icon.svg?url";
1✔
9
import rightArrowIcon from "../../../assets/header/Right_Arrow.svg?url";
1✔
10
import { hasPermission, Permissions } from "../../../config/AuthPermissions";
1✔
11
import { ActionHandlers, ActionId, HeaderLinks } from "../../../config/HeaderConfig";
1✔
12
import { Logger } from "../../../utils";
1✔
13
import APITokenDialog from "../../APITokenDialog";
1✔
14
import { useAuthContext } from "../../Contexts/AuthContext";
1✔
15
import UploaderToolDialog from "../../UploaderToolDialog";
1✔
16

17
import Logo from "./LogoMobile";
1✔
18

19
const HeaderBanner = styled("div")({
1✔
20
  width: "100%",
1✔
21
});
1✔
22

23
const HeaderContainer = styled("div")({
1✔
24
  margin: "0 auto",
1✔
25
  paddingLeft: "16px",
1✔
26
  boxShadow: "-0.1px 6px 9px -6px rgba(0, 0, 0, 0.5)",
1✔
27
  "& .headerLowerContainer": {
1✔
28
    display: "flex",
1✔
29
    margin: "16px 0 4px 0",
1✔
30
    height: "51px",
1✔
31
  },
1✔
32
  "& .menuButton": {
1✔
33
    width: "89px",
1✔
34
    height: "45px",
1✔
35
    background: "#1F4671",
1✔
36
    borderRadius: "5px",
1✔
37
    fontFamily: "Open Sans",
1✔
38
    fontWeight: 700,
1✔
39
    fontSize: "20px",
1✔
40
    lineHeight: "45px",
1✔
41
    color: "#FFFFFF",
1✔
42
    textAlign: "center",
1✔
43
    "&:hover": {
1✔
44
      cursor: "pointer",
1✔
45
    },
1✔
46
  },
1✔
47
});
1✔
48

49
const NavMobileContainer = styled("div", {
1✔
50
  shouldForwardProp: (prop) => prop !== "display",
1✔
51
})<HTMLProps<HTMLDivElement> & { display: string }>(({ display }) => ({
1✔
52
  display,
4✔
53
  position: "absolute",
4✔
54
  left: 0,
4✔
55
  top: 0,
4✔
56
  height: "100%",
4✔
57
  width: "100%",
4✔
58
  zIndex: 1200,
4✔
59
}));
1✔
60

61
const MenuArea = styled("div")({
1✔
62
  height: "100%",
1✔
63
  width: "100%",
1✔
64
  display: "flex",
1✔
65
  "& .menuContainer": {
1✔
66
    background: "#ffffff",
1✔
67
    width: "300px",
1✔
68
    height: "100%",
1✔
69
    padding: "21px 16px",
1✔
70
  },
1✔
71
  "& .greyContainer": {
1✔
72
    width: "100%",
1✔
73
    height: "100%",
1✔
74
    background: "rgba(0,0,0,.2)",
1✔
75
  },
1✔
76
  "& .closeIcon": {
1✔
77
    height: "14px",
1✔
78
    marginBottom: "29px",
1✔
79
  },
1✔
80
  "& .closeIconImg": {
1✔
81
    float: "right",
1✔
82
    "&:hover": {
1✔
83
      cursor: "pointer",
1✔
84
    },
1✔
85
  },
1✔
86
  "& .backButton": {
1✔
87
    fontFamily: "Open Sans",
1✔
88
    fontWeight: 600,
1✔
89
    fontSize: "16px",
1✔
90
    lineHeight: "16px",
1✔
91
    color: "#007BBD",
1✔
92
    paddingLeft: "16px",
1✔
93
    background: `url("${leftArrowIcon}") left no-repeat`,
1✔
94
    "&:hover": {
1✔
95
      cursor: "pointer",
1✔
96
    },
1✔
97
  },
1✔
98
  "& .navMobileContainer": {
1✔
99
    padding: "24px 0 0 0",
1✔
100
    "& a": {
1✔
101
      textDecoration: "none",
1✔
102
      color: "#3D4551",
1✔
103
    },
1✔
104
  },
1✔
105
  "& .navMobileItem": {
1✔
106
    width: "268px",
1✔
107
    padding: "8px 24px 8px 16px",
1✔
108
    fontFamily: "Open Sans",
1✔
109
    fontWeight: 400,
1✔
110
    fontSize: "16px",
1✔
111
    lineHeight: "16px",
1✔
112
    borderTop: "1px solid #F0F0F0",
1✔
113
    borderBottom: "1px solid #F0F0F0",
1✔
114
    color: "#3D4551",
1✔
115
    "&:hover": {
1✔
116
      backgroundColor: "#f9f9f7",
1✔
117
    },
1✔
118
  },
1✔
119
  "& .SubItem": {
1✔
120
    paddingLeft: "24px",
1✔
121
  },
1✔
122
  "& .clickable": {
1✔
123
    background: `url("${rightArrowIcon}") 90% no-repeat`,
1✔
124
    cursor: "pointer",
1✔
125
  },
1✔
126
  "& .action": {
1✔
127
    cursor: "pointer",
1✔
128
  },
1✔
129
});
1✔
130

131
const Header = () => {
1✔
132
  const { isLoggedIn, user, logout } = useAuthContext();
4✔
133
  const navigate = useNavigate();
4✔
134
  const location = useLocation();
4✔
135
  const { enqueueSnackbar } = useSnackbar();
4✔
136

137
  const [navMobileDisplay, setNavMobileDisplay] = useState("none");
4✔
138
  const [openAPITokenDialog, setOpenAPITokenDialog] = useState<boolean>(false);
4✔
139
  const [uploaderToolOpen, setUploaderToolOpen] = useState<boolean>(false);
4✔
140
  const [selectedList, setSelectedList] = useState<NavBarItem[] | NavBarSubItem[]>(HeaderLinks);
4✔
141
  const [restorePath, setRestorePath] = useState<string | null>(null);
4✔
142

143
  const displayName = user?.firstName || "N/A";
4!
144

145
  const handleLogout = async () => {
4✔
146
    const logoutStatus = await logout?.();
×
147
    if (logoutStatus) {
×
148
      navigate("/");
×
149
      enqueueSnackbar("You have been logged out.", { variant: "success" });
×
UNCOV
150
    }
×
UNCOV
151
  };
×
152

153
  const actionHandlers: ActionHandlers = useMemo(
4✔
154
    () => ({
4✔
155
      logout: handleLogout,
4✔
156
      openAPITokenDialog: () => setOpenAPITokenDialog(true),
4✔
157
      openCLIToolDialog: () => setUploaderToolOpen(true),
4✔
158
    }),
4✔
159
    [logout]
4✔
160
  );
4✔
161

162
  const handleItemClick = (item: ActionId) => {
4✔
163
    if (!item) {
×
164
      Logger.error(`HeaderTabletAndMobile.tsx: No action found for actionId '${item}'`);
×
165
      return;
×
UNCOV
166
    }
×
167

168
    actionHandlers[item]?.();
×
UNCOV
169
  };
×
170

171
  const clickNavItem = (clickTitle: string) => {
4✔
172
    const list: NavBarItem = HeaderLinks?.find(
×
173
      (link) => link.name === clickTitle && "columns" in link
×
UNCOV
174
    );
×
175
    setSelectedList(flatMap(list?.columns));
×
UNCOV
176
  };
×
177

178
  const checkPermissions = (permissions: AuthPermissions[]) => {
4✔
179
    if (!permissions?.length) {
24!
180
      return true; // No permissions required
24✔
181
    }
24!
182

UNCOV
183
    return permissions.every((permission) => {
×
UNCOV
184
      const [entityRaw, actionRaw] = permission.split(":", 2);
×
185

UNCOV
186
      if (!entityRaw || !actionRaw) {
×
187
        return false;
×
UNCOV
188
      }
×
189

UNCOV
190
      const entity = entityRaw as keyof Permissions;
×
UNCOV
191
      const action = actionRaw as Permissions[keyof Permissions]["action"];
×
192

UNCOV
193
      return hasPermission(user, entity, action, null, true);
×
UNCOV
194
    });
×
195
  };
24✔
196

197
  useEffect(() => {
4✔
198
    if (!location?.pathname || location?.pathname === "/") {
4✔
199
      setRestorePath(null);
4✔
200
      return;
4✔
201
    }
4!
202

203
    setRestorePath(location?.pathname);
4✔
204
  }, [location]);
4✔
205

206
  return (
4✔
207
    <>
4✔
208
      <HeaderBanner data-testid="navigation-header-mobile">
4✔
209
        <HeaderContainer>
4✔
210
          <Logo />
4✔
211
          <div className="headerLowerContainer">
4✔
212
            <div
4✔
213
              role="button"
4✔
214
              id="header-navbar-open-menu-button"
4✔
215
              tabIndex={0}
4✔
216
              className="menuButton"
4✔
217
              onKeyDown={(e) => {
4✔
218
                if (e.key === "Enter") {
×
219
                  setNavMobileDisplay("block");
×
UNCOV
220
                }
×
UNCOV
221
              }}
×
222
              onClick={() => setNavMobileDisplay("block")}
4✔
223
            >
224
              Menu
225
            </div>
4✔
226
          </div>
4✔
227
        </HeaderContainer>
4✔
228
      </HeaderBanner>
4✔
229
      <NavMobileContainer display={navMobileDisplay}>
4✔
230
        <MenuArea>
4✔
231
          <div className="menuContainer">
4✔
232
            <div
4✔
233
              role="button"
4✔
234
              id="navbar-close-navbar-button"
4✔
235
              tabIndex={0}
4✔
236
              className="closeIcon"
4✔
237
              onKeyDown={(e) => {
4✔
238
                if (e.key === "Enter") {
×
239
                  setNavMobileDisplay("none");
×
UNCOV
240
                }
×
UNCOV
241
              }}
×
242
              onClick={() => setNavMobileDisplay("none")}
4✔
243
            >
244
              <img className="closeIconImg" src={menuClearIcon} alt="menuClearButton" />
4✔
245
            </div>
4✔
246
            {selectedList !== HeaderLinks && (
4!
UNCOV
247
              <div
×
UNCOV
248
                role="button"
×
UNCOV
249
                id="navbar-back-to-main-menu-button"
×
UNCOV
250
                tabIndex={0}
×
UNCOV
251
                className="backButton"
×
UNCOV
252
                onKeyDown={(e) => {
×
253
                  if (e.key === "Enter") {
×
254
                    setSelectedList(HeaderLinks);
×
UNCOV
255
                  }
×
UNCOV
256
                }}
×
257
                onClick={() => setSelectedList(HeaderLinks)}
×
258
              >
259
                Main Menu
UNCOV
260
              </div>
×
261
            )}
262
            <div className="navMobileContainer">
4✔
263
              {selectedList
4✔
264
                ?.filter((headerLinks) => headerLinks.name !== "User")
4✔
265
                ?.map((navMobileItem: NavBarItem | NavBarSubItem) => {
4✔
266
                  const hasEveryPermission = checkPermissions(navMobileItem?.permissions);
24✔
267
                  if (!hasEveryPermission) {
24!
UNCOV
268
                    return null;
×
UNCOV
269
                  }
×
270

271
                  return (
24✔
272
                    <React.Fragment key={`mobile_${navMobileItem.id}`}>
24✔
273
                      {navMobileItem.className === "navMobileItem" && (
24✔
274
                        <NavLink
16✔
275
                          id={navMobileItem.id}
16✔
276
                          to={navMobileItem.link}
16✔
277
                          target={navMobileItem.link.startsWith("https://") ? "_blank" : "_self"}
16✔
278
                          onClick={() => setNavMobileDisplay("none")}
16✔
279
                        >
280
                          <div className="navMobileItem">{navMobileItem.name}</div>
16✔
281
                        </NavLink>
16✔
282
                      )}
283
                      {navMobileItem.className === "navMobileItem clickable" && (
24✔
284
                        <div
8✔
285
                          id={navMobileItem.id}
8✔
286
                          role="button"
8✔
287
                          tabIndex={0}
8✔
288
                          className="navMobileItem clickable"
8✔
289
                          onKeyDown={(e) => {
8✔
290
                            if (e.key === "Enter") {
×
291
                              clickNavItem(navMobileItem.name);
×
UNCOV
292
                            }
×
UNCOV
293
                          }}
×
294
                          onClick={() => clickNavItem(navMobileItem.name)}
8✔
295
                        >
296
                          {navMobileItem.name}
8✔
297
                        </div>
8✔
298
                      )}
299
                      {navMobileItem.className === "navMobileSubItem action" &&
24!
UNCOV
300
                      "actionId" in navMobileItem &&
×
UNCOV
301
                      typeof navMobileItem.actionId === "string" ? (
×
UNCOV
302
                        <div
×
UNCOV
303
                          id={navMobileItem.id}
×
UNCOV
304
                          role="button"
×
UNCOV
305
                          tabIndex={0}
×
UNCOV
306
                          className="navMobileItem SubItem action"
×
UNCOV
307
                          onKeyDown={(e) => {
×
308
                            if (e.key === "Enter") {
×
309
                              handleItemClick(navMobileItem.actionId as ActionId);
×
UNCOV
310
                            }
×
UNCOV
311
                          }}
×
312
                          onClick={() => handleItemClick(navMobileItem.actionId as ActionId)}
×
313
                        >
UNCOV
314
                          {navMobileItem.name}
×
UNCOV
315
                        </div>
×
316
                      ) : null}
24✔
317
                      {navMobileItem.className === "navMobileSubItem" && (
24!
UNCOV
318
                        <Link
×
UNCOV
319
                          id={navMobileItem.id}
×
UNCOV
320
                          to={navMobileItem.link}
×
UNCOV
321
                          target={
×
UNCOV
322
                            navMobileItem.link.startsWith("https://") ||
×
UNCOV
323
                            navMobileItem.link.endsWith(".pdf")
×
UNCOV
324
                              ? "_blank"
×
UNCOV
325
                              : "_self"
×
326
                          }
327
                        >
UNCOV
328
                          <div
×
UNCOV
329
                            role="button"
×
UNCOV
330
                            tabIndex={0}
×
UNCOV
331
                            className="navMobileItem SubItem"
×
UNCOV
332
                            onKeyDown={(e) => {
×
333
                              if (e.key === "Enter") {
×
334
                                setNavMobileDisplay("none");
×
335
                                if (navMobileItem.name === "Logout") {
×
336
                                  handleLogout();
×
337
                                  setSelectedList(HeaderLinks);
×
UNCOV
338
                                }
×
UNCOV
339
                              }
×
UNCOV
340
                            }}
×
UNCOV
341
                            onClick={() => {
×
342
                              setNavMobileDisplay("none");
×
343
                              if (navMobileItem.name === "Logout") {
×
344
                                handleLogout();
×
345
                                setSelectedList(HeaderLinks);
×
UNCOV
346
                              }
×
UNCOV
347
                            }}
×
348
                          >
UNCOV
349
                            {navMobileItem.name}
×
UNCOV
350
                          </div>
×
UNCOV
351
                        </Link>
×
352
                      )}
353
                      {navMobileItem.className === "navMobileSubTitle" && (
24!
UNCOV
354
                        <div className="navMobileItem">{navMobileItem.name}</div>
×
355
                      )}
356
                    </React.Fragment>
24✔
357
                  );
358
                })}
4✔
359
              {/* eslint-disable-next-line no-nested-ternary */}
360
              {selectedList === HeaderLinks ? (
4✔
361
                isLoggedIn ? (
4!
UNCOV
362
                  <div
×
UNCOV
363
                    id="navbar-dropdown-name"
×
UNCOV
364
                    role="button"
×
UNCOV
365
                    tabIndex={0}
×
UNCOV
366
                    className="navMobileItem clickable"
×
UNCOV
367
                    onKeyDown={(e) => {
×
368
                      if (e.key === "Enter") {
×
369
                        clickNavItem("User");
×
UNCOV
370
                      }
×
UNCOV
371
                    }}
×
372
                    onClick={() => clickNavItem("User")}
×
373
                  >
UNCOV
374
                    {displayName}
×
UNCOV
375
                  </div>
×
376
                ) : (
377
                  <Link id="navbar-link-login" to="/login" state={{ redirectState: restorePath }}>
4✔
378
                    <div
4✔
379
                      role="button"
4✔
380
                      tabIndex={0}
4✔
381
                      className="navMobileItem"
4✔
382
                      onKeyDown={(e) => {
4✔
383
                        if (e.key === "Enter") {
×
384
                          setNavMobileDisplay("none");
×
UNCOV
385
                        }
×
UNCOV
386
                      }}
×
387
                      onClick={() => setNavMobileDisplay("none")}
4✔
388
                    >
389
                      Login
390
                    </div>
4✔
391
                  </Link>
4!
392
                )
UNCOV
393
              ) : null}
×
394
            </div>
4✔
395
          </div>
4✔
396
          <div
4✔
397
            role="button"
4✔
398
            id="navbar-close-navbar-grey-section"
4✔
399
            tabIndex={0}
4✔
400
            className="greyContainer"
4✔
401
            onKeyDown={(e) => {
4✔
402
              if (e.key === "Enter") {
×
403
                setNavMobileDisplay("none");
×
UNCOV
404
              }
×
UNCOV
405
            }}
×
406
            onClick={() => setNavMobileDisplay("none")}
4✔
407
            aria-label="greyContainer"
4✔
408
          />
409
        </MenuArea>
4✔
410
        <APITokenDialog open={openAPITokenDialog} onClose={() => setOpenAPITokenDialog(false)} />
4✔
411
        <UploaderToolDialog open={uploaderToolOpen} onClose={() => setUploaderToolOpen(false)} />
4✔
412
      </NavMobileContainer>
4✔
413
    </>
4✔
414
  );
415
};
4✔
416

417
export default Header;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc