Skip to content

Commit 5d33df0

Browse files
zalexa19Copilot
authored andcommitted
added a dark mode toggle (HackYourFuture#302)
* Refactored the navbar * Hiding vanigation links for unauthenticated users * added a dark mode toggle * Update client/src/layout/NavBar/navigation/NavigationLinksMobile.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fixed pr comments --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent f70f68b commit 5d33df0

File tree

8 files changed

+315
-208
lines changed

8 files changed

+315
-208
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { Box, Switch, styled } from '@mui/material';
2+
3+
import DarkModeIcon from '@mui/icons-material/DarkMode';
4+
import LightModeIcon from '@mui/icons-material/LightMode';
5+
import { useColorScheme } from '@mui/material/styles';
6+
7+
const SWITCH_COLORS = {
8+
sun: '#FFA500',
9+
moon: '#2C1810',
10+
trackActive: '#B8814E',
11+
trackInactive: '#f5f5f5',
12+
iconLight: '#FFF',
13+
iconDark: '#fff',
14+
} as const;
15+
16+
const StyledSwitch = styled(Switch)(() => ({
17+
width: 62,
18+
height: 34,
19+
padding: 7,
20+
'& .MuiSwitch-switchBase': {
21+
margin: 1,
22+
padding: 0,
23+
transform: 'translateX(0px)',
24+
'&.Mui-checked': {
25+
color: '#fff',
26+
transform: 'translateX(22px)',
27+
'& .MuiSwitch-thumb': {
28+
backgroundColor: SWITCH_COLORS.moon,
29+
},
30+
'& + .MuiSwitch-track': {
31+
opacity: 1,
32+
backgroundColor: SWITCH_COLORS.trackActive,
33+
},
34+
},
35+
},
36+
'& .MuiSwitch-thumb': {
37+
backgroundColor: SWITCH_COLORS.sun,
38+
width: 32,
39+
height: 32,
40+
display: 'flex',
41+
alignItems: 'center',
42+
justifyContent: 'center',
43+
},
44+
'& .MuiSwitch-track': {
45+
opacity: 1,
46+
backgroundColor: SWITCH_COLORS.trackInactive,
47+
borderRadius: 20 / 2,
48+
},
49+
}));
50+
51+
export const DarkModeToggle = () => {
52+
const { mode, setMode, systemMode } = useColorScheme();
53+
54+
const isDark = mode === 'system' ? systemMode === 'dark' : mode === 'dark';
55+
56+
const handleToggle = () => {
57+
setMode(isDark ? 'light' : 'dark');
58+
};
59+
60+
return (
61+
<StyledSwitch
62+
checked={isDark}
63+
onChange={handleToggle}
64+
slotProps={{
65+
input: {
66+
'aria-label': 'Toggle dark mode',
67+
},
68+
}}
69+
icon={
70+
<Box
71+
sx={{
72+
width: 32,
73+
height: 32,
74+
display: 'flex',
75+
alignItems: 'center',
76+
justifyContent: 'center',
77+
backgroundColor: SWITCH_COLORS.sun,
78+
borderRadius: '50%',
79+
}}
80+
>
81+
<LightModeIcon sx={{ fontSize: 20, color: SWITCH_COLORS.iconLight }} />
82+
</Box>
83+
}
84+
checkedIcon={
85+
<Box
86+
sx={{
87+
width: 32,
88+
height: 32,
89+
display: 'flex',
90+
alignItems: 'center',
91+
justifyContent: 'center',
92+
backgroundColor: SWITCH_COLORS.moon,
93+
borderRadius: '50%',
94+
}}
95+
>
96+
<DarkModeIcon sx={{ fontSize: 20, color: SWITCH_COLORS.iconDark }} />
97+
</Box>
98+
}
99+
/>
100+
);
101+
};

client/src/features/login/LoginPage.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Button, Stack } from '@mui/material';
1+
import { Box, Button } from '@mui/material';
22

33
import { ErrorBox } from '../../components';
44
import HYFLogo from '../../assets/hyf-logo-red.png';
@@ -10,17 +10,30 @@ import { useAuth } from '../../auth/hooks/useAuth';
1010
const LoginPage = () => {
1111
const { login, errorMessage } = useAuth();
1212
return (
13-
<div className="login-container">
13+
<Box
14+
sx={{
15+
paddingTop: '20vh',
16+
maxWidth: 400,
17+
margin: 'auto',
18+
display: 'flex',
19+
flexDirection: 'column',
20+
alignItems: 'center',
21+
gap: 3,
22+
}}
23+
>
1424
<img src={HYFLogo} alt="HYF logo" className="hyf-logo-img" />
15-
<div className="login-button">
16-
<Stack spacing={2} direction="column">
17-
<Button onClick={() => login()} variant="contained">
18-
Login with Google
19-
</Button>
20-
</Stack>
21-
</div>
25+
26+
<Button
27+
onClick={() => login()}
28+
variant="contained"
29+
fullWidth
30+
sx={{ alignSelf: 'center', padding: 2, minWidth: 300 }}
31+
>
32+
Login with Google
33+
</Button>
34+
2235
{errorMessage && <ErrorBox errorMessage={errorMessage} />}
23-
</div>
36+
</Box>
2437
);
2538
};
2639

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Avatar, Box, Button, IconButton, Menu, MenuItem, Tooltip } from '@mui/material';
2+
3+
import { DarkModeToggle } from '../../../features/dark-mode/DarkModeToggle';
4+
import React from 'react';
5+
import SearchIcon from '@mui/icons-material/Search';
6+
import { useAuth } from '../../../auth/hooks/useAuth';
7+
import { useNavigate } from 'react-router-dom';
8+
import { useState } from 'react';
9+
10+
export const NavBarActions: React.FC = () => {
11+
const [anchorElUser, setAnchorElUser] = useState<null | HTMLElement>(null);
12+
13+
const { logout, user } = useAuth();
14+
15+
const navigate = useNavigate();
16+
17+
/**
18+
* Function to handle opening the user menu onClick event.
19+
*
20+
* @param {HTMLElement} event The click event coming from the user.
21+
*/
22+
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
23+
setAnchorElUser(event.currentTarget);
24+
};
25+
26+
/**
27+
* Function to handle closing the user menu onClick event.
28+
*/
29+
const handleCloseUserMenu = () => {
30+
setAnchorElUser(null);
31+
};
32+
33+
return (
34+
<Box sx={{ flexGrow: 0, display: 'flex', alignItems: 'center', gap: 1 }}>
35+
<DarkModeToggle />
36+
<IconButton onClick={() => navigate('/search')} size="large" aria-label="search" color="inherit">
37+
<SearchIcon />
38+
</IconButton>
39+
<Tooltip title="Open user menu">
40+
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
41+
<Avatar alt={user?.name ?? 'User image'} src={user?.imageUrl ?? ''} />
42+
</IconButton>
43+
</Tooltip>
44+
45+
<Menu
46+
sx={{ mt: '45px' }}
47+
id="user-menu-appbar"
48+
anchorEl={anchorElUser}
49+
anchorOrigin={{
50+
vertical: 'top',
51+
horizontal: 'right',
52+
}}
53+
keepMounted
54+
transformOrigin={{
55+
vertical: 'top',
56+
horizontal: 'right',
57+
}}
58+
open={Boolean(anchorElUser)}
59+
onClose={handleCloseUserMenu}
60+
>
61+
<MenuItem key="Logout">
62+
<Button onClick={() => logout()} color="inherit">
63+
Log out
64+
</Button>
65+
</MenuItem>
66+
</Menu>
67+
</Box>
68+
);
69+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Box, Button, Stack } from '@mui/material';
2+
import type { FC } from 'react';
3+
4+
import { NAVIGATION_LINKS } from './constants';
5+
import { useNavigate } from 'react-router-dom';
6+
7+
export const NavigationLinksDesktop: FC = () => {
8+
const navigate = useNavigate();
9+
10+
return (
11+
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
12+
<Stack direction="row" spacing={2}>
13+
{NAVIGATION_LINKS.map((link) => (
14+
<Button key={link.name} onClick={() => navigate(link.path)} sx={{ my: 2, color: 'white', display: 'block' }}>
15+
{link.name}
16+
</Button>
17+
))}
18+
</Stack>
19+
</Box>
20+
);
21+
};
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { Box, Button, IconButton, Menu, MenuItem } from '@mui/material';
2+
3+
import MenuIcon from '@mui/icons-material/Menu';
4+
import { NAVIGATION_LINKS } from './constants';
5+
import React from 'react';
6+
import { useNavigate } from 'react-router-dom';
7+
import { useState } from 'react';
8+
9+
export const NavigationLinksMobile: React.FC = () => {
10+
const navigate = useNavigate();
11+
12+
const [menuAnchor, setMenuAnchor] = useState<null | HTMLElement>(null);
13+
14+
/**
15+
* Function to handle opening the navigation burger menu onClick event.
16+
*
17+
* @param {HTMLElement} event the click event coming from the user.
18+
*/
19+
const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
20+
setMenuAnchor(event.currentTarget);
21+
};
22+
23+
/**
24+
* Function to handle closing the navigation burger menu onClick event.
25+
*/
26+
const handleCloseNavMenu = () => {
27+
setMenuAnchor(null);
28+
};
29+
30+
const handleNavClick = (path: string) => {
31+
navigate(path);
32+
handleCloseNavMenu();
33+
};
34+
const menuId = 'main-menu-appbar';
35+
return (
36+
<Box sx={{ flexGrow: 0, display: { xs: 'flex', md: 'none' } }}>
37+
<IconButton
38+
size="large"
39+
aria-label="menu of current user"
40+
aria-controls={menuId}
41+
aria-haspopup="true"
42+
onClick={handleOpenNavMenu}
43+
color="inherit"
44+
sx={{ p: 0 }}
45+
>
46+
<MenuIcon />
47+
</IconButton>
48+
<Menu
49+
id={menuId}
50+
anchorEl={menuAnchor}
51+
anchorOrigin={{
52+
vertical: 'bottom',
53+
horizontal: 'right',
54+
}}
55+
keepMounted
56+
transformOrigin={{
57+
vertical: 'top',
58+
horizontal: 'right',
59+
}}
60+
open={Boolean(menuAnchor)}
61+
onClose={handleCloseNavMenu}
62+
sx={{
63+
display: { xs: 'block', md: 'none' },
64+
}}
65+
>
66+
{NAVIGATION_LINKS.map((link) => (
67+
<MenuItem key={link.name}>
68+
<Button
69+
onClick={() => handleNavClick(link.path)}
70+
sx={{
71+
color: 'inherit',
72+
display: 'block',
73+
textAlign: 'center',
74+
}}
75+
>
76+
{link.name}
77+
</Button>
78+
</MenuItem>
79+
))}
80+
</Menu>
81+
</Box>
82+
);
83+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const NAVIGATION_LINKS = [
2+
{ name: 'Home', path: '/home' },
3+
{ name: 'Cohorts', path: '/cohorts' },
4+
{ name: 'Dashboard', path: '/dashboard' },
5+
];

0 commit comments

Comments
 (0)