diff --git a/site/src/components/AvatarData/AvatarData.stories.tsx b/site/src/components/AvatarData/AvatarData.stories.tsx new file mode 100644 index 0000000000000..da03672a13ef6 --- /dev/null +++ b/site/src/components/AvatarData/AvatarData.stories.tsx @@ -0,0 +1,23 @@ +import { Story } from "@storybook/react" +import React from "react" +import { AvatarData, AvatarDataProps } from "./AvatarData" + +export default { + title: "components/AvatarData", + component: AvatarData, +} + +const Template: Story = (args: AvatarDataProps) => + +export const Example = Template.bind({}) +Example.args = { + title: "coder", + subtitle: "coder@coder.com", +} + +export const WithLink = Template.bind({}) +WithLink.args = { + title: "coder", + subtitle: "coder@coder.com", + link: "/users/coder", +} diff --git a/site/src/components/AvatarData/AvatarData.tsx b/site/src/components/AvatarData/AvatarData.tsx new file mode 100644 index 0000000000000..730d6f1f742b5 --- /dev/null +++ b/site/src/components/AvatarData/AvatarData.tsx @@ -0,0 +1,67 @@ +import Avatar from "@material-ui/core/Avatar" +import Link from "@material-ui/core/Link" +import { makeStyles } from "@material-ui/core/styles" +import React from "react" +import { Link as RouterLink } from "react-router-dom" +import { combineClasses } from "../../util/combineClasses" +import { firstLetter } from "../../util/firstLetter" + +export interface AvatarDataProps { + title: string + subtitle: string + link?: string +} + +export const AvatarData: React.FC = ({ title, subtitle, link }) => { + const styles = useStyles() + + return ( +
+ + {firstLetter(title)} + + + {link ? ( + + {title} + {subtitle} + + ) : ( +
+ {title} + {subtitle} +
+ )} +
+ ) +} + +const useStyles = makeStyles((theme) => ({ + root: { + display: "flex", + alignItems: "center", + }, + avatar: { + borderRadius: 2, + marginRight: theme.spacing(1), + width: 24, + height: 24, + fontSize: 16, + }, + info: { + display: "flex", + flexDirection: "column", + color: theme.palette.text.primary, + + "& span": { + fontSize: 12, + color: theme.palette.text.secondary, + }, + }, + link: { + textDecoration: "none", + "&:hover": { + textDecoration: "underline", + }, + }, +})) diff --git a/site/src/components/Header/Header.test.tsx b/site/src/components/Header/Header.test.tsx deleted file mode 100644 index 35d38de89c7c3..0000000000000 --- a/site/src/components/Header/Header.test.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { screen } from "@testing-library/react" -import React from "react" -import { render } from "../../testHelpers/renderHelpers" -import { Header } from "./Header" - -describe("Header", () => { - it("renders title and subtitle", async () => { - // When - render(
) - - // Then - const titleElement = await screen.findByText("Title Test") - expect(titleElement).toBeDefined() - - const subTitleElement = await screen.findByText("Subtitle Test") - expect(subTitleElement).toBeDefined() - }) - - it("renders button if specified", async () => { - // When - render(
) - - // Then - const buttonElement = await screen.findByRole("button") - expect(buttonElement).toBeDefined() - expect(buttonElement.textContent).toEqual("Button Test") - }) -}) diff --git a/site/src/components/Header/Header.tsx b/site/src/components/Header/Header.tsx deleted file mode 100644 index 3fdf1141ece6b..0000000000000 --- a/site/src/components/Header/Header.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import Box from "@material-ui/core/Box" -import { makeStyles } from "@material-ui/core/styles" -import Typography from "@material-ui/core/Typography" -import React from "react" -import { maxWidth, sidePadding } from "../../theme/constants" -import { HeaderButton } from "../HeaderButton/HeaderButton" - -export interface HeaderAction { - readonly text: string - readonly onClick?: (event: MouseEvent) => void -} - -export interface HeaderProps { - description?: string - title: string - subTitle?: string - action?: HeaderAction -} - -export const Header: React.FC = ({ description, title, subTitle, action }) => { - const styles = useStyles() - - return ( -
-
-
- -
- - - - {title} - - - - {subTitle && ( -
- {subTitle} -
- )} -
- {description && ( - - {description} - - )} -
-
- - {action && ( - <> -
- -
- - )} -
-
-
- ) -} - -const secondaryText = "#B5BFD2" -const useStyles = makeStyles((theme) => ({ - root: {}, - top: { - position: "relative", - display: "flex", - alignItems: "center", - height: 126, - background: theme.palette.background.default, - boxShadow: theme.shadows[3], - }, - topInner: { - display: "flex", - alignItems: "center", - maxWidth, - margin: "0 auto", - flex: 1, - height: 68, - minWidth: 0, - padding: `0 ${sidePadding}`, - }, - title: { - display: "flex", - alignItems: "center", - fontWeight: "bold", - whiteSpace: "nowrap", - minWidth: 0, - color: theme.palette.primary.contrastText, - }, - description: { - display: "block", - marginTop: theme.spacing(1) / 2, - marginBottom: -26, - color: secondaryText, - }, - subtitle: { - position: "relative", - top: 2, - display: "flex", - alignItems: "center", - borderLeft: `1px solid ${theme.palette.divider}`, - height: 28, - marginLeft: 16, - paddingLeft: 16, - color: secondaryText, - }, - actions: { - paddingLeft: "50px", - paddingRight: 0, - flex: 1, - display: "flex", - flexDirection: "row", - justifyContent: "flex-end", - alignItems: "center", - }, -})) diff --git a/site/src/components/HeaderButton/HeaderButton.tsx b/site/src/components/HeaderButton/HeaderButton.tsx deleted file mode 100644 index ecc4559050e5c..0000000000000 --- a/site/src/components/HeaderButton/HeaderButton.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import Button from "@material-ui/core/Button" -import { makeStyles } from "@material-ui/core/styles" -import React from "react" - -export interface HeaderButtonProps { - readonly text: string - readonly disabled?: boolean - readonly onClick?: (event: MouseEvent) => void -} - -export const HeaderButton: React.FC = (props) => { - const styles = useStyles() - - return ( - - ) -} - -const useStyles = makeStyles(() => ({ - pageButton: { - whiteSpace: "nowrap", - }, -})) diff --git a/site/src/components/RoleSelect/RoleSelect.tsx b/site/src/components/RoleSelect/RoleSelect.tsx index 2527521e4d794..b644d353d15b4 100644 --- a/site/src/components/RoleSelect/RoleSelect.tsx +++ b/site/src/components/RoleSelect/RoleSelect.tsx @@ -55,5 +55,10 @@ const useStyles = makeStyles((theme: Theme) => ({ // Set a fixed width for the select. It avoids selects having different sizes // depending on how many roles they have selected. width: theme.spacing(25), + "& .MuiSelect-root": { + // Adjusting padding because it does not have label + paddingTop: theme.spacing(1.5), + paddingBottom: theme.spacing(1.5), + }, }, })) diff --git a/site/src/components/UsersTable/UsersTable.tsx b/site/src/components/UsersTable/UsersTable.tsx index 62075d66d51ef..1ed0552caa976 100644 --- a/site/src/components/UsersTable/UsersTable.tsx +++ b/site/src/components/UsersTable/UsersTable.tsx @@ -6,11 +6,11 @@ import TableHead from "@material-ui/core/TableHead" import TableRow from "@material-ui/core/TableRow" import React from "react" import * as TypesGen from "../../api/typesGenerated" +import { AvatarData } from "../AvatarData/AvatarData" import { EmptyState } from "../EmptyState/EmptyState" import { RoleSelect } from "../RoleSelect/RoleSelect" import { TableLoader } from "../TableLoader/TableLoader" import { TableRowMenu } from "../TableRowMenu/TableRowMenu" -import { UserCell } from "../UserCell/UserCell" export const Language = { pageTitle: "Users", @@ -60,7 +60,7 @@ export const UsersTable: React.FC = ({ users.map((u) => ( - {" "} + {canEditUsers ? ( diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index 92426747e8e54..f23841e20d27e 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -1,5 +1,3 @@ -import Avatar from "@material-ui/core/Avatar" -import Box from "@material-ui/core/Box" import Link from "@material-ui/core/Link" import { makeStyles } from "@material-ui/core/styles" import Table from "@material-ui/core/Table" @@ -12,10 +10,10 @@ import relativeTime from "dayjs/plugin/relativeTime" import React from "react" import { Link as RouterLink } from "react-router-dom" import * as TypesGen from "../../api/typesGenerated" +import { AvatarData } from "../../components/AvatarData/AvatarData" import { Margins } from "../../components/Margins/Margins" import { Stack } from "../../components/Stack/Stack" import { TableLoader } from "../../components/TableLoader/TableLoader" -import { firstLetter } from "../../util/firstLetter" dayjs.extend(relativeTime) @@ -73,15 +71,11 @@ export const TemplatesPageView: React.FC = (props) => { {props.templates?.map((template) => ( - - - {firstLetter(template.name)} - - - {template.name} - {template.description} - - + {Language.developerCount(template.workspace_owner_count)} @@ -114,24 +108,4 @@ const useStyles = makeStyles((theme) => ({ lineHeight: `${theme.spacing(3)}px`, }, }, - templateAvatar: { - borderRadius: 2, - marginRight: theme.spacing(1), - width: 24, - height: 24, - fontSize: 16, - }, - templateLink: { - display: "flex", - flexDirection: "column", - color: theme.palette.text.primary, - textDecoration: "none", - "&:hover": { - textDecoration: "underline", - }, - "& span": { - fontSize: 12, - color: theme.palette.text.secondary, - }, - }, })) diff --git a/site/src/pages/UsersPage/UsersPage.test.tsx b/site/src/pages/UsersPage/UsersPage.test.tsx index ea57bbb978970..d0671b068e3c2 100644 --- a/site/src/pages/UsersPage/UsersPage.test.tsx +++ b/site/src/pages/UsersPage/UsersPage.test.tsx @@ -103,13 +103,13 @@ describe("Users Page", () => { expect(users.length).toEqual(2) }) - it("shows 'New user' button to an authorized user", () => { + it("shows 'Create user' button to an authorized user", () => { render() - const newUserButton = screen.queryByText(UsersViewLanguage.newUserButton) - expect(newUserButton).toBeDefined() + const createUserButton = screen.queryByText(UsersViewLanguage.createButton) + expect(createUserButton).toBeDefined() }) - it("does not show 'New user' button to unauthorized user", () => { + it("does not show 'Create user' button to unauthorized user", () => { server.use( rest.post("/api/v2/users/:userId/authorization", async (req, res, ctx) => { const permissions = Object.keys(permissionsToCheck) @@ -125,8 +125,8 @@ describe("Users Page", () => { }), ) render() - const newUserButton = screen.queryByText(UsersViewLanguage.newUserButton) - expect(newUserButton).toBeNull() + const createUserButton = screen.queryByText(UsersViewLanguage.createButton) + expect(createUserButton).toBeNull() }) describe("suspend user", () => { diff --git a/site/src/pages/UsersPage/UsersPageView.tsx b/site/src/pages/UsersPage/UsersPageView.tsx index be5367d748ab5..9bf6a4d93ac65 100644 --- a/site/src/pages/UsersPage/UsersPageView.tsx +++ b/site/src/pages/UsersPage/UsersPageView.tsx @@ -1,14 +1,16 @@ +import Button from "@material-ui/core/Button" +import { makeStyles } from "@material-ui/core/styles" +import AddCircleOutline from "@material-ui/icons/AddCircleOutline" import React from "react" import * as TypesGen from "../../api/typesGenerated" import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" -import { Header } from "../../components/Header/Header" import { Margins } from "../../components/Margins/Margins" import { Stack } from "../../components/Stack/Stack" import { UsersTable } from "../../components/UsersTable/UsersTable" export const Language = { pageTitle: "Users", - newUserButton: "New user", + createButton: "New user", } export interface UsersPageViewProps { @@ -38,11 +40,20 @@ export const UsersPageView: React.FC = ({ canCreateUser, isLoading, }) => { - const newUserAction = canCreateUser ? { text: Language.newUserButton, onClick: openUserCreationDialog } : undefined + const styles = useStyles() + return ( -
+
+
+ {canCreateUser && ( + + )} +
+
{error ? ( ) : ( @@ -61,3 +72,16 @@ export const UsersPageView: React.FC = ({ ) } + +const useStyles = makeStyles((theme) => ({ + actions: { + marginTop: theme.spacing(3), + marginBottom: theme.spacing(3), + display: "flex", + height: theme.spacing(6), + + "& > *": { + marginLeft: "auto", + }, + }, +})) diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index 5340005dc1e07..502bd76b2c0c7 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -139,8 +139,12 @@ describe("Workspace Page", () => { it("shows the timeline build", async () => { await renderWorkspacePage() const table = await screen.findByTestId("builds-table") - const rows = table.querySelectorAll("tbody > tr") - expect(rows).toHaveLength(MockBuilds.length) + + // Wait for the results to be loaded + await waitFor(async () => { + const rows = table.querySelectorAll("tbody > tr") + expect(rows).toHaveLength(MockBuilds.length) + }) }) }) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx index f0a4b44ac94b1..bdf72223b998a 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx @@ -1,4 +1,3 @@ -import Avatar from "@material-ui/core/Avatar" import Button from "@material-ui/core/Button" import Link from "@material-ui/core/Link" import { makeStyles, Theme } from "@material-ui/core/styles" @@ -14,9 +13,9 @@ import relativeTime from "dayjs/plugin/relativeTime" import React from "react" import { Link as RouterLink } from "react-router-dom" import * as TypesGen from "../../api/typesGenerated" +import { AvatarData } from "../../components/AvatarData/AvatarData" import { Margins } from "../../components/Margins/Margins" import { Stack } from "../../components/Stack/Stack" -import { firstLetter } from "../../util/firstLetter" import { getDisplayStatus } from "../../util/workspace" dayjs.extend(relativeTime) @@ -73,15 +72,11 @@ export const WorkspacesPageView: React.FC = (props) => return ( -
- - {firstLetter(workspace.name)} - - - {workspace.name} - {workspace.owner_name} - -
+
{workspace.template_name} @@ -134,28 +129,4 @@ const useStyles = makeStyles((theme) => ({ lineHeight: `${theme.spacing(3)}px`, }, }, - workspaceAvatar: { - borderRadius: 2, - marginRight: theme.spacing(1), - width: 24, - height: 24, - fontSize: 16, - }, - workspaceName: { - display: "flex", - alignItems: "center", - }, - workspaceLink: { - display: "flex", - flexDirection: "column", - color: theme.palette.text.primary, - textDecoration: "none", - "&:hover": { - textDecoration: "underline", - }, - "& span": { - fontSize: 12, - color: theme.palette.text.secondary, - }, - }, }))