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

CBIIT / crdc-datahub-ui / 16006182009

01 Jul 2025 05:32PM UTC coverage: 62.703% (-8.6%) from 71.278%
16006182009

Pull #756

github

web-flow
Merge pull request #755 from CBIIT/revert-omb-date

revert: OMB expiration update
Pull Request #756: Sync 3.4.0 with 3.3.0

3560 of 6102 branches covered (58.34%)

Branch coverage included in aggregate %.

4920 of 7422 relevant lines covered (66.29%)

227.7 hits per line

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

39.2
/src/components/Header/components/HeaderTabletAndMobile.tsx
1
import React, { HTMLProps, useEffect, useMemo, useState } from "react";
2
import { NavLink, Link, useNavigate, useLocation } from "react-router-dom";
3
import { flatMap } from "lodash";
4
import { styled } from "@mui/material";
5
import { useSnackbar } from "notistack";
6
import Logo from "./LogoMobile";
7
import menuClearIcon from "../../../assets/header/Menu_Cancel_Icon.svg";
8
import rightArrowIcon from "../../../assets/header/Right_Arrow.svg";
9
import leftArrowIcon from "../../../assets/header/Left_Arrow.svg";
10
import { ActionHandlers, ActionId, HeaderLinks } from "../../../config/HeaderConfig";
11
import { useAuthContext } from "../../Contexts/AuthContext";
12
import APITokenDialog from "../../APITokenDialog";
13
import UploaderToolDialog from "../../UploaderToolDialog";
14
import { hasPermission, Permissions } from "../../../config/AuthPermissions";
15
import { Logger } from "../../../utils";
16

17
const HeaderBanner = styled("div")({
2✔
18
  width: "100%",
19
});
20

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

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

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

129
const Header = () => {
2✔
130
  const { isLoggedIn, user, logout } = useAuthContext();
2✔
131
  const navigate = useNavigate();
2✔
132
  const location = useLocation();
2✔
133
  const { enqueueSnackbar } = useSnackbar();
2✔
134

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

141
  const displayName = user?.firstName || "N/A";
2✔
142

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

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

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

166
    actionHandlers[item]?.();
×
167
  };
168

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

176
  const checkPermissions = (permissions: AuthPermissions[]) => {
2✔
177
    if (!permissions?.length) {
12✔
178
      return true; // No permissions required
10✔
179
    }
180

181
    return permissions.every((permission) => {
2✔
182
      const [entityRaw, actionRaw] = permission.split(":", 2);
2✔
183

184
      if (!entityRaw || !actionRaw) {
2!
185
        return false;
×
186
      }
187

188
      const entity = entityRaw as keyof Permissions;
2✔
189
      const action = actionRaw as Permissions[keyof Permissions]["action"];
2✔
190

191
      return hasPermission(user, entity, action, null, true);
2✔
192
    });
193
  };
194

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

201
    setRestorePath(location?.pathname);
×
202
  }, [location]);
203

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

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

415
export default Header;
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