diff --git a/site/.eslintrc.yaml b/site/.eslintrc.yaml
index 0991916c76064..e2d919bcd85e9 100644
--- a/site/.eslintrc.yaml
+++ b/site/.eslintrc.yaml
@@ -142,6 +142,8 @@ rules:
- name: "@mui/material/Typography"
message:
"You should use the native HTML elements as span, p, h1, h2, h3..."
+ - name: "@mui/material/Box"
+ message: "You should use a
Active Users
How do we calculate active users?
@@ -148,7 +146,7 @@ export const ActiveUsersTitle = () => {
considered an active user. e.g. apps, web terminal, SSH
-
+
);
};
diff --git a/site/src/components/Alert/Alert.tsx b/site/src/components/Alert/Alert.tsx
index bba4044c4ce86..6191de2c55592 100644
--- a/site/src/components/Alert/Alert.tsx
+++ b/site/src/components/Alert/Alert.tsx
@@ -1,9 +1,15 @@
-import { useState, FC, ReactNode } from "react";
+import {
+ useState,
+ type FC,
+ type ReactNode,
+ type PropsWithChildren,
+} from "react";
import Collapse from "@mui/material/Collapse";
// eslint-disable-next-line no-restricted-imports -- It is the base component
-import MuiAlert, { AlertProps as MuiAlertProps } from "@mui/material/Alert";
+import MuiAlert, {
+ type AlertProps as MuiAlertProps,
+} from "@mui/material/Alert";
import Button from "@mui/material/Button";
-import Box from "@mui/material/Box";
export type AlertProps = MuiAlertProps & {
actions?: ReactNode;
@@ -32,7 +38,7 @@ export const Alert: FC = ({
@@ -62,15 +68,13 @@ export const Alert: FC = ({
);
};
-export const AlertDetail = ({ children }: { children: ReactNode }) => {
+export const AlertDetail: FC = ({ children }) => {
return (
- theme.palette.text.secondary}
- fontSize={13}
+ ({ color: theme.palette.text.secondary, fontSize: 13 })}
data-chromatic="ignore"
>
{children}
-
+
);
};
diff --git a/site/src/components/Avatar/Avatar.tsx b/site/src/components/Avatar/Avatar.tsx
index f47f85e32657d..ebc234e8480a3 100644
--- a/site/src/components/Avatar/Avatar.tsx
+++ b/site/src/components/Avatar/Avatar.tsx
@@ -1,9 +1,10 @@
// This is the only place MuiAvatar can be used
// eslint-disable-next-line no-restricted-imports -- Read above
-import MuiAvatar, { AvatarProps as MuiAvatarProps } from "@mui/material/Avatar";
-import { FC, useId } from "react";
+import MuiAvatar, {
+ type AvatarProps as MuiAvatarProps,
+} from "@mui/material/Avatar";
+import { type FC, useId } from "react";
import { css, type Interpolation, type Theme } from "@emotion/react";
-import { Box } from "@mui/system";
import { visuallyHidden } from "@mui/utils";
export type AvatarProps = MuiAvatarProps & {
@@ -78,6 +79,8 @@ export const AvatarIcon: FC = ({ src, alt }) => {
const hookId = useId();
const avatarId = `${hookId}-avatar`;
+ // We use a `visuallyHidden` element instead of setting `alt` to avoid
+ // splatting the text out on the screen if the image fails to load.
return (
<>
= ({ src, alt }) => {
css={{ maxWidth: "50%" }}
aria-labelledby={avatarId}
/>
-
+
{alt}
-
+
>
);
};
diff --git a/site/src/components/Dashboard/DashboardLayout.tsx b/site/src/components/Dashboard/DashboardLayout.tsx
index e06888c6b3776..35cc12611e59d 100644
--- a/site/src/components/Dashboard/DashboardLayout.tsx
+++ b/site/src/components/Dashboard/DashboardLayout.tsx
@@ -1,18 +1,17 @@
-import { DeploymentBanner } from "./DeploymentBanner/DeploymentBanner";
+import Snackbar from "@mui/material/Snackbar";
+import Link from "@mui/material/Link";
+import Button from "@mui/material/Button";
+import InfoOutlined from "@mui/icons-material/InfoOutlined";
+import { type FC, type HTMLAttributes, Suspense } from "react";
+import { Outlet } from "react-router-dom";
import { LicenseBanner } from "components/Dashboard/LicenseBanner/LicenseBanner";
import { Loader } from "components/Loader/Loader";
import { ServiceBanner } from "components/Dashboard/ServiceBanner/ServiceBanner";
import { usePermissions } from "hooks/usePermissions";
-import { FC, Suspense } from "react";
-import { Outlet } from "react-router-dom";
import { dashboardContentBottomPadding } from "theme/constants";
-import { Navbar } from "./Navbar/Navbar";
-import Snackbar from "@mui/material/Snackbar";
-import Link from "@mui/material/Link";
-import Box, { BoxProps } from "@mui/material/Box";
-import InfoOutlined from "@mui/icons-material/InfoOutlined";
-import Button from "@mui/material/Button";
import { docs } from "utils/docs";
+import { Navbar } from "./Navbar/Navbar";
+import { DeploymentBanner } from "./DeploymentBanner/DeploymentBanner";
import { useUpdateCheck } from "./useUpdateCheck";
export const DashboardLayout: FC = () => {
@@ -74,21 +73,21 @@ export const DashboardLayout: FC = () => {
}),
}}
message={
-
+
({
+ css={(theme) => ({
fontSize: 16,
height: 20, // 20 is the height of the text line so we can align them
color: theme.palette.info.light,
})}
/>
-
+
Coder {updateCheck.data?.version} is now available. View the{" "}
release notes and{" "}
upgrade instructions{" "}
for more information.
-
{`
This change will result in ${inactiveWorkspacesToGoDormant} workspaces being immediately transitioned to the dormant state and ${inactiveWorkspacesToGoDormantInWeek} over the next seven days. To prevent this, do you want to reset the inactivity period for all template workspaces?`}
> = ({
{showDeletionWarning && (
<>
-
{"Dormancy Auto-Deletion"}
+
Dormancy Auto-Deletion
-
{`This change will result in ${dormantWorkspacesToBeDeleted} workspaces being immediately deleted and ${dormantWorkspacesToBeDeletedInWeek} over the next 7 days. To prevent this, do you want to reset the dormancy period for all template workspaces?`}
+
+ This change will result in {dormantWorkspacesToBeDeleted}{" "}
+ workspaces being immediately deleted and{" "}
+ {dormantWorkspacesToBeDeletedInWeek} over the next 7 days. To
+ prevent this, do you want to reset the dormancy period for all
+ template workspaces?
+
Deleting these workspaces is irreversible! Are you sure you want to
proceed? Type{" "}
- theme.palette.text.primary,
+ color: theme.palette.text.primary,
fontWeight: 600,
}}
>
`DELETE`
- {" "}
+ {" "}
to confirm.
-
+
{
const value = e.currentTarget?.value;
setConfirmation((c) => ({ ...c, value }));
diff --git a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx
index cbbd8e63fa537..a50510a0ccaa7 100644
--- a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx
+++ b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx
@@ -1,11 +1,10 @@
-import { type PropsWithChildren, type ReactNode, useState } from "react";
+import { type FC, type ReactNode, useState } from "react";
import { type Template } from "api/typesGenerated";
import { type UseQueryResult } from "react-query";
import {
Link as RouterLink,
LinkProps as RouterLinkProps,
} from "react-router-dom";
-import Box from "@mui/system/Box";
import Button from "@mui/material/Button";
import Link from "@mui/material/Link";
import AddIcon from "@mui/icons-material/AddOutlined";
@@ -25,16 +24,17 @@ const ICON_SIZE = 18;
type TemplatesQuery = UseQueryResult;
-type WorkspacesButtonProps = PropsWithChildren<{
+interface WorkspacesButtonProps {
+ children?: ReactNode;
templatesFetchStatus: TemplatesQuery["status"];
templates: TemplatesQuery["data"];
-}>;
+}
-export function WorkspacesButton({
+export const WorkspacesButton: FC = ({
children,
templatesFetchStatus,
templates,
-}: WorkspacesButtonProps) {
+}) => {
// Dataset should always be small enough that client-side filtering should be
// good enough. Can swap out down the line if it becomes an issue
const [searchTerm, setSearchTerm] = useState("");
@@ -69,15 +69,16 @@ export function WorkspacesButton({
onValueChange={(newValue) => setSearchTerm(newValue)}
placeholder="Type/select a workspace template"
label="Template select for workspace"
- sx={{ flexShrink: 0, columnGap: 1.5 }}
+ css={{ flexShrink: 0, columnGap: 12 }}
/>
{templatesFetchStatus === "loading" ? (
@@ -93,7 +94,7 @@ export function WorkspacesButton({
)}
- ({
padding: "8px 0",
borderTop: `1px solid ${theme.palette.divider}`,
@@ -105,20 +106,23 @@ export function WorkspacesButton({
display: "flex",
alignItems: "center",
columnGap: 12,
-
color: theme.palette.primary.main,
})}
>
See all templates
-
+
);
+};
+
+interface WorkspaceResultsRowProps {
+ template: Template;
}
-function WorkspaceResultsRow({ template }: { template: Template }) {
+const WorkspaceResultsRow: FC = ({ template }) => {
return (
- ({
color: theme.palette.text.primary,
display: "flex",
@@ -170,15 +174,15 @@ function WorkspaceResultsRow({ template }: { template: Template }) {
developer
{template.active_user_count === 1 ? "" : "s"}
-
+
);
-}
+};
-function PopoverLink(props: RouterLinkProps) {
+const PopoverLink: FC = ({ children, ...linkProps }) => {
return (
({
color: theme.palette.text.primary,
padding: "8px 16px",
@@ -193,9 +197,11 @@ function PopoverLink(props: RouterLinkProps) {
backgroundColor: theme.palette.action.hover,
},
})}
- />
+ >
+ {children}
+
);
-}
+};
function sortTemplatesByUsersDesc(
templates: readonly Template[],
diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx
index fe096ef9c065d..c68e1cdfbcae6 100644
--- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx
+++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx
@@ -12,7 +12,6 @@ import { ErrorAlert } from "components/Alert/ErrorAlert";
import { WorkspacesFilter } from "./filter/filter";
import { hasError, isApiValidationError } from "api/errors";
import { TableToolbar } from "components/TableToolbar/TableToolbar";
-import Box from "@mui/material/Box";
import DeleteOutlined from "@mui/icons-material/DeleteOutlined";
import { WorkspacesButton } from "./WorkspacesButton";
import { UseQueryResult } from "react-query";
@@ -136,11 +135,11 @@ export const WorkspacesPageView = ({
{checkedWorkspaces.length > 0 ? (
<>
-
+