mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +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> & {
|
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
|
// 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 type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { chromaticWithTablet } from "testHelpers/chromatic";
|
import { chromaticWithTablet } from "testHelpers/chromatic";
|
||||||
import { MockUser, MockUser2 } from "testHelpers/entities";
|
import { MockUser, MockUser2 } from "testHelpers/entities";
|
||||||
|
import { withDashboardProvider } from "testHelpers/storybook";
|
||||||
import { NavbarView } from "./NavbarView";
|
import { NavbarView } from "./NavbarView";
|
||||||
|
|
||||||
const meta: Meta<typeof NavbarView> = {
|
const meta: Meta<typeof NavbarView> = {
|
||||||
@ -10,6 +11,7 @@ const meta: Meta<typeof NavbarView> = {
|
|||||||
args: {
|
args: {
|
||||||
user: MockUser,
|
user: MockUser,
|
||||||
},
|
},
|
||||||
|
decorators: [withDashboardProvider],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { expect, screen, userEvent, within, waitFor } from "@storybook/test";
|
import { expect, screen, userEvent, within, waitFor } from "@storybook/test";
|
||||||
import { MockBuildInfo, MockUser } from "testHelpers/entities";
|
import { MockBuildInfo, MockUser } from "testHelpers/entities";
|
||||||
|
import { withDashboardProvider } from "testHelpers/storybook";
|
||||||
import { UserDropdown } from "./UserDropdown";
|
import { UserDropdown } from "./UserDropdown";
|
||||||
|
|
||||||
const meta: Meta<typeof UserDropdown> = {
|
const meta: Meta<typeof UserDropdown> = {
|
||||||
@ -16,6 +17,7 @@ const meta: Meta<typeof UserDropdown> = {
|
|||||||
{ icon: "/icon/aws.svg", name: "Amazon Web Services", target: "" },
|
{ icon: "/icon/aws.svg", name: "Amazon Web Services", target: "" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
decorators: [withDashboardProvider],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { css, type Interpolation, type Theme, useTheme } from "@emotion/react";
|
import { css, type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||||
import Badge from "@mui/material/Badge";
|
import Badge from "@mui/material/Badge";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { myOrganizations } from "api/queries/users";
|
||||||
import type * as TypesGen from "api/typesGenerated";
|
import type * as TypesGen from "api/typesGenerated";
|
||||||
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
|
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
|
||||||
import {
|
import {
|
||||||
@ -9,6 +11,7 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "components/Popover/Popover";
|
} from "components/Popover/Popover";
|
||||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||||
|
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||||
import { BUTTON_SM_HEIGHT, navHeight } from "theme/constants";
|
import { BUTTON_SM_HEIGHT, navHeight } from "theme/constants";
|
||||||
import { UserDropdownContent } from "./UserDropdownContent";
|
import { UserDropdownContent } from "./UserDropdownContent";
|
||||||
|
|
||||||
@ -26,6 +29,11 @@ export const UserDropdown: FC<UserDropdownProps> = ({
|
|||||||
onSignOut,
|
onSignOut,
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const organizationsQuery = useQuery({
|
||||||
|
...myOrganizations(),
|
||||||
|
enabled: Boolean(localStorage.getItem("enableMultiOrganizationUi")),
|
||||||
|
});
|
||||||
|
const { organizationId, setOrganizationId } = useDashboard();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
@ -63,6 +71,9 @@ export const UserDropdown: FC<UserDropdownProps> = ({
|
|||||||
user={user}
|
user={user}
|
||||||
buildInfo={buildInfo}
|
buildInfo={buildInfo}
|
||||||
supportLinks={supportLinks}
|
supportLinks={supportLinks}
|
||||||
|
organizations={organizationsQuery.data}
|
||||||
|
organizationId={organizationId}
|
||||||
|
setOrganizationId={setOrganizationId}
|
||||||
onSignOut={onSignOut}
|
onSignOut={onSignOut}
|
||||||
/>
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
|
@ -84,14 +84,20 @@ const styles = {
|
|||||||
|
|
||||||
export interface UserDropdownContentProps {
|
export interface UserDropdownContentProps {
|
||||||
user: TypesGen.User;
|
user: TypesGen.User;
|
||||||
|
organizations?: TypesGen.Organization[];
|
||||||
|
organizationId?: string;
|
||||||
|
setOrganizationId?: (id: string) => void;
|
||||||
buildInfo?: TypesGen.BuildInfoResponse;
|
buildInfo?: TypesGen.BuildInfoResponse;
|
||||||
supportLinks?: readonly TypesGen.LinkConfig[];
|
supportLinks?: readonly TypesGen.LinkConfig[];
|
||||||
onSignOut: () => void;
|
onSignOut: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserDropdownContent: FC<UserDropdownContentProps> = ({
|
export const UserDropdownContent: FC<UserDropdownContentProps> = ({
|
||||||
buildInfo,
|
|
||||||
user,
|
user,
|
||||||
|
organizations,
|
||||||
|
organizationId,
|
||||||
|
setOrganizationId,
|
||||||
|
buildInfo,
|
||||||
supportLinks,
|
supportLinks,
|
||||||
onSignOut,
|
onSignOut,
|
||||||
}) => {
|
}) => {
|
||||||
@ -128,6 +134,43 @@ export const UserDropdownContent: FC<UserDropdownContentProps> = ({
|
|||||||
|
|
||||||
<Divider css={{ marginBottom: 8 }} />
|
<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}>
|
<Link to="/settings/account" css={styles.link}>
|
||||||
<MenuItem css={styles.menuItem} onClick={onPopoverClose}>
|
<MenuItem css={styles.menuItem} onClick={onPopoverClose}>
|
||||||
<AccountIcon css={styles.menuItemIcon} />
|
<AccountIcon css={styles.menuItemIcon} />
|
||||||
|
Reference in New Issue
Block a user