mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
feat(site): add an organization switcher to the user menu (#13269)
This commit is contained in:
committed by
GitHub
parent
1f5788feff
commit
fc6f18aa96
@ -249,3 +249,10 @@ export const updateAppearanceSettings = (
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const myOrganizations = () => {
|
||||
return {
|
||||
queryKey: ["organizations", "me"],
|
||||
queryFn: () => API.getOrganizations(),
|
||||
};
|
||||
};
|
||||
|
@ -67,7 +67,7 @@ export const RequireAuth: FC = () => {
|
||||
};
|
||||
|
||||
type RequireKeys<T, R extends keyof T> = Omit<T, R> & {
|
||||
[K in keyof Pick<T, R>]: NonNullable<T[K]>;
|
||||
[K in keyof Pick<T, R>]-?: NonNullable<T[K]>;
|
||||
};
|
||||
|
||||
// We can do some TS magic here but I would rather to be explicit on what
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { chromaticWithTablet } from "testHelpers/chromatic";
|
||||
import { MockUser, MockUser2 } from "testHelpers/entities";
|
||||
import { withDashboardProvider } from "testHelpers/storybook";
|
||||
import { NavbarView } from "./NavbarView";
|
||||
|
||||
const meta: Meta<typeof NavbarView> = {
|
||||
@ -10,6 +11,7 @@ const meta: Meta<typeof NavbarView> = {
|
||||
args: {
|
||||
user: MockUser,
|
||||
},
|
||||
decorators: [withDashboardProvider],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { expect, screen, userEvent, within, waitFor } from "@storybook/test";
|
||||
import { MockBuildInfo, MockUser } from "testHelpers/entities";
|
||||
import { withDashboardProvider } from "testHelpers/storybook";
|
||||
import { UserDropdown } from "./UserDropdown";
|
||||
|
||||
const meta: Meta<typeof UserDropdown> = {
|
||||
@ -16,6 +17,7 @@ const meta: Meta<typeof UserDropdown> = {
|
||||
{ icon: "/icon/aws.svg", name: "Amazon Web Services", target: "" },
|
||||
],
|
||||
},
|
||||
decorators: [withDashboardProvider],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { css, type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import Badge from "@mui/material/Badge";
|
||||
import type { FC } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { myOrganizations } from "api/queries/users";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
|
||||
import {
|
||||
@ -9,6 +11,7 @@ import {
|
||||
PopoverTrigger,
|
||||
} from "components/Popover/Popover";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import { BUTTON_SM_HEIGHT, navHeight } from "theme/constants";
|
||||
import { UserDropdownContent } from "./UserDropdownContent";
|
||||
|
||||
@ -26,6 +29,11 @@ export const UserDropdown: FC<UserDropdownProps> = ({
|
||||
onSignOut,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const organizationsQuery = useQuery({
|
||||
...myOrganizations(),
|
||||
enabled: Boolean(localStorage.getItem("enableMultiOrganizationUi")),
|
||||
});
|
||||
const { organizationId, setOrganizationId } = useDashboard();
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
@ -63,6 +71,9 @@ export const UserDropdown: FC<UserDropdownProps> = ({
|
||||
user={user}
|
||||
buildInfo={buildInfo}
|
||||
supportLinks={supportLinks}
|
||||
organizations={organizationsQuery.data}
|
||||
organizationId={organizationId}
|
||||
setOrganizationId={setOrganizationId}
|
||||
onSignOut={onSignOut}
|
||||
/>
|
||||
</PopoverContent>
|
||||
|
@ -84,14 +84,20 @@ const styles = {
|
||||
|
||||
export interface UserDropdownContentProps {
|
||||
user: TypesGen.User;
|
||||
organizations?: TypesGen.Organization[];
|
||||
organizationId?: string;
|
||||
setOrganizationId?: (id: string) => void;
|
||||
buildInfo?: TypesGen.BuildInfoResponse;
|
||||
supportLinks?: readonly TypesGen.LinkConfig[];
|
||||
onSignOut: () => void;
|
||||
}
|
||||
|
||||
export const UserDropdownContent: FC<UserDropdownContentProps> = ({
|
||||
buildInfo,
|
||||
user,
|
||||
organizations,
|
||||
organizationId,
|
||||
setOrganizationId,
|
||||
buildInfo,
|
||||
supportLinks,
|
||||
onSignOut,
|
||||
}) => {
|
||||
@ -128,6 +134,43 @@ export const UserDropdownContent: FC<UserDropdownContentProps> = ({
|
||||
|
||||
<Divider css={{ marginBottom: 8 }} />
|
||||
|
||||
{organizations && (
|
||||
<>
|
||||
<div>
|
||||
<div
|
||||
css={{
|
||||
padding: "8px 20px 6px",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1.1,
|
||||
lineHeight: 1.1,
|
||||
fontSize: "0.8em",
|
||||
}}
|
||||
>
|
||||
My teams
|
||||
</div>
|
||||
{organizations.map((org) => (
|
||||
<MenuItem
|
||||
key={org.id}
|
||||
css={styles.menuItem}
|
||||
onClick={() => {
|
||||
setOrganizationId?.(org.id);
|
||||
popover.setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
{/* <LogoutIcon css={styles.menuItemIcon} /> */}
|
||||
<Stack direction="row" spacing={1} css={styles.menuItemText}>
|
||||
{org.name}
|
||||
{organizationId === org.id && (
|
||||
<span css={{ fontSize: 12, color: "gray" }}>Current</span>
|
||||
)}
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
))}
|
||||
</div>
|
||||
<Divider css={{ marginTop: 8, marginBottom: 8 }} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Link to="/settings/account" css={styles.link}>
|
||||
<MenuItem css={styles.menuItem} onClick={onPopoverClose}>
|
||||
<AccountIcon css={styles.menuItemIcon} />
|
||||
|
Reference in New Issue
Block a user