mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: migrate Alert component from MUI to shadcn and update to new Alert designs (#18412)
--- Although originally created with Blink most of the PR has been re-written since then ## Summary This PR migrates the Alert component from MUI to shadcn implementation while maintaining full backward compatibility with the existing API. Updates Alerts to new design in Figma. Figma design: https://www.figma.com/design/WfqIgsTFXN2BscBSSyXWF8/Coder-kit?node-id=3522-3005&m=dev <img width="1008" height="623" alt="Screenshot 2025-12-18 at 20 37 32" src="https://github.com/user-attachments/assets/8b2077f1-f746-4a9a-8b58-3ec3477c247b" /> --------- Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com> Co-authored-by: jaaydenh <1858163+jaaydenh@users.noreply.github.com> Co-authored-by: Jaayden Halko <jaayden@coder.com>
This commit is contained in:
@@ -54,3 +54,37 @@ export const WarningWithActionAndDismiss: Story = {
|
||||
severity: "warning",
|
||||
},
|
||||
};
|
||||
|
||||
export const Info: Story = {
|
||||
args: {
|
||||
children: "This is an informational message",
|
||||
severity: "info",
|
||||
},
|
||||
};
|
||||
|
||||
export const ErrorSeverity: Story = {
|
||||
args: {
|
||||
children: "This is an error message",
|
||||
severity: "error",
|
||||
},
|
||||
};
|
||||
|
||||
export const WarningProminent: Story = {
|
||||
args: {
|
||||
children:
|
||||
"This is a high risk warning. Use this design only for high risk warnings.",
|
||||
severity: "warning",
|
||||
prominent: true,
|
||||
dismissible: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const ErrorProminent: Story = {
|
||||
args: {
|
||||
children:
|
||||
"This is a crucial error. Use this design only for crucial errors.",
|
||||
severity: "error",
|
||||
prominent: true,
|
||||
dismissible: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,21 +1,81 @@
|
||||
import MuiAlert, {
|
||||
type AlertColor as MuiAlertColor,
|
||||
type AlertProps as MuiAlertProps,
|
||||
} from "@mui/material/Alert";
|
||||
import Collapse from "@mui/material/Collapse";
|
||||
import { cva } from "class-variance-authority";
|
||||
import { Button } from "components/Button/Button";
|
||||
import {
|
||||
CircleAlertIcon,
|
||||
CircleCheckIcon,
|
||||
InfoIcon,
|
||||
TriangleAlertIcon,
|
||||
XIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
type FC,
|
||||
forwardRef,
|
||||
type PropsWithChildren,
|
||||
type ReactNode,
|
||||
useState,
|
||||
} from "react";
|
||||
export type AlertColor = MuiAlertColor;
|
||||
import { cn } from "utils/cn";
|
||||
|
||||
export type AlertProps = MuiAlertProps & {
|
||||
const alertVariants = cva(
|
||||
"relative w-full rounded-lg border border-solid p-4 text-left",
|
||||
{
|
||||
variants: {
|
||||
severity: {
|
||||
info: "",
|
||||
success: "",
|
||||
warning: "",
|
||||
error: "",
|
||||
},
|
||||
prominent: {
|
||||
true: "",
|
||||
false: "",
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
prominent: false,
|
||||
className: "border-border-default bg-surface-secondary",
|
||||
},
|
||||
{
|
||||
severity: "success",
|
||||
prominent: true,
|
||||
className: "border-border-success bg-surface-green",
|
||||
},
|
||||
{
|
||||
severity: "warning",
|
||||
prominent: true,
|
||||
className: "border-border-warning bg-surface-orange",
|
||||
},
|
||||
{
|
||||
severity: "error",
|
||||
prominent: true,
|
||||
className: "border-border-destructive bg-surface-red",
|
||||
},
|
||||
],
|
||||
defaultVariants: {
|
||||
severity: "info",
|
||||
prominent: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const severityIcons = {
|
||||
info: { icon: InfoIcon, className: "text-highlight-sky" },
|
||||
success: { icon: CircleCheckIcon, className: "text-content-success" },
|
||||
warning: { icon: TriangleAlertIcon, className: "text-content-warning" },
|
||||
error: { icon: CircleAlertIcon, className: "text-content-destructive" },
|
||||
} as const;
|
||||
|
||||
export type AlertColor = "info" | "success" | "warning" | "error";
|
||||
|
||||
export type AlertProps = {
|
||||
actions?: ReactNode;
|
||||
dismissible?: boolean;
|
||||
onDismiss?: () => void;
|
||||
severity?: AlertColor;
|
||||
prominent?: boolean;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const Alert: FC<AlertProps> = ({
|
||||
@@ -23,59 +83,69 @@ export const Alert: FC<AlertProps> = ({
|
||||
actions,
|
||||
dismissible,
|
||||
severity = "info",
|
||||
prominent = false,
|
||||
onDismiss,
|
||||
...alertProps
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
// Can't only rely on MUI's hiding behavior inside flex layouts, because even
|
||||
// though MUI will make a dismissed alert have zero height, the alert will
|
||||
// still behave as a flex child and introduce extra row/column gaps
|
||||
if (!open) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapse in>
|
||||
<MuiAlert
|
||||
{...alertProps}
|
||||
css={{ textAlign: "left" }}
|
||||
severity={severity}
|
||||
action={
|
||||
<>
|
||||
{/* CTAs passed in by the consumer */}
|
||||
{actions}
|
||||
const { icon: Icon, className: iconClassName } = severityIcons[severity];
|
||||
|
||||
{/* close CTA */}
|
||||
{dismissible && (
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
onDismiss?.();
|
||||
}}
|
||||
data-testid="dismiss-banner-btn"
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</MuiAlert>
|
||||
</Collapse>
|
||||
return (
|
||||
<div
|
||||
role="alert"
|
||||
className={cn(alertVariants({ severity, prominent }), className)}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4 text-sm">
|
||||
<div className="flex flex-row items-start gap-3">
|
||||
<Icon className={cn("size-icon-sm mt-[3px]", iconClassName)} />
|
||||
<div className="flex-1">{children}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{actions}
|
||||
|
||||
{dismissible && (
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="icon"
|
||||
className="!size-auto !min-w-0 !p-0"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
onDismiss?.();
|
||||
}}
|
||||
data-testid="dismiss-banner-btn"
|
||||
aria-label="Dismiss"
|
||||
>
|
||||
<XIcon className="!size-icon-sm !p-0" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AlertDetail: FC<PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<span
|
||||
css={(theme) => ({ color: theme.palette.text.secondary, fontSize: 13 })}
|
||||
data-chromatic="ignore"
|
||||
>
|
||||
<span className="m-0 text-sm" data-chromatic="ignore">
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const AlertTitle = forwardRef<
|
||||
HTMLHeadingElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<h1
|
||||
ref={ref}
|
||||
className={cn("m-0 mb-1 text-sm font-medium", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import AlertTitle from "@mui/material/AlertTitle";
|
||||
import { getErrorDetail, getErrorMessage, getErrorStatus } from "api/errors";
|
||||
import type { FC } from "react";
|
||||
import { Link } from "../Link/Link";
|
||||
import { Alert, AlertDetail, type AlertProps } from "./Alert";
|
||||
import { Alert, AlertDetail, type AlertProps, AlertTitle } from "./Alert";
|
||||
|
||||
type ErrorAlertProps = Readonly<
|
||||
Omit<AlertProps, "severity" | "children"> & { error: unknown }
|
||||
@@ -18,7 +17,7 @@ export const ErrorAlert: FC<ErrorAlertProps> = ({ error, ...alertProps }) => {
|
||||
const shouldDisplayDetail = message !== detail;
|
||||
|
||||
return (
|
||||
<Alert severity="error" {...alertProps}>
|
||||
<Alert severity="error" prominent {...alertProps}>
|
||||
{
|
||||
// When the error is a Forbidden response we include a link for the user to
|
||||
// go back to a known viewable page.
|
||||
|
||||
@@ -24,7 +24,7 @@ const badgeVariants = cva(
|
||||
"border border-solid border-border-destructive bg-surface-red text-highlight-red shadow",
|
||||
green:
|
||||
"border border-solid border-border-green bg-surface-green text-highlight-green shadow",
|
||||
info: "border border-solid border-border-sky bg-surface-sky text-highlight-sky shadow",
|
||||
info: "border border-solid border-border-pending bg-surface-sky text-highlight-sky shadow",
|
||||
},
|
||||
size: {
|
||||
xs: "text-2xs font-regular h-5 [&_svg]:hidden rounded px-1.5",
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import AlertTitle from "@mui/material/AlertTitle";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import Link from "@mui/material/Link";
|
||||
import type { ApiErrorResponse } from "api/errors";
|
||||
import type { ExternalAuthDevice } from "api/typesGenerated";
|
||||
import { isAxiosError } from "axios";
|
||||
import { Alert, AlertDetail } from "components/Alert/Alert";
|
||||
import { Alert, AlertDetail, AlertTitle } from "components/Alert/Alert";
|
||||
import { CopyButton } from "components/CopyButton/CopyButton";
|
||||
import { ExternalLinkIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
@@ -102,7 +101,9 @@ export const GitDeviceAuth: FC<GitDeviceAuthProps> = ({
|
||||
break;
|
||||
case DeviceExchangeError.AccessDenied:
|
||||
status = (
|
||||
<Alert severity="error">Access to the Git provider was denied.</Alert>
|
||||
<Alert severity="error" prominent>
|
||||
Access to the Git provider was denied.
|
||||
</Alert>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import type { Theme } from "@emotion/react";
|
||||
import AlertTitle from "@mui/material/AlertTitle";
|
||||
import { Alert, type AlertColor, AlertDetail } from "components/Alert/Alert";
|
||||
import {
|
||||
Alert,
|
||||
type AlertColor,
|
||||
AlertDetail,
|
||||
AlertTitle,
|
||||
} from "components/Alert/Alert";
|
||||
import { ProvisionerTag } from "modules/provisioners/ProvisionerTag";
|
||||
import type { FC } from "react";
|
||||
import { cn } from "utils/cn";
|
||||
|
||||
export enum AlertVariant {
|
||||
// Alerts are usually styled with a full rounded border and meant to use as a visually distinct element of the page.
|
||||
// The Standalone variant conforms to this styling.
|
||||
@@ -21,20 +26,21 @@ interface ProvisionerAlertProps {
|
||||
variant?: AlertVariant;
|
||||
}
|
||||
|
||||
const getAlertStyles = (variant: AlertVariant, severity: AlertColor) => {
|
||||
switch (variant) {
|
||||
case AlertVariant.Inline:
|
||||
return {
|
||||
css: (theme: Theme) => ({
|
||||
borderRadius: 0,
|
||||
border: 0,
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
borderLeft: `2px solid ${theme.palette[severity].main}`,
|
||||
}),
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
const severityBorderColors: Record<AlertColor, string> = {
|
||||
info: "border-l-highlight-sky",
|
||||
success: "border-l-content-success",
|
||||
warning: "border-l-content-warning",
|
||||
error: "border-l-content-destructive",
|
||||
};
|
||||
|
||||
const getAlertClassName = (variant: AlertVariant, severity: AlertColor) => {
|
||||
if (variant === AlertVariant.Inline) {
|
||||
return cn(
|
||||
"rounded-none border-0 border-b border-l-2 border-solid border-b-border-default",
|
||||
severityBorderColors[severity],
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const ProvisionerAlert: FC<ProvisionerAlertProps> = ({
|
||||
@@ -45,7 +51,7 @@ export const ProvisionerAlert: FC<ProvisionerAlertProps> = ({
|
||||
variant = AlertVariant.Standalone,
|
||||
}) => {
|
||||
return (
|
||||
<Alert severity={severity} {...getAlertStyles(variant, severity)}>
|
||||
<Alert severity={severity} className={getAlertClassName(variant, severity)}>
|
||||
<AlertTitle>{title}</AlertTitle>
|
||||
<AlertDetail>
|
||||
<div>{detail}</div>
|
||||
|
||||
@@ -40,6 +40,7 @@ export const WildcardHostnameWarning: FC<WildcardHostnameWarningProps> = ({
|
||||
return (
|
||||
<Alert
|
||||
severity="warning"
|
||||
prominent
|
||||
className={
|
||||
hasResources
|
||||
? "rounded-none border-0 border-l-2 border-l-warning border-b-divider"
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ export const ClassicParameterFlowDeprecationWarning: FC<
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert severity="warning" className="mb-2">
|
||||
<Alert severity="warning" className="mb-2" prominent>
|
||||
<div>
|
||||
This template is using the classic parameter flow, which will be{" "}
|
||||
<strong>deprecated</strong> and removed in a future release. Please
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { css } from "@emotion/css";
|
||||
import AlertTitle from "@mui/material/AlertTitle";
|
||||
import Autocomplete from "@mui/material/Autocomplete";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import { templateVersions } from "api/queries/templates";
|
||||
import type { TemplateVersion, Workspace } from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { Alert, AlertTitle } from "components/Alert/Alert";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
|
||||
|
||||
@@ -146,7 +146,7 @@ export const DownloadLogsDialog: FC<DownloadLogsDialogProps> = ({
|
||||
</p>
|
||||
|
||||
{!isWorkspaceHealthy && isLoadingFiles && (
|
||||
<Alert severity="warning">
|
||||
<Alert severity="warning" prominent>
|
||||
Your workspace is unhealthy. Some logs may be unavailable for
|
||||
download.
|
||||
</Alert>
|
||||
|
||||
@@ -428,7 +428,7 @@ const fillNameAndDisplayWithFilename = async (
|
||||
|
||||
const ProvisionerWarning: FC = () => {
|
||||
return (
|
||||
<Alert severity="warning" css={{ marginBottom: 16 }}>
|
||||
<Alert severity="warning" css={{ marginBottom: 16 }} prominent>
|
||||
This organization does not have any provisioners. Before you create a
|
||||
template, you'll need to configure a provisioner.{" "}
|
||||
<Link href={docs("/admin/provisioners#organization-scoped-provisioners")}>
|
||||
|
||||
@@ -520,7 +520,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
||||
</hgroup>
|
||||
<div className="flex flex-col gap-4">
|
||||
{Boolean(error) && !hasAllRequiredExternalAuth && (
|
||||
<Alert severity="error">
|
||||
<Alert severity="error" prominent>
|
||||
To create a workspace using this template, please connect to
|
||||
all required external authentication providers listed below.
|
||||
</Alert>
|
||||
|
||||
@@ -69,6 +69,7 @@ export const NotificationEvents: FC<NotificationEventsProps> = ({
|
||||
{hasWebhookNotifications && !isWebhookConfigured && (
|
||||
<Alert
|
||||
severity="warning"
|
||||
prominent
|
||||
actions={
|
||||
<Button variant="subtle" size="sm" asChild>
|
||||
<a
|
||||
@@ -88,6 +89,7 @@ export const NotificationEvents: FC<NotificationEventsProps> = ({
|
||||
{hasSMTPNotifications && !isSMTPConfigured && (
|
||||
<Alert
|
||||
severity="warning"
|
||||
prominent
|
||||
actions={
|
||||
<Button variant="subtle" size="sm" asChild>
|
||||
<a
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import AlertTitle from "@mui/material/AlertTitle";
|
||||
import type {
|
||||
DAUsResponse,
|
||||
Experiment,
|
||||
@@ -15,7 +14,7 @@ import { Stack } from "components/Stack/Stack";
|
||||
import type { FC } from "react";
|
||||
import { useDeploymentOptions } from "utils/deployOptions";
|
||||
import { docs } from "utils/docs";
|
||||
import { Alert } from "../../../components/Alert/Alert";
|
||||
import { Alert, AlertTitle } from "../../../components/Alert/Alert";
|
||||
import OptionsTable from "../OptionsTable";
|
||||
import { UserEngagementChart } from "./UserEngagementChart";
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ const AccessURLPage = () => {
|
||||
actions={<HealthMessageDocsLink {...warning} />}
|
||||
key={warning.code}
|
||||
severity="warning"
|
||||
prominent
|
||||
>
|
||||
{warning.message}
|
||||
</Alert>
|
||||
|
||||
@@ -67,6 +67,7 @@ const DERPPage: FC = () => {
|
||||
actions={<HealthMessageDocsLink {...warning} />}
|
||||
key={warning.code}
|
||||
severity="warning"
|
||||
prominent
|
||||
>
|
||||
{warning.message}
|
||||
</Alert>
|
||||
|
||||
@@ -81,6 +81,7 @@ const DERPRegionPage: FC = () => {
|
||||
actions={<HealthMessageDocsLink {...warning} />}
|
||||
key={warning.code}
|
||||
severity="warning"
|
||||
prominent
|
||||
>
|
||||
{warning.message}
|
||||
</Alert>
|
||||
|
||||
@@ -37,6 +37,7 @@ const DatabasePage = () => {
|
||||
actions={<HealthMessageDocsLink {...warning} />}
|
||||
key={warning.code}
|
||||
severity="warning"
|
||||
prominent
|
||||
>
|
||||
{warning.message}
|
||||
</Alert>
|
||||
|
||||
@@ -30,7 +30,11 @@ const ProvisionerDaemonsPage: FC = () => {
|
||||
</Header>
|
||||
|
||||
<Main>
|
||||
{daemons.error && <Alert severity="error">{daemons.error}</Alert>}
|
||||
{daemons.error && (
|
||||
<Alert severity="error" prominent>
|
||||
{daemons.error}
|
||||
</Alert>
|
||||
)}
|
||||
{daemons.warnings.map((warning) => {
|
||||
return (
|
||||
<Alert
|
||||
|
||||
@@ -38,11 +38,15 @@ const WebsocketPage = () => {
|
||||
</Header>
|
||||
|
||||
<Main>
|
||||
{websocket.error && <Alert severity="error">{websocket.error}</Alert>}
|
||||
{websocket.error && (
|
||||
<Alert severity="error" prominent>
|
||||
{websocket.error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{websocket.warnings.map((warning) => {
|
||||
return (
|
||||
<Alert key={warning.code} severity="warning">
|
||||
<Alert key={warning.code} severity="warning" prominent>
|
||||
{warning.message}
|
||||
</Alert>
|
||||
);
|
||||
|
||||
@@ -42,7 +42,9 @@ const WorkspaceProxyPage: FC = () => {
|
||||
|
||||
<Main>
|
||||
{workspace_proxy.error && (
|
||||
<Alert severity="error">{workspace_proxy.error}</Alert>
|
||||
<Alert severity="error" prominent>
|
||||
{workspace_proxy.error}
|
||||
</Alert>
|
||||
)}
|
||||
{workspace_proxy.warnings.map((warning) => {
|
||||
return (
|
||||
@@ -50,6 +52,7 @@ const WorkspaceProxyPage: FC = () => {
|
||||
actions={<HealthMessageDocsLink {...warning} />}
|
||||
key={warning.code}
|
||||
severity="warning"
|
||||
prominent
|
||||
>
|
||||
{warning.message}
|
||||
</Alert>
|
||||
|
||||
@@ -114,7 +114,9 @@ export const SignInForm: FC<SignInFormProps> = ({
|
||||
)}
|
||||
|
||||
{!passwordEnabled && !oAuthEnabled && (
|
||||
<Alert severity="error">No authentication methods configured!</Alert>
|
||||
<Alert severity="error" prominent>
|
||||
No authentication methods configured!
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import AlertTitle from "@mui/material/AlertTitle";
|
||||
import Autocomplete from "@mui/material/Autocomplete";
|
||||
import Checkbox from "@mui/material/Checkbox";
|
||||
import Link from "@mui/material/Link";
|
||||
@@ -7,7 +6,7 @@ import TextField from "@mui/material/TextField";
|
||||
import { countries } from "api/countriesGenerated";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { isAxiosError } from "axios";
|
||||
import { Alert, AlertDetail } from "components/Alert/Alert";
|
||||
import { Alert, AlertDetail, AlertTitle } from "components/Alert/Alert";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { ExternalImage } from "components/ExternalImage/ExternalImage";
|
||||
import { FormFields, VerticalForm } from "components/Form/Form";
|
||||
@@ -352,7 +351,7 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
|
||||
)}
|
||||
|
||||
{isAxiosError(error) && error.response?.data?.message && (
|
||||
<Alert severity="error">
|
||||
<Alert severity="error" prominent>
|
||||
<AlertTitle>{error.response.data.message}</AlertTitle>
|
||||
{error.response.data.detail && (
|
||||
<AlertDetail>
|
||||
|
||||
@@ -334,6 +334,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
|
||||
>
|
||||
<Alert
|
||||
severity="success"
|
||||
prominent
|
||||
dismissible
|
||||
actions={
|
||||
<Button
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import Link from "@mui/material/Link";
|
||||
import type { WorkspaceAgent } from "api/typesGenerated";
|
||||
import { Alert, type AlertProps } from "components/Alert/Alert";
|
||||
import {
|
||||
Alert,
|
||||
type AlertColor,
|
||||
type AlertProps,
|
||||
} from "components/Alert/Alert";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { type FC, useEffect, useRef, useState } from "react";
|
||||
import { cn } from "utils/cn";
|
||||
import { docs } from "utils/docs";
|
||||
import type { ConnectionStatus } from "./types";
|
||||
|
||||
@@ -153,19 +158,22 @@ const LoadedScriptsAlert: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const severityBorderColors: Record<AlertColor, string> = {
|
||||
info: "border-l-highlight-sky",
|
||||
success: "border-l-content-success",
|
||||
warning: "border-l-content-warning",
|
||||
error: "border-l-content-destructive",
|
||||
};
|
||||
|
||||
const TerminalAlert: FC<AlertProps> = (props) => {
|
||||
const severity = props.severity ?? "info";
|
||||
return (
|
||||
<Alert
|
||||
{...props}
|
||||
css={(theme) => ({
|
||||
borderRadius: 0,
|
||||
borderWidth: 0,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: theme.palette.divider,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderLeft: `3px solid ${theme.palette[props.severity!].light}`,
|
||||
marginBottom: 1,
|
||||
})}
|
||||
className={cn(
|
||||
"rounded-none border-0 border-b border-l-[3px] border-b-border-default bg-surface-primary mb-px [&>div]:items-center",
|
||||
severityBorderColors[severity],
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -174,6 +174,7 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
|
||||
{build.transition === "delete" && build.job.status === "failed" && (
|
||||
<Alert
|
||||
severity="error"
|
||||
prominent
|
||||
className="rounded-none border-0 border-b border-solid border-border"
|
||||
>
|
||||
<div>
|
||||
@@ -190,6 +191,7 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
|
||||
{build?.job?.logs_overflowed && (
|
||||
<Alert
|
||||
severity="warning"
|
||||
prominent
|
||||
className="rounded-none border-0 border-b border-solid border-border"
|
||||
>
|
||||
Provisioner logs exceeded the max size of 1MB. Will not continue
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import AlertTitle from "@mui/material/AlertTitle";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { Alert, AlertDetail } from "components/Alert/Alert";
|
||||
import { Alert, AlertDetail, AlertTitle } from "components/Alert/Alert";
|
||||
import { SidebarIconButton } from "components/FullPageLayout/Sidebar";
|
||||
import { Link } from "components/Link/Link";
|
||||
import { useSearchParamsKey } from "hooks/useSearchParamsKey";
|
||||
import { BlocksIcon, HistoryIcon } from "lucide-react";
|
||||
import { ProvisionerStatusAlert } from "modules/provisioners/ProvisionerStatusAlert";
|
||||
@@ -193,14 +193,14 @@ export const Workspace: FC<WorkspaceProps> = ({
|
||||
)}
|
||||
|
||||
{workspace.latest_build.job.error && (
|
||||
<Alert severity="error">
|
||||
<Alert severity="error" prominent>
|
||||
<AlertTitle>Workspace build failed</AlertTitle>
|
||||
<AlertDetail>{workspace.latest_build.job.error}</AlertDetail>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{!workspace.health.healthy && (
|
||||
<Alert severity="warning">
|
||||
<Alert severity="warning" prominent>
|
||||
<AlertTitle>Workspace is unhealthy</AlertTitle>
|
||||
<AlertDetail>
|
||||
<p>
|
||||
@@ -208,7 +208,12 @@ export const Workspace: FC<WorkspaceProps> = ({
|
||||
{workspace.health.failing_agents.length > 1
|
||||
? `${workspace.health.failing_agents.length} agents are unhealthy`
|
||||
: "1 agent is unhealthy"}
|
||||
.
|
||||
.{" "}
|
||||
{troubleshootingURL && (
|
||||
<Link href={troubleshootingURL} target="_blank">
|
||||
View docs to troubleshoot
|
||||
</Link>
|
||||
)}
|
||||
</p>
|
||||
{hasActions && (
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -219,15 +224,6 @@ export const Workspace: FC<WorkspaceProps> = ({
|
||||
Restart
|
||||
</NotificationActionButton>
|
||||
)}
|
||||
{troubleshootingURL && (
|
||||
<NotificationActionButton
|
||||
onClick={() =>
|
||||
window.open(troubleshootingURL, "_blank")
|
||||
}
|
||||
>
|
||||
Troubleshooting
|
||||
</NotificationActionButton>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</AlertDetail>
|
||||
|
||||
@@ -16,7 +16,7 @@ export const WorkspaceDeletedBanner: FC<WorkspaceDeletedBannerProps> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<Alert severity="warning" actions={NewWorkspaceButton}>
|
||||
<Alert severity="warning" prominent actions={NewWorkspaceButton}>
|
||||
This workspace has been deleted and cannot be edited.
|
||||
</Alert>
|
||||
);
|
||||
|
||||
@@ -105,7 +105,7 @@ const NotificationItem: FC<NotificationItemProps> = ({ notification }) => {
|
||||
};
|
||||
|
||||
export const NotificationActionButton: FC<ButtonProps> = (props) => {
|
||||
return <Button variant="outline" size="sm" {...props} />;
|
||||
return <Button variant="default" size="sm" {...props} />;
|
||||
};
|
||||
|
||||
const styles = {
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ export const WorkspaceParametersForm: FC<WorkspaceParameterFormProps> = ({
|
||||
return (
|
||||
<>
|
||||
{disabled && (
|
||||
<Alert severity="warning">
|
||||
<Alert severity="warning" prominent>
|
||||
The template for this workspace requires automatic updates. Update the
|
||||
workspace to edit parameters.
|
||||
</Alert>
|
||||
|
||||
+3
-3
@@ -112,15 +112,15 @@ export const WorkspaceParametersPageViewExperimental: FC<
|
||||
return (
|
||||
<>
|
||||
{disabled && (
|
||||
<Alert severity="warning" className="mb-8">
|
||||
<Alert severity="warning" className="mb-8" prominent>
|
||||
The template for this workspace requires automatic updates. Update the
|
||||
workspace to edit parameters.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{hasIncompatibleParameters && (
|
||||
<Alert severity="error">
|
||||
<p className="text-lg leading-tight font-bold m-0">
|
||||
<Alert severity="error" prominent>
|
||||
<p className="text-lg leading-normal font-bold m-0">
|
||||
Workspace update blocked
|
||||
</p>
|
||||
<p className="mb-0">
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ const WorkspaceSchedulePage: FC = () => {
|
||||
rel="noreferrer"
|
||||
>
|
||||
Prebuilt Workspaces Scheduling
|
||||
</Link>
|
||||
</Link>{" "}
|
||||
documentation page.
|
||||
</Alert>
|
||||
) : (
|
||||
|
||||
@@ -59,7 +59,7 @@ module.exports = {
|
||||
DEFAULT: "hsl(var(--border-default))",
|
||||
warning: "hsl(var(--border-warning))",
|
||||
green: "hsl(var(--border-green))",
|
||||
sky: "hsl(var(--border-sky))",
|
||||
pending: "hsl(var(--border-sky))",
|
||||
destructive: "hsl(var(--border-destructive))",
|
||||
success: "hsl(var(--border-success))",
|
||||
hover: "hsl(var(--border-hover))",
|
||||
|
||||
Reference in New Issue
Block a user