source: components/Navbar.tsx@ 1d8f088

main
Last change on this file since 1d8f088 was 87b79bc, checked in by Mihail <mihail2.naumov@…>, 6 months ago

Fixed suggests component and also added hamburger menu for small screens for the navbar.

  • Property mode set to 100644
File size: 14.7 KB
Line 
1import React, { useEffect, useState } from "react";
2import AppBar from "@mui/material/AppBar";
3import Toolbar from "@mui/material/Toolbar";
4import Typography from "@mui/material/Typography";
5import Button from "@mui/material/Button";
6import Box from "@mui/material/Box";
7import Menu from "@mui/material/Menu";
8import MenuItem from "@mui/material/MenuItem";
9import IconButton from "@mui/material/IconButton";
10import Drawer from "@mui/material/Drawer";
11import List from "@mui/material/List";
12import ListItem from "@mui/material/ListItem";
13import ListItemButton from "@mui/material/ListItemButton";
14import ListItemIcon from "@mui/material/ListItemIcon";
15import ListItemText from "@mui/material/ListItemText";
16import Divider from "@mui/material/Divider";
17import Collapse from "@mui/material/Collapse";
18
19import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
20import MenuIcon from '@mui/icons-material/Menu';
21import CloseIcon from '@mui/icons-material/Close';
22import BuildIcon from '@mui/icons-material/Build';
23import ViewListIcon from '@mui/icons-material/ViewList';
24import ExpandLess from '@mui/icons-material/ExpandLess';
25import ExpandMore from '@mui/icons-material/ExpandMore';
26import PersonIcon from '@mui/icons-material/Person';
27import LogoutIcon from '@mui/icons-material/Logout';
28import LoginIcon from '@mui/icons-material/Login';
29import PersonAddIcon from '@mui/icons-material/PersonAdd';
30
31import {
32 Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions
33} from "@mui/material";
34
35import MemoryIcon from '@mui/icons-material/Memory';
36import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
37import StorageIcon from '@mui/icons-material/Storage';
38import SdStorageIcon from '@mui/icons-material/SdStorage';
39import RouterIcon from '@mui/icons-material/Router';
40import LanIcon from '@mui/icons-material/Lan';
41import SpeakerIcon from '@mui/icons-material/Speaker';
42import AlbumIcon from '@mui/icons-material/Album';
43import SdCardIcon from '@mui/icons-material/SdCard';
44import CableIcon from '@mui/icons-material/Cable';
45
46import LogoUrl from '../assets/projectlogo.png';
47import { onGetAuthState } from "../pages/+Layout.telefunc";
48
49import ComponentDialog from "./ComponentDialog";
50
51type AuthState = { isLoggedIn: boolean; username: string | null; isAdmin?: boolean };
52
53const COMPONENT_CATEGORIES = [
54 { id: 'cpu', label: 'Processors', icon: <MemoryIcon fontSize="small" /> },
55 { id: 'gpu', label: 'Graphics Cards', icon: <DeveloperBoardIcon fontSize="small" /> },
56 { id: 'motherboard', label: 'Motherboards', icon: <DeveloperBoardIcon fontSize="small" /> },
57 { id: 'memory', label: 'Memory (RAM)', icon: <SdStorageIcon fontSize="small" /> },
58 { id: 'storage', label: 'Storage', icon: <StorageIcon fontSize="small" /> },
59 { id: 'case', label: 'Cases', icon: <StorageIcon fontSize="small" /> },
60 { id: 'power_supply', label: 'Power Supplies', icon: <StorageIcon fontSize="small" /> },
61 { id: 'cooler', label: 'Cooling', icon: <StorageIcon fontSize="small" /> },
62 { id: 'network_adapter', label: 'Network Adapters (WiFi)', icon: <RouterIcon fontSize="small" /> },
63 { id: 'network_card', label: 'Network Cards (Ethernet)', icon: <LanIcon fontSize="small" /> },
64 { id: 'sound_card', label: 'Sound Cards', icon: <SpeakerIcon fontSize="small" /> },
65 { id: 'optical_drive', label: 'Optical Drives', icon: <AlbumIcon fontSize="small" /> },
66 { id: 'memory_card', label: 'Memory Cards', icon: <SdCardIcon fontSize="small" /> },
67 { id: 'cables', label: 'Cables', icon: <CableIcon fontSize="small" /> },
68];
69
70export default function Navbar() {
71 const [auth, setAuth] = useState<AuthState | null>(null);
72 const [openLogoutDialog, setOpenLogoutDialog] = useState(false);
73 const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
74 const [mobileComponentsOpen, setMobileComponentsOpen] = useState(false);
75
76 const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
77 const openMenu = Boolean(anchorEl);
78
79 const [browserOpen, setBrowserOpen] = useState(false);
80 const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
81
82 const handleComponentsClick = (event: React.MouseEvent<HTMLButtonElement>) => {
83 setAnchorEl(event.currentTarget);
84 };
85
86 const handleMenuClose = () => {
87 setAnchorEl(null);
88 };
89
90 const handleCategorySelect = (categoryId: string) => {
91 setSelectedCategory(categoryId);
92 setBrowserOpen(true);
93 handleMenuClose();
94 setMobileDrawerOpen(false);
95 setMobileComponentsOpen(false);
96 };
97
98 const handleLogoutClick = (e: React.MouseEvent) => {
99 e.preventDefault();
100 setOpenLogoutDialog(true);
101 setMobileDrawerOpen(false);
102 };
103
104 const confirmLogout = async () => {
105 setAuth({ isLoggedIn: false, username: null, isAdmin: false });
106 setOpenLogoutDialog(false);
107 const csrfRes = await fetch("/api/auth/csrf");
108 const { csrfToken } = await csrfRes.json();
109 await fetch("/api/auth/signout", {
110 method: "POST",
111 headers: { "Content-Type": "application/x-www-form-urlencoded" },
112 body: new URLSearchParams({ csrfToken: csrfToken, callbackUrl: "/" }),
113 });
114 window.location.href = "/";
115 };
116
117 useEffect(() => {
118 let active = true;
119 onGetAuthState()
120 .then((data) => active && setAuth({
121 isLoggedIn: data.isLoggedIn,
122 username: data.username,
123 isAdmin: data.isAdmin
124 }))
125 .catch(() => active && setAuth({ isLoggedIn: false, username: null, isAdmin: false }));
126 return () => { active = false; };
127 }, []);
128
129 const checkDashboardUrl = auth?.isAdmin ? '/dashboard/admin' : '/dashboard/user';
130 const onHoverNav = {
131 color: 'inherit',
132 '&:hover': { backgroundColor: '#ff8201', color: 'white', fontWeight: 'bold' }
133 };
134
135 return (
136 <>
137 <AppBar position="static" color="default" enableColorOnDark>
138 <Toolbar>
139 <Box sx={{ display: 'flex', alignItems: 'center', mr: { xs: 1, md: 4 } }}>
140 <Box
141 component="img"
142 src={LogoUrl}
143 alt="PC Forge Logo"
144 sx={{ height: 40, mr: { xs: 1, md: 2 }, cursor: 'pointer' }}
145 onClick={() => window.location.href='/'}
146 />
147 <Typography
148 variant="h6"
149 component="a"
150 href="/"
151 sx={{
152 textDecoration: "none",
153 color: "inherit",
154 fontWeight: "bold",
155 display: { xs: 'none', sm: 'block' }
156 }}
157 >
158 PC Forge
159 </Typography>
160 </Box>
161
162 <Box sx={{ display: { xs: "none", md: "flex" }, gap: 2 }}>
163 <Button color="inherit" href="/forge" sx={onHoverNav}>Forge</Button>
164
165 <Button
166 color="inherit"
167 onClick={handleComponentsClick}
168 endIcon={<KeyboardArrowDownIcon />}
169 sx={onHoverNav}
170 >
171 Components
172 </Button>
173 <Menu
174 anchorEl={anchorEl}
175 open={openMenu}
176 onClose={handleMenuClose}
177 MenuListProps={{ 'aria-labelledby': 'basic-button' }}
178 >
179 {COMPONENT_CATEGORIES.map((cat) => (
180 <MenuItem key={cat.id} onClick={() => handleCategorySelect(cat.id)}>
181 <ListItemIcon>{cat.icon}</ListItemIcon>
182 <ListItemText>{cat.label}</ListItemText>
183 </MenuItem>
184 ))}
185 </Menu>
186
187 <Button color="inherit" href="/completed-builds" sx={onHoverNav}>Completed Builds</Button>
188 </Box>
189
190 <Box sx={{ flexGrow: 1 }} />
191
192 <Box sx={{ display: { xs: 'none', md: 'flex' }, gap: 1 }}>
193 {auth?.isLoggedIn ? (
194 <>
195 <Button sx={onHoverNav} color="inherit" href={checkDashboardUrl}>{auth.username}</Button>
196 <Button sx={onHoverNav} color="inherit" onClick={handleLogoutClick}>Logout</Button>
197 </>
198 ) : (
199 <>
200 <Button color="inherit" href="/auth/login" sx={onHoverNav}>Login</Button>
201 <Button color="inherit" href="/auth/register" sx={onHoverNav}>Register</Button>
202 </>
203 )}
204 </Box>
205
206 <IconButton
207 color="inherit"
208 edge="end"
209 onClick={() => setMobileDrawerOpen(true)}
210 sx={{ display: { xs: 'block', md: 'none' } }}
211 >
212 <MenuIcon />
213 </IconButton>
214 </Toolbar>
215 </AppBar>
216
217 <Drawer
218 anchor="right"
219 open={mobileDrawerOpen}
220 onClose={() => setMobileDrawerOpen(false)}
221 sx={{
222 display: { xs: 'block', md: 'none' },
223 '& .MuiDrawer-paper': { width: 280 , height: '60%'}
224 }}
225 >
226 <Box sx={{ p: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
227 <Typography variant="h6" fontWeight="bold">Menu</Typography>
228 <IconButton onClick={() => setMobileDrawerOpen(false)}>
229 <CloseIcon />
230 </IconButton>
231 </Box>
232 <Divider />
233
234 <List>
235 <ListItem disablePadding>
236 <ListItemButton onClick={() => { window.location.href = '/forge'; }}>
237 <ListItemIcon><BuildIcon /></ListItemIcon>
238 <ListItemText primary="Forge" />
239 </ListItemButton>
240 </ListItem>
241
242 <ListItem disablePadding>
243 <ListItemButton onClick={() => setMobileComponentsOpen(!mobileComponentsOpen)}>
244 <ListItemIcon><ViewListIcon /></ListItemIcon>
245 <ListItemText primary="Components" />
246 {mobileComponentsOpen ? <ExpandLess /> : <ExpandMore />}
247 </ListItemButton>
248 </ListItem>
249 <Collapse in={mobileComponentsOpen} timeout="auto" unmountOnExit>
250 <List component="div" disablePadding>
251 {COMPONENT_CATEGORIES.map((cat) => (
252 <ListItemButton
253 key={cat.id}
254 sx={{ pl: 4 }}
255 onClick={() => handleCategorySelect(cat.id)}
256 >
257 <ListItemIcon>{cat.icon}</ListItemIcon>
258 <ListItemText primary={cat.label} />
259 </ListItemButton>
260 ))}
261 </List>
262 </Collapse>
263
264 <ListItem disablePadding>
265 <ListItemButton onClick={() => { window.location.href = '/completed-builds'; }}>
266 <ListItemIcon><ViewListIcon /></ListItemIcon>
267 <ListItemText primary="Completed Builds" />
268 </ListItemButton>
269 </ListItem>
270
271 <Divider sx={{ my: 1 }} />
272
273 {auth?.isLoggedIn ? (
274 <>
275 <ListItem disablePadding>
276 <ListItemButton onClick={() => { window.location.href = checkDashboardUrl; }}>
277 <ListItemIcon><PersonIcon /></ListItemIcon>
278 <ListItemText primary={"My Profile"} />
279 </ListItemButton>
280 </ListItem>
281 <ListItem disablePadding>
282 <ListItemButton onClick={handleLogoutClick}>
283 <ListItemIcon><LogoutIcon /></ListItemIcon>
284 <ListItemText primary="Logout" />
285 </ListItemButton>
286 </ListItem>
287 </>
288 ) : (
289 <>
290 <ListItem disablePadding>
291 <ListItemButton onClick={() => { window.location.href = '/auth/login'; }}>
292 <ListItemIcon><LoginIcon /></ListItemIcon>
293 <ListItemText primary="Login" />
294 </ListItemButton>
295 </ListItem>
296 <ListItem disablePadding>
297 <ListItemButton onClick={() => { window.location.href = '/auth/register'; }}>
298 <ListItemIcon><PersonAddIcon /></ListItemIcon>
299 <ListItemText primary="Register" />
300 </ListItemButton>
301 </ListItem>
302 </>
303 )}
304 </List>
305 </Drawer>
306
307 <Dialog open={openLogoutDialog} onClose={() => setOpenLogoutDialog(false)}>
308 <DialogTitle>Confirm Logout</DialogTitle>
309 <DialogContent>
310 <DialogContentText>Are you sure you want to leave the Forge?</DialogContentText>
311 </DialogContent>
312 <DialogActions>
313 <Button onClick={() => setOpenLogoutDialog(false)}>Cancel</Button>
314 <Button onClick={confirmLogout} color="error" variant="contained" autoFocus>Logout</Button>
315 </DialogActions>
316 </Dialog>
317
318 <ComponentDialog
319 open={browserOpen}
320 category={selectedCategory}
321 onClose={() => setBrowserOpen(false)}
322 />
323 </>
324 );
325}
Note: See TracBrowser for help on using the repository browser.