Files
coder/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx
Bruno Quaresma 842bb1f014 chore: replace MUI icons - 6 (#17751)
1. Replaced CheckCircleOutlined with CircleCheckIcon (Lucide)
2. Replaced Close/CloseIcon with XIcon (Lucide)
3. Replaced DoNotDisturbOnOutlined with CircleMinusIcon (Lucide)
4. Replaced Sell with TagIcon (Lucide)
2025-05-09 17:07:57 -03:00

200 lines
5.2 KiB
TypeScript

import type { Interpolation, Theme } from "@emotion/react";
import WarningOutlined from "@mui/icons-material/WarningOutlined";
import Button from "@mui/material/Button";
import Drawer from "@mui/material/Drawer";
import IconButton from "@mui/material/IconButton";
import { visuallyHidden } from "@mui/utils";
import { JobError } from "api/queries/templates";
import type { TemplateVersion } from "api/typesGenerated";
import { Loader } from "components/Loader/Loader";
import { X as XIcon } from "lucide-react";
import { AlertVariant } from "modules/provisioners/ProvisionerAlert";
import { ProvisionerStatusAlert } from "modules/provisioners/ProvisionerStatusAlert";
import { useWatchVersionLogs } from "modules/templates/useWatchVersionLogs";
import { WorkspaceBuildLogs } from "modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs";
import { type FC, useLayoutEffect, useRef } from "react";
import { navHeight } from "theme/constants";
type BuildLogsDrawerProps = {
error: unknown;
open: boolean;
onClose: () => void;
templateVersion: TemplateVersion | undefined;
variablesSectionRef: React.RefObject<HTMLDivElement>;
};
export const BuildLogsDrawer: FC<BuildLogsDrawerProps> = ({
templateVersion,
error,
variablesSectionRef,
...drawerProps
}) => {
const matchingProvisioners = templateVersion?.matched_provisioners?.count;
const availableProvisioners =
templateVersion?.matched_provisioners?.available;
const logs = useWatchVersionLogs(templateVersion);
const logsContainer = useRef<HTMLDivElement>(null);
const scrollToBottom = () => {
setTimeout(() => {
if (logsContainer.current) {
logsContainer.current.scrollTop = logsContainer.current.scrollHeight;
}
}, 0);
};
// biome-ignore lint/correctness/useExhaustiveDependencies: consider refactoring
useLayoutEffect(() => {
scrollToBottom();
}, [logs]);
// biome-ignore lint/correctness/useExhaustiveDependencies: consider refactoring
useLayoutEffect(() => {
if (drawerProps.open) {
scrollToBottom();
}
}, [drawerProps.open]);
const isMissingVariables =
error instanceof JobError &&
error.job.error_code === "REQUIRED_TEMPLATE_VARIABLES";
return (
<Drawer anchor="right" {...drawerProps}>
<div css={styles.root}>
<header css={styles.header}>
<h3 css={styles.title}>Creating template...</h3>
<IconButton size="small" onClick={drawerProps.onClose}>
<XIcon css={styles.closeIcon} />
<span style={visuallyHidden}>Close build logs</span>
</IconButton>
</header>
{}
{isMissingVariables ? (
<MissingVariablesBanner
onFillVariables={() => {
variablesSectionRef.current?.scrollIntoView({
behavior: "smooth",
});
const firstVariableInput =
variablesSectionRef.current?.querySelector("input");
setTimeout(() => firstVariableInput?.focus(), 0);
drawerProps.onClose();
}}
/>
) : logs ? (
<section ref={logsContainer} css={styles.logs}>
<WorkspaceBuildLogs logs={logs} css={{ border: 0 }} />
</section>
) : (
<>
<ProvisionerStatusAlert
matchingProvisioners={matchingProvisioners}
availableProvisioners={availableProvisioners}
tags={templateVersion?.job.tags ?? {}}
variant={AlertVariant.Inline}
/>
<Loader />
</>
)}
</div>
</Drawer>
);
};
type MissingVariablesBannerProps = { onFillVariables: () => void };
const MissingVariablesBanner: FC<MissingVariablesBannerProps> = ({
onFillVariables,
}) => {
return (
<div css={bannerStyles.root}>
<div css={bannerStyles.content}>
<WarningOutlined css={bannerStyles.icon} />
<h4 css={bannerStyles.title}>Missing variables</h4>
<p css={bannerStyles.description}>
During the build process, we identified some missing variables. Rest
assured, we have automatically added them to the form for you.
</p>
<Button
css={bannerStyles.button}
size="small"
variant="outlined"
onClick={onFillVariables}
>
Fill variables
</Button>
</div>
</div>
);
};
const styles = {
root: {
width: 800,
height: "100%",
display: "flex",
flexDirection: "column",
},
header: (theme) => ({
height: navHeight,
padding: "0 24px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
borderBottom: `1px solid ${theme.palette.divider}`,
}),
title: {
margin: 0,
fontWeight: 500,
fontSize: 16,
},
closeIcon: {
fontSize: 20,
},
logs: (theme) => ({
flex: 1,
overflow: "auto",
backgroundColor: theme.palette.background.default,
}),
} satisfies Record<string, Interpolation<Theme>>;
const bannerStyles = {
root: {
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: 40,
},
content: {
display: "flex",
flexDirection: "column",
alignItems: "center",
textAlign: "center",
maxWidth: 360,
},
icon: (theme) => ({
fontSize: 32,
color: theme.roles.warning.fill.outline,
}),
title: {
fontWeight: 500,
lineHeight: "1",
margin: 0,
marginTop: 16,
},
description: (theme) => ({
color: theme.palette.text.secondary,
fontSize: 14,
margin: 0,
marginTop: 8,
lineHeight: "1.5",
}),
button: {
marginTop: 16,
},
} satisfies Record<string, Interpolation<Theme>>;