Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions packages/primitives/src/CustomNavBar/UserProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import Divider from '@mui/material/Divider';
import Button from '@mui/material/Button';
import get from 'lodash/get';
import LogoutLogo from '@clients/ui-atoms/LogoutLogo';
import { useFlyteApi } from '@clients/flyte-api/ApiProvider';
import { Flyte } from '../types/flyteTypes';
import { SignOutPanel } from '../SessionManagent/SignOutPanel';

const StyledAvatar = styled(Avatar)(({ theme }) => ({
background: 'transparent',
Expand Down Expand Up @@ -67,7 +67,7 @@ export interface UserProfileProps {
/** Displays User name when user is logged in - would be used as User settings entry in future */
export const UserProfile = ({ profile }: UserProfileProps) => {
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
const apiContext = useFlyteApi();
const [signOutOpen, setSignOutOpen] = useState(false);

const handlePopoverOpen: MouseEventHandler = (event) => {
setAnchorEl(event.currentTarget);
Expand Down Expand Up @@ -139,7 +139,10 @@ export const UserProfile = ({ profile }: UserProfileProps) => {
variant="text"
color="inherit"
className="actionButton"
href={apiContext.getLogoutUrl()}
onClick={() => {
handlePopoverClose();
setSignOutOpen(true);
}}
data-cy="logout-button"
startIcon={<LogoutLogo className="logoutIcon" />}
>
Expand All @@ -149,6 +152,9 @@ export const UserProfile = ({ profile }: UserProfileProps) => {
</Box>
</Popover>
</AvatarWrapper>

{/* Outside the Popover so closing the menu doesn't unmount the dialog. */}
<SignOutPanel open={signOutOpen} onCancel={() => setSignOutOpen(false)} />
</>
);
};
42 changes: 3 additions & 39 deletions packages/primitives/src/SessionManagent/LoginPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,11 @@
import React, { useEffect } from 'react';
import { useFlyteApi } from '@clients/flyte-api/ApiProvider';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogTitle from '@mui/material/DialogTitle';
import Box from '@mui/material/Box';
import styled from '@mui/system/styled';
import { FlyteLogo } from '../assets/icons/FlyteLogo';

const StyledDialog = styled(Dialog)(() => ({
'& .MuiDialog-paper': {
width: '100%',
maxWidth: '448px',
padding: '40px 64px',
gap: '24px',
},

h2: {
fontSize: '22px',
lineHeight: '28px',
},
'& .MuiDialogTitle-root': {
padding: 0,
},

'& .MuiDialogActions-root': {
padding: 0,

button: {
width: '100%',
},
},

'& .displayColumn': {
display: 'flex',
flexDirection: 'column',
gap: '8px',
},
'& .centerAlign': {
alignItems: 'center',
justifyContent: 'center',
},
}));
import { SessionDialog } from './SessionDialog';

/** A shared panel rendered along the right side of the UI. Content can be
* rendered into it using `LoginPanelContent`
Expand All @@ -56,7 +20,7 @@ export const LoginPanel: React.FC<unknown> = () => {
}, [expired]);

return (
<StyledDialog
<SessionDialog
open={isLoginModalOpen}
onClose={() => {
setIsLoginModalOpen(false);
Expand Down Expand Up @@ -86,6 +50,6 @@ export const LoginPanel: React.FC<unknown> = () => {
</Button>
</Box>
</DialogActions>
</StyledDialog>
</SessionDialog>
);
};
41 changes: 41 additions & 0 deletions packages/primitives/src/SessionManagent/SessionDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Dialog from '@mui/material/Dialog';
import styled from '@mui/system/styled';

/** Shared shell for the session dialogs (login expired, sign out): centered Flyte
* logo, a title, and full-width stacked actions. */
export const SessionDialog = styled(Dialog)(() => ({
'& .MuiDialog-paper': {
width: '100%',
maxWidth: '448px',
padding: '40px 64px',
gap: '24px',
},

h2: {
fontSize: '22px',
lineHeight: '28px',
},
'& .MuiDialogTitle-root': {
padding: 0,
},

'& .MuiDialogActions-root': {
padding: 0,

button: {
width: '100%',
},
},

'& .displayColumn': {
display: 'flex',
flexDirection: 'column',
gap: '8px',
},
'& .centerAlign': {
alignItems: 'center',
justifyContent: 'center',
},
}));

export default SessionDialog;
44 changes: 44 additions & 0 deletions packages/primitives/src/SessionManagent/SignOutPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';
import { useFlyteApi } from '@clients/flyte-api/ApiProvider';
import Button from '@mui/material/Button';
import DialogActions from '@mui/material/DialogActions';
import DialogTitle from '@mui/material/DialogTitle';
import Box from '@mui/material/Box';
import { FlyteLogo } from '../assets/icons/FlyteLogo';
import { SessionDialog } from './SessionDialog';
import t from './strings';

export interface SignOutPanelProps {
open: boolean;
onCancel: () => void;
}

/** Confirmation shown before signing out. Signing out ends the Admin session, so
* it is worth a deliberate click rather than firing on the menu item itself. */
export const SignOutPanel: React.FC<SignOutPanelProps> = ({ open, onCancel }) => {
const { getLogoutUrl } = useFlyteApi();

return (
<SessionDialog
open={open}
onClose={onCancel}
aria-labelledby="signout-dialog-title"
data-cy="signout-dialog"
>
<DialogTitle id="signout-dialog-title" className="centerAlign displayColumn">
<FlyteLogo size={50} background="light" />
{t('signOutTitle')}
</DialogTitle>
<DialogActions className="centerAlign">
<Box className="centerAlign displayColumn">
<Button variant="contained" href={getLogoutUrl()} autoFocus data-cy="signout-confirm">
{t('signOutConfirm')}
</Button>
<Button variant="outlined" onClick={onCancel} data-cy="signout-cancel">
{t('signOutCancel')}
</Button>
</Box>
</DialogActions>
</SessionDialog>
);
};
2 changes: 2 additions & 0 deletions packages/primitives/src/SessionManagent/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './LoginPanel';
export * from './SessionDialog';
export * from './SignOutPanel';
42 changes: 42 additions & 0 deletions packages/primitives/src/SessionManagent/signOutPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { muiTheme } from '@clients/theme/Theme/muiTheme';
import { ThemeProvider } from '@mui/material/styles';
import { SignOutPanel } from './SignOutPanel';

jest.mock('@clients/flyte-api/ApiProvider', () => ({
useFlyteApi: () => ({ getLogoutUrl: () => '/logout?redirect_url=/select-project' }),
}));

const Wrapper = (props: { children: React.ReactNode }) => (
<ThemeProvider theme={muiTheme}>{props.children}</ThemeProvider>
);

describe('SignOutPanel', () => {
it('renders nothing while closed', () => {
const { queryByText } = render(
<Wrapper>
<SignOutPanel open={false} onCancel={jest.fn()} />
</Wrapper>,
);
expect(queryByText('Sign out of Flyte?')).not.toBeInTheDocument();
});

it('confirms to the logout url and cancels without leaving', () => {
const onCancel = jest.fn();
const { getByText, getByRole } = render(
<Wrapper>
<SignOutPanel open onCancel={onCancel} />
</Wrapper>,
);

expect(getByText('Sign out of Flyte?')).toBeInTheDocument();
expect(getByRole('link', { name: 'Sign Out' })).toHaveAttribute(
'href',
'/logout?redirect_url=/select-project',
);

fireEvent.click(getByRole('button', { name: 'Cancel' }));
expect(onCancel).toHaveBeenCalled();
});
});
9 changes: 9 additions & 0 deletions packages/primitives/src/SessionManagent/strings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createLocalizedString } from '@clients/locale/locale';

const str = {
signOutTitle: 'Sign out of Flyte?',
signOutConfirm: 'Sign Out',
signOutCancel: 'Cancel',
};

export default createLocalizedString(str);
Loading