qpp-style 9.16.9 → 9.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/.husky/pre-commit +5 -0
  2. package/components/Alert/Alert.stories.js +1 -1
  3. package/components/Alert/index.js +8 -1
  4. package/components/GovBanner/index.js +65 -57
  5. package/components/Header/HeaderContainer.jsx +1 -1
  6. package/components/Header/HeaderMenuItem.jsx +13 -6
  7. package/components/Modal/Modal.jsx +1 -1
  8. package/components/SideNav/AnimationGroup/AnimationGroup.jsx +2 -2
  9. package/components/SideNav/Content/LevelOneContent.jsx +121 -156
  10. package/components/SideNav/Content/SelectRole/index.js +79 -0
  11. package/components/SideNav/Content/SelectRole/utils.js +120 -0
  12. package/components/SideNav/Details/PracticeDetails.jsx +1 -1
  13. package/components/SideNav/Links/NavLinkDrawer.jsx +1 -1
  14. package/components/SideNav/Links/NavLinkInline.jsx +6 -1
  15. package/components/SideNav/UI/SideNavUI.jsx +7 -3
  16. package/components/SideNav/helpers.js +213 -0
  17. package/components/Tabs/Tab.js +1 -1
  18. package/components/Tabs/TabPanel.js +8 -1
  19. package/components/Tabs/index.js +46 -41
  20. package/components/TextInput/index.js +1 -1
  21. package/components/hooks/useGetConfig.js +1 -0
  22. package/dist/browser.js +1 -1
  23. package/dist/browser.js.LICENSE.txt +10 -1
  24. package/dist/browser.js.map +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/index.js.LICENSE.txt +10 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/react/index.js +1 -1
  29. package/dist/react/index.js.LICENSE.txt +9 -0
  30. package/dist/react/index.js.map +1 -1
  31. package/images/icons/svg/arrow-down.svg +3 -1
  32. package/images/icons/svg/arrow-up.svg +3 -0
  33. package/lib/SvgComponents.jsx +15 -0
  34. package/package.json +10 -8
  35. package/storybook-static/favicon.ico +0 -0
  36. package/styles/qppds/components/_dropdown.scss +1 -0
  37. package/styles/qppds/components/_sidebar.scss +1 -0
  38. package/styles/qppds/components/sidebar/_links.scss +14 -1
  39. package/styles/qppds/components/sidebar/_select-role-dropdown.scss +21 -0
  40. package/styles/qppds/components/sidebar/_select-role.scss +13 -0
  41. package/coverage/clover.xml +0 -1294
  42. package/coverage/coverage-final.json +0 -70
  43. package/coverage/lcov-report/base.css +0 -224
  44. package/coverage/lcov-report/block-navigation.js +0 -87
  45. package/coverage/lcov-report/favicon.png +0 -0
  46. package/coverage/lcov-report/index.html +0 -461
  47. package/coverage/lcov-report/prettify.css +0 -1
  48. package/coverage/lcov-report/prettify.js +0 -2
  49. package/coverage/lcov-report/react/components/Accordion/index.html +0 -116
  50. package/coverage/lcov-report/react/components/Accordion/index.jsx.html +0 -364
  51. package/coverage/lcov-report/react/components/Button/index.html +0 -116
  52. package/coverage/lcov-report/react/components/Button/index.js.html +0 -355
  53. package/coverage/lcov-report/react/components/Error/Collapsible.jsx.html +0 -349
  54. package/coverage/lcov-report/react/components/Error/ErrorUI.jsx.html +0 -178
  55. package/coverage/lcov-report/react/components/Error/error.js.html +0 -163
  56. package/coverage/lcov-report/react/components/Error/index.html +0 -146
  57. package/coverage/lcov-report/react/components/Footer/FooterUI.jsx.html +0 -880
  58. package/coverage/lcov-report/react/components/Footer/LegacyFooterUI.jsx.html +0 -667
  59. package/coverage/lcov-report/react/components/Footer/SocialLinks.jsx.html +0 -265
  60. package/coverage/lcov-report/react/components/Footer/Subscribe.jsx.html +0 -187
  61. package/coverage/lcov-report/react/components/Footer/footer.js.html +0 -175
  62. package/coverage/lcov-report/react/components/Footer/index.html +0 -176
  63. package/coverage/lcov-report/react/components/GovBanner/index.html +0 -116
  64. package/coverage/lcov-report/react/components/GovBanner/index.js.html +0 -409
  65. package/coverage/lcov-report/react/components/Header/HeaderAccountMenu.jsx.html +0 -592
  66. package/coverage/lcov-report/react/components/Header/HeaderCancel.jsx.html +0 -133
  67. package/coverage/lcov-report/react/components/Header/HeaderContainer.jsx.html +0 -280
  68. package/coverage/lcov-report/react/components/Header/HeaderLogo.jsx.html +0 -211
  69. package/coverage/lcov-report/react/components/Header/HeaderMenuButton.js.html +0 -235
  70. package/coverage/lcov-report/react/components/Header/HeaderMenuItem.jsx.html +0 -781
  71. package/coverage/lcov-report/react/components/Header/HeaderMenuLink.js.html +0 -235
  72. package/coverage/lcov-report/react/components/Header/HeaderMenuSignOutButton.js.html +0 -271
  73. package/coverage/lcov-report/react/components/Header/HeaderMobileButton.js.html +0 -196
  74. package/coverage/lcov-report/react/components/Header/HeaderUI.jsx.html +0 -586
  75. package/coverage/lcov-report/react/components/Header/HelpIcon.jsx.html +0 -181
  76. package/coverage/lcov-report/react/components/Header/ImpersonatorBanner.jsx.html +0 -331
  77. package/coverage/lcov-report/react/components/Header/NavigationButtonIcon.jsx.html +0 -166
  78. package/coverage/lcov-report/react/components/Header/header.js.html +0 -205
  79. package/coverage/lcov-report/react/components/Header/hooks.js.html +0 -241
  80. package/coverage/lcov-report/react/components/Header/index.html +0 -341
  81. package/coverage/lcov-report/react/components/Header/utag-helpers.js.html +0 -112
  82. package/coverage/lcov-report/react/components/Infotip/Infotip.jsx.html +0 -310
  83. package/coverage/lcov-report/react/components/Infotip/InfotipIcon.jsx.html +0 -208
  84. package/coverage/lcov-report/react/components/Infotip/index.html +0 -146
  85. package/coverage/lcov-report/react/components/Infotip/index.js.html +0 -94
  86. package/coverage/lcov-report/react/components/Modal/LegacyModal.jsx.html +0 -301
  87. package/coverage/lcov-report/react/components/Modal/Modal.jsx.html +0 -505
  88. package/coverage/lcov-report/react/components/Modal/index.html +0 -146
  89. package/coverage/lcov-report/react/components/Modal/index.jsx.html +0 -151
  90. package/coverage/lcov-report/react/components/NotificationBanner/index.html +0 -116
  91. package/coverage/lcov-report/react/components/NotificationBanner/index.js.html +0 -880
  92. package/coverage/lcov-report/react/components/SanitizedContent/index.html +0 -116
  93. package/coverage/lcov-report/react/components/SanitizedContent/index.jsx.html +0 -685
  94. package/coverage/lcov-report/react/components/SessionDialog/index.html +0 -116
  95. package/coverage/lcov-report/react/components/SessionDialog/sessionDialog.js.html +0 -163
  96. package/coverage/lcov-report/react/components/SessionDialogUI.jsx.html +0 -868
  97. package/coverage/lcov-report/react/components/SideNav/AnimationGroup/AnimationGroup.jsx.html +0 -166
  98. package/coverage/lcov-report/react/components/SideNav/AnimationGroup/index.html +0 -116
  99. package/coverage/lcov-report/react/components/SideNav/Chart/ScoreChart.jsx.html +0 -889
  100. package/coverage/lcov-report/react/components/SideNav/Chart/index.html +0 -131
  101. package/coverage/lcov-report/react/components/SideNav/Chart/index.js.html +0 -94
  102. package/coverage/lcov-report/react/components/SideNav/Content/LevelOneContent.jsx.html +0 -709
  103. package/coverage/lcov-report/react/components/SideNav/Content/LevelTwoContent.jsx.html +0 -733
  104. package/coverage/lcov-report/react/components/SideNav/Content/index.html +0 -146
  105. package/coverage/lcov-report/react/components/SideNav/Content/index.js.html +0 -97
  106. package/coverage/lcov-report/react/components/SideNav/Details/IndividualDetails.jsx.html +0 -139
  107. package/coverage/lcov-report/react/components/SideNav/Details/PracticeDetails.jsx.html +0 -220
  108. package/coverage/lcov-report/react/components/SideNav/Details/index.html +0 -146
  109. package/coverage/lcov-report/react/components/SideNav/Details/index.js.html +0 -97
  110. package/coverage/lcov-report/react/components/SideNav/Links/CmsSwitchLink.jsx.html +0 -208
  111. package/coverage/lcov-report/react/components/SideNav/Links/NavItemInline.jsx.html +0 -247
  112. package/coverage/lcov-report/react/components/SideNav/Links/NavLinkContainer.jsx.html +0 -199
  113. package/coverage/lcov-report/react/components/SideNav/Links/NavLinkDrawer.jsx.html +0 -883
  114. package/coverage/lcov-report/react/components/SideNav/Links/NavLinkInline.jsx.html +0 -406
  115. package/coverage/lcov-report/react/components/SideNav/Links/NavLinkToggle.jsx.html +0 -187
  116. package/coverage/lcov-report/react/components/SideNav/Links/index.html +0 -206
  117. package/coverage/lcov-report/react/components/SideNav/Links/index.js.html +0 -124
  118. package/coverage/lcov-report/react/components/SideNav/UI/SideNavUI.jsx.html +0 -1087
  119. package/coverage/lcov-report/react/components/SideNav/UI/index.html +0 -131
  120. package/coverage/lcov-report/react/components/SideNav/UI/index.js.html +0 -94
  121. package/coverage/lcov-report/react/components/SideNav/helpers.js.html +0 -238
  122. package/coverage/lcov-report/react/components/SideNav/index.html +0 -131
  123. package/coverage/lcov-report/react/components/SideNav/index.js.html +0 -244
  124. package/coverage/lcov-report/react/components/Tooltip/Tooltip.jsx.html +0 -349
  125. package/coverage/lcov-report/react/components/Tooltip/index.html +0 -146
  126. package/coverage/lcov-report/react/components/Tooltip/index.js.html +0 -94
  127. package/coverage/lcov-report/react/components/Tooltip/position.js.html +0 -289
  128. package/coverage/lcov-report/react/components/hooks/index.html +0 -116
  129. package/coverage/lcov-report/react/components/hooks/useGetConfig.js.html +0 -307
  130. package/coverage/lcov-report/react/components/index.html +0 -116
  131. package/coverage/lcov-report/react/index.html +0 -116
  132. package/coverage/lcov-report/react/index.js.html +0 -178
  133. package/coverage/lcov-report/react/lib/Chevron.jsx.html +0 -181
  134. package/coverage/lcov-report/react/lib/SvgComponents.jsx.html +0 -1702
  135. package/coverage/lcov-report/react/lib/index.html +0 -146
  136. package/coverage/lcov-report/react/lib/svg-definitions.svg.html +0 -460
  137. package/coverage/lcov-report/react/session/index.html +0 -161
  138. package/coverage/lcov-report/react/session/index.js.html +0 -100
  139. package/coverage/lcov-report/react/session/logout.js.html +0 -307
  140. package/coverage/lcov-report/react/session/refresh.js.html +0 -232
  141. package/coverage/lcov-report/react/session/ttl.js.html +0 -196
  142. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  143. package/coverage/lcov-report/sorter.js +0 -196
  144. package/coverage/lcov.info +0 -2924
@@ -0,0 +1,79 @@
1
+ import React, { useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import cookie from 'cookie';
4
+ import Dropdown from '../../../Dropdown';
5
+ import {
6
+ loadRoleOptions,
7
+ redirectPage,
8
+ getLocalStorageRoleState,
9
+ initializeLocalStorageRoleState,
10
+ updateLocalStorageRoleState,
11
+ } from './utils';
12
+
13
+ const SelectRole = ({ selectedRole, setSelectedRole }) => {
14
+ const {
15
+ qpp_has_authorizations: hasAuthorizations,
16
+ qpp_cms_internal_authorized: cmsInternalRoleValues,
17
+ qpp_impersonated_user: isHelpdeskRoleAndImpersonating,
18
+ } = cookie.parse(document.cookie);
19
+
20
+ const ROLE_OPTIONS = loadRoleOptions([
21
+ hasAuthorizations,
22
+ cmsInternalRoleValues,
23
+ ]);
24
+
25
+ const handleSelection = (e) => {
26
+ e.preventDefault();
27
+ const dropdownValue = e.target.value;
28
+ const newRoleSelected = ROLE_OPTIONS.filter(
29
+ (opt) => opt.value === dropdownValue
30
+ )[0].value;
31
+ // Update localStorage with the current selected role in state & the new role selected
32
+ updateLocalStorageRoleState(selectedRole, newRoleSelected);
33
+ setSelectedRole(newRoleSelected);
34
+ redirectPage(newRoleSelected);
35
+ };
36
+
37
+ // Set role on init render
38
+ // below is executed on every page refresh including on side nav link navigation & on role selection
39
+ useEffect(() => {
40
+ // Check localstorage & set initial selected role
41
+ const { selectedRole, previousSelectedRole } = getLocalStorageRoleState();
42
+ const roleValue = selectedRole || ROLE_OPTIONS[0].value;
43
+
44
+ // Set localStorage values if not set
45
+ initializeLocalStorageRoleState(roleValue);
46
+
47
+ // Update state
48
+ setSelectedRole(roleValue);
49
+
50
+ // No previous role set && current role is QPP User in localStorage values indicates
51
+ // initial render, where a user has NOT used the dropdown to change the user role.
52
+ // If the above condition is not met, redirect to default page of selected role
53
+ if (!previousSelectedRole && selectedRole !== 'QPP User') {
54
+ redirectPage(roleValue);
55
+ }
56
+ }, []);
57
+
58
+ return (
59
+ <div className="SelectRole__Container">
60
+ <p className="SelectRole__Text">You are viewing as a</p>
61
+ <Dropdown
62
+ value={selectedRole}
63
+ name="user-role-selection"
64
+ ariaLabel="Select User Role"
65
+ className="qpp-u-width--100 SelectRole__Dropdown"
66
+ options={ROLE_OPTIONS}
67
+ onChange={handleSelection}
68
+ disabled={isHelpdeskRoleAndImpersonating}
69
+ />
70
+ </div>
71
+ );
72
+ };
73
+
74
+ SelectRole.propTypes = {
75
+ selectedRole: PropTypes.string,
76
+ setSelectedRole: PropTypes.func,
77
+ };
78
+
79
+ export default SelectRole;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Extract values from cookies, return parsed values in an array
3
+ * @param {array} cookies
4
+ * @returns {array}
5
+ */
6
+ const extractCookieValues = (cookies) => {
7
+ const [hasAuthorizations, cmsInternalRoleValues] = cookies;
8
+ const cmsInternalRoles = cmsInternalRoleValues
9
+ ? JSON.parse(cmsInternalRoleValues)
10
+ : null;
11
+ return [hasAuthorizations, cmsInternalRoles];
12
+ };
13
+
14
+ /**
15
+ * Check if internal cms roles has Reviewer role
16
+ * @param {array} cmsInternalRoles array of values
17
+ * @returns {boolean}
18
+ */
19
+ const hasReviewerRole = (cmsInternalRoles) => {
20
+ if (!cmsInternalRoles) return false;
21
+ return cmsInternalRoles.some(
22
+ (name) =>
23
+ name === 'QPP Self-Nomination' ||
24
+ name === 'QPP Targeted Review & Exceptions'
25
+ );
26
+ };
27
+
28
+ /**
29
+ * Check if internal cms roles includes Helpdek role
30
+ * @param {array} cmsInternalRoles array of values
31
+ * @returns {boolean}
32
+ */
33
+ const hasHelpdeskRole = (cmsInternalRoles) => {
34
+ if (!cmsInternalRoles) return false;
35
+ return cmsInternalRoles.includes('QPP Helpdesk');
36
+ };
37
+
38
+ /**
39
+ * Transforms cookie values into optios format for use in role seelction dropdown
40
+ * @param {array} values extracted & parsed vookie values
41
+ * @returns {array}
42
+ */
43
+ const transformCookieValues = (values) => {
44
+ const optionValues = [];
45
+ const [hasAuthorizations, cmsInternalRoles] = values;
46
+
47
+ if (hasAuthorizations === 'true') {
48
+ optionValues.push({ name: 'QPP User', value: 'QPP User' });
49
+ }
50
+ if (hasReviewerRole(cmsInternalRoles)) {
51
+ optionValues.push({ name: 'Reviewer', value: 'Reviewer' });
52
+ }
53
+ if (hasHelpdeskRole(cmsInternalRoles)) {
54
+ optionValues.push({ name: 'Helpdesk Viewer', value: 'Helpdesk Viewer' });
55
+ }
56
+ return optionValues;
57
+ };
58
+
59
+ /**
60
+ *
61
+ * @param {array} cookies authz & cms_internal_authz values parsed from cookies
62
+ * @returns {array} of objects for use in role dropdown
63
+ */
64
+ export const loadRoleOptions = (cookies) => {
65
+ const cookieValues = extractCookieValues(cookies);
66
+ return transformCookieValues(cookieValues);
67
+ };
68
+
69
+ const defaultPageByRole = {
70
+ 'QPP User': '/user/submissions',
71
+ 'Helpdesk Viewer': '/user/helpdesk-viewing-tool',
72
+ Reviewer: '/user/reviewers',
73
+ };
74
+
75
+ /**
76
+ * Get redirect url/page by role
77
+ * @param {string} role
78
+ * @returns {string}
79
+ */
80
+ export const redirectPage = (role) => {
81
+ const defaultPage = defaultPageByRole[role];
82
+ const { pathname } = window.location;
83
+ if (pathname === defaultPage) return;
84
+ else window.location.assign(defaultPage);
85
+ };
86
+
87
+ /**
88
+ * Get localStorage values related to user role
89
+ * @returns {object}
90
+ */
91
+ export const getLocalStorageRoleState = () => ({
92
+ selectedRole: localStorage.getItem('selectedRole'),
93
+ previousSelectedRole: localStorage.getItem('previousSelectedRole'),
94
+ });
95
+
96
+ /**
97
+ * Initialize role values in localStorage
98
+ * @param {string} roleValue selector role value from dropdown
99
+ * @returns {void}
100
+ */
101
+ export const initializeLocalStorageRoleState = (roleValue) => {
102
+ const localStorageValues = getLocalStorageRoleState();
103
+
104
+ if (!localStorageValues.selectedRole) {
105
+ localStorage.setItem('selectedRole', roleValue);
106
+ }
107
+ if (!localStorageValues.previousSelectedRole) {
108
+ localStorage.setItem('previousSelectedRole', '');
109
+ }
110
+ };
111
+
112
+ /**
113
+ * Update role values in localStorage
114
+ * @param {string} currentRole current/existing user role
115
+ * @param {string} updatedRole new user role selected from dropdown
116
+ */
117
+ export const updateLocalStorageRoleState = (currentRole, updatedRole) => {
118
+ localStorage.setItem('previousSelectedRole', currentRole);
119
+ localStorage.setItem('selectedRole', updatedRole);
120
+ };
@@ -7,7 +7,7 @@ const PracticeDetails = ({
7
7
  apmEntityId,
8
8
  vgId,
9
9
  cpcPlusId,
10
- pcfId
10
+ pcfId,
11
11
  }) => {
12
12
  function renderId() {
13
13
  if (cpcPlusId) {
@@ -216,7 +216,7 @@ const NavLinkDrawer = ({
216
216
  <div className={isExpanded ? 'link-body' : 'link-body collapsed'}>
217
217
  {renderLeftIcon(leftIcon)}
218
218
  <AnimationGroup display={isExpanded}>
219
- <span>{label}</span>
219
+ <span className="sidebar-inline-link-label">{label}</span>
220
220
  </AnimationGroup>
221
221
  </div>
222
222
  {isExpanded && !isAlwaysOpen && (
@@ -8,6 +8,7 @@ const NavLinkInline = ({
8
8
  urlExpressionToMatch,
9
9
  className,
10
10
  icon,
11
+ rightIcon,
11
12
  label,
12
13
  isActive,
13
14
  showLabel,
@@ -78,7 +79,10 @@ const NavLinkInline = ({
78
79
  {icon}
79
80
  </svg>
80
81
  <AnimationGroup display={showLabel}>
81
- <span tabIndex="-1">{label}</span>
82
+ <span tabIndex="-1" className="sidebar-inline-link-label">
83
+ {label}
84
+ {rightIcon}
85
+ </span>
82
86
  </AnimationGroup>
83
87
  </a>
84
88
  {showTooltip && (
@@ -95,6 +99,7 @@ NavLinkInline.propTypes = {
95
99
  url: PropTypes.string,
96
100
  className: PropTypes.string,
97
101
  icon: PropTypes.element,
102
+ rightIcon: PropTypes.element,
98
103
  label: PropTypes.string,
99
104
  isActive: PropTypes.bool,
100
105
  showLabel: PropTypes.bool,
@@ -91,15 +91,19 @@ const SideNavUI = ({
91
91
 
92
92
  const hasDarkerBackground = (darkerBackground) => {
93
93
  return darkerBackground ? 'hr-smaller-vertical-spacing' : '';
94
- }
94
+ };
95
95
 
96
96
  const itemsMarkup = (contentItems || []).map((item, index) => {
97
97
  const key = `${JSON.stringify(item)}-${index}-${containerRecursionId}`;
98
98
  return (
99
99
  {
100
100
  divider: (
101
- <AnimationGroup display={isExpandedState} key={key} darkerBackground={item.darkerBackground}>
102
- <hr className={`${hasDarkerBackground(item.darkerBackground)}`}/>
101
+ <AnimationGroup
102
+ display={isExpandedState}
103
+ key={key}
104
+ darkerBackground={item.darkerBackground}
105
+ >
106
+ <hr className={`${hasDarkerBackground(item.darkerBackground)}`} />
103
107
  </AnimationGroup>
104
108
  ),
105
109
  container: getDynamicContent(
@@ -1,3 +1,20 @@
1
+ import cookie from 'cookie';
2
+ import {
3
+ AccountHomeIcon,
4
+ DashboardIcon,
5
+ FacilityBasedPreviewIcon,
6
+ HardshipIcon,
7
+ HelpSupportIcon,
8
+ ManageUsersIcon,
9
+ MyApplicationsIcon,
10
+ MyTestDataIcon,
11
+ PaymentIcon,
12
+ PhysicianCompareIcon,
13
+ StarIcon,
14
+ TargetIcon,
15
+ IndividualReporting,
16
+ } from '../../lib/SvgComponents';
17
+
1
18
  const submissionsUrl = '/user/submissions';
2
19
  const dashboardUrl = '/user/dashboard';
3
20
  const manageUrl = '/user/manage-access';
@@ -36,6 +53,195 @@ const handleNavigation = (e, linkCallbackFunction, label) => {
36
53
  });
37
54
  };
38
55
 
56
+ /**
57
+ * Load icon by url
58
+ * @param {string} url
59
+ * @returns svg icon
60
+ */
61
+ const getIcon = (url) => {
62
+ const icons = {
63
+ [dashboardUrl]: DashboardIcon,
64
+ [feedbackUrl]: StarIcon,
65
+ [manageUrl]: ManageUsersIcon,
66
+ [physicianCompareUrl]: PhysicianCompareIcon,
67
+ [reportsPortalUrl]: HardshipIcon,
68
+ [submissionsUrl]: AccountHomeIcon,
69
+ [facilityBasedPreviewBaseUrl]: FacilityBasedPreviewIcon,
70
+ '/developers': HelpSupportIcon,
71
+ '/resources/help-and-support': HelpSupportIcon,
72
+ '/user/apm-incentive-payments': PaymentIcon,
73
+ '/user/applications': MyApplicationsIcon,
74
+ '/user/exception/#/landing': HardshipIcon,
75
+ '/user/targeted-review/#/landing': TargetIcon,
76
+ '/user/test-data': MyTestDataIcon,
77
+ '/user/self-nomination/#/landing': IndividualReporting,
78
+ '/user/reviewers': AccountHomeIcon,
79
+ '/reviewer/exception': HardshipIcon,
80
+ '/reviewer/targeted-review': TargetIcon,
81
+ '/self-nomination': IndividualReporting,
82
+ };
83
+ return icons[url];
84
+ };
85
+
86
+ /**
87
+ * Check if Reviewer or Self-Nom role
88
+ * @param {string} name
89
+ * @returns {boolean}
90
+ */
91
+ const isReviewerOrSelfNomRole = (name) =>
92
+ name === 'QPP Self-Nomination' || name === 'QPP Targeted Review & Exceptions';
93
+
94
+ /**
95
+ * Check if user is currently impersonating
96
+ * @param {string} docCookie document cookie
97
+ * @returns {boolean}
98
+ */
99
+ const isImpersonating = (docCookie) => {
100
+ const parsedCookies = cookie.parse(docCookie);
101
+ return (
102
+ parsedCookies.qpp_can_impersonate && parsedCookies.qpp_impersonated_user
103
+ );
104
+ };
105
+
106
+ /**
107
+ * Check if user has multiple roles, used to display Select Role dropdown
108
+ * @param {string} docCookie document cookie
109
+ * @returns {boolean}
110
+ */
111
+ const isMultiRoleUser = (docCookie) => {
112
+ const { qpp_cms_internal_authorized } = cookie.parse(docCookie);
113
+ let internalAuthz = [];
114
+ if (qpp_cms_internal_authorized) {
115
+ internalAuthz = JSON.parse(qpp_cms_internal_authorized);
116
+ }
117
+ // Filter out test internalCms authz
118
+ const filteredInternalAuthz = internalAuthz.filter((internalAuth) => {
119
+ return (
120
+ internalAuth !== 'Test internalCms Resource' &&
121
+ internalAuth !== 'Test internalCms Resource 2'
122
+ );
123
+ });
124
+ return filteredInternalAuthz.length > 1;
125
+ };
126
+
127
+ /**
128
+ * Transform cookie values to permissions object
129
+ * @param {object} cookies parsed document.cookie values
130
+ * @returns {object}
131
+ */
132
+ const transformCookieValues = (cookies) => {
133
+ const {
134
+ qpp_can_impersonate,
135
+ qpp_has_authorizations,
136
+ user_has_apm_payments,
137
+ qpp_cms_internal_authorized,
138
+ qpp_ehr_authorized,
139
+ qpp_has_non_registry_authorizations,
140
+ } = cookies;
141
+ return {
142
+ canImpersonate: qpp_can_impersonate === 'true',
143
+ hasAuthorizations: qpp_has_authorizations === 'true',
144
+ hasApmPayments: user_has_apm_payments === 'true',
145
+ internalReviewerNames: qpp_cms_internal_authorized
146
+ ? JSON.parse(qpp_cms_internal_authorized)
147
+ : [],
148
+ ehrAuthorized: qpp_ehr_authorized === 'true',
149
+ hasNonRegistryAuthorizations:
150
+ qpp_has_non_registry_authorizations === 'true',
151
+ };
152
+ };
153
+
154
+ const permissionsByRole = {
155
+ 'QPP User': {
156
+ canImpersonate: false,
157
+ hasAuthorizations: true,
158
+ hasApmPayments: true,
159
+ },
160
+ Reviewer: {
161
+ hasAuthorizations: false,
162
+ hasApmPaymentss: false,
163
+ },
164
+ 'Helpdesk Viewer': {
165
+ hasAuthorizations: false,
166
+ hasApmPayments: false,
167
+ hasNonRegistryAuthorizations: false,
168
+ internalReviewerNames: ['QPP Helpdesk'],
169
+ },
170
+ // Helpdesk role && impersonating (use impersonated user authz values)
171
+ Impersonation: {
172
+ internalReviewerNames: ['QPP Helpdesk'],
173
+ },
174
+ };
175
+
176
+ /**
177
+ * Return permissions by role
178
+ * @param {string} selectedRole selected user role
179
+ * @param {object} permissionValues default permission values from transformed parsed cookie
180
+ * @returns {object}
181
+ */
182
+ const loadUserPermissionsByRole = (selectedRole, permissionValues) => {
183
+ return {
184
+ ...permissionValues,
185
+ ...permissionsByRole[selectedRole],
186
+ };
187
+ };
188
+
189
+ /**
190
+ * Helper fn to return user permissions/properties from cookies set on login
191
+ * @param {string} docCookie cookie values set on login
192
+ * @param {string} selectedRole string of selected role if user has multiple roles
193
+ * @returns {object}
194
+ */
195
+ const loadUserPermissions = (docCookie, selectedRole) => {
196
+ const parsedCookies = cookie.parse(docCookie);
197
+ const permissionValues = transformCookieValues(parsedCookies);
198
+ const impersonating = isImpersonating(docCookie);
199
+ const role = impersonating ? 'Impersonation' : selectedRole;
200
+
201
+ if (role) {
202
+ return loadUserPermissionsByRole(role, permissionValues);
203
+ }
204
+
205
+ return permissionValues;
206
+ };
207
+
208
+ /**
209
+ * Return map of link conditionals based on user cookies for side nav
210
+ * @param {object} userPermissions user permissions/properties derived from cookies set on login
211
+ * @returns {object}
212
+ */
213
+ const getUrlConditionMap = (userPermissions) => {
214
+ return {
215
+ [dashboardUrl]: userPermissions.hasAuthorizations,
216
+ [feedbackUrl]: userPermissions.hasAuthorizations,
217
+ '/user/apm-incentive-payments': userPermissions.hasApmPayments,
218
+ [physicianCompareUrl]: userPermissions.hasNonRegistryAuthorizations,
219
+ [reportsPortalUrl]: userPermissions.hasAuthorizations,
220
+ [facilityBasedPreviewBaseUrl]: userPermissions.hasAuthorizations,
221
+ '/user/helpdesk-viewing-tool': userPermissions.canImpersonate,
222
+ '/reviewer/exception': userPermissions.internalReviewerNames?.includes(
223
+ 'QPP Targeted Review & Exceptions'
224
+ ),
225
+ '/reviewer/targeted-review':
226
+ userPermissions.internalReviewerNames?.includes(
227
+ 'QPP Targeted Review & Exceptions'
228
+ ),
229
+ '/self-nomination': userPermissions.internalReviewerNames?.includes(
230
+ 'QPP Self-Nomination'
231
+ ),
232
+
233
+ // dev pre
234
+ '/user/applications': userPermissions.ehrAuthorized,
235
+ '/user/test-data': userPermissions.ehrAuthorized,
236
+ };
237
+ };
238
+
239
+ const isImpersonationLink = (docCookie, linkLabel) => {
240
+ if (!isImpersonating(docCookie)) return;
241
+ const helpdeskLinks = ['Account Home', 'Manage Access', 'Help and Support'];
242
+ return !helpdeskLinks.includes(linkLabel);
243
+ };
244
+
39
245
  module.exports = {
40
246
  submissionsUrl,
41
247
  dashboardUrl,
@@ -48,4 +254,11 @@ module.exports = {
48
254
  reportsPortalUrl,
49
255
  facilityBasedPreviewBaseUrl,
50
256
  viewingToolUrl,
257
+ loadUserPermissions,
258
+ getUrlConditionMap,
259
+ isMultiRoleUser,
260
+ isReviewerOrSelfNomRole,
261
+ getIcon,
262
+ isImpersonating,
263
+ isImpersonationLink,
51
264
  };
@@ -23,4 +23,4 @@ Tab.propTypes = {
23
23
  href: PropTypes.string,
24
24
  };
25
25
 
26
- export default Tab
26
+ export default Tab;
@@ -1,7 +1,14 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
 
4
- export const TabPanel = ({ children, value, id, index, className, disabled }) => {
4
+ export const TabPanel = ({
5
+ children,
6
+ value,
7
+ id,
8
+ index,
9
+ className,
10
+ disabled,
11
+ }) => {
5
12
  const classes = ['qpp-c-tabs__panel', className].filter(Boolean).join(' ');
6
13
  return (
7
14
  <div
@@ -1,61 +1,69 @@
1
- import React, { useEffect, useState } from 'react'
2
- import PropTypes from 'prop-types'
3
- import MuiTabs from '@material-ui/core/Tabs'
4
- import TabPanel from './TabPanel'
5
- import LinkTab from './Tab'
1
+ import React, { useEffect, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import MuiTabs from '@material-ui/core/Tabs';
4
+ import TabPanel from './TabPanel';
5
+ import LinkTab from './Tab';
6
6
 
7
- const isTabPanel = (child) => child?.type === TabPanel
7
+ const isTabPanel = (child) => child?.type === TabPanel;
8
8
 
9
9
  const getPanelChildren = (children) => {
10
- return React.Children.toArray(children).filter(isTabPanel)
10
+ return React.Children.toArray(children).filter(isTabPanel);
11
11
  };
12
12
 
13
- const Tabs = ({ children, onChange, selected, defaultSelectedId, ariaLabel }) => {
14
- const [value, setValue] = useState(defaultSelectedId || selected)
15
-
13
+ const Tabs = ({
14
+ children,
15
+ onChange,
16
+ selected,
17
+ defaultSelectedId,
18
+ ariaLabel,
19
+ }) => {
20
+ const [value, setValue] = useState(defaultSelectedId || selected);
21
+
16
22
  useEffect(() => {
17
23
  if (selected) {
18
- setValue(selected)
24
+ setValue(selected);
19
25
  }
20
- }, [selected])
26
+ }, [selected]);
21
27
 
22
- if (!children) return null
28
+ if (!children) return null;
23
29
 
24
30
  const handleChange = (event, newValue) => {
25
31
  if (onChange) {
26
- onChange(tabList[newValue]?.id, event)
32
+ onChange(tabList[newValue]?.id, event);
27
33
  }
28
34
  if (setValue) {
29
- setValue(tabList[newValue]?.id)
35
+ setValue(tabList[newValue]?.id);
30
36
  }
31
- }
37
+ };
32
38
 
33
39
  const renderChildren = () => {
34
40
  return React.Children.map(children, (child, index) => {
35
41
  if (isTabPanel(child)) {
36
- return React.cloneElement(child, { index, value: tabIndex })
42
+ return React.cloneElement(child, { index, value: tabIndex });
37
43
  }
38
- return child
39
- })
40
- }
44
+ return child;
45
+ });
46
+ };
47
+
48
+ const panels = getPanelChildren(children);
49
+
50
+ const tabList = panels.map((panel) => {
51
+ const { id, tab, tabHref, disabled } = panel?.props || {};
52
+ return { tab, tabHref, disabled, id };
53
+ });
41
54
 
42
- const panels = getPanelChildren(children)
43
-
44
- const tabList = panels.map(
45
- (panel) => {
46
- const { id, tab, tabHref, disabled } = panel?.props || {}
47
- return { tab, tabHref, disabled, id }
48
- },
49
- )
55
+ const foundIndex = tabList.findIndex(({ id }) => id === value);
50
56
 
51
- const foundIndex = tabList.findIndex(({ id }) => id === value)
52
-
53
- const tabIndex = foundIndex >= 0 ? foundIndex : 0
57
+ const tabIndex = foundIndex >= 0 ? foundIndex : 0;
54
58
 
55
59
  return (
56
60
  <>
57
61
  <div className="qpp-c-tabs qpp-c-tabs--mui">
58
- <MuiTabs value={tabIndex} onChange={handleChange} aria-label={ariaLabel}>
62
+ <MuiTabs
63
+ value={tabIndex}
64
+ onChange={handleChange}
65
+ aria-label={ariaLabel}
66
+ >
59
67
  {tabList.map(({ tab, tabHref, disabled, id }, index) => (
60
68
  <LinkTab
61
69
  index={index}
@@ -70,19 +78,16 @@ const Tabs = ({ children, onChange, selected, defaultSelectedId, ariaLabel }) =>
70
78
  </div>
71
79
  {renderChildren()}
72
80
  </>
73
- )
74
- }
81
+ );
82
+ };
75
83
 
76
84
  Tabs.propTypes = {
77
85
  ariaLabel: PropTypes.string,
78
86
  selected: PropTypes.string,
79
87
  defaultSelectedId: PropTypes.string,
80
88
  onChange: PropTypes.func,
81
- children: PropTypes.oneOfType([
82
- PropTypes.node,
83
- PropTypes.array,
84
- ])
85
- }
89
+ children: PropTypes.oneOfType([PropTypes.node, PropTypes.array]),
90
+ };
86
91
 
87
92
  Tabs.defaultProps = {
88
93
  ariaLabel: 'qpp tabs',
@@ -90,6 +95,6 @@ Tabs.defaultProps = {
90
95
  defaultSelectedId: null,
91
96
  onChange: null,
92
97
  children: null,
93
- }
98
+ };
94
99
 
95
- export default Tabs
100
+ export default Tabs;
@@ -105,7 +105,7 @@ TextInput.propTypes = {
105
105
  variant: PropTypes.oneOf(['success']),
106
106
  parentElement: PropTypes.string,
107
107
  label: PropTypes.string,
108
- size: PropTypes.oneOf(['big'])
108
+ size: PropTypes.oneOf(['big']),
109
109
  };
110
110
 
111
111
  TextInput.defaultProps = {
@@ -36,6 +36,7 @@ const useGetConfig = ({
36
36
  const storageItem = {
37
37
  expiration,
38
38
  };
39
+
39
40
  if (
40
41
  data?.content &&
41
42
  storageContent?.content &&