diff --git a/components/dashboard/src/components/Header.tsx b/components/dashboard/src/components/Header.tsx index 33817f250f3d10..827d151a6ea298 100644 --- a/components/dashboard/src/components/Header.tsx +++ b/components/dashboard/src/components/Header.tsx @@ -6,7 +6,7 @@ import { useEffect } from "react"; import { useLocation } from "react-router"; -import Separator from "./Separator"; +import { Separator } from "./Separator"; import TabMenuItem from "./TabMenuItem"; export interface HeaderProps { diff --git a/components/dashboard/src/components/Separator.tsx b/components/dashboard/src/components/Separator.tsx index d0c9a53c83021b..15f51fbf864928 100644 --- a/components/dashboard/src/components/Separator.tsx +++ b/components/dashboard/src/components/Separator.tsx @@ -4,6 +4,16 @@ * See License.AGPL.txt in the project root for license information. */ -export default function Separator() { - return
; -} +import classNames from "classnames"; +import { FC } from "react"; + +type Props = { + className?: string; +}; +export const Separator: FC = ({ className }) => { + return ( +
+ ); +}; diff --git a/components/dashboard/src/index.css b/components/dashboard/src/index.css index 9ed055a4c28a4a..e2fe644c9d4905 100644 --- a/components/dashboard/src/index.css +++ b/components/dashboard/src/index.css @@ -35,7 +35,7 @@ } .app-container { - @apply lg:px-28 px-10; + @apply lg:px-28 px-4; } .btn-login { @apply rounded-md border-none bg-gray-100 hover:bg-gray-200 text-gray-500 dark:text-gray-200 dark:bg-gray-800 dark:hover:bg-gray-600; diff --git a/components/dashboard/src/menu/Menu.tsx b/components/dashboard/src/menu/Menu.tsx index 1a1755e1fa78ef..07c0328f2e0e36 100644 --- a/components/dashboard/src/menu/Menu.tsx +++ b/components/dashboard/src/menu/Menu.tsx @@ -5,7 +5,7 @@ */ import { User } from "@gitpod/gitpod-protocol"; -import { useContext, useEffect, useState } from "react"; +import { FC, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { Link } from "react-router-dom"; import { useLocation } from "react-router"; import { Location } from "history"; @@ -13,14 +13,15 @@ import { countries } from "countries-list"; import gitpodIcon from "../icons/gitpod.svg"; import { getGitpodService, gitpodHostUrl } from "../service/service"; import { useCurrentUser } from "../user-context"; -import ContextMenu from "../components/ContextMenu"; -import Separator from "../components/Separator"; +import ContextMenu, { ContextMenuEntry } from "../components/ContextMenu"; +import { Separator } from "../components/Separator"; import PillMenuItem from "../components/PillMenuItem"; import { PaymentContext } from "../payment-context"; import FeedbackFormModal from "../feedback-form/FeedbackModal"; import { isGitpodIo } from "../utils"; import OrganizationSelector from "./OrganizationSelector"; import { getAdminTabs } from "../admin/admin.routes"; +import classNames from "classnames"; interface Entry { title: string; @@ -34,12 +35,6 @@ export default function Menu() { const { setCurrency, setIsStudent, setIsChargebeeCustomer } = useContext(PaymentContext); const [isFeedbackFormVisible, setFeedbackFormVisible] = useState(false); - function isSelected(entry: Entry, location: Location) { - const all = [entry.link, ...(entry.alternatives || [])].map((l) => l.toLowerCase()); - const path = location.pathname.toLowerCase(); - return all.some((n) => n === path || n + "/" === path); - } - useEffect(() => { const { server } = getGitpodService(); Promise.all([ @@ -52,64 +47,49 @@ export default function Menu() { ]).then((setters) => setters.forEach((s) => s())); }, [setCurrency, setIsChargebeeCustomer, setIsStudent]); - const leftMenu: Entry[] = [ - { - title: "Workspaces", - link: "/workspaces", - alternatives: ["/"], - }, - { - title: "Projects", - link: `/projects`, - alternatives: [] as string[], - }, - ]; - - const adminMenu: Entry = { - title: "Admin", - link: "/admin", - alternatives: [ - ...getAdminTabs().reduce( - (prevEntry, currEntry) => - currEntry.alternatives - ? [...prevEntry, ...currEntry.alternatives, currEntry.link] - : [...prevEntry, currEntry.link], - [] as string[], - ), - ], - }; + const adminMenu: Entry = useMemo( + () => ({ + title: "Admin", + link: "/admin", + alternatives: [ + ...getAdminTabs().reduce( + (prevEntry, currEntry) => + currEntry.alternatives + ? [...prevEntry, ...currEntry.alternatives, currEntry.link] + : [...prevEntry, currEntry.link], + [] as string[], + ), + ], + }), + [], + ); - const handleFeedbackFormClick = () => { + const handleFeedbackFormClick = useCallback(() => { setFeedbackFormVisible(true); - }; + }, []); - const onFeedbackFormClose = () => { + const onFeedbackFormClose = useCallback(() => { setFeedbackFormVisible(false); - }; + }, []); return ( <> -
-
-
- +
+
+
+ {/* hidden on smaller screens */} + Gitpod's logo -
- {leftMenu.map((entry) => ( -
- -
- ))} + {/* hidden on smaller screens (in it's own menu below on smaller screens) */} +
+
-
+ {/* only shown on small screens */} + + {/* only shown on small screens */} + ); } + +type OrgPagesNavProps = { + className?: string; +}; +const OrgPagesNav: FC = ({ className }) => { + const location = useLocation(); + + const leftMenu: Entry[] = useMemo( + () => [ + { + title: "Workspaces", + link: "/workspaces", + alternatives: ["/"], + }, + { + title: "Projects", + link: `/projects`, + alternatives: [] as string[], + }, + ], + [], + ); + + return ( +
+ {leftMenu.map((entry) => ( +
+ +
+ ))} +
+ ); +}; + +type UserMenuProps = { + user?: User; + className?: string; + withAdminLink?: boolean; + withFeedbackLink?: boolean; + onFeedback?: () => void; +}; +const UserMenu: FC = ({ user, className, withAdminLink, withFeedbackLink, onFeedback }) => { + const extraSection = useMemo(() => { + const items: ContextMenuEntry[] = []; + + if (withAdminLink && user?.rolesOrPermissions?.includes("admin")) { + items.push({ + title: "Admin", + link: "/admin", + }); + } + if (withFeedbackLink && isGitpodIo()) { + items.push({ + title: "Feedback", + onClick: onFeedback, + }); + } + + // Add a separator to the last item + if (items.length > 0) { + items[items.length - 1].separator = true; + } + + return items; + }, [onFeedback, user?.rolesOrPermissions, withAdminLink, withFeedbackLink]); + + return ( +
+ + {user?.name + +
+ ); +}; + +function isSelected(entry: Entry, location: Location) { + const all = [entry.link, ...(entry.alternatives || [])].map((l) => l.toLowerCase()); + const path = location.pathname.toLowerCase(); + return all.some((n) => n === path || n + "/" === path); +} diff --git a/components/dashboard/src/onboarding/UserOnboarding.tsx b/components/dashboard/src/onboarding/UserOnboarding.tsx index dcc779fcc71c1a..82582d0ac44573 100644 --- a/components/dashboard/src/onboarding/UserOnboarding.tsx +++ b/components/dashboard/src/onboarding/UserOnboarding.tsx @@ -7,7 +7,7 @@ import { User } from "@gitpod/gitpod-protocol"; import { FunctionComponent, useCallback, useContext, useState } from "react"; import gitpodIcon from "../icons/gitpod.svg"; -import Separator from "../components/Separator"; +import { Separator } from "../components/Separator"; import { useHistory, useLocation } from "react-router"; import { StepUserInfo } from "./StepUserInfo"; import { UserContext } from "../user-context";