diff --git a/webui/package.json b/webui/package.json index 99e5a6545..af215acb4 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "openvsx-webui", - "version": "0.13.1", + "version": "0.13.1-next.dee19c0f", "description": "User interface for Eclipse Open VSX", "keywords": [ "react", diff --git a/webui/src/default/menu-content.tsx b/webui/src/default/menu-content.tsx index 0345216a8..7a38fb07f 100644 --- a/webui/src/default/menu-content.tsx +++ b/webui/src/default/menu-content.tsx @@ -8,46 +8,143 @@ * SPDX-License-Identifier: EPL-2.0 ********************************************************************************/ -import React, { FunctionComponent, PropsWithChildren } from 'react'; -import { Typography, MenuItem, Link, Button } from '@mui/material'; +import React, { FunctionComponent, PropsWithChildren, useContext } from 'react'; +import { Typography, MenuItem, Link, Button, IconButton, Accordion, AccordionSummary, Avatar, AccordionDetails } from '@mui/material'; import { useLocation } from 'react-router-dom'; import { Link as RouteLink } from 'react-router-dom'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import GitHubIcon from '@mui/icons-material/GitHub'; import MenuBookIcon from '@mui/icons-material/MenuBook'; import ForumIcon from '@mui/icons-material/Forum'; import InfoIcon from '@mui/icons-material/Info'; import PublishIcon from '@mui/icons-material/Publish'; +import AccountBoxIcon from '@mui/icons-material/AccountBox'; +import { UserAvatar } from '../pages/user/avatar'; import { UserSettingsRoutes } from '../pages/user/user-settings'; import { styled, Theme } from '@mui/material/styles'; +import { MainContext } from '../context'; +import SettingsIcon from '@mui/icons-material/Settings'; +import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings'; +import LogoutIcon from '@mui/icons-material/Logout'; +import { AdminDashboardRoutes } from '../pages/admin-dashboard/admin-dashboard'; +import { LogoutForm } from '../pages/user/logout'; //-------------------- Mobile View --------------------// -const MobileMenuItem = styled(MenuItem)({ +export const MobileMenuItem = styled(MenuItem)({ cursor: 'auto', '&>a': { textDecoration: 'none' } }); -const itemIcon = { +export const itemIcon = { mr: 1, width: '16px', height: '16px', }; -const MobileMenuItemText: FunctionComponent = ({ children }) => { +export const MobileMenuItemText: FunctionComponent = ({ children }) => { return ( - + {children} ); }; +export const MobileUserAvatar: FunctionComponent = () => { + const context = useContext(MainContext); + const user = context.user; + if (!user) { + return null; + } + + return + } + aria-controls='user-actions' + id='user-avatar' + > + + + {user.loginName} + + + + + + + + {user.loginName} + + + + + + + + Settings + + + + { + user.role === 'admin' + ? + + + + Admin Dashboard + + + + : null + } + + + + + Log Out + + + + + ; +}; + export const MobileMenuContent: FunctionComponent = () => { const location = useLocation(); + const { service, user } = useContext(MainContext); return <> + { + user + ? + : + + + + Log In + + + + } + { + !location.pathname.startsWith(UserSettingsRoutes.ROOT) + ? + + + + Publish Extension + + + + : null + } @@ -80,24 +177,12 @@ export const MobileMenuContent: FunctionComponent = () => { - { - !location.pathname.startsWith(UserSettingsRoutes.ROOT) - ? - - - - Publish Extension - - - - : null - } ; }; //-------------------- Default View --------------------// -const headerItem = ({ theme }: { theme: Theme }) => ({ +export const headerItem = ({ theme }: { theme: Theme }) => ({ margin: theme.spacing(2.5), color: theme.palette.text.primary, textDecoration: 'none', @@ -111,10 +196,11 @@ const headerItem = ({ theme }: { theme: Theme }) => ({ } }); -const MenuLink = styled(Link)(headerItem); -const MenuRouteLink = styled(RouteLink)(headerItem); +export const MenuLink = styled(Link)(headerItem); +export const MenuRouteLink = styled(RouteLink)(headerItem); export const DefaultMenuContent: FunctionComponent = () => { + const { service, user } = useContext(MainContext); return <> Documentation @@ -128,5 +214,16 @@ export const DefaultMenuContent: FunctionComponent = () => { + { + user ? + + : + + + + } ; }; diff --git a/webui/src/header-menu.tsx b/webui/src/header-menu.tsx index dc55e8758..01ddd3047 100644 --- a/webui/src/header-menu.tsx +++ b/webui/src/header-menu.tsx @@ -56,6 +56,7 @@ export const MobileHeaderMenu: FunctionComponent = props setOpen(false)} > diff --git a/webui/src/other-pages.tsx b/webui/src/other-pages.tsx index 648a0b80e..74f906fba 100644 --- a/webui/src/other-pages.tsx +++ b/webui/src/other-pages.tsx @@ -1,12 +1,10 @@ import React, { FunctionComponent, useContext, useEffect, useState } from 'react'; import { Routes, Route } from 'react-router-dom'; -import { AppBar, Box, IconButton, Toolbar } from '@mui/material'; +import { AppBar, Box, Toolbar } from '@mui/material'; import { styled, Theme } from '@mui/material/styles'; -import AccountBoxIcon from '@mui/icons-material/AccountBox'; import { Banner } from './components/banner'; import { MainContext } from './context'; import { HeaderMenu } from './header-menu'; -import { UserAvatar } from './pages/user/avatar'; import { ExtensionListContainer, ExtensionListRoutes } from './pages/extension-list/extension-list-container'; import { UserSettings, UserSettingsRoutes } from './pages/user/user-settings'; import { NamespaceDetail, NamespaceDetailRoutes } from './pages/namespace-detail/namespace-detail'; @@ -38,7 +36,7 @@ const Footer = styled('footer')(({ theme }: { theme: Theme }) => ({ })); export const OtherPages: FunctionComponent = (props) => { - const { service, pageSettings } = useContext(MainContext); + const { pageSettings } = useContext(MainContext); const { additionalRoutes: AdditionalRoutes, banner: BannerComponent, @@ -89,17 +87,6 @@ export const OtherPages: FunctionComponent = (props) => { - { - props.user ? - - : - - - - } diff --git a/webui/src/pages/user/avatar.tsx b/webui/src/pages/user/avatar.tsx index b78ba5361..43428912c 100644 --- a/webui/src/pages/user/avatar.tsx +++ b/webui/src/pages/user/avatar.tsx @@ -8,53 +8,28 @@ * SPDX-License-Identifier: EPL-2.0 ********************************************************************************/ -import React, { FunctionComponent, useContext, useEffect, useRef, useState } from 'react'; +import React, { FunctionComponent, useContext, useRef, useState } from 'react'; import { styled } from '@mui/material/styles'; -import { Avatar, Button, Menu, Typography, MenuItem, Link, Divider, IconButton } from '@mui/material'; +import { Avatar, Menu, Typography, MenuItem, Link, Divider, IconButton } from '@mui/material'; import { Link as RouteLink } from 'react-router-dom'; -import { isError, CsrfTokenJson } from '../../extension-registry-types'; import { UserSettingsRoutes } from './user-settings'; import { AdminDashboardRoutes } from '../admin-dashboard/admin-dashboard'; import { MainContext } from '../../context'; +import { LogoutForm } from './logout'; -const link = { +const AvatarRouteLink = styled(RouteLink)({ cursor: 'pointer', textDecoration: 'none' -}; +}); -const AvatarRouteLink = styled(RouteLink)(link); const AvatarMenuItem = styled(MenuItem)({ cursor: 'auto' }); -const LogoutButton = styled(Button)({ - ...link, - border: 'none', - background: 'none', - padding: 0 -}); + export const UserAvatar: FunctionComponent = () => { const [open, setOpen] = useState(false); - const [csrf, setCsrf] = useState(); const context = useContext(MainContext); const avatarButton = useRef(); - const abortController = useRef(new AbortController()); - useEffect(() => { - updateCsrf(); - return () => abortController.current.abort(); - }, []); - - const updateCsrf = async () => { - try { - const csrfResponse = await context.service.getCsrfToken(abortController.current); - if (!isError(csrfResponse)) { - const csrfToken = csrfResponse as CsrfTokenJson; - setCsrf(csrfToken.value); - } - } catch (err) { - context.handleError(err); - } - }; - const handleAvatarClick = () => { setOpen(!open); }; @@ -116,14 +91,11 @@ export const UserAvatar: FunctionComponent = () => { '' } -
- {csrf ? : null} - - - Log Out - - -
+ + + Log Out + +
; diff --git a/webui/src/pages/user/logout.tsx b/webui/src/pages/user/logout.tsx new file mode 100644 index 000000000..a628a93fa --- /dev/null +++ b/webui/src/pages/user/logout.tsx @@ -0,0 +1,53 @@ +/** ****************************************************************************** + * Copyright (c) 2024 Precies. Software OU and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * ****************************************************************************** */ + +import React, { FunctionComponent, PropsWithChildren, useContext, useEffect, useRef, useState } from 'react'; +import { Button } from '@mui/material'; +import { styled } from '@mui/material/styles'; +import { isError, CsrfTokenJson } from '../../extension-registry-types'; +import { MainContext } from '../../context'; + +const LogoutButton = styled(Button)({ + cursor: 'pointer', + textDecoration: 'none', + border: 'none', + background: 'none', + padding: 0 +}); + +export const LogoutForm: FunctionComponent = ({ children }) => { + const [csrf, setCsrf] = useState(); + const context = useContext(MainContext); + + const abortController = useRef(new AbortController()); + useEffect(() => { + updateCsrf(); + return () => abortController.current.abort(); + }, []); + + const updateCsrf = async () => { + try { + const csrfResponse = await context.service.getCsrfToken(abortController.current); + if (!isError(csrfResponse)) { + const csrfToken = csrfResponse as CsrfTokenJson; + setCsrf(csrfToken.value); + } + } catch (err) { + context.handleError(err); + } + }; + + return
+ {csrf ? : null} + + {children} + +
; +}; \ No newline at end of file