Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a user menu which includes the option to log in and the theme control #870

Merged
merged 9 commits into from
Nov 30, 2023
114 changes: 114 additions & 0 deletions src/Components/LoginMenu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, {useState} from 'react'
import {useAuth0} from '@auth0/auth0-react'
import Avatar from '@mui/material/Avatar'
import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem'
import Typography from '@mui/material/Typography'
import useTheme from '@mui/styles/useTheme'
import {TooltipIconButton} from './Buttons'
import AccountBoxOutlinedIcon from '@mui/icons-material/AccountBoxOutlined'
import GitHubIcon from '@mui/icons-material/GitHub'
import NightlightOutlinedIcon from '@mui/icons-material/NightlightOutlined'
import WbSunnyOutlinedIcon from '@mui/icons-material/WbSunnyOutlined'


/**
* LoginMenu contains the option to log in/log out and to theme control
*
* @return {object} LoginMenu react component
*/
export default function LoginMenu() {
const [anchorEl, setAnchorEl] = useState(null)
const open = Boolean(anchorEl)
const theme = useTheme()
const {isAuthenticated, user, logout} = useAuth0()
const {loginWithRedirect} = useAuth0()

const handleClick = (event) => {
setAnchorEl(event.currentTarget)
}

const handleClose = () => {
setAnchorEl(null)
}

const handleLogin = async () => {
await loginWithRedirect({
appState: {
returnTo: window.location.pathname,
},
})
}

const handleLogout = () => {
logout({returnTo: process.env.OAUTH2_REDIRECT_URI || window.location.origin})
}


return (
<>
<TooltipIconButton
title={'Users menu'}
placement='left'
variant='rounded'
icon={isAuthenticated ?
<Avatar
alt={user.name}
src={user.picture}
sx={{width: 22, height: 22}}
/> :
<AccountBoxOutlinedIcon className='icon-share' color='secondary'/>}
onClick={handleClick}
/>
<Menu
elevation={1}
id='basic-menu'
anchorEl={anchorEl}
open={open}
onClose={handleClose}
anchorOrigin={{vertical: 'top', horizontal: 'center'}}
transformOrigin={{vertical: 'top', horizontal: 'center'}}
PaperProps={{
style: {
left: '300px',
transform: 'translateX(-60px) translateY(0px)',
},
sx: {
'color': theme.palette.primary.contrastText,
'& .Mui-selected': {
color: theme.palette.secondary.main,
fontWeight: 800,
},
},
}}
>
<MenuItem onClick={
isAuthenticated ? () => handleLogout() :
() => handleLogin()}
>
<GitHubIcon/>
{isAuthenticated ?
<Typography sx={{marginLeft: '10px'}} variant='overline'>Log out</Typography> :
<Typography sx={{marginLeft: '10px'}} variant='overline'>Log in with Github</Typography>
}

</MenuItem>
<MenuItem onClick={() => {
handleClose()
theme.toggleColorMode()
}}
>
{theme.palette.mode === 'light' ?
<WbSunnyOutlinedIcon className='icon-share' color='secondary'/> :
<NightlightOutlinedIcon className='icon-share'/> }
<Typography
sx={{marginLeft: '10px'}}
variant='overline'
>
{`${theme.palette.mode === 'light' ? 'Day' : 'Night'} theme`}
</Typography>
</MenuItem>
</Menu>
</>
)
}
58 changes: 58 additions & 0 deletions src/Components/LoginMenu.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react'
import {fireEvent, render} from '@testing-library/react'
import {mockedUseAuth0, mockedUserLoggedIn, mockedUserLoggedOut} from '../__mocks__/authentication'
import LoginMenu from './LoginMenu'
import ShareMock from '../ShareMock'


describe('LoginMenu', () => {
it('renders the login button when not logged in', async () => {
mockedUseAuth0.mockReturnValue(mockedUserLoggedOut)
const {debug, findByTitle, findByText} = render(<ShareMock><LoginMenu/></ShareMock>)
const usersMenu = await findByTitle('Users menu')
fireEvent.click(usersMenu)
debug()
const LoginWithGithub = await findByText('Log in with Github')
expect(LoginWithGithub).toBeInTheDocument()
})

it('renders the user avatar when logged in', async () => {
mockedUseAuth0.mockReturnValue(mockedUserLoggedIn)
const {debug, findByTitle, findByText} = render(<ShareMock><LoginMenu/></ShareMock>)
const usersMenu = await findByTitle('Users menu')
fireEvent.click(usersMenu)
debug()
const LoginWithGithub = await findByText('Log out')
expect(LoginWithGithub).toBeInTheDocument()
})

it('renders the theme selection', async () => {
mockedUseAuth0.mockReturnValue(mockedUserLoggedIn)
const {debug, findByTitle, findByText} = render(<ShareMock><LoginMenu/></ShareMock>)
const usersMenu = await findByTitle('Users menu')
fireEvent.click(usersMenu)
debug()
const dayThemeButton = await findByText('Day theme')
expect(dayThemeButton).toBeInTheDocument()
})

it('renders the night theme when selected', async () => {
mockedUseAuth0.mockReturnValue(mockedUserLoggedIn)
const {debug, findByTitle, findByText} = render(<ShareMock><LoginMenu/></ShareMock>)
const usersMenu = await findByTitle('Users menu')
fireEvent.click(usersMenu)
const dayThemeButton = await findByText('Day theme')
fireEvent.click(dayThemeButton)
debug()
const nighThemeButton = await findByText('Night theme')
expect(nighThemeButton).toBeInTheDocument()
})

it('renders users avatar when logged in', async () => {
mockedUseAuth0.mockReturnValue(mockedUserLoggedIn)
const {debug, findByAltText} = render(<ShareMock><LoginMenu/></ShareMock>)
debug()
const avatarImage = await findByAltText('Unit Testing')
expect(avatarImage).toBeInTheDocument()
})
})
22 changes: 3 additions & 19 deletions src/Components/OperationsGroup.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import React from 'react'
import Box from '@mui/material/Box'
import ButtonGroup from '@mui/material/ButtonGroup'
import useTheme from '@mui/styles/useTheme'
import useStore from '../store/useStore'
import {useIsMobile} from './Hooks'
import CameraControl from './CameraControl'
import LoginMenu from './LoginMenu'
import ShareControl from './ShareControl'
import ImagineControl from './ImagineControl'
import {TooltipIconButton} from './Buttons'
import AuthNav from './AuthNav'
import AppStoreIcon from '../assets/icons/AppStore.svg'
import {useExistInFeature} from '../hooks/useExistInFeature'
import ChatOutlinedIcon from '@mui/icons-material/ChatOutlined'
import NightlightOutlinedIcon from '@mui/icons-material/NightlightOutlined'
import WbSunnyOutlinedIcon from '@mui/icons-material/WbSunnyOutlined'
import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted'


Expand Down Expand Up @@ -62,15 +59,14 @@ export default function OperationsGroup({deselectItems}) {
}
}

const theme = useTheme()
return (
<ButtonGroup
orientation='vertical'
variant='contained'
sx={{'margin': '1em', '& > *:not(:last-child)': {mb: .6}}} // Add space between buttons
>
{isLoginVisible &&
<AuthNav/>
<LoginMenu/>
}
{isCollaborationGroupVisible &&
<Box sx={{marginTop: '8px'}}>
Expand Down Expand Up @@ -105,25 +101,13 @@ export default function OperationsGroup({deselectItems}) {
/>
}

{isSettingsVisible &&
<>
{isAppStoreEnabled &&
{isSettingsVisible && isAppStoreEnabled &&
<TooltipIconButton
title='Open App Store'
icon={<AppStoreIcon/>}
selected={isAppStoreOpen}
onClick={() => toggleAppStoreDrawer()}
/>
}
<TooltipIconButton
title={`${theme.palette.mode === 'light' ? 'Day' : 'Night'} theme`}
onClick={() => theme.toggleColorMode()}
icon={
theme.palette.mode === 'light' ?
<WbSunnyOutlinedIcon className='icon-share' color='secondary'/> :
<NightlightOutlinedIcon className='icon-share'/> }
/>
</>
}
{isCollaborationGroupVisible &&
<Box sx={{marginTop: '8px'}}>
Expand Down