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

alkem-io / client-web / #10238

06 Feb 2025 08:20AM UTC coverage: 5.792%. First build
#10238

Pull #7599

travis-ci

Pull Request #7599: Pending Memberships optimization

192 of 11023 branches covered (1.74%)

Branch coverage included in aggregate %.

1 of 10 new or added lines in 4 files covered. (10.0%)

1545 of 18969 relevant lines covered (8.14%)

0.18 hits per line

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

0.0
/src/main/ui/platformNavigation/PlatformNavigationUserMenu.tsx
1
import {
2
  AssignmentIndOutlined,
3
  DashboardOutlined,
4
  ExitToAppOutlined,
5
  HdrStrongOutlined,
6
  LanguageOutlined,
7
  MeetingRoomOutlined,
8
} from '@mui/icons-material';
9
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
10
import LocalOfferOutlinedIcon from '@mui/icons-material/LocalOfferOutlined';
11
import SettingsIcon from '@mui/icons-material/SettingsOutlined';
12
import { Box, Divider, FormControlLabel, Menu, MenuItem, MenuList, Switch, Typography } from '@mui/material';
13
import FocusTrap from '@mui/material/Unstable_TrapFocus';
14
import { type PropsWithChildren, type ReactNode, Suspense, useState } from 'react';
15
import { useTranslation } from 'react-i18next';
16
import { useLocation } from 'react-router-dom';
17
import { AuthorizationPrivilege, RoleName } from '@/core/apollo/generated/graphql-schema';
18
import { AUTH_LOGOUT_PATH } from '@/core/auth/authentication/constants/authentication.constants';
19
import { lazyWithGlobalErrorHandler } from '@/core/lazyLoading/lazyWithGlobalErrorHandler';
20
import Avatar from '@/core/ui/avatar/Avatar';
21
import Gutters from '@/core/ui/grid/Gutters';
22
import { gutters } from '@/core/ui/grid/utils';
23
import useLanguageSelect from '@/core/ui/language/useLanguageSelect';
24
import GlobalMenuSurface from '@/core/ui/menu/GlobalMenuSurface';
25
import NavigatableMenuItem from '@/core/ui/menu/NavigatableMenuItem';
26
import { BlockTitle, Caption } from '@/core/ui/typography';
27
import {
28
  PendingMembershipsDialogType,
29
  usePendingMembershipsDialog,
30
} from '@/domain/community/pendingMembership/PendingMembershipsDialogContext';
31
import { usePendingInvitationsCount } from '@/domain/community/pendingMembership/usePendingInvitationsCount';
32
import { useCurrentUserContext } from '@/domain/community/userCurrent/useCurrentUserContext';
33
import { ROUTE_HOME, ROUTE_USER_ME } from '@/domain/platform/routes/constants';
34
import usePlatformOrigin from '@/domain/platform/routes/usePlatformOrigin';
35
import { useDesignVersionToggle } from '@/main/crdPages/useDesignVersionToggle';
36
import { buildLoginUrl, buildUserAccountUrl } from '@/main/routing/urlBuilders';
37
import { PLATFORM_NAVIGATION_MENU_Z_INDEX } from './constants';
38

39
const PendingMembershipsDialog = lazyWithGlobalErrorHandler(
40
  () => import('@/domain/community/pendingMembership/PendingMembershipsDialog')
41
);
42
const HelpDialog = lazyWithGlobalErrorHandler(() => import('@/core/help/dialog/HelpDialog'));
43

44
interface PlatformNavigationUserMenuProps {
×
45
  surface: boolean;
46
  footer?: ReactNode;
×
47
  onClose?: () => void;
48
}
×
49

50
export const UserMenuDivider = () => <Divider sx={{ width: '85%', marginX: 'auto' }} />;
×
51

52
const PlatformNavigationUserMenu = ({
×
53
  ref,
×
54
  surface,
NEW
55
  onClose,
×
NEW
56
  footer,
×
57
  children,
58
}: PropsWithChildren<PlatformNavigationUserMenuProps> & {
×
59
  ref?: React.Ref<HTMLDivElement>;
60
}) => {
×
61
  const { t } = useTranslation();
NEW
62

×
NEW
63
  const { pathname, search } = useLocation();
×
64

65
  const platformOrigin = usePlatformOrigin();
66
  const homeUrl = platformOrigin && `${platformOrigin}${ROUTE_HOME}`;
×
67

×
68
  const [isHelpDialogOpen, setIsHelpDialogOpen] = useState(false);
×
69
  const { setOpenDialog } = usePendingMembershipsDialog();
70

×
71
  const { userModel, platformPrivilegeWrapper: userWrapper, isAuthenticated, platformRoles } = useCurrentUserContext();
72

×
73
  const isAdmin = userWrapper?.hasPlatformPrivilege?.(AuthorizationPrivilege.PlatformAdmin);
74

×
75
  const { count: pendingInvitationsCount } = usePendingInvitationsCount();
76

×
77
  const designVersionToggle = useDesignVersionToggle();
78

×
79
  const {
80
    openSelect,
×
81
    isOpen: isLanguageMenuOpen,
82
    menuProps: languageMenuProps,
83
    languages,
84
  } = useLanguageSelect({
85
    anchorOrigin: {
×
86
      vertical: 'bottom',
87
      horizontal: 'left',
×
88
    },
89
    transformOrigin: {
90
      vertical: 'top',
×
91
      horizontal: 'left',
92
    },
93
    zIndex: PLATFORM_NAVIGATION_MENU_Z_INDEX + 1,
94
  });
95

96
  // the roles should follow the order
97
  const getRole = (): string | null => {
98
    for (const platformRole of platformRoles) {
99
      switch (platformRole) {
×
100
        case RoleName.GlobalAdmin:
101
          return t('common.roles.GLOBAL_ADMIN');
102
        case RoleName.GlobalSupport:
103
          return t('common.roles.GLOBAL_SUPPORT');
104
        case RoleName.GlobalLicenseManager:
105
          return t('common.roles.GLOBAL_LICENSE_MANAGER');
106
        case RoleName.PlatformBetaTester:
107
          return t('common.roles.PLATFORM_BETA_TESTER');
108
        case RoleName.PlatformVcCampaign:
×
109
          return t('common.roles.PLATFORM_VC_CAMPAIGN');
110
      }
111
    }
112
    return null;
113
  };
114
  const role = getRole();
115

116
  const Wrapper = surface ? GlobalMenuSurface : Box;
117

118
  return (
119
    <>
120
      <Wrapper ref={ref}>
121
        {userModel && (
122
          <Gutters disableGap={true} alignItems="center" sx={{ paddingBottom: 1 }}>
×
123
            <Avatar
124
              size="large"
125
              src={userModel.profile?.avatar?.uri}
126
              alt={
127
                userModel.profile?.displayName
×
128
                  ? t('common.avatar-of', { user: userModel.profile?.displayName })
129
                  : t('common.avatar')
130
              }
NEW
131
            />
×
NEW
132
            <BlockTitle lineHeight={gutters(2)}>{userModel.profile?.displayName}</BlockTitle>
×
133
            {role && (
134
              <Caption color="neutralMedium.main" textTransform="uppercase">
135
                {role}
136
              </Caption>
137
            )}
138
          </Gutters>
139
        )}
140
        <FocusTrap open={true}>
×
141
          <MenuList autoFocus={true} disablePadding={true} sx={{ paddingY: 1, outline: 'none' }}>
142
            {!isAuthenticated && (
143
              <NavigatableMenuItem
144
                iconComponent={MeetingRoomOutlined}
145
                route={buildLoginUrl(pathname, search)}
146
                onClick={onClose}
147
              >
148
                <Typography variant="inherit" fontWeight="bold">
149
                  {t('topBar.sign-in')}
150
                </Typography>
151
              </NavigatableMenuItem>
152
            )}
153
            <NavigatableMenuItem iconComponent={DashboardOutlined} route={homeUrl} onClick={onClose}>
154
              {t('pages.home.title')}
155
            </NavigatableMenuItem>
156
            {userModel && (
157
              <NavigatableMenuItem iconComponent={AssignmentIndOutlined} route={ROUTE_USER_ME} onClick={onClose}>
×
158
                {t('pages.user-profile.title')}
159
              </NavigatableMenuItem>
×
160
            )}
161
            {userModel && (
162
              <NavigatableMenuItem
163
                iconComponent={LocalOfferOutlinedIcon}
164
                route={buildUserAccountUrl(ROUTE_USER_ME)}
165
                onClick={onClose}
166
              >
167
                {t('pages.home.mainNavigation.myAccount')}
168
              </NavigatableMenuItem>
×
169
            )}
×
170
            {userModel && (
171
              <NavigatableMenuItem
172
                iconComponent={HdrStrongOutlined}
173
                onClick={() => {
174
                  setOpenDialog({ type: PendingMembershipsDialogType.PendingMembershipsList });
×
175
                  onClose?.();
176
                }}
177
              >
178
                {t('community.pendingMembership.pendingMembershipsWithCount', { count: pendingInvitationsCount })}
179
              </NavigatableMenuItem>
180
            )}
181
            <UserMenuDivider />
182
            {children}
183
            {isAdmin && (
184
              <NavigatableMenuItem iconComponent={SettingsIcon} route="/admin" onClick={onClose}>
185
                {t('common.administration')}
186
              </NavigatableMenuItem>
×
187
            )}
×
188
            <NavigatableMenuItem
189
              id="language-button"
190
              iconComponent={LanguageOutlined}
191
              onClick={event => openSelect(event.currentTarget as HTMLElement)}
192
              aria-controls={isLanguageMenuOpen ? 'language-menu' : undefined}
193
              aria-haspopup="true"
194
              aria-expanded={isLanguageMenuOpen ? 'true' : undefined}
195
            >
196
              {t('buttons.changeLanguage')}
197
            </NavigatableMenuItem>
198
            <NavigatableMenuItem
199
              iconComponent={HelpOutlineIcon}
200
              onClick={() => {
201
                setIsHelpDialogOpen(true);
202
                onClose?.();
203
              }}
204
            >
205
              {t('buttons.getHelp')}
206
            </NavigatableMenuItem>
207
            {designVersionToggle.isVisible && (
208
              <>
209
                <UserMenuDivider />
210
                <Box sx={{ paddingX: gutters(1), paddingY: gutters(0.5) }}>
211
                  <FormControlLabel
212
                    control={
213
                      <Switch
214
                        size="small"
215
                        checked={designVersionToggle.enabled}
216
                        onChange={(_event, checked) => designVersionToggle.onChange(checked)}
217
                        disabled={designVersionToggle.isPending}
218
                      />
219
                    }
220
                    label={t('topBar.designVersion.label')}
221
                    labelPlacement="start"
222
                    sx={{ marginLeft: 0, marginRight: 0, width: '100%', justifyContent: 'space-between' }}
223
                  />
224
                </Box>
225
                <UserMenuDivider />
226
              </>
227
            )}
228
            {isAuthenticated && (
229
              <NavigatableMenuItem iconComponent={MeetingRoomOutlined} route={AUTH_LOGOUT_PATH} onClick={onClose}>
230
                {t('buttons.sign-out')}
231
              </NavigatableMenuItem>
232
            )}
233
            <NavigatableMenuItem tabOnly={true} iconComponent={ExitToAppOutlined} onClick={onClose}>
234
              {t('components.navigation.exitMenu')}
235
            </NavigatableMenuItem>
236
            {footer}
237
          </MenuList>
238
        </FocusTrap>
239
      </Wrapper>
240
      {userModel && (
241
        <Suspense fallback={null}>
242
          <PendingMembershipsDialog />
243
        </Suspense>
244
      )}
245
      <Suspense fallback={null}>
246
        <HelpDialog open={isHelpDialogOpen} onClose={() => setIsHelpDialogOpen(false)} />
247
      </Suspense>
248
      <Menu {...languageMenuProps}>
249
        {languages.map(lng => (
250
          <MenuItem key={lng.key} selected={lng.selected} onClick={lng.onClick}>
251
            <Caption lang={lng.lang}>{lng.label}</Caption>
252
          </MenuItem>
253
        ))}
254
      </Menu>
255
    </>
256
  );
257
};
258

259
export default PlatformNavigationUserMenu;
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