chore: use emotion for styling (pt. 5) (#10261)

This commit is contained in:
Kayla Washburn
2023-10-16 12:41:15 -06:00
committed by GitHub
parent 4240200b5d
commit eaea918a59
28 changed files with 654 additions and 837 deletions
@@ -1,6 +1,6 @@
import { css } from "@emotion/css";
import { useTheme } from "@emotion/react";
import Popover, { PopoverProps } from "@mui/material/Popover";
import Popover, { type PopoverProps } from "@mui/material/Popover";
import type { FC, PropsWithChildren } from "react";
type BorderedMenuVariant = "user-dropdown";
@@ -18,7 +18,7 @@ export const BorderedMenu: FC<PropsWithChildren<BorderedMenuProps>> = ({
const paper = css`
width: 260px;
border-radius: ${theme.shape.borderRadius};
border-radius: ${theme.shape.borderRadius}px;
box-shadow: ${theme.shadows[6]};
`;
@@ -47,7 +47,7 @@ const SidebarNavItem: FC<
font-size: 14px;
text-decoration: none;
padding: ${theme.spacing(1.5, 1.5, 1.5, 2)};
border-radius: ${theme.shape.borderRadius / 2};
border-radius: ${theme.shape.borderRadius / 2}px;
transition: background-color 0.15s ease-in-out;
margin-bottom: 1;
position: relative;
@@ -1,14 +1,15 @@
import Button from "@mui/material/Button";
import Link from "@mui/material/Link";
import { makeStyles } from "@mui/styles";
import RefreshOutlined from "@mui/icons-material/RefreshOutlined";
import { BuildInfoResponse } from "api/typesGenerated";
import { type FC, useEffect, useState } from "react";
import { Helmet } from "react-helmet-async";
import { css } from "@emotion/css";
import { useTheme, type Interpolation, type Theme } from "@emotion/react";
import type { BuildInfoResponse } from "api/typesGenerated";
import { CopyButton } from "components/CopyButton/CopyButton";
import { CoderIcon } from "components/Icons/CoderIcon";
import { FullScreenLoader } from "components/Loader/FullScreenLoader";
import { Stack } from "components/Stack/Stack";
import { FC, useEffect, useState } from "react";
import { Helmet } from "react-helmet-async";
import { Margins } from "components/Margins/Margins";
const fetchDynamicallyImportedModuleError =
@@ -17,7 +18,7 @@ const fetchDynamicallyImportedModuleError =
export type RuntimeErrorStateProps = { error: Error };
export const RuntimeErrorState: FC<RuntimeErrorStateProps> = ({ error }) => {
const styles = useStyles();
const theme = useTheme();
const [checkingError, setCheckingError] = useState(true);
const [staticBuildInfo, setStaticBuildInfo] = useState<BuildInfoResponse>();
const coderVersion = staticBuildInfo?.version;
@@ -52,11 +53,11 @@ export const RuntimeErrorState: FC<RuntimeErrorStateProps> = ({ error }) => {
<title>Something went wrong...</title>
</Helmet>
{!checkingError ? (
<Margins className={styles.root}>
<div className={styles.innerRoot}>
<CoderIcon className={styles.logo} />
<h1 className={styles.title}>Something went wrong...</h1>
<p className={styles.text}>
<Margins css={styles.root}>
<div css={{ width: "100%" }}>
<CoderIcon css={styles.logo} />
<h1 css={styles.title}>Something went wrong...</h1>
<p css={styles.text}>
Please try reloading the page, if that doesn&lsquo;t work, you can
ask for help in the{" "}
<Link href="https://discord.gg/coder">
@@ -93,20 +94,33 @@ export const RuntimeErrorState: FC<RuntimeErrorStateProps> = ({ error }) => {
</Button>
</Stack>
{error.stack && (
<div className={styles.stack}>
<div className={styles.stackHeader}>
<div css={styles.stack}>
<div css={styles.stackHeader}>
Stacktrace
<CopyButton
buttonClassName={styles.copyButton}
buttonClassName={css`
background-color: transparent;
border: 0;
border-radius: 999px;
min-height: ${theme.spacing(4)};
min-width: ${theme.spacing(4)};
height: ${theme.spacing(4)};
width: ${theme.spacing(4)};
& svg {
width: 16px;
height: 16px;
}
`}
text={error.stack}
tooltipTitle="Copy stacktrace"
/>
</div>
<pre className={styles.stackCode}>{error.stack}</pre>
<pre css={styles.stackCode}>{error.stack}</pre>
</div>
)}
{coderVersion && (
<div className={styles.version}>Version: {coderVersion}</div>
<div css={styles.version}>Version: {coderVersion}</div>
)}
</div>
</Margins>
@@ -132,8 +146,8 @@ const getStaticBuildInfo = () => {
}
};
const useStyles = makeStyles((theme) => ({
root: {
const styles = {
root: (theme) => ({
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(4),
textAlign: "center",
@@ -142,36 +156,34 @@ const useStyles = makeStyles((theme) => ({
justifyContent: "center",
minHeight: "100%",
maxWidth: theme.spacing(75),
},
}),
innerRoot: { width: "100%" },
logo: {
logo: (theme) => ({
fontSize: theme.spacing(8),
},
}),
title: {
title: (theme) => ({
fontSize: theme.spacing(4),
fontWeight: 400,
},
}),
text: {
text: (theme) => ({
fontSize: 16,
color: theme.palette.text.secondary,
lineHeight: "160%",
marginBottom: theme.spacing(4),
},
}),
stack: {
stack: (theme) => ({
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
borderRadius: 4,
marginTop: theme.spacing(8),
display: "block",
textAlign: "left",
},
}),
stackHeader: {
stackHeader: (theme) => ({
fontSize: 10,
textTransform: "uppercase",
fontWeight: 600,
@@ -184,33 +196,18 @@ const useStyles = makeStyles((theme) => ({
flexAlign: "center",
justifyContent: "space-between",
alignItems: "center",
},
}),
stackCode: {
stackCode: (theme) => ({
padding: theme.spacing(2),
margin: 0,
wordWrap: "break-word",
whiteSpace: "break-spaces",
},
}),
copyButton: {
backgroundColor: "transparent",
border: 0,
borderRadius: 999,
minHeight: theme.spacing(4),
minWidth: theme.spacing(4),
height: theme.spacing(4),
width: theme.spacing(4),
"& svg": {
width: 16,
height: 16,
},
},
version: {
version: (theme) => ({
marginTop: theme.spacing(4),
fontSize: 12,
color: theme.palette.text.secondary,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
+30 -35
View File
@@ -1,13 +1,12 @@
import { makeStyles } from "@mui/styles";
import { Stack } from "components/Stack/Stack";
import { FC, DragEvent, useRef, ReactNode } from "react";
import { type FC, type DragEvent, useRef, type ReactNode } from "react";
import UploadIcon from "@mui/icons-material/CloudUploadOutlined";
import { useClickable } from "hooks/useClickable";
import CircularProgress from "@mui/material/CircularProgress";
import { combineClasses } from "utils/combineClasses";
import IconButton from "@mui/material/IconButton";
import RemoveIcon from "@mui/icons-material/DeleteOutline";
import FileIcon from "@mui/icons-material/FolderOutlined";
import { css, type Interpolation, type Theme } from "@emotion/react";
const useFileDrop = (
callback: (file: File) => void,
@@ -62,7 +61,6 @@ export const FileUpload: FC<FileUploadProps> = ({
extension,
fileTypeRequired,
}) => {
const styles = useStyles();
const inputRef = useRef<HTMLInputElement>(null);
const tarDrop = useFileDrop(onUpload, fileTypeRequired);
@@ -75,7 +73,7 @@ export const FileUpload: FC<FileUploadProps> = ({
if (!isUploading && file) {
return (
<Stack
className={styles.file}
css={styles.file}
direction="row"
justifyContent="space-between"
alignItems="center"
@@ -95,10 +93,7 @@ export const FileUpload: FC<FileUploadProps> = ({
return (
<>
<div
className={combineClasses({
[styles.root]: true,
[styles.disabled]: isUploading,
})}
css={[styles.root, isUploading && styles.disabled]}
{...clickable}
{...tarDrop}
>
@@ -106,12 +101,12 @@ export const FileUpload: FC<FileUploadProps> = ({
{isUploading ? (
<CircularProgress size={32} />
) : (
<UploadIcon className={styles.icon} />
<UploadIcon css={styles.icon} />
)}
<Stack alignItems="center" spacing={0.5}>
<span className={styles.title}>{title}</span>
<span className={styles.description}>{description}</span>
<span css={styles.title}>{title}</span>
<span css={styles.description}>{description}</span>
</Stack>
</Stack>
</div>
@@ -120,7 +115,7 @@ export const FileUpload: FC<FileUploadProps> = ({
type="file"
data-testid="file-upload"
ref={inputRef}
className={styles.input}
css={styles.input}
accept={extension}
onChange={(event) => {
const file = event.currentTarget.files?.[0];
@@ -133,48 +128,48 @@ export const FileUpload: FC<FileUploadProps> = ({
);
};
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: theme.shape.borderRadius,
border: `2px dashed ${theme.palette.divider}`,
padding: theme.spacing(6),
cursor: "pointer",
const styles = {
root: (theme) => css`
display: flex;
align-items: center;
justify-content: center;
border-radius: ${theme.shape.borderRadius}px;
border: 2px dashed ${theme.palette.divider};
padding: ${theme.spacing(6)};
cursor: pointer;
"&:hover": {
backgroundColor: theme.palette.background.paper,
},
},
&:hover {
background-color: ${theme.palette.background.paper};
}
`,
disabled: {
pointerEvents: "none",
opacity: 0.75,
},
icon: {
icon: (theme) => ({
fontSize: theme.spacing(8),
},
}),
title: {
title: (theme) => ({
fontSize: theme.spacing(2),
},
}),
description: {
description: (theme) => ({
color: theme.palette.text.secondary,
textAlign: "center",
maxWidth: theme.spacing(50),
},
}),
input: {
display: "none",
},
file: {
file: (theme) => ({
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,
padding: theme.spacing(2),
background: theme.palette.background.paper,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,11 +1,11 @@
import { Margins } from "components/Margins/Margins";
import { FC, ReactNode } from "react";
import { type FC, type ReactNode } from "react";
import {
PageHeader,
PageHeaderTitle,
PageHeaderSubtitle,
} from "components/PageHeader/PageHeader";
import { makeStyles } from "@mui/styles";
import { useTheme } from "@emotion/react";
export interface FullPageFormProps {
title: string;
@@ -17,11 +17,11 @@ export const FullPageForm: FC<React.PropsWithChildren<FullPageFormProps>> = ({
detail,
children,
}) => {
const styles = useStyles();
const theme = useTheme();
return (
<Margins size="small">
<PageHeader className={styles.pageHeader}>
<PageHeader css={{ paddingBottom: theme.spacing(3) }}>
<PageHeaderTitle>{title}</PageHeaderTitle>
{detail && <PageHeaderSubtitle>{detail}</PageHeaderSubtitle>}
</PageHeader>
@@ -30,9 +30,3 @@ export const FullPageForm: FC<React.PropsWithChildren<FullPageFormProps>> = ({
</Margins>
);
};
const useStyles = makeStyles((theme) => ({
pageHeader: {
paddingBottom: theme.spacing(3),
},
}));
@@ -2,10 +2,10 @@ import IconButton from "@mui/material/IconButton";
import Snackbar, {
SnackbarProps as MuiSnackbarProps,
} from "@mui/material/Snackbar";
import { makeStyles } from "@mui/styles";
import CloseIcon from "@mui/icons-material/Close";
import { FC } from "react";
import { combineClasses } from "utils/combineClasses";
import { type FC } from "react";
import { css } from "@emotion/css";
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
type EnterpriseSnackbarVariant = "error" | "info" | "success";
@@ -30,7 +30,18 @@ export interface EnterpriseSnackbarProps extends MuiSnackbarProps {
export const EnterpriseSnackbar: FC<
React.PropsWithChildren<EnterpriseSnackbarProps>
> = ({ onClose, variant = "info", ContentProps = {}, action, ...rest }) => {
const styles = useStyles();
const theme = useTheme();
const snackbarContentStyles = css`
border: 1px solid ${theme.palette.divider};
border-left: 4px solid ${variantColor(variant, theme)};
border-radius: ${theme.shape.borderRadius}px;
padding: ${theme.spacing(1, 3, 1, 2)};
box-shadow: ${theme.shadows[6]};
align-items: inherit;
background-color: ${theme.palette.background.paper};
color: ${theme.palette.text.secondary};
`;
return (
<Snackbar
@@ -40,67 +51,41 @@ export const EnterpriseSnackbar: FC<
}}
{...rest}
action={
<div className={styles.actionWrapper}>
<div css={styles.actionWrapper}>
{action}
<IconButton
onClick={onClose}
className={styles.iconButton}
size="large"
>
<CloseIcon className={styles.closeIcon} aria-label="close" />
<IconButton onClick={onClose} css={{ padding: 0 }} size="large">
<CloseIcon css={styles.closeIcon} aria-label="close" />
</IconButton>
</div>
}
ContentProps={{
...ContentProps,
className: combineClasses({
[styles.snackbarContent]: true,
[styles.snackbarContentInfo]: variant === "info",
[styles.snackbarContentError]: variant === "error",
[styles.snackbarContentSuccess]: variant === "success",
}),
className: snackbarContentStyles,
}}
onClose={onClose}
/>
);
};
const useStyles = makeStyles((theme) => ({
const variantColor = (variant: EnterpriseSnackbarVariant, theme: Theme) => {
switch (variant) {
case "error":
return theme.palette.error.main;
case "info":
return theme.palette.info.main;
case "success":
return theme.palette.success.main;
}
};
const styles = {
actionWrapper: {
display: "flex",
alignItems: "center",
},
iconButton: {
padding: 0,
},
closeIcon: {
closeIcon: (theme) => ({
width: 25,
height: 25,
color: theme.palette.primary.contrastText,
},
snackbarContent: {
border: `1px solid ${theme.palette.divider}`,
borderLeft: `4px solid ${theme.palette.primary.main}`,
borderRadius: theme.shape.borderRadius,
padding: `
${theme.spacing(1)}px
${theme.spacing(3)}px
${theme.spacing(1)}px
${theme.spacing(2)}px
`,
boxShadow: theme.shadows[6],
alignItems: "inherit",
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.secondary,
},
snackbarContentInfo: {
// Use success color as a highlight
borderLeftColor: theme.palette.primary.main,
},
snackbarContentError: {
borderLeftColor: theme.palette.error.main,
},
snackbarContentSuccess: {
borderLeftColor: theme.palette.success.main,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,19 +1,19 @@
import { makeStyles } from "@mui/styles";
import { useCallback, useState, FC } from "react";
import { type FC, useCallback, useState } from "react";
import { useCustomEvent } from "hooks/events";
import { CustomEventListener } from "utils/events";
import type { CustomEventListener } from "utils/events";
import { EnterpriseSnackbar } from "./EnterpriseSnackbar";
import { ErrorIcon } from "../Icons/ErrorIcon";
import { Typography } from "../Typography/Typography";
import {
AdditionalMessage,
type AdditionalMessage,
isNotificationList,
isNotificationText,
isNotificationTextPrefixed,
MsgType,
NotificationMsg,
type NotificationMsg,
SnackbarEventType,
} from "./utils";
import { type Interpolation, type Theme } from "@emotion/react";
const variantFromMsgType = (type: MsgType) => {
if (type === MsgType.Error) {
@@ -26,7 +26,6 @@ const variantFromMsgType = (type: MsgType) => {
};
export const GlobalSnackbar: FC = () => {
const styles = useStyles();
const [open, setOpen] = useState<boolean>(false);
const [notification, setNotification] = useState<NotificationMsg>();
@@ -47,7 +46,7 @@ export const GlobalSnackbar: FC = () => {
key={idx}
gutterBottom
variant="body2"
className={styles.messageSubtitle}
css={styles.messageSubtitle}
>
{msg}
</Typography>
@@ -58,17 +57,17 @@ export const GlobalSnackbar: FC = () => {
key={idx}
gutterBottom
variant="body2"
className={styles.messageSubtitle}
css={styles.messageSubtitle}
>
<strong>{msg.prefix}:</strong> {msg.text}
</Typography>
);
} else if (isNotificationList(msg)) {
return (
<ul className={styles.list} key={idx}>
<ul css={styles.list} key={idx}>
{msg.map((item, idx) => (
<li key={idx}>
<Typography variant="body2" className={styles.messageSubtitle}>
<Typography variant="body2" css={styles.messageSubtitle}>
{item}
</Typography>
</li>
@@ -89,12 +88,12 @@ export const GlobalSnackbar: FC = () => {
open={open}
variant={variantFromMsgType(notification.msgType)}
message={
<div className={styles.messageWrapper}>
<div css={styles.messageWrapper}>
{notification.msgType === MsgType.Error && (
<ErrorIcon className={styles.errorIcon} />
<ErrorIcon css={styles.errorIcon} />
)}
<div className={styles.message}>
<Typography variant="body1" className={styles.messageTitle}>
<div css={styles.message}>
<Typography variant="body1" css={styles.messageTitle}>
{notification.msg}
</Typography>
{notification.additionalMsgs &&
@@ -112,7 +111,7 @@ export const GlobalSnackbar: FC = () => {
);
};
const useStyles = makeStyles((theme) => ({
const styles = {
list: {
paddingLeft: 0,
},
@@ -126,11 +125,11 @@ const useStyles = makeStyles((theme) => ({
fontSize: 14,
fontWeight: 600,
},
messageSubtitle: {
messageSubtitle: (theme) => ({
marginTop: theme.spacing(1.5),
},
errorIcon: {
}),
errorIcon: (theme) => ({
color: theme.palette.error.contrastText,
marginRight: theme.spacing(2),
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,16 +1,39 @@
import { makeStyles } from "@mui/styles";
import { FC, PropsWithChildren } from "react";
import { combineClasses } from "utils/combineClasses";
import { type CSSObject, useTheme } from "@emotion/react";
import { type FC, type PropsWithChildren } from "react";
export const FullWidthPageHeader: FC<
PropsWithChildren & { sticky?: boolean }
> = ({ children, sticky = true }) => {
const styles = useStyles();
const theme = useTheme();
return (
<header
className={combineClasses([styles.header, sticky ? styles.sticky : ""])}
data-testid="header"
css={[
{
...(theme.typography.body2 as CSSObject),
padding: theme.spacing(3),
background: theme.palette.background.paper,
borderBottom: `1px solid ${theme.palette.divider}`,
display: "flex",
alignItems: "center",
gap: theme.spacing(6),
zIndex: 10,
flexWrap: "wrap",
[theme.breakpoints.down("lg")]: {
position: "unset",
alignItems: "flex-start",
},
[theme.breakpoints.down("md")]: {
flexDirection: "column",
},
},
sticky && {
position: "sticky",
top: 0,
},
]}
>
{children}
</header>
@@ -18,60 +41,47 @@ export const FullWidthPageHeader: FC<
};
export const PageHeaderActions: FC<PropsWithChildren> = ({ children }) => {
const styles = useStyles();
return <div className={styles.actions}>{children}</div>;
const theme = useTheme();
return (
<div
css={{
marginLeft: "auto",
[theme.breakpoints.down("md")]: {
marginLeft: "unset",
},
}}
>
{children}
</div>
);
};
export const PageHeaderTitle: FC<PropsWithChildren> = ({ children }) => {
const styles = useStyles();
return <h1 className={styles.title}>{children}</h1>;
return (
<h1
css={{
fontSize: 18,
fontWeight: 500,
margin: 0,
lineHeight: "24px",
}}
>
{children}
</h1>
);
};
export const PageHeaderSubtitle: FC<PropsWithChildren> = ({ children }) => {
const styles = useStyles();
return <span className={styles.subtitle}>{children}</span>;
const theme = useTheme();
return (
<span
css={{
fontSize: 14,
color: theme.palette.text.secondary,
display: "block",
}}
>
{children}
</span>
);
};
const useStyles = makeStyles((theme) => ({
header: {
...theme.typography.body2,
padding: theme.spacing(3),
background: theme.palette.background.paper,
borderBottom: `1px solid ${theme.palette.divider}`,
display: "flex",
alignItems: "center",
gap: theme.spacing(6),
zIndex: 10,
flexWrap: "wrap",
[theme.breakpoints.down("lg")]: {
position: "unset",
alignItems: "flex-start",
},
[theme.breakpoints.down("md")]: {
flexDirection: "column",
},
},
sticky: {
position: "sticky",
top: 0,
},
actions: {
marginLeft: "auto",
[theme.breakpoints.down("md")]: {
marginLeft: "unset",
},
},
title: {
fontSize: 18,
fontWeight: 500,
margin: 0,
lineHeight: "24px",
},
subtitle: {
fontSize: 14,
color: theme.palette.text.secondary,
display: "block",
},
}));
@@ -1,5 +1,5 @@
import Button from "@mui/material/Button";
import { makeStyles } from "@mui/styles";
import { css, useTheme } from "@emotion/react";
interface PageButtonProps {
activePage?: number;
@@ -18,14 +18,20 @@ export const PageButton = ({
onPageClick,
disabled = false,
}: PageButtonProps): JSX.Element => {
const styles = useStyles();
const theme = useTheme();
return (
<Button
className={
activePage === page
? `${styles.pageButton} ${styles.activePageButton}`
: styles.pageButton
}
css={[
css`
&:not(:last-of-type) {
margin-right: ${theme.spacing(0.5)};
}
`,
activePage === page && {
borderColor: `${theme.palette.info.main}`,
backgroundColor: `${theme.palette.info.dark}`,
},
]}
aria-label={`${page === activePage ? "Current Page" : ""} ${
page === numPages ? "Last Page" : ""
} Page${page}`}
@@ -37,16 +43,3 @@ export const PageButton = ({
</Button>
);
};
const useStyles = makeStyles((theme) => ({
pageButton: {
"&:not(:last-of-type)": {
marginRight: theme.spacing(0.5),
},
},
activePageButton: {
borderColor: `${theme.palette.info.main}`,
backgroundColor: `${theme.palette.info.dark}`,
},
}));
@@ -1,8 +1,8 @@
import Button from "@mui/material/Button";
import { makeStyles, useTheme } from "@mui/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
import { useTheme } from "@emotion/react";
import { PageButton } from "./PageButton";
import { buildPagedList } from "./utils";
@@ -21,7 +21,6 @@ export const PaginationWidgetBase = ({
}: PaginationWidgetBaseProps): JSX.Element | null => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
const styles = useStyles();
const numPages = Math.ceil(count / limit);
const isFirstPage = page === 0;
const isLastPage = page === numPages - 1;
@@ -31,9 +30,19 @@ export const PaginationWidgetBase = ({
}
return (
<div className={styles.defaultContainerStyles}>
<div
css={{
justifyContent: "center",
alignItems: "center",
display: "flex",
flexDirection: "row",
padding: "20px",
}}
>
<Button
className={styles.prevLabelStyles}
css={{
marginRight: theme.spacing(0.5),
}}
aria-label="Previous page"
disabled={isFirstPage}
onClick={() => {
@@ -84,17 +93,3 @@ export const PaginationWidgetBase = ({
</div>
);
};
const useStyles = makeStyles((theme) => ({
defaultContainerStyles: {
justifyContent: "center",
alignItems: "center",
display: "flex",
flexDirection: "row",
padding: "20px",
},
prevLabelStyles: {
marginRight: theme.spacing(0.5),
},
}));
+17 -18
View File
@@ -1,9 +1,9 @@
import Box from "@mui/material/Box";
import Chip from "@mui/material/Chip";
import { makeStyles } from "@mui/styles";
import Typography from "@mui/material/Typography";
import { type FC, type ReactNode } from "react";
import { type Interpolation, type Theme } from "@emotion/react";
import { Stack } from "components/Stack/Stack";
import { FC, ReactNode } from "react";
export interface PaywallProps {
message: string;
@@ -13,17 +13,16 @@ export interface PaywallProps {
export const Paywall: FC<React.PropsWithChildren<PaywallProps>> = (props) => {
const { message, description, cta } = props;
const styles = useStyles();
return (
<Box className={styles.root}>
<div className={styles.header}>
<Box css={styles.root}>
<div css={styles.header}>
<Stack direction="row" alignItems="center" justifyContent="center">
<Typography variant="h5" className={styles.title}>
<Typography variant="h5" css={styles.title}>
{message}
</Typography>
<Chip
className={styles.enterpriseChip}
css={styles.enterpriseChip}
label="Enterprise"
size="small"
color="primary"
@@ -34,7 +33,7 @@ export const Paywall: FC<React.PropsWithChildren<PaywallProps>> = (props) => {
<Typography
variant="body2"
color="textSecondary"
className={styles.description}
css={styles.description}
>
{description}
</Typography>
@@ -45,8 +44,8 @@ export const Paywall: FC<React.PropsWithChildren<PaywallProps>> = (props) => {
);
};
const useStyles = makeStyles((theme) => ({
root: {
const styles = {
root: (theme) => ({
display: "flex",
flexDirection: "column",
justifyContent: "center",
@@ -57,24 +56,24 @@ const useStyles = makeStyles((theme) => ({
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
},
header: {
}),
header: (theme) => ({
marginBottom: theme.spacing(3),
},
}),
title: {
fontWeight: 600,
fontFamily: "inherit",
},
description: {
description: (theme) => ({
marginTop: theme.spacing(1),
fontFamily: "inherit",
maxWidth: 420,
lineHeight: "160%",
},
enterpriseChip: {
}),
enterpriseChip: (theme) => ({
background: theme.palette.success.dark,
color: theme.palette.success.contrastText,
border: `1px solid ${theme.palette.success.light}`,
fontSize: 13,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
+15 -21
View File
@@ -1,13 +1,13 @@
import { useRef, useState, FC } from "react";
import { makeStyles, useTheme } from "@mui/styles";
import { type FC, useRef, useState } from "react";
import { useTheme } from "@emotion/react";
import { Theme } from "@mui/material/styles";
import type { WorkspaceAgent, DERPRegion } from "api/typesGenerated";
import {
HelpTooltipText,
HelpPopover,
HelpTooltipTitle,
} from "components/HelpTooltip/HelpTooltip";
import { Stack } from "components/Stack/Stack";
import { WorkspaceAgent, DERPRegion } from "api/typesGenerated";
import { getLatencyColor } from "utils/latency";
const getDisplayLatency = (theme: Theme, agent: WorkspaceAgent) => {
@@ -30,12 +30,11 @@ const getDisplayLatency = (theme: Theme, agent: WorkspaceAgent) => {
};
export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
const theme: Theme = useTheme();
const theme = useTheme();
const anchorRef = useRef<HTMLButtonElement>(null);
const [isOpen, setIsOpen] = useState(false);
const id = isOpen ? "latency-popover" : undefined;
const latency = getDisplayLatency(theme, agent);
const styles = useStyles();
if (!latency || !agent.latency) {
return null;
@@ -49,8 +48,7 @@ export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
ref={anchorRef}
onMouseEnter={() => setIsOpen(true)}
onMouseLeave={() => setIsOpen(false)}
className={styles.trigger}
style={{ color: latency.color }}
css={{ cursor: "pointer", color: latency.color }}
>
{Math.round(Math.round(latency.latency_ms))}ms
</span>
@@ -67,7 +65,11 @@ export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
first row is the preferred relay.
</HelpTooltipText>
<HelpTooltipText>
<Stack direction="column" spacing={1} className={styles.regions}>
<Stack
direction="column"
spacing={1}
css={{ marginTop: theme.spacing(2) }}
>
{Object.entries(agent.latency)
.sort(([, a], [, b]) => a.latency_ms - b.latency_ms)
.map(([regionName, region]) => (
@@ -76,7 +78,11 @@ export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
key={regionName}
spacing={0.5}
justifyContent="space-between"
className={region.preferred ? styles.preferred : undefined}
css={
region.preferred && {
color: theme.palette.text.primary,
}
}
>
<strong>{regionName}</strong>
{Math.round(region.latency_ms)}ms
@@ -88,15 +94,3 @@ export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
</>
);
};
const useStyles = makeStyles((theme) => ({
trigger: {
cursor: "pointer",
},
regions: {
marginTop: theme.spacing(2),
},
preferred: {
color: theme.palette.text.primary,
},
}));
@@ -1,15 +1,16 @@
import Popover from "@mui/material/Popover";
import { makeStyles } from "@mui/styles";
import { SecondaryAgentButton } from "components/Resources/AgentButton";
import { useRef, useState } from "react";
import { CodeExample } from "../../CodeExample/CodeExample";
import { Stack } from "../../Stack/Stack";
import { css } from "@emotion/css";
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import { type FC, type PropsWithChildren, useRef, useState } from "react";
import {
HelpTooltipLink,
HelpTooltipLinksGroup,
HelpTooltipText,
} from "components/HelpTooltip/HelpTooltip";
import { docs } from "utils/docs";
import { CodeExample } from "../../CodeExample/CodeExample";
import { Stack } from "../../Stack/Stack";
import { SecondaryAgentButton } from "../AgentButton";
export interface SSHButtonProps {
workspaceName: string;
@@ -18,16 +19,16 @@ export interface SSHButtonProps {
sshPrefix?: string;
}
export const SSHButton: React.FC<React.PropsWithChildren<SSHButtonProps>> = ({
export const SSHButton: FC<PropsWithChildren<SSHButtonProps>> = ({
workspaceName,
agentName,
defaultIsOpen = false,
sshPrefix,
}) => {
const theme = useTheme();
const anchorRef = useRef<HTMLButtonElement>(null);
const [isOpen, setIsOpen] = useState(defaultIsOpen);
const id = isOpen ? "schedule-popover" : undefined;
const styles = useStyles();
const onClose = () => {
setIsOpen(false);
@@ -45,7 +46,14 @@ export const SSHButton: React.FC<React.PropsWithChildren<SSHButtonProps>> = ({
</SecondaryAgentButton>
<Popover
classes={{ paper: styles.popoverPaper }}
classes={{
paper: css`
padding: ${theme.spacing(2, 3, 3)};
width: ${theme.spacing(38)};
color: ${theme.palette.text.secondary};
margin-top: ${theme.spacing(0.25)};
`,
}}
id={id}
open={isOpen}
anchorEl={anchorRef.current}
@@ -63,10 +71,10 @@ export const SSHButton: React.FC<React.PropsWithChildren<SSHButtonProps>> = ({
Run the following commands to connect with SSH:
</HelpTooltipText>
<Stack spacing={0.5} className={styles.codeExamples}>
<Stack spacing={0.5} css={styles.codeExamples}>
<div>
<HelpTooltipText>
<strong className={styles.codeExampleLabel}>
<strong css={styles.codeExampleLabel}>
Configure SSH hosts on machine:
</strong>
</HelpTooltipText>
@@ -75,7 +83,7 @@ export const SSHButton: React.FC<React.PropsWithChildren<SSHButtonProps>> = ({
<div>
<HelpTooltipText>
<strong className={styles.codeExampleLabel}>
<strong css={styles.codeExampleLabel}>
Connect to the agent:
</strong>
</HelpTooltipText>
@@ -104,23 +112,12 @@ export const SSHButton: React.FC<React.PropsWithChildren<SSHButtonProps>> = ({
);
};
const useStyles = makeStyles((theme) => ({
popoverPaper: {
padding: `${theme.spacing(2)} ${theme.spacing(3)} ${theme.spacing(3)}`,
width: theme.spacing(38),
color: theme.palette.text.secondary,
marginTop: theme.spacing(0.25),
},
codeExamples: {
const styles = {
codeExamples: (theme) => ({
marginTop: theme.spacing(1.5),
},
}),
codeExampleLabel: {
fontSize: 12,
},
textHelper: {
fontWeight: 400,
},
}));
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,10 +1,10 @@
import IconButton from "@mui/material/IconButton";
import { makeStyles } from "@mui/styles";
import Tooltip from "@mui/material/Tooltip";
import VisibilityOffOutlined from "@mui/icons-material/VisibilityOffOutlined";
import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined";
import { CopyableValue } from "components/CopyableValue/CopyableValue";
import { useState } from "react";
import { css } from "@emotion/react";
import { CopyableValue } from "components/CopyableValue/CopyableValue";
const Language = {
showLabel: "Show value",
@@ -13,7 +13,6 @@ const Language = {
export const SensitiveValue: React.FC<{ value: string }> = ({ value }) => {
const [shouldDisplay, setShouldDisplay] = useState(false);
const styles = useStyles();
const displayValue = shouldDisplay ? value : "••••••••";
const buttonLabel = shouldDisplay ? Language.hideLabel : Language.showLabel;
const icon = shouldDisplay ? (
@@ -23,13 +22,35 @@ export const SensitiveValue: React.FC<{ value: string }> = ({ value }) => {
);
return (
<div className={styles.sensitiveValue}>
<CopyableValue value={value} className={styles.value}>
<div
css={(theme) => ({
display: "flex",
alignItems: "center",
gap: theme.spacing(0.5),
})}
>
<CopyableValue
value={value}
css={{
// 22px is the button width
width: "calc(100% - 22px)",
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
}}
>
{displayValue}
</CopyableValue>
<Tooltip title={buttonLabel}>
<IconButton
className={styles.button}
css={css`
color: inherit;
& .MuiSvgIcon-root {
width: 16px;
height: 16px;
}
`}
onClick={() => {
setShouldDisplay((value) => !value);
}}
@@ -42,28 +63,3 @@ export const SensitiveValue: React.FC<{ value: string }> = ({ value }) => {
</div>
);
};
const useStyles = makeStyles((theme) => ({
value: {
// 22px is the button width
width: "calc(100% - 22px)",
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
},
sensitiveValue: {
display: "flex",
alignItems: "center",
gap: theme.spacing(0.5),
},
button: {
color: "inherit",
"& .MuiSvgIcon-root": {
width: 16,
height: 16,
},
},
}));
@@ -1,40 +1,40 @@
import { makeStyles } from "@mui/styles";
import { FC, ReactNode } from "react";
export const useStyles = makeStyles((theme) => ({
root: {
flex: 1,
height: "-webkit-fill-available",
display: "flex",
justifyContent: "center",
alignItems: "center",
},
layout: {
display: "flex",
flexDirection: "column",
alignItems: "center",
},
container: {
maxWidth: 385,
display: "flex",
flexDirection: "column",
alignItems: "center",
},
footer: {
fontSize: 12,
color: theme.palette.text.secondary,
marginTop: theme.spacing(3),
},
}));
import { type FC, type ReactNode } from "react";
export const SignInLayout: FC<{ children: ReactNode }> = ({ children }) => {
const styles = useStyles();
return (
<div className={styles.root}>
<div className={styles.layout}>
<div className={styles.container}>{children}</div>
<div className={styles.footer}>
<div
css={{
flex: 1,
height: "-webkit-fill-available",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<div
css={{
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<div
css={{
maxWidth: 385,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
{children}
</div>
<div
css={(theme) => ({
fontSize: 12,
color: theme.palette.text.secondary,
marginTop: theme.spacing(3),
})}
>
{`\u00a9 ${new Date().getFullYear()} Coder Technologies, Inc.`}
</div>
</div>
+11 -12
View File
@@ -1,28 +1,27 @@
import { makeStyles } from "@mui/styles";
import TableCell from "@mui/material/TableCell";
import TableRow, { TableRowProps } from "@mui/material/TableRow";
import { FC, ReactNode, cloneElement, isValidElement } from "react";
import TableRow, { type TableRowProps } from "@mui/material/TableRow";
import { type FC, type ReactNode, cloneElement, isValidElement } from "react";
import { useTheme } from "@emotion/react";
import { Loader } from "../Loader/Loader";
export const TableLoader: FC = () => {
const styles = useStyles();
const theme = useTheme();
return (
<TableRow>
<TableCell colSpan={999} className={styles.cell}>
<TableCell
colSpan={999}
css={{
textAlign: "center",
height: theme.spacing(20),
}}
>
<Loader />
</TableCell>
</TableRow>
);
};
const useStyles = makeStyles((theme) => ({
cell: {
textAlign: "center",
height: theme.spacing(20),
},
}));
export const TableLoaderSkeleton = ({
rows = 4,
children,
@@ -1,7 +1,7 @@
import { makeStyles } from "@mui/styles";
import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow";
import { FC } from "react";
import { type FC } from "react";
import { css, useTheme } from "@emotion/react";
import { createDisplayDate } from "./utils";
export interface TimelineDateRow {
@@ -9,32 +9,31 @@ export interface TimelineDateRow {
}
export const TimelineDateRow: FC<TimelineDateRow> = ({ date }) => {
const styles = useStyles();
const theme = useTheme();
return (
<TableRow className={styles.dateRow}>
<TableCell className={styles.dateCell} title={date.toLocaleDateString()}>
<TableRow
css={css`
background: ${theme.palette.background.paper};
&:not(:first-of-type) td {
border-top: 1px solid ${theme.palette.divider};
}
`}
>
<TableCell
css={{
padding: `${theme.spacing(1, 4)} !important`,
background: `${theme.palette.background.paperLight} !important`,
fontSize: 12,
position: "relative",
color: theme.palette.text.secondary,
textTransform: "capitalize",
}}
title={date.toLocaleDateString()}
>
{createDisplayDate(date)}
</TableCell>
</TableRow>
);
};
const useStyles = makeStyles((theme) => ({
dateRow: {
background: theme.palette.background.paper,
"&:not(:first-of-type) td": {
borderTop: `1px solid ${theme.palette.divider}`,
},
},
dateCell: {
padding: `${theme.spacing(1, 4)} !important`,
background: `${theme.palette.background.paperLight} !important`,
fontSize: 12,
position: "relative",
color: theme.palette.text.secondary,
textTransform: "capitalize",
},
}));
+31 -30
View File
@@ -1,6 +1,6 @@
import { makeStyles } from "@mui/styles";
import Typography from "@mui/material/Typography";
import { FC, PropsWithChildren } from "react";
import { type FC, type PropsWithChildren } from "react";
import { css, useTheme } from "@emotion/react";
import { CoderIcon } from "../Icons/CoderIcon";
const Language = {
@@ -14,40 +14,41 @@ const Language = {
export const Welcome: FC<
PropsWithChildren<{ message?: JSX.Element | string }>
> = ({ message = Language.defaultMessage }) => {
const styles = useStyles();
const theme = useTheme();
return (
<div>
<div className={styles.logoBox}>
<CoderIcon className={styles.logo} />
<div
css={{
display: "flex",
justifyContent: "center",
}}
>
<CoderIcon
css={{
color: theme.palette.text.primary,
fontSize: theme.spacing(8),
}}
/>
</div>
<Typography className={styles.title} variant="h1">
<Typography
css={css`
text-align: center;
font-size: ${theme.spacing(4)};
font-weight: 400;
margin: 0;
margin-bottom: ${theme.spacing(2)};
margin-top: ${theme.spacing(2)};
line-height: 1.25;
& strong {
font-weight: 600;
}
`}
variant="h1"
>
{message}
</Typography>
</div>
);
};
const useStyles = makeStyles((theme) => ({
logoBox: {
display: "flex",
justifyContent: "center",
},
logo: {
color: theme.palette.text.primary,
fontSize: theme.spacing(8),
},
title: {
textAlign: "center",
fontSize: theme.spacing(4),
fontWeight: 400,
margin: 0,
marginBottom: theme.spacing(2),
marginTop: theme.spacing(2),
lineHeight: 1.25,
"& strong": {
fontWeight: 600,
},
},
}));
+52 -76
View File
@@ -1,20 +1,20 @@
import { makeStyles } from "@mui/styles";
import { useOrganizationId } from "hooks/useOrganizationId";
import { createContext, FC, Suspense, useContext } from "react";
import { css } from "@emotion/css";
import { useTheme } from "@emotion/react";
import { createContext, type FC, Suspense, useContext } from "react";
import { useQuery } from "react-query";
import { NavLink, Outlet, useNavigate, useParams } from "react-router-dom";
import { combineClasses } from "utils/combineClasses";
import { Margins } from "components/Margins/Margins";
import { Stack } from "components/Stack/Stack";
import { Loader } from "components/Loader/Loader";
import { TemplatePageHeader } from "./TemplatePageHeader";
import type { AuthorizationRequest } from "api/typesGenerated";
import {
checkAuthorization,
getTemplateByName,
getTemplateVersion,
} from "api/api";
import { useQuery } from "react-query";
import { AuthorizationRequest } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Margins } from "components/Margins/Margins";
import { Stack } from "components/Stack/Stack";
import { Loader } from "components/Loader/Loader";
import { useOrganizationId } from "hooks/useOrganizationId";
import { TemplatePageHeader } from "./TemplatePageHeader";
const templatePermissions = (
templateId: string,
@@ -63,8 +63,8 @@ export const useTemplateLayoutContext = (): TemplateLayoutContextValue => {
export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
children = <Outlet />,
}) => {
const theme = useTheme();
const navigate = useNavigate();
const styles = useStyles();
const orgId = useOrganizationId();
const { template: templateName } = useParams() as { template: string };
const { data, error, isLoading } = useQuery({
@@ -75,7 +75,7 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
if (error) {
return (
<div className={styles.error}>
<div css={{ margin: theme.spacing(2) }}>
<ErrorAlert error={error} />
</div>
);
@@ -85,6 +85,34 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
return <Loader />;
}
const itemStyles = css`
text-decoration: none;
color: ${theme.palette.text.secondary};
font-size: 14;
display: block;
padding: ${theme.spacing(0, 2, 2)};
&:hover {
color: ${theme.palette.text.primary};
}
`;
const activeItemStyles = css`
${itemStyles}
color: ${theme.palette.text.primary};
position: relative;
&:before {
content: "";
left: 0;
bottom: 0;
height: 2;
width: 100%;
background: ${theme.palette.secondary.dark};
position: absolute;
}
`;
return (
<>
<TemplatePageHeader
@@ -96,17 +124,19 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
}}
/>
<div className={styles.tabs}>
<div
css={{
borderBottom: `1px solid ${theme.palette.divider}`,
marginBottom: theme.spacing(5),
}}
>
<Margins>
<Stack direction="row" spacing={0.25}>
<NavLink
end
to={`/templates/${templateName}`}
className={({ isActive }) =>
combineClasses([
styles.tabItem,
isActive ? styles.tabItemActive : undefined,
])
isActive ? activeItemStyles : itemStyles
}
>
Summary
@@ -115,10 +145,7 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
end
to={`/templates/${templateName}/docs`}
className={({ isActive }) =>
combineClasses([
styles.tabItem,
isActive ? styles.tabItemActive : undefined,
])
isActive ? activeItemStyles : itemStyles
}
>
Docs
@@ -127,10 +154,7 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
<NavLink
to={`/templates/${templateName}/files`}
className={({ isActive }) =>
combineClasses([
styles.tabItem,
isActive ? styles.tabItemActive : undefined,
])
isActive ? activeItemStyles : itemStyles
}
>
Source Code
@@ -139,10 +163,7 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
<NavLink
to={`/templates/${templateName}/versions`}
className={({ isActive }) =>
combineClasses([
styles.tabItem,
isActive ? styles.tabItemActive : undefined,
])
isActive ? activeItemStyles : itemStyles
}
>
Versions
@@ -150,10 +171,7 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
<NavLink
to={`/templates/${templateName}/embed`}
className={({ isActive }) =>
combineClasses([
styles.tabItem,
isActive ? styles.tabItemActive : undefined,
])
isActive ? activeItemStyles : itemStyles
}
>
Embed
@@ -162,10 +180,7 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
<NavLink
to={`/templates/${templateName}/insights`}
className={({ isActive }) =>
combineClasses([
styles.tabItem,
isActive ? styles.tabItemActive : undefined,
])
isActive ? activeItemStyles : itemStyles
}
>
Insights
@@ -183,42 +198,3 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
</>
);
};
export const useStyles = makeStyles((theme) => {
return {
error: {
margin: theme.spacing(2),
},
tabs: {
borderBottom: `1px solid ${theme.palette.divider}`,
marginBottom: theme.spacing(5),
},
tabItem: {
textDecoration: "none",
color: theme.palette.text.secondary,
fontSize: 14,
display: "block",
padding: theme.spacing(0, 2, 2),
"&:hover": {
color: theme.palette.text.primary,
},
},
tabItemActive: {
color: theme.palette.text.primary,
position: "relative",
"&:before": {
content: `""`,
left: 0,
bottom: 0,
height: 2,
width: "100%",
background: theme.palette.secondary.dark,
position: "absolute",
},
},
};
});
@@ -1,15 +1,14 @@
import {
import { type ComponentProps, type FC } from "react";
import type {
CreateTemplateVersionRequest,
TemplateVersion,
TemplateVersionVariable,
} from "api/typesGenerated";
import { Alert } from "components/Alert/Alert";
import { ComponentProps, FC } from "react";
import { TemplateVariablesForm } from "./TemplateVariablesForm";
import { makeStyles } from "@mui/styles";
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Stack } from "components/Stack/Stack";
import { TemplateVariablesForm } from "./TemplateVariablesForm";
export interface TemplateVariablesPageViewProps {
templateVersion?: TemplateVersion;
@@ -41,16 +40,15 @@ export const TemplateVariablesPageView: FC<TemplateVariablesPageViewProps> = ({
errors = {},
initialTouched,
}) => {
const classes = useStyles();
const hasError = Object.values(errors).some((error) => Boolean(error));
return (
<>
<PageHeader className={classes.pageHeader}>
<PageHeader css={{ paddingTop: 0 }}>
<PageHeaderTitle>Template variables</PageHeaderTitle>
</PageHeader>
{hasError && (
<Stack className={classes.errorContainer}>
<Stack css={(theme) => ({ marginBottom: theme.spacing(8) })}>
{Boolean(errors.buildError) && (
<ErrorAlert error={errors.buildError} />
)}
@@ -78,17 +76,3 @@ export const TemplateVariablesPageView: FC<TemplateVariablesPageViewProps> = ({
</>
);
};
const useStyles = makeStyles((theme) => ({
errorContainer: {
marginBottom: theme.spacing(8),
},
goBackSection: {
display: "flex",
width: "100%",
marginTop: 32,
},
pageHeader: {
paddingTop: 0,
},
}));
+17 -18
View File
@@ -1,13 +1,13 @@
import { type Interpolation, type Theme } from "@emotion/react";
import Button from "@mui/material/Button";
import Link from "@mui/material/Link";
import { makeStyles } from "@mui/styles";
import { TemplateExample } from "api/typesGenerated";
import { Link as RouterLink } from "react-router-dom";
import { type FC } from "react";
import type { TemplateExample } from "api/typesGenerated";
import { CodeExample } from "components/CodeExample/CodeExample";
import { Stack } from "components/Stack/Stack";
import { TableEmpty } from "components/TableEmpty/TableEmpty";
import { TemplateExampleCard } from "components/TemplateExampleCard/TemplateExampleCard";
import { FC } from "react";
import { Link as RouterLink } from "react-router-dom";
import { docs } from "utils/docs";
// Those are from https://github.com/coder/coder/tree/main/examples/templates
@@ -39,7 +39,6 @@ export const EmptyTemplates: FC<{
canCreateTemplates: boolean;
examples: TemplateExample[];
}> = ({ canCreateTemplates, examples }) => {
const styles = useStyles();
const featuredExamples = findFeaturedExamples(examples);
if (canCreateTemplates) {
@@ -63,12 +62,12 @@ export const EmptyTemplates: FC<{
}
cta={
<Stack alignItems="center" spacing={4}>
<div className={styles.featuredExamples}>
<div css={styles.featuredExamples}>
{featuredExamples.map((example) => (
<TemplateExampleCard
example={example}
key={example.id}
className={styles.template}
css={styles.template}
/>
))}
</div>
@@ -77,7 +76,7 @@ export const EmptyTemplates: FC<{
size="small"
component={RouterLink}
to="/starter-templates"
className={styles.viewAllButton}
css={styles.viewAllButton}
>
View all starter templates
</Button>
@@ -89,12 +88,12 @@ export const EmptyTemplates: FC<{
return (
<TableEmpty
className={styles.withImage}
css={styles.withImage}
message="Create a Template"
description="Contact your Coder administrator to create a template. You can share the code below."
cta={<CodeExample code="coder templates init" />}
image={
<div className={styles.emptyImage}>
<div css={styles.emptyImage}>
<img src="/featured/templates.webp" alt="" />
</div>
}
@@ -102,12 +101,12 @@ export const EmptyTemplates: FC<{
);
};
const useStyles = makeStyles((theme) => ({
const styles = {
withImage: {
paddingBottom: 0,
},
emptyImage: {
emptyImage: (theme) => ({
maxWidth: "50%",
height: theme.spacing(40),
overflow: "hidden",
@@ -116,25 +115,25 @@ const useStyles = makeStyles((theme) => ({
"& img": {
maxWidth: "100%",
},
},
}),
featuredExamples: {
featuredExamples: (theme) => ({
maxWidth: theme.spacing(100),
display: "grid",
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
gap: theme.spacing(2),
gridAutoRows: "min-content",
},
}),
template: {
template: (theme) => ({
backgroundColor: theme.palette.background.paperLight,
"&:hover": {
backgroundColor: theme.palette.divider,
},
},
}),
viewAllButton: {
borderRadius: 9999,
},
}));
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,11 +1,11 @@
import { makeStyles } from "@mui/styles";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import CircularProgress from "@mui/material/CircularProgress";
import { GitSSHKey } from "api/typesGenerated";
import { type FC, type PropsWithChildren } from "react";
import { useTheme } from "@emotion/react";
import type { GitSSHKey } from "api/typesGenerated";
import { CodeExample } from "components/CodeExample/CodeExample";
import { Stack } from "components/Stack/Stack";
import { FC } from "react";
import { ErrorAlert } from "components/Alert/ErrorAlert";
export interface SSHKeysPageViewProps {
@@ -16,16 +16,14 @@ export interface SSHKeysPageViewProps {
onRegenerateClick: () => void;
}
export const SSHKeysPageView: FC<
React.PropsWithChildren<SSHKeysPageViewProps>
> = ({
export const SSHKeysPageView: FC<PropsWithChildren<SSHKeysPageViewProps>> = ({
isLoading,
getSSHKeyError,
regenerateSSHKeyError,
sshKey,
onRegenerateClick,
}) => {
const styles = useStyles();
const theme = useTheme();
if (isLoading) {
return (
@@ -45,11 +43,28 @@ export const SSHKeysPageView: FC<
)}
{sshKey && (
<>
<p className={styles.description}>
<p
css={{
fontSize: 14,
color: theme.palette.text.secondary,
margin: 0,
}}
>
The following public key is used to authenticate Git in workspaces.
You may add it to Git services (such as GitHub) that you need to
access from your workspace. Coder configures authentication via{" "}
<code className={styles.code}>$GIT_SSH_COMMAND</code>.
<code
css={{
background: theme.palette.divider,
fontSize: 12,
padding: "2px 4px",
color: theme.palette.text.primary,
borderRadius: 2,
}}
>
$GIT_SSH_COMMAND
</code>
.
</p>
<CodeExample code={sshKey.public_key.trim()} />
<div>
@@ -62,18 +77,3 @@ export const SSHKeysPageView: FC<
</Stack>
);
};
const useStyles = makeStyles((theme) => ({
description: {
fontSize: 14,
color: theme.palette.text.secondary,
margin: 0,
},
code: {
background: theme.palette.divider,
fontSize: 12,
padding: "2px 4px",
color: theme.palette.text.primary,
borderRadius: 2,
},
}));
@@ -1,6 +1,6 @@
import { makeStyles } from "@mui/styles";
import { FC } from "react";
import * as TypesGen from "api/typesGenerated";
import { type FC } from "react";
import { useTheme } from "@emotion/react";
import type * as TypesGen from "api/typesGenerated";
import { CodeExample } from "components/CodeExample/CodeExample";
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
@@ -26,12 +26,20 @@ export const Language = {
export const ResetPasswordDialog: FC<
React.PropsWithChildren<ResetPasswordDialogProps>
> = ({ open, onClose, onConfirm, user, newPassword, loading }) => {
const styles = useStyles();
const theme = useTheme();
const description = (
<>
<p>{Language.message(user?.username)}</p>
<CodeExample code={newPassword ?? ""} className={styles.codeExample} />
<CodeExample
code={newPassword ?? ""}
css={{
minHeight: "auto",
userSelect: "all",
width: "100%",
marginTop: theme.spacing(3),
}}
/>
</>
);
@@ -49,12 +57,3 @@ export const ResetPasswordDialog: FC<
/>
);
};
const useStyles = makeStyles((theme) => ({
codeExample: {
minHeight: "auto",
userSelect: "all",
width: "100%",
marginTop: theme.spacing(3),
},
}));
@@ -1,32 +1,30 @@
import Box, { BoxProps } from "@mui/material/Box";
import { makeStyles, useTheme } from "@mui/styles";
import Box, { type BoxProps } from "@mui/material/Box";
import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow";
import Skeleton from "@mui/material/Skeleton";
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import { type FC } from "react";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import type * as TypesGen from "api/typesGenerated";
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
import { Pill } from "components/Pill/Pill";
import { type FC } from "react";
import * as TypesGen from "api/typesGenerated";
import { combineClasses } from "utils/combineClasses";
import { AvatarData } from "components/AvatarData/AvatarData";
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton";
import { EmptyState } from "components/EmptyState/EmptyState";
import {
TableLoaderSkeleton,
TableRowSkeleton,
} from "components/TableLoader/TableLoader";
import { TableRowMenu } from "components/TableRowMenu/TableRowMenu";
import { EditRolesButton } from "./EditRolesButton";
import { Stack } from "components/Stack/Stack";
import { EnterpriseBadge } from "components/DeploySettingsLayout/Badges";
import dayjs from "dayjs";
import { SxProps, Theme } from "@mui/material/styles";
import { EditRolesButton } from "./EditRolesButton";
import HideSourceOutlined from "@mui/icons-material/HideSourceOutlined";
import KeyOutlined from "@mui/icons-material/KeyOutlined";
import GitHub from "@mui/icons-material/GitHub";
import PasswordOutlined from "@mui/icons-material/PasswordOutlined";
import relativeTime from "dayjs/plugin/relativeTime";
import ShieldOutlined from "@mui/icons-material/ShieldOutlined";
import Skeleton from "@mui/material/Skeleton";
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton";
dayjs.extend(relativeTime);
@@ -89,8 +87,6 @@ export const UsersTableBody: FC<
actorID,
oidcRoleSyncEnabled,
}) => {
const styles = useStyles();
return (
<ChooseOne>
<Cond condition={Boolean(isLoading)}>
@@ -185,10 +181,10 @@ export const UsersTableBody: FC<
<Pill
key={role.name}
text={role.display_name}
className={combineClasses({
[styles.rolePill]: true,
[styles.rolePillOwner]: isOwnerRole(role),
})}
css={[
styles.rolePill,
isOwnerRole(role) && styles.rolePillOwner,
]}
/>
))}
</Stack>
@@ -200,12 +196,10 @@ export const UsersTableBody: FC<
/>
</TableCell>
<TableCell
className={combineClasses([
css={[
styles.status,
user.status === "suspended"
? styles.suspended
: undefined,
])}
user.status === "suspended" && styles.suspended,
]}
>
<Box>{user.status}</Box>
<LastSeen value={user.last_seen_at} sx={{ fontSize: 12 }} />
@@ -273,9 +267,9 @@ const LoginType = ({
authMethods: TypesGen.AuthMethods;
value: TypesGen.LoginType;
}) => {
let displayName = value as string;
let displayName: string = value;
let icon = <></>;
const iconStyles: SxProps = { width: 14, height: 14 };
const iconStyles = { width: 14, height: 14 };
if (value === "password") {
displayName = "Password";
@@ -314,7 +308,7 @@ const LoginType = ({
};
const LastSeen = ({ value, ...boxProps }: { value: string } & BoxProps) => {
const theme: Theme = useTheme();
const theme = useTheme();
const t = dayjs(value);
const now = dayjs();
@@ -348,19 +342,19 @@ const LastSeen = ({ value, ...boxProps }: { value: string } & BoxProps) => {
);
};
const useStyles = makeStyles((theme) => ({
const styles = {
status: {
textTransform: "capitalize",
},
suspended: {
suspended: (theme) => ({
color: theme.palette.text.secondary,
},
rolePill: {
}),
rolePill: (theme) => ({
backgroundColor: theme.palette.background.paperLight,
borderColor: theme.palette.divider,
},
rolePillOwner: {
}),
rolePillOwner: (theme) => ({
backgroundColor: theme.palette.info.dark,
borderColor: theme.palette.info.light,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
+25 -46
View File
@@ -1,16 +1,15 @@
import { makeStyles } from "@mui/styles";
import TableCell from "@mui/material/TableCell";
import { WorkspaceBuild } from "api/typesGenerated";
import { type CSSObject, type Interpolation, type Theme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import type { WorkspaceBuild } from "api/typesGenerated";
import { BuildAvatar } from "components/BuildAvatar/BuildAvatar";
import { Stack } from "components/Stack/Stack";
import { TimelineEntry } from "components/Timeline/TimelineEntry";
import { useClickable } from "hooks/useClickable";
import { useNavigate } from "react-router-dom";
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
import {
displayWorkspaceBuildDuration,
getDisplayWorkspaceBuildInitiatedBy,
} from "utils/workspace";
import { BuildAvatar } from "components/BuildAvatar/BuildAvatar";
export interface BuildRowProps {
build: WorkspaceBuild;
@@ -23,7 +22,6 @@ const transitionMessages = {
};
export const BuildRow: React.FC<BuildRowProps> = ({ build }) => {
const styles = useStyles();
const initiatedBy = getDisplayWorkspaceBuildInitiatedBy(build);
const navigate = useNavigate();
const clickableProps = useClickable<HTMLTableRowElement>(() =>
@@ -32,26 +30,18 @@ export const BuildRow: React.FC<BuildRowProps> = ({ build }) => {
return (
<TimelineEntry hover data-testid={`build-${build.id}`} {...clickableProps}>
<TableCell className={styles.buildCell}>
<Stack
direction="row"
alignItems="center"
className={styles.buildWrapper}
>
<Stack
direction="row"
alignItems="center"
className={styles.fullWidth}
>
<TableCell css={styles.buildCell}>
<Stack direction="row" alignItems="center" css={styles.buildWrapper}>
<Stack direction="row" alignItems="center" css={styles.fullWidth}>
<BuildAvatar build={build} />
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
className={styles.fullWidth}
css={styles.fullWidth}
>
<Stack
className={styles.buildSummary}
css={styles.buildSummary}
direction="row"
alignItems="center"
spacing={1}
@@ -63,22 +53,22 @@ export const BuildRow: React.FC<BuildRowProps> = ({ build }) => {
workspace
</span>
<span className={styles.buildTime}>
<span css={styles.buildTime}>
{new Date(build.created_at).toLocaleTimeString()}
</span>
</Stack>
<Stack direction="row" spacing={1}>
<span className={styles.buildInfo}>
<span css={styles.buildInfo}>
Reason: <strong>{build.reason}</strong>
</span>
<span className={styles.buildInfo}>
<span css={styles.buildInfo}>
Duration:{" "}
<strong>{displayWorkspaceBuildDuration(build)}</strong>
</span>
<span className={styles.buildInfo}>
<span css={styles.buildInfo}>
Version: <strong>{build.template_version_name}</strong>
</span>
</Stack>
@@ -90,10 +80,10 @@ export const BuildRow: React.FC<BuildRowProps> = ({ build }) => {
);
};
const useStyles = makeStyles((theme) => ({
buildWrapper: {
const styles = {
buildWrapper: (theme) => ({
padding: theme.spacing(2, 4),
},
}),
buildCell: {
padding: "0 !important",
@@ -101,36 +91,25 @@ const useStyles = makeStyles((theme) => ({
borderBottom: 0,
},
buildSummary: {
...theme.typography.body1,
buildSummary: (theme) => ({
...(theme.typography.body1 as CSSObject),
fontFamily: "inherit",
},
}),
buildInfo: {
...theme.typography.body2,
buildInfo: (theme) => ({
...(theme.typography.body2 as CSSObject),
fontSize: 12,
fontFamily: "inherit",
color: theme.palette.text.secondary,
display: "block",
},
}),
buildTime: {
buildTime: (theme) => ({
color: theme.palette.text.secondary,
fontSize: 12,
},
buildRight: {
width: "auto",
},
buildExtraInfo: {
...theme.typography.body2,
fontFamily: MONOSPACE_FONT_FAMILY,
color: theme.palette.text.secondary,
whiteSpace: "nowrap",
},
}),
fullWidth: {
width: "100%",
},
}));
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,25 +1,26 @@
import { makeStyles } from "@mui/styles";
import Dialog from "@mui/material/Dialog";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import { DialogProps } from "components/Dialogs/Dialog";
import { FC } from "react";
import DialogActions from "@mui/material/DialogActions";
import Button from "@mui/material/Button";
import { useFormik } from "formik";
import * as Yup from "yup";
import { type FC } from "react";
import { css } from "@emotion/css";
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import { getFormHelpers } from "utils/formUtils";
import type { DialogProps } from "components/Dialogs/Dialog";
import { FormFields, VerticalForm } from "components/Form/Form";
import {
import type {
TemplateVersionParameter,
WorkspaceBuildParameter,
} from "api/typesGenerated";
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput";
import { useFormik } from "formik";
import {
getInitialRichParameterValues,
useValidationSchemaForRichParameters,
} from "utils/richParameters";
import * as Yup from "yup";
import DialogActions from "@mui/material/DialogActions";
import Button from "@mui/material/Button";
export type UpdateBuildParametersDialogProps = DialogProps & {
onClose: () => void;
@@ -30,7 +31,7 @@ export type UpdateBuildParametersDialogProps = DialogProps & {
export const UpdateBuildParametersDialog: FC<
UpdateBuildParametersDialogProps
> = ({ missedParameters, onUpdate, ...dialogProps }) => {
const styles = useStyles();
const theme = useTheme();
const form = useFormik({
initialValues: {
rich_parameter_values: getInitialRichParameterValues(missedParameters),
@@ -56,17 +57,26 @@ export const UpdateBuildParametersDialog: FC<
>
<DialogTitle
id="update-build-parameters-title"
classes={{ root: styles.title }}
classes={{
root: css`
padding: ${theme.spacing(3, 5)};
& h2 {
font-size: ${theme.spacing(2.5)};
font-weight: 400;
}
`,
}}
>
Workspace parameters
</DialogTitle>
<DialogContent className={styles.content}>
<DialogContentText className={styles.info}>
<DialogContent css={styles.content}>
<DialogContentText css={{ margin: 0 }}>
This template has new parameters that must be configured to complete
the update
</DialogContentText>
<VerticalForm
className={styles.form}
css={styles.form}
onSubmit={form.handleSubmit}
id="updateParameters"
>
@@ -96,7 +106,7 @@ export const UpdateBuildParametersDialog: FC<
)}
</VerticalForm>
</DialogContent>
<DialogActions disableSpacing className={styles.dialogActions}>
<DialogActions disableSpacing css={styles.dialogActions}>
<Button fullWidth type="button" onClick={dialogProps.onClose}>
Cancel
</Button>
@@ -114,48 +124,18 @@ export const UpdateBuildParametersDialog: FC<
);
};
const useStyles = makeStyles((theme) => ({
title: {
padding: theme.spacing(3, 5),
"& h2": {
fontSize: theme.spacing(2.5),
fontWeight: 400,
},
},
content: {
const styles = {
content: (theme) => ({
padding: theme.spacing(0, 5, 0, 5),
},
}),
info: {
margin: 0,
},
form: {
form: (theme) => ({
paddingTop: theme.spacing(4),
},
}),
infoTitle: {
fontSize: theme.spacing(2),
fontWeight: 600,
display: "flex",
alignItems: "center",
gap: theme.spacing(1),
},
warningIcon: {
color: theme.palette.warning.light,
fontSize: theme.spacing(1.5),
},
formFooter: {
flexDirection: "column",
},
dialogActions: {
dialogActions: (theme) => ({
padding: theme.spacing(5),
flexDirection: "column",
gap: theme.spacing(1),
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
+33 -74
View File
@@ -1,22 +1,14 @@
import { type Interpolation, type Theme } from "@emotion/react";
import Button from "@mui/material/Button";
import { makeStyles } from "@mui/styles";
import { Avatar } from "components/Avatar/Avatar";
import { AgentRow } from "components/Resources/AgentRow";
import {
ActiveTransition,
WorkspaceBuildProgress,
} from "./WorkspaceBuildProgress";
import { FC, useEffect, useState } from "react";
import AlertTitle from "@mui/material/AlertTitle";
import { type FC, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import * as TypesGen from "api/typesGenerated";
import dayjs from "dayjs";
import type * as TypesGen from "api/typesGenerated";
import { Alert, AlertDetail } from "components/Alert/Alert";
import { BuildsTable } from "./BuildsTable";
import { Margins } from "components/Margins/Margins";
import { Resources } from "components/Resources/Resources";
import { Stack } from "components/Stack/Stack";
import { WorkspaceActions } from "pages/WorkspacePage/WorkspaceActions/WorkspaceActions";
import { WorkspaceDeletedBanner } from "./WorkspaceDeletedBanner";
import { WorkspaceStats } from "./WorkspaceStats";
import {
FullWidthPageHeader,
PageHeaderActions,
@@ -26,9 +18,17 @@ import {
import { TemplateVersionWarnings } from "components/TemplateVersionWarnings/TemplateVersionWarnings";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { DormantWorkspaceBanner } from "components/WorkspaceDeletion";
import { Avatar } from "components/Avatar/Avatar";
import { AgentRow } from "components/Resources/AgentRow";
import { useLocalStorage } from "hooks";
import AlertTitle from "@mui/material/AlertTitle";
import dayjs from "dayjs";
import { WorkspaceActions } from "pages/WorkspacePage/WorkspaceActions/WorkspaceActions";
import {
ActiveTransition,
WorkspaceBuildProgress,
} from "./WorkspaceBuildProgress";
import { BuildsTable } from "./BuildsTable";
import { WorkspaceDeletedBanner } from "./WorkspaceDeletedBanner";
import { WorkspaceStats } from "./WorkspaceStats";
export enum WorkspaceErrors {
GET_BUILDS_ERROR = "getBuildsError",
@@ -112,7 +112,6 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
isLoadingMoreBuilds,
hasMoreBuilds,
}) => {
const styles = useStyles();
const navigate = useNavigate();
const serverVersion = buildInfo?.version || "";
const { saveLocal, getLocal } = useLocalStorage();
@@ -219,12 +218,8 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
)}
</FullWidthPageHeader>
<Margins className={styles.content}>
<Stack
direction="column"
className={styles.firstColumnSpacer}
spacing={4}
>
<Margins css={styles.content}>
<Stack direction="column" css={styles.firstColumnSpacer} spacing={4}>
{workspace.outdated && (
<Alert severity="info">
<AlertTitle>An update is available for your workspace</AlertTitle>
@@ -282,7 +277,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
<Alert severity="info">
<AlertTitle>Workspace build is pending</AlertTitle>
<AlertDetail>
<div className={styles.alertPendingInQueue}>
<div css={styles.alertPendingInQueue}>
This workspace build job is waiting for a provisioner to
become available. If you have been waiting for an extended
period of time, please contact your administrator for
@@ -364,58 +359,22 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
);
};
const spacerWidth = 300;
const styles = {
content: (theme) => ({
marginTop: theme.spacing(4),
}),
export const useStyles = makeStyles((theme) => {
return {
content: {
marginTop: theme.spacing(4),
actions: (theme) => ({
[theme.breakpoints.down("md")]: {
flexDirection: "column",
},
}),
statusBadge: {
marginLeft: theme.spacing(2),
},
firstColumnSpacer: {
flex: 2,
},
actions: {
[theme.breakpoints.down("md")]: {
flexDirection: "column",
},
},
firstColumnSpacer: {
flex: 2,
},
secondColumnSpacer: {
flex: `0 0 ${spacerWidth}px`,
},
layout: {
alignItems: "flex-start",
},
main: {
width: "100%",
},
timelineContents: {
margin: 0,
},
logs: {
border: `1px solid ${theme.palette.divider}`,
},
errorDetails: {
color: theme.palette.text.secondary,
fontSize: 12,
},
fullWidth: {
width: "100%",
},
alertPendingInQueue: {
marginBottom: 12,
},
};
});
alertPendingInQueue: {
marginBottom: 12,
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,16 +1,16 @@
import { makeStyles } from "@mui/styles";
import { Sidebar } from "./Sidebar";
import { Stack } from "components/Stack/Stack";
import { createContext, FC, Suspense, useContext } from "react";
import { createContext, type FC, Suspense, useContext } from "react";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page";
import { Loader } from "components/Loader/Loader";
import { Outlet, useParams } from "react-router-dom";
import { Margins } from "components/Margins/Margins";
import { workspaceByOwnerAndName } from "api/queries/workspaces";
import { useQuery } from "react-query";
import { useTheme } from "@emotion/react";
import type { Workspace } from "api/typesGenerated";
import { workspaceByOwnerAndName } from "api/queries/workspaces";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { type Workspace } from "api/typesGenerated";
import { Loader } from "components/Loader/Loader";
import { Margins } from "components/Margins/Margins";
import { Stack } from "components/Stack/Stack";
import { pageTitle } from "utils/page";
import { Sidebar } from "./Sidebar";
const WorkspaceSettings = createContext<Workspace | undefined>(undefined);
@@ -26,7 +26,7 @@ export function useWorkspaceSettings() {
}
export const WorkspaceSettingsLayout: FC = () => {
const styles = useStyles();
const theme = useTheme();
const params = useParams() as {
workspace: string;
username: string;
@@ -51,14 +51,18 @@ export const WorkspaceSettingsLayout: FC = () => {
</Helmet>
<Margins>
<Stack className={styles.wrapper} direction="row" spacing={10}>
<Stack
css={{ padding: theme.spacing(6, 0) }}
direction="row"
spacing={10}
>
{isError ? (
<ErrorAlert error={error} />
) : (
<WorkspaceSettings.Provider value={workspace}>
<Sidebar workspace={workspace} username={username} />
<Suspense fallback={<Loader />}>
<main className={styles.content}>
<main css={{ width: "100%" }}>
<Outlet />
</main>
</Suspense>
@@ -69,13 +73,3 @@ export const WorkspaceSettingsLayout: FC = () => {
</>
);
};
const useStyles = makeStyles((theme) => ({
wrapper: {
padding: theme.spacing(6, 0),
},
content: {
width: "100%",
},
}));