Skip to content

Commit fc6f18a

Browse files
authored
feat(site): add an organization switcher to the user menu (coder#13269)
1 parent 1f5788f commit fc6f18a

File tree

6 files changed

+67
-2
lines changed

6 files changed

+67
-2
lines changed

site/src/api/queries/users.ts

+7
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,10 @@ export const updateAppearanceSettings = (
249249
},
250250
};
251251
};
252+
253+
export const myOrganizations = () => {
254+
return {
255+
queryKey: ["organizations", "me"],
256+
queryFn: () => API.getOrganizations(),
257+
};
258+
};

site/src/contexts/auth/RequireAuth.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const RequireAuth: FC = () => {
6767
};
6868

6969
type RequireKeys<T, R extends keyof T> = Omit<T, R> & {
70-
[K in keyof Pick<T, R>]: NonNullable<T[K]>;
70+
[K in keyof Pick<T, R>]-?: NonNullable<T[K]>;
7171
};
7272

7373
// We can do some TS magic here but I would rather to be explicit on what

site/src/modules/dashboard/Navbar/NavbarView.stories.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Meta, StoryObj } from "@storybook/react";
22
import { chromaticWithTablet } from "testHelpers/chromatic";
33
import { MockUser, MockUser2 } from "testHelpers/entities";
4+
import { withDashboardProvider } from "testHelpers/storybook";
45
import { NavbarView } from "./NavbarView";
56

67
const meta: Meta<typeof NavbarView> = {
@@ -10,6 +11,7 @@ const meta: Meta<typeof NavbarView> = {
1011
args: {
1112
user: MockUser,
1213
},
14+
decorators: [withDashboardProvider],
1315
};
1416

1517
export default meta;

site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Meta, StoryObj } from "@storybook/react";
22
import { expect, screen, userEvent, within, waitFor } from "@storybook/test";
33
import { MockBuildInfo, MockUser } from "testHelpers/entities";
4+
import { withDashboardProvider } from "testHelpers/storybook";
45
import { UserDropdown } from "./UserDropdown";
56

67
const meta: Meta<typeof UserDropdown> = {
@@ -16,6 +17,7 @@ const meta: Meta<typeof UserDropdown> = {
1617
{ icon: "/icon/aws.svg", name: "Amazon Web Services", target: "" },
1718
],
1819
},
20+
decorators: [withDashboardProvider],
1921
};
2022

2123
export default meta;

site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { css, type Interpolation, type Theme, useTheme } from "@emotion/react";
22
import Badge from "@mui/material/Badge";
33
import type { FC } from "react";
4+
import { useQuery } from "react-query";
5+
import { myOrganizations } from "api/queries/users";
46
import type * as TypesGen from "api/typesGenerated";
57
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
68
import {
@@ -9,6 +11,7 @@ import {
911
PopoverTrigger,
1012
} from "components/Popover/Popover";
1113
import { UserAvatar } from "components/UserAvatar/UserAvatar";
14+
import { useDashboard } from "modules/dashboard/useDashboard";
1215
import { BUTTON_SM_HEIGHT, navHeight } from "theme/constants";
1316
import { UserDropdownContent } from "./UserDropdownContent";
1417

@@ -26,6 +29,11 @@ export const UserDropdown: FC<UserDropdownProps> = ({
2629
onSignOut,
2730
}) => {
2831
const theme = useTheme();
32+
const organizationsQuery = useQuery({
33+
...myOrganizations(),
34+
enabled: Boolean(localStorage.getItem("enableMultiOrganizationUi")),
35+
});
36+
const { organizationId, setOrganizationId } = useDashboard();
2937

3038
return (
3139
<Popover>
@@ -63,6 +71,9 @@ export const UserDropdown: FC<UserDropdownProps> = ({
6371
user={user}
6472
buildInfo={buildInfo}
6573
supportLinks={supportLinks}
74+
organizations={organizationsQuery.data}
75+
organizationId={organizationId}
76+
setOrganizationId={setOrganizationId}
6677
onSignOut={onSignOut}
6778
/>
6879
</PopoverContent>

site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.tsx

+44-1
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,20 @@ const styles = {
8484

8585
export interface UserDropdownContentProps {
8686
user: TypesGen.User;
87+
organizations?: TypesGen.Organization[];
88+
organizationId?: string;
89+
setOrganizationId?: (id: string) => void;
8790
buildInfo?: TypesGen.BuildInfoResponse;
8891
supportLinks?: readonly TypesGen.LinkConfig[];
8992
onSignOut: () => void;
9093
}
9194

9295
export const UserDropdownContent: FC<UserDropdownContentProps> = ({
93-
buildInfo,
9496
user,
97+
organizations,
98+
organizationId,
99+
setOrganizationId,
100+
buildInfo,
95101
supportLinks,
96102
onSignOut,
97103
}) => {
@@ -128,6 +134,43 @@ export const UserDropdownContent: FC<UserDropdownContentProps> = ({
128134

129135
<Divider css={{ marginBottom: 8 }} />
130136

137+
{organizations && (
138+
<>
139+
<div>
140+
<div
141+
css={{
142+
padding: "8px 20px 6px",
143+
textTransform: "uppercase",
144+
letterSpacing: 1.1,
145+
lineHeight: 1.1,
146+
fontSize: "0.8em",
147+
}}
148+
>
149+
My teams
150+
</div>
151+
{organizations.map((org) => (
152+
<MenuItem
153+
key={org.id}
154+
css={styles.menuItem}
155+
onClick={() => {
156+
setOrganizationId?.(org.id);
157+
popover.setIsOpen(false);
158+
}}
159+
>
160+
{/* <LogoutIcon css={styles.menuItemIcon} /> */}
161+
<Stack direction="row" spacing={1} css={styles.menuItemText}>
162+
{org.name}
163+
{organizationId === org.id && (
164+
<span css={{ fontSize: 12, color: "gray" }}>Current</span>
165+
)}
166+
</Stack>
167+
</MenuItem>
168+
))}
169+
</div>
170+
<Divider css={{ marginTop: 8, marginBottom: 8 }} />
171+
</>
172+
)}
173+
131174
<Link to="/settings/account" css={styles.link}>
132175
<MenuItem css={styles.menuItem} onClick={onPopoverClose}>
133176
<AccountIcon css={styles.menuItemIcon} />

0 commit comments

Comments
 (0)