Skip to content

Commit c5e6a2f

Browse files
Merge branch 'develop' into SWAP-4929-user-officer-experiment-table-filter
2 parents db25e46 + c10c2f8 commit c5e6a2f

File tree

7 files changed

+160
-97
lines changed

7 files changed

+160
-97
lines changed

apps/e2e/cypress/e2e/invites.cy.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ context('Invites tests', () => {
101101
.should('be.enabled')
102102
.click();
103103

104-
cy.get('.MuiChip-label').should('not.exist');
105104
cy.get('[data-cy="co-proposers"]').contains(lastName);
105+
cy.get('[data-cy="invites-chips"]').should('not.exist');
106106
});
107107

108108
it('Should not be able to invite email already invited on proposal', function () {
@@ -370,7 +370,9 @@ context('Invites tests', () => {
370370
`${randomSearch}{enter}`
371371
);
372372

373-
cy.get('.MuiChip-label').should('not.exist');
373+
cy.get('[data-cy="participant-selector"] .MuiChip-label').should(
374+
'not.exist'
375+
);
374376
});
375377
});
376378

@@ -410,7 +412,9 @@ context('Invites tests', () => {
410412
.should('be.enabled')
411413
.click();
412414

413-
cy.get('.MuiChip-label').should('not.exist');
415+
cy.get('[data-cy="participant-selector"] .MuiChip-label').should(
416+
'not.exist'
417+
);
414418
cy.get('[data-cy="co-proposers"]').contains(lastName);
415419
});
416420

@@ -435,7 +439,9 @@ context('Invites tests', () => {
435439
cy.get('[data-cy="invite-user-autocomplete"]').type('{enter}');
436440
cy.get('[role=presentation]').contains(`No results found for "${email}"`);
437441
cy.get('[data-cy="invite-user-submit-button"]').should('be.disabled');
438-
cy.get('.MuiChip-label').should('not.exist');
442+
cy.get('[data-cy="participant-selector"] .MuiChip-label').should(
443+
'not.exist'
444+
);
439445
});
440446

441447
it('Should not be able to add duplicate co-proposer in modal', () => {
@@ -465,7 +471,7 @@ context('Invites tests', () => {
465471
.should('be.enabled')
466472
.click();
467473

468-
cy.get('[data-cy="add-participant-button"]').click();
474+
cy.get('[data-cy="add-participant-button"]').click({ force: true });
469475

470476
cy.get('[data-cy="invite-user-autocomplete"]').type(email);
471477
cy.get('[role=presentation]')

apps/e2e/cypress/e2e/personalInformation.cy.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ context('Personal information tests', () => {
5555
cy.login('user1');
5656
cy.visit('/');
5757

58-
cy.get('[data-cy="active-user-profile"]').click();
58+
cy.get('[data-cy="profile-page-btn"]').click();
59+
60+
cy.get('[data-cy="manage-account-button"]').click();
5961

6062
cy.get("[name='firstname']").clear().type(newFirstName);
6163

apps/e2e/cypress/e2e/visits.cy.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,11 @@ context('visits tests', () => {
273273
cy.finishedLoading();
274274
cy.get('[name=email]').type('[email protected]{enter}');
275275
cy.finishedLoading();
276-
cy.contains('Beckley').parent().find('[type=checkbox]').click();
276+
cy.get('[data-cy=co-proposers]')
277+
.contains('Beckley')
278+
.parent()
279+
.find('[type=checkbox]')
280+
.click();
277281
cy.get('[data-cy=assign-selected-users]').click();
278282

279283
// specify team lead

apps/frontend/src/components/AppToolbar/AccountActionButton.tsx

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { ExitToApp } from '@mui/icons-material';
1+
import { ExitToApp, ManageAccounts } from '@mui/icons-material';
22
import AccountCircle from '@mui/icons-material/AccountCircle';
33
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
44
import SupervisedUserCircleIcon from '@mui/icons-material/SupervisedUserCircle';
55
import SwitchAccountOutlinedIcon from '@mui/icons-material/SwitchAccountOutlined';
6+
import { Chip, Divider } from '@mui/material';
67
import Badge from '@mui/material/Badge';
78
import Box from '@mui/material/Box';
89
import Button from '@mui/material/Button';
@@ -12,26 +13,37 @@ import IconButton from '@mui/material/IconButton';
1213
import Menu from '@mui/material/Menu';
1314
import MenuItem from '@mui/material/MenuItem';
1415
import Tooltip from '@mui/material/Tooltip';
15-
import React, { useContext, useEffect, useState } from 'react';
16-
import { useSearchParams } from 'react-router-dom';
16+
import React, { useContext, useEffect, useMemo, useState } from 'react';
17+
import { useNavigate, useSearchParams } from 'react-router-dom';
1718

1819
import ImpersonateButton from 'components/common/ImpersonateButton';
1920
import StyledDialog from 'components/common/StyledDialog';
2021
import UOLoader from 'components/common/UOLoader';
22+
import { SettingsContext } from 'context/SettingsContextProvider';
2123
import { UserContext } from 'context/UserContextProvider';
24+
import { SettingsId } from 'generated/sdk';
2225
import { getUniqueArrayBy } from 'utils/helperFunctions';
2326

27+
import ProfileInfo from './ProfileInfo';
2428
import RoleSelection from './RoleSelection';
2529

2630
const AccountActionButton = () => {
31+
const navigate = useNavigate();
2732
const [isLoggingOut, setIsLoggingOut] = useState(false);
2833
const [show, setShow] = useState(false);
29-
const { roles, handleLogout, impersonatingUserId } = useContext(UserContext);
34+
const { user } = useContext(UserContext);
35+
const settingsContext = useContext(SettingsContext);
36+
const { roles, currentRole, handleLogout, impersonatingUserId } =
37+
useContext(UserContext);
3038
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
31-
3239
const [searchParams, setSearchParams] = useSearchParams();
33-
3440
const hasMultipleRoles = getUniqueArrayBy(roles, 'id').length > 1;
41+
const humanReadableActiveRole = useMemo(
42+
() =>
43+
roles.find(({ shortCode }) => shortCode.toUpperCase() === currentRole)
44+
?.title ?? 'Unknown',
45+
[roles, currentRole]
46+
);
3547

3648
useEffect(() => {
3749
if (searchParams.get('selectRoles')) {
@@ -46,6 +58,10 @@ const AccountActionButton = () => {
4658

4759
const isUserImpersonated = typeof impersonatingUserId === 'number';
4860

61+
const externalProfileLink = settingsContext.settingsMap.get(
62+
SettingsId.PROFILE_PAGE_LINK
63+
)?.settingsValue;
64+
4965
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
5066
setAnchorEl(event.currentTarget);
5167
};
@@ -63,6 +79,15 @@ const AccountActionButton = () => {
6379
});
6480
};
6581

82+
const handleManageAccountClick = () => {
83+
handleClose();
84+
if (externalProfileLink) {
85+
window.open(externalProfileLink, '_blank', 'noopener,noreferrer');
86+
} else {
87+
navigate(`/ProfilePage/${user.id}`);
88+
}
89+
};
90+
6691
return (
6792
<>
6893
<StyledDialog
@@ -121,6 +146,18 @@ const AccountActionButton = () => {
121146
open={Boolean(anchorEl)}
122147
onClose={handleClose}
123148
>
149+
<ProfileInfo />
150+
<Divider style={{ marginBottom: '7px' }} />
151+
<MenuItem
152+
onClick={handleManageAccountClick}
153+
disabled={isLoggingOut}
154+
data-cy="manage-account-button"
155+
>
156+
<Box paddingRight={1} paddingTop={1}>
157+
<ManageAccounts />
158+
</Box>
159+
Manage account
160+
</MenuItem>
124161
{hasMultipleRoles && (
125162
<MenuItem
126163
onClick={() => {
@@ -132,7 +169,26 @@ const AccountActionButton = () => {
132169
<Box paddingRight={1} paddingTop={1}>
133170
<SupervisedUserCircleIcon />
134171
</Box>
135-
Roles
172+
<Box
173+
sx={{
174+
display: 'flex',
175+
justifyContent: 'space-between',
176+
alignItems: 'center',
177+
flex: 1,
178+
}}
179+
>
180+
<Box>Roles</Box>
181+
<Chip
182+
label={humanReadableActiveRole}
183+
color="primary"
184+
size="small"
185+
sx={{
186+
fontSize: '10px',
187+
height: 'inherit',
188+
lineHeight: 2,
189+
}}
190+
/>
191+
</Box>
136192
</MenuItem>
137193
)}
138194
{isUserImpersonated && (

apps/frontend/src/components/AppToolbar/AppToolbar.tsx

Lines changed: 29 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
/* eslint-disable @typescript-eslint/no-var-requires */
22
import MenuIcon from '@mui/icons-material/Menu';
3+
import { Box } from '@mui/material';
34
import AppBar from '@mui/material/AppBar';
4-
import Box from '@mui/material/Box';
55
import IconButton from '@mui/material/IconButton';
6-
import MuiLink from '@mui/material/Link';
76
import { useTheme } from '@mui/material/styles';
87
import Toolbar from '@mui/material/Toolbar';
98
import Typography from '@mui/material/Typography';
109
import useMediaQuery from '@mui/material/useMediaQuery';
1110
import PropTypes from 'prop-types';
12-
import React, { useContext, useMemo, useEffect, useState } from 'react';
11+
import React, { useContext, useEffect, useState } from 'react';
1312
import { Link, useLocation } from 'react-router-dom';
1413

1514
import { SettingsContext } from 'context/SettingsContextProvider';
16-
import { UserContext } from 'context/UserContextProvider';
1715
import { SettingsId } from 'generated/sdk';
1816

1917
import AccountActionButton from './AccountActionButton';
@@ -44,18 +42,6 @@ const AppToolbar = ({ open, handleDrawerOpen, header }: AppToolbarProps) => {
4442
setLogo('/images/' + logoFilename);
4543
}, [logoFilename]);
4644

47-
const { user, roles, currentRole } = useContext(UserContext);
48-
const settingsContext = useContext(SettingsContext);
49-
const humanReadableActiveRole = useMemo(
50-
() =>
51-
roles.find(({ shortCode }) => shortCode.toUpperCase() === currentRole)
52-
?.title ?? 'Unknown',
53-
[roles, currentRole]
54-
);
55-
const externalProfileLink = settingsContext.settingsMap.get(
56-
SettingsId.PROFILE_PAGE_LINK
57-
)?.settingsValue;
58-
5945
return (
6046
<AppBar
6147
position="fixed"
@@ -80,75 +66,36 @@ const AppToolbar = ({ open, handleDrawerOpen, header }: AppToolbarProps) => {
8066
}}
8167
>
8268
<Toolbar>
83-
<IconButton
84-
edge="start"
85-
color="inherit"
86-
aria-label="Open drawer"
87-
onClick={handleDrawerOpen}
88-
sx={{
89-
marginRight: 15,
90-
...(open && { display: isTabletOrMobile ? 'inline-flex' : 'none' }),
91-
}}
92-
data-cy="open-drawer"
93-
>
94-
<MenuIcon />
95-
</IconButton>
96-
{(!isTabletOrMobile || !isPortraitMode) && logo && (
97-
<Link className={'header-logo-container'} to="/">
98-
<img src={logo} alt="Institution logo" className={'header-logo'} />
99-
</Link>
100-
)}
101-
{(!isTabletOrMobile || !isPortraitMode) && (
102-
<Typography
103-
component="h1"
104-
variant="h6"
69+
<Box sx={{ display: 'flex', flexGrow: 1, alignItems: 'center' }}>
70+
<IconButton
71+
edge="start"
10572
color="inherit"
106-
noWrap
107-
sx={{ flexGrow: 1 }}
73+
aria-label="Open drawer"
74+
onClick={handleDrawerOpen}
75+
sx={{
76+
...(open && {
77+
marginRight: '220px',
78+
display: isTabletOrMobile ? 'inline-flex' : 'none',
79+
}),
80+
}}
81+
data-cy="open-drawer"
10882
>
109-
{location.pathname === '/' ? 'Dashboard' : header}
110-
</Typography>
111-
)}
112-
<Box sx={{ marginLeft: 'auto', margin: theme.spacing(0, 0.5) }}>
113-
Logged in as{' '}
114-
{externalProfileLink ? (
115-
<MuiLink
116-
href={externalProfileLink}
117-
target="_blank"
118-
rel="noreferrer"
119-
sx={{
120-
color: theme.palette.common.white,
121-
textDecoration: 'none',
122-
borderBottom: '1px dashed',
123-
borderBottomColor: theme.palette.common.white,
124-
padding: '3px',
125-
'&:hover': {
126-
textDecoration: 'none',
127-
},
128-
}}
129-
>
130-
{user.email}
131-
</MuiLink>
132-
) : (
133-
<MuiLink
134-
data-cy="active-user-profile"
135-
component={Link}
136-
to={`/ProfilePage/${user.id}`}
137-
sx={{
138-
color: theme.palette.common.white,
139-
textDecoration: 'none',
140-
borderBottom: '1px dashed',
141-
borderBottomColor: theme.palette.common.white,
142-
padding: '3px',
143-
'&:hover': {
144-
textDecoration: 'none',
145-
},
146-
}}
147-
>
148-
{user.email}
149-
</MuiLink>
83+
<MenuIcon />
84+
</IconButton>
85+
{(!isTabletOrMobile || !isPortraitMode) && logo && (
86+
<Link className={'header-logo-container'} to="/">
87+
<img
88+
src={logo}
89+
alt="Institution logo"
90+
className={'header-logo'}
91+
/>
92+
</Link>
93+
)}
94+
{(!isTabletOrMobile || !isPortraitMode) && (
95+
<Typography component="h1" variant="h6" color="inherit" noWrap>
96+
{location.pathname === '/' ? 'Dashboard' : header}
97+
</Typography>
15098
)}
151-
{roles.length > 1 && ` (${humanReadableActiveRole})`}
15299
</Box>
153100
<AccountActionButton />
154101
</Toolbar>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// react functional component
2+
3+
import Avatar from '@mui/material/Avatar';
4+
import Box from '@mui/material/Box';
5+
import Typography from '@mui/material/Typography';
6+
import React, { useContext } from 'react';
7+
8+
import { UserContext } from 'context/UserContextProvider';
9+
import { getFullUserName } from 'utils/user';
10+
11+
const ProfileInfo = () => {
12+
const { user } = useContext(UserContext);
13+
14+
return (
15+
<Box
16+
sx={{
17+
display: 'flex',
18+
alignItems: 'center',
19+
p: '15px 30px 15px 15px',
20+
}}
21+
>
22+
<Avatar sx={{ width: 50, height: 50, mr: '15px' }} />
23+
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
24+
<Typography
25+
noWrap
26+
sx={{
27+
color: 'black',
28+
fontSize: '18px',
29+
lineHeight: 1.3,
30+
}}
31+
>
32+
{getFullUserName(user)}
33+
</Typography>
34+
<Typography
35+
sx={{
36+
color: 'dimgray',
37+
fontSize: '14px',
38+
lineHeight: 1.3,
39+
}}
40+
>
41+
{user?.email.toLocaleLowerCase()}
42+
</Typography>
43+
</Box>
44+
</Box>
45+
);
46+
};
47+
48+
export default ProfileInfo;

0 commit comments

Comments
 (0)