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

CBIIT / crdc-datahub-ui / 14889426582

07 May 2025 05:22PM UTC coverage: 62.642% (+0.1%) from 62.504%
14889426582

push

github

web-flow
Merge pull request #691 from CBIIT/CRDCDH-2594

CRDCDH-2594 Add support for auth permission scoping

3444 of 5894 branches covered (58.43%)

Branch coverage included in aggregate %.

48 of 52 new or added lines in 4 files covered. (92.31%)

1 existing line in 1 file now uncovered.

4764 of 7209 relevant lines covered (66.08%)

199.03 hits per line

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

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

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

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

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

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

127
const Header = () => {
2✔
128
  const { isLoggedIn, user, logout } = useAuthContext();
2✔
129
  const navigate = useNavigate();
2✔
130
  const location = useLocation();
2✔
131

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

139
  const displayName = user?.firstName || "N/A";
2✔
140

141
  const handleLogout = async () => {
2✔
142
    const logoutStatus = await logout?.();
×
143
    if (logoutStatus) {
×
144
      navigate("/");
×
145
      setShowLogoutAlert(true);
×
146
      setTimeout(() => setShowLogoutAlert(false), 10000);
×
147
    }
148
  };
149

150
  HeaderSubLinks[displayName] = [
2✔
151
    {
152
      name: "User Profile",
153
      link: `/profile/${user?._id}`,
154
      id: "navbar-dropdown-item-user-profile",
155
      className: "navMobileSubItem",
156
    },
157
    {
158
      name: "Uploader CLI Tool",
159
      onClick: () => setUploaderToolOpen(true),
×
160
      id: "navbar-dropdown-item-uploader-tool",
161
      className: "navMobileSubItem action",
162
    },
163
    {
164
      name: "API Token",
165
      onClick: () => setOpenAPITokenDialog(true),
×
166
      id: "navbar-dropdown-item-api-token",
167
      className: "navMobileSubItem action",
168
      permissions: ["data_submission:create"],
169
    },
170
    {
171
      name: "Manage Studies",
172
      link: "/studies",
173
      id: "navbar-dropdown-item-studies-manage",
174
      className: "navMobileSubItem",
175
      permissions: ["study:manage"],
176
    },
177
    {
178
      name: "Manage Programs",
179
      link: "/programs",
180
      id: "navbar-dropdown-item-program-manage",
181
      className: "navMobileSubItem",
182
      permissions: ["program:manage"],
183
    },
184
    {
185
      name: "Manage Institutions",
186
      link: "/institutions",
187
      id: "navbar-dropdown-item-institution-manage",
188
      className: "navMobileSubItem",
189
      permissions: ["institution:manage"],
190
    },
191
    {
192
      name: "Manage Users",
193
      link: "/users",
194
      id: "navbar-dropdown-item-user-manage",
195
      className: "navMobileSubItem",
196
      permissions: ["user:manage"],
197
    },
198
    {
199
      name: "Logout",
200
      link: "/logout",
201
      id: "navbar-dropdown-item-logout",
202
      className: "navMobileSubItem",
203
    },
204
  ];
205

206
  const clickNavItem = (e) => {
2✔
207
    const clickTitle = e.target.textContent;
×
208
    setSelectedList(HeaderSubLinks[clickTitle]);
×
209
  };
210

211
  const checkPermissions = (permissions: AuthPermissions[]) => {
2✔
212
    if (!permissions?.length) {
12✔
213
      return true; // No permissions required
10✔
214
    }
215

216
    return permissions.every((permission) => {
2✔
217
      const [entityRaw, actionRaw] = permission.split(":", 2);
2✔
218

219
      if (!entityRaw || !actionRaw) {
2!
NEW
220
        return false;
×
221
      }
222

223
      const entity = entityRaw as keyof Permissions;
2✔
224
      const action = actionRaw as Permissions[keyof Permissions]["action"];
2✔
225

226
      return hasPermission(user, entity, action, null, true);
2✔
227
    });
228
  };
229

230
  useEffect(() => {
2✔
231
    if (!location?.pathname || location?.pathname === "/") {
2!
232
      setRestorePath(null);
2✔
233
      return;
2✔
234
    }
235

236
    setRestorePath(location?.pathname);
×
237
  }, [location]);
238

239
  return (
2✔
240
    <>
241
      <GenericAlert open={showLogoutAlert}>
242
        <span>You have been logged out.</span>
243
      </GenericAlert>
244
      <HeaderBanner data-testid="navigation-header-mobile">
245
        <HeaderContainer>
246
          <Logo />
247
          <div className="headerLowerContainer">
248
            <div
249
              role="button"
250
              id="header-navbar-open-menu-button"
251
              tabIndex={0}
252
              className="menuButton"
253
              onKeyDown={(e) => {
254
                if (e.key === "Enter") {
×
255
                  setNavMobileDisplay("block");
×
256
                }
257
              }}
258
              onClick={() => setNavMobileDisplay("block")}
×
259
            >
260
              Menu
261
            </div>
262
          </div>
263
        </HeaderContainer>
264
      </HeaderBanner>
265
      <NavMobileContainer display={navMobileDisplay}>
266
        <MenuArea>
267
          <div className="menuContainer">
268
            <div
269
              role="button"
270
              id="navbar-close-navbar-button"
271
              tabIndex={0}
272
              className="closeIcon"
273
              onKeyDown={(e) => {
274
                if (e.key === "Enter") {
×
275
                  setNavMobileDisplay("none");
×
276
                }
277
              }}
278
              onClick={() => setNavMobileDisplay("none")}
×
279
            >
280
              <img className="closeIconImg" src={menuClearIcon} alt="menuClearButton" />
281
            </div>
282
            {selectedList !== HeaderLinks && (
1!
283
              <div
284
                role="button"
285
                id="navbar-back-to-main-menu-button"
286
                tabIndex={0}
287
                className="backButton"
288
                onKeyDown={(e) => {
289
                  if (e.key === "Enter") {
×
290
                    setSelectedList(HeaderLinks);
×
291
                  }
292
                }}
293
                onClick={() => setSelectedList(HeaderLinks)}
×
294
              >
295
                Main Menu
296
              </div>
297
            )}
298
            <div className="navMobileContainer">
299
              {selectedList?.map((navMobileItem: NavBarItem | NavBarSubItem) => {
300
                const hasEveryPermission = checkPermissions(navMobileItem?.permissions);
12✔
301
                if (!hasEveryPermission) {
12✔
302
                  return null;
2✔
303
                }
304

305
                return (
10✔
306
                  <React.Fragment key={`mobile_${navMobileItem.id}`}>
307
                    {navMobileItem.className === "navMobileItem" && (
8✔
308
                      <NavLink
309
                        id={navMobileItem.id}
310
                        to={navMobileItem.link}
311
                        target={navMobileItem.link.startsWith("https://") ? "_blank" : "_self"}
3✔
312
                        onClick={() => setNavMobileDisplay("none")}
×
313
                      >
314
                        <div className="navMobileItem">{navMobileItem.name}</div>
315
                      </NavLink>
316
                    )}
317
                    {navMobileItem.className === "navMobileItem clickable" && (
7✔
318
                      <div
319
                        id={navMobileItem.id}
320
                        role="button"
321
                        tabIndex={0}
322
                        className="navMobileItem clickable"
323
                        onKeyDown={(e) => {
324
                          if (e.key === "Enter") {
×
325
                            clickNavItem(e);
×
326
                          }
327
                        }}
328
                        onClick={clickNavItem}
329
                      >
330
                        {navMobileItem.name}
331
                      </div>
332
                    )}
333
                    {navMobileItem.className === "navMobileSubItem action" &&
10!
334
                    "onClick" in navMobileItem &&
335
                    typeof navMobileItem.onClick === "function" ? (
336
                      <div
337
                        id={navMobileItem.id}
338
                        role="button"
339
                        tabIndex={0}
340
                        className="navMobileItem SubItem action"
341
                        onKeyDown={(e) => {
342
                          if (e.key === "Enter") {
×
343
                            navMobileItem.onClick();
×
344
                          }
345
                        }}
346
                        onClick={() => navMobileItem.onClick()}
×
347
                      >
348
                        {navMobileItem.name}
349
                      </div>
350
                    ) : null}
351
                    {navMobileItem.className === "navMobileSubItem" && (
5!
352
                      <Link
353
                        id={navMobileItem.id}
354
                        to={navMobileItem.link}
355
                        target={
356
                          navMobileItem.link.startsWith("https://") ||
×
357
                          navMobileItem.link.endsWith(".pdf")
358
                            ? "_blank"
359
                            : "_self"
360
                        }
361
                      >
362
                        <div
363
                          role="button"
364
                          tabIndex={0}
365
                          className="navMobileItem SubItem"
366
                          onKeyDown={(e) => {
367
                            if (e.key === "Enter") {
×
368
                              setNavMobileDisplay("none");
×
369
                              if (navMobileItem.name === "Logout") {
×
370
                                handleLogout();
×
371
                                setSelectedList(HeaderLinks);
×
372
                              }
373
                            }
374
                          }}
375
                          onClick={() => {
376
                            setNavMobileDisplay("none");
×
377
                            if (navMobileItem.name === "Logout") {
×
378
                              handleLogout();
×
379
                              setSelectedList(HeaderLinks);
×
380
                            }
381
                          }}
382
                        >
383
                          {navMobileItem.name}
384
                        </div>
385
                      </Link>
386
                    )}
387
                    {navMobileItem.className === "navMobileSubTitle" && (
5!
388
                      <div className="navMobileItem">{navMobileItem.name}</div>
389
                    )}
390
                  </React.Fragment>
391
                );
392
              })}
393
              {/* eslint-disable-next-line no-nested-ternary */}
394
              {selectedList === HeaderLinks ? (
1!
395
                isLoggedIn ? (
1!
396
                  <div
397
                    id="navbar-dropdown-name"
398
                    role="button"
399
                    tabIndex={0}
400
                    className="navMobileItem clickable"
401
                    onKeyDown={(e) => {
402
                      if (e.key === "Enter") {
×
403
                        clickNavItem(e);
×
404
                      }
405
                    }}
406
                    onClick={clickNavItem}
407
                  >
408
                    {displayName}
409
                  </div>
410
                ) : (
411
                  <Link id="navbar-link-login" to="/login" state={{ redirectState: restorePath }}>
412
                    <div
413
                      role="button"
414
                      tabIndex={0}
415
                      className="navMobileItem"
416
                      onKeyDown={(e) => {
417
                        if (e.key === "Enter") {
×
418
                          setNavMobileDisplay("none");
×
419
                        }
420
                      }}
421
                      onClick={() => setNavMobileDisplay("none")}
×
422
                    >
423
                      Login
424
                    </div>
425
                  </Link>
426
                )
427
              ) : null}
428
            </div>
429
          </div>
430
          <div
431
            role="button"
432
            id="navbar-close-navbar-grey-section"
433
            tabIndex={0}
434
            className="greyContainer"
435
            onKeyDown={(e) => {
436
              if (e.key === "Enter") {
×
437
                setNavMobileDisplay("none");
×
438
              }
439
            }}
440
            onClick={() => setNavMobileDisplay("none")}
×
441
            aria-label="greyContainer"
442
          />
443
        </MenuArea>
444
        <APITokenDialog open={openAPITokenDialog} onClose={() => setOpenAPITokenDialog(false)} />
×
445
        <UploaderToolDialog open={uploaderToolOpen} onClose={() => setUploaderToolOpen(false)} />
×
446
      </NavMobileContainer>
447
    </>
448
  );
449
};
450

451
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