chore: use emotion for styling (pt. 9) (#10474)

This commit is contained in:
Kayla Washburn
2023-11-02 17:51:23 +00:00
committed by GitHub
parent 839a16e299
commit 921b6eb4ee
30 changed files with 559 additions and 629 deletions
+31 -30
View File
@@ -1,9 +1,9 @@
import { css, Global, useTheme } from "@emotion/react";
import Button from "@mui/material/Button";
import InputAdornment from "@mui/material/InputAdornment";
import TextField, { TextFieldProps } from "@mui/material/TextField";
import { makeStyles } from "@mui/styles";
import TextField, { type TextFieldProps } from "@mui/material/TextField";
import Picker from "@emoji-mart/react";
import { FC } from "react";
import { type FC } from "react";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { Stack } from "components/Stack/Stack";
import { colors } from "theme/colors";
@@ -48,7 +48,7 @@ const IconField: FC<IconFieldProps> = ({ onPickEmoji, ...textFieldProps }) => {
throw new Error(`Invalid icon value "${typeof textFieldProps.value}"`);
}
const styles = useStyles();
const theme = useTheme();
const hasIcon = textFieldProps.value && textFieldProps.value !== "";
return (
@@ -59,7 +59,21 @@ const IconField: FC<IconFieldProps> = ({ onPickEmoji, ...textFieldProps }) => {
label="Icon"
InputProps={{
endAdornment: hasIcon ? (
<InputAdornment position="end" className={styles.adornment}>
<InputAdornment
position="end"
css={{
width: theme.spacing(3),
height: theme.spacing(3),
display: "flex",
alignItems: "center",
justifyContent: "center",
"& img": {
maxWidth: "100%",
objectFit: "contain",
},
}}
>
<img
alt=""
src={textFieldProps.value}
@@ -85,6 +99,18 @@ const IconField: FC<IconFieldProps> = ({ onPickEmoji, ...textFieldProps }) => {
id="emoji"
css={{ marginTop: 0, ".MuiPaper-root": { width: "auto" } }}
>
<Global
styles={css`
em-emoji-picker {
--rgb-background: ${theme.palette.background.paper};
--rgb-input: ${colors.gray[17]};
--rgb-color: ${colors.gray[4]};
// Hack to prevent the right side from being cut off
width: 350px;
}
`}
/>
<Picker
set="twitter"
theme="dark"
@@ -104,29 +130,4 @@ const IconField: FC<IconFieldProps> = ({ onPickEmoji, ...textFieldProps }) => {
);
};
const useStyles = makeStyles((theme) => ({
"@global": {
"em-emoji-picker": {
"--rgb-background": theme.palette.background.paper,
"--rgb-input": colors.gray[17],
"--rgb-color": colors.gray[4],
// Hack to prevent the right side from being cut off
width: 350,
},
},
adornment: {
width: theme.spacing(3),
height: theme.spacing(3),
display: "flex",
alignItems: "center",
justifyContent: "center",
"& img": {
maxWidth: "100%",
objectFit: "contain",
},
},
}));
export default IconField;
@@ -5,7 +5,7 @@ import Skeleton from "@mui/material/Skeleton";
export const TableToolbar = styled(Box)(({ theme }) => ({
fontSize: 13,
marginBottom: theme.spacing(1),
marginTop: theme.spacing(0),
marginTop: 0,
height: 36, // The size of a small button
color: theme.palette.text.secondary,
"& strong": { color: theme.palette.text.primary },
@@ -1,11 +1,17 @@
import { css } from "@emotion/css";
import { useTheme } from "@emotion/react";
import CircularProgress from "@mui/material/CircularProgress";
import { makeStyles } from "@mui/styles";
import TextField from "@mui/material/TextField";
import Autocomplete from "@mui/material/Autocomplete";
import { User } from "api/typesGenerated";
import type { User } from "api/typesGenerated";
import { Avatar } from "components/Avatar/Avatar";
import { AvatarData } from "components/AvatarData/AvatarData";
import { ChangeEvent, ComponentProps, FC, useState } from "react";
import {
type ChangeEvent,
type ComponentProps,
type FC,
useState,
} from "react";
import Box from "@mui/material/Box";
import { useDebouncedFunction } from "hooks/debounce";
import { useQuery } from "react-query";
@@ -27,7 +33,7 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
className,
size = "small",
}) => {
const styles = useStyles();
const theme = useTheme();
const [autoComplete, setAutoComplete] = useState<{
value: string;
open: boolean;
@@ -101,7 +107,11 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
size={size}
label={label}
placeholder="User email or username"
className={styles.textField}
css={{
"&:not(:has(label))": {
margin: 0,
},
}}
InputProps={{
...params.InputProps,
onChange: debouncedInputOnChange,
@@ -119,7 +129,12 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
</>
),
classes: {
root: styles.inputRoot,
root: css`
padding-left: ${theme.spacing(
1.75,
)} !important; // Same padding left as input
gap: ${theme.spacing(0.5)};
`,
},
}}
InputLabelProps={{
@@ -130,15 +145,3 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
/>
);
};
export const useStyles = makeStyles((theme) => ({
textField: {
"&:not(:has(label))": {
margin: 0,
},
},
inputRoot: {
paddingLeft: `${theme.spacing(1.75)} !important`, // Same padding left as input
gap: theme.spacing(0.5),
},
}));
@@ -1,11 +1,10 @@
import { makeStyles } from "@mui/styles";
import dayjs from "dayjs";
import { ComponentProps, FC, Fragment } from "react";
import { ProvisionerJobLog } from "api/typesGenerated";
import { type ComponentProps, type FC, Fragment } from "react";
import type { ProvisionerJobLog } from "api/typesGenerated";
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
import { Logs } from "./Logs";
import Box from "@mui/material/Box";
import { combineClasses } from "utils/combineClasses";
import { type Interpolation, type Theme } from "@emotion/react";
const Language = {
seconds: "seconds",
@@ -53,7 +52,6 @@ export const WorkspaceBuildLogs: FC<WorkspaceBuildLogsProps> = ({
}) => {
const groupedLogsByStage = groupLogsByStage(logs);
const stages = Object.keys(groupedLogsByStage);
const styles = useStyles();
return (
<Box
@@ -79,15 +77,10 @@ export const WorkspaceBuildLogs: FC<WorkspaceBuildLogsProps> = ({
return (
<Fragment key={stage}>
<div
className={combineClasses([
styles.header,
sticky ? styles.sticky : "",
])}
>
<div css={[styles.header, sticky && styles.sticky]}>
<div>{stage}</div>
{shouldDisplayDuration && (
<div className={styles.duration}>
<div css={styles.duration}>
{duration} {Language.seconds}
</div>
)}
@@ -100,8 +93,8 @@ export const WorkspaceBuildLogs: FC<WorkspaceBuildLogsProps> = ({
);
};
const useStyles = makeStyles((theme) => ({
header: {
const styles = {
header: (theme) => ({
fontSize: 13,
fontWeight: 600,
padding: theme.spacing(0.5, 3),
@@ -119,16 +112,16 @@ const useStyles = makeStyles((theme) => ({
"&:first-child": {
borderRadius: "8px 8px 0 0",
},
},
}),
sticky: {
position: "sticky",
top: 0,
},
duration: {
duration: (theme) => ({
marginLeft: "auto",
color: theme.palette.text.secondary,
fontSize: 12,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,15 +1,17 @@
import { Workspace } from "api/typesGenerated";
import type { Workspace } from "api/typesGenerated";
import { Pill } from "components/Pill/Pill";
import { FC, PropsWithChildren } from "react";
import { makeStyles } from "@mui/styles";
import { combineClasses } from "utils/combineClasses";
import { type FC, type PropsWithChildren } from "react";
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
import { DormantDeletionText } from "components/WorkspaceDeletion";
import { getDisplayWorkspaceStatus } from "utils/workspace";
import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip";
import Tooltip, {
type TooltipProps,
tooltipClasses,
} from "@mui/material/Tooltip";
import { styled } from "@mui/material/styles";
import Box from "@mui/material/Box";
import ErrorOutline from "@mui/icons-material/ErrorOutline";
import { type Interpolation, type Theme } from "@emotion/react";
export type WorkspaceStatusBadgeProps = {
workspace: Workspace;
@@ -56,7 +58,6 @@ export const WorkspaceStatusBadge: FC<
export const WorkspaceStatusText: FC<
PropsWithChildren<WorkspaceStatusBadgeProps>
> = ({ workspace, className }) => {
const styles = useStyles();
const { text, type } = getDisplayWorkspaceStatus(
workspace.latest_build.status,
);
@@ -71,11 +72,8 @@ export const WorkspaceStatusText: FC<
<span
role="status"
data-testid="build-status"
className={combineClasses([
className,
styles.root,
styles[`type-${type}`],
])}
className={className}
css={[styles.root, styles[`type-${type}`]]}
>
{text}
</span>
@@ -95,27 +93,22 @@ const FailureTooltip = styled(({ className, ...props }: TooltipProps) => (
},
}));
const useStyles = makeStyles((theme) => ({
const styles = {
root: { fontWeight: 600 },
"type-error": {
"type-error": (theme) => ({
color: theme.palette.error.light,
},
"type-warning": {
}),
"type-warning": (theme) => ({
color: theme.palette.warning.light,
},
"type-success": {
}),
"type-success": (theme) => ({
color: theme.palette.success.light,
},
"type-info": {
}),
"type-info": (theme) => ({
color: theme.palette.info.light,
},
"type-undefined": {
}),
"type-undefined": (theme) => ({
color: theme.palette.text.secondary,
},
"type-primary": {
color: theme.palette.text.primary,
},
"type-secondary": {
color: theme.palette.text.secondary,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
+16 -20
View File
@@ -1,11 +1,11 @@
import { useTheme, type CSSObject } from "@emotion/react";
import { type MouseEventHandler } from "react";
import { type TableRowProps } from "@mui/material/TableRow";
import { makeStyles } from "@mui/styles";
import { useClickable, type UseClickableResult } from "./useClickable";
type UseClickableTableRowResult = UseClickableResult<HTMLTableRowElement> &
TableRowProps & {
className: string;
css: CSSObject;
hover: true;
onAuxClick: MouseEventHandler<HTMLTableRowElement>;
};
@@ -28,12 +28,24 @@ export const useClickableTableRow = ({
onDoubleClick,
onMiddleClick,
}: UseClickableTableRowConfig): UseClickableTableRowResult => {
const styles = useStyles();
const clickableProps = useClickable(onClick);
const theme = useTheme();
return {
...clickableProps,
className: styles.row,
css: {
cursor: "pointer",
"&:focus": {
outline: `1px solid ${theme.palette.secondary.dark}`,
outlineOffset: -1,
},
"&:last-of-type": {
borderBottomLeftRadius: theme.shape.borderRadius,
borderBottomRightRadius: theme.shape.borderRadius,
},
},
hover: true,
onDoubleClick,
onAuxClick: (event) => {
@@ -46,19 +58,3 @@ export const useClickableTableRow = ({
},
};
};
const useStyles = makeStyles((theme) => ({
row: {
cursor: "pointer",
"&:focus": {
outline: `1px solid ${theme.palette.secondary.dark}`,
outlineOffset: -1,
},
"&:last-of-type": {
borderBottomLeftRadius: theme.shape.borderRadius,
borderBottomRightRadius: theme.shape.borderRadius,
},
},
}));
@@ -1,7 +1,7 @@
import { type Interpolation, type Theme } from "@emotion/react";
import Checkbox from "@mui/material/Checkbox";
import { makeStyles } from "@mui/styles";
import TextField from "@mui/material/TextField";
import {
import type {
ProvisionerJobLog,
Template,
TemplateExample,
@@ -9,10 +9,10 @@ import {
VariableValue,
} from "api/typesGenerated";
import { Stack } from "components/Stack/Stack";
import { TemplateUpload, TemplateUploadProps } from "./TemplateUpload";
import { TemplateUpload, type TemplateUploadProps } from "./TemplateUpload";
import { useFormik } from "formik";
import { SelectedTemplate } from "pages/CreateWorkspacePage/SelectedTemplate";
import { FC, useEffect } from "react";
import { type FC, useEffect } from "react";
import {
nameValidator,
getFormHelpers,
@@ -43,8 +43,8 @@ import {
} from "pages/TemplateSettingsPage/TemplateSchedulePage/AutostopRequirementHelperText";
import MenuItem from "@mui/material/MenuItem";
import {
TemplateAutostartRequirementDaysValue,
TemplateAutostopRequirementDaysValue,
type TemplateAutostartRequirementDaysValue,
type TemplateAutostopRequirementDaysValue,
} from "utils/schedule";
import {
TemplateScheduleAutostart,
@@ -218,7 +218,6 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
allowDisableEveryoneAccess,
allowAutostopRequirement,
} = props;
const styles = useStyles();
const form = useFormik<CreateTemplateData>({
initialValues: getInitialValues({
allowAdvancedScheduling,
@@ -338,7 +337,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
description="Define when workspaces created from this template automatically stop."
>
<FormFields>
<Stack direction="row" className={styles.ttlFields}>
<Stack direction="row" css={styles.ttlFields}>
<TextField
{...getFieldHelpers(
"default_ttl_hours",
@@ -373,7 +372,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
</Stack>
{allowAutostopRequirement && (
<Stack direction="row" className={styles.ttlFields}>
<Stack direction="row" css={styles.ttlFields}>
<TextField
{...getFieldHelpers(
"autostop_requirement_days_of_week",
@@ -481,7 +480,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
<strong>
Allow users to customize autostop duration for workspaces.
</strong>
<span className={styles.optionHelperText}>
<span css={styles.optionHelperText}>
Workspaces will always use the default TTL if this is set.
Regardless of this setting, workspaces can only stay on for
the max TTL.
@@ -514,7 +513,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
direction="row"
alignItems="center"
spacing={0.5}
className={styles.optionText}
css={styles.optionText}
>
<strong>
Allow users to cancel in-progress workspace jobs
@@ -527,7 +526,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
</HelpTooltipText>
</HelpTooltip>
</Stack>
<span className={styles.optionHelperText}>
<span css={styles.optionHelperText}>
Depending on your template, canceling builds may leave
workspaces in an unhealthy state. This option isn&apos;t
recommended for most use cases.
@@ -552,7 +551,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
direction="row"
alignItems="center"
spacing={0.5}
className={styles.optionText}
css={styles.optionText}
>
<strong>Allow everyone to use the template</strong>
@@ -569,7 +568,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
</HelpTooltipText>
</HelpTooltip>
</Stack>
<span className={styles.optionHelperText}>
<span css={styles.optionHelperText}>
This setting requires an enterprise license for the&nbsp;
<Link href={docs("/admin/rbac")}>
&apos;Template RBAC&apos;
@@ -610,14 +609,14 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
{jobError && (
<Stack>
<div className={styles.error}>
<h5 className={styles.errorTitle}>Error during provisioning</h5>
<p className={styles.errorDescription}>
<div css={styles.error}>
<h5 css={styles.errorTitle}>Error during provisioning</h5>
<p css={styles.errorDescription}>
Looks like we found an error during the template provisioning. You
can see the logs bellow.
</p>
<code className={styles.errorDetails}>{jobError}</code>
<code css={styles.errorDetails}>{jobError}</code>
</div>
<WorkspaceBuildLogs logs={logs ?? []} />
@@ -690,43 +689,43 @@ const MaxTTLHelperText = (props: { ttl?: number }) => {
);
};
const useStyles = makeStyles((theme) => ({
const styles = {
ttlFields: {
width: "100%",
},
optionText: {
optionText: (theme) => ({
fontSize: theme.spacing(2),
color: theme.palette.text.primary,
},
}),
optionHelperText: {
optionHelperText: (theme) => ({
fontSize: theme.spacing(1.5),
color: theme.palette.text.secondary,
},
}),
error: {
error: (theme) => ({
padding: theme.spacing(3),
borderRadius: theme.spacing(1),
background: theme.palette.background.paper,
border: `1px solid ${theme.palette.error.main}`,
},
}),
errorTitle: {
fontSize: 16,
margin: 0,
},
errorDescription: {
errorDescription: (theme) => ({
margin: 0,
color: theme.palette.text.secondary,
marginTop: theme.spacing(0.5),
},
}),
errorDetails: {
errorDetails: (theme) => ({
display: "block",
marginTop: theme.spacing(1),
color: theme.palette.error.light,
fontSize: theme.spacing(2),
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,8 +1,10 @@
import { css } from "@emotion/css";
import { useTheme, type Interpolation, type Theme } from "@emotion/react";
import TextField from "@mui/material/TextField";
import * as TypesGen from "api/typesGenerated";
import type * as TypesGen from "api/typesGenerated";
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
import { FormikContextType, useFormik } from "formik";
import { FC, useEffect, useState } from "react";
import { type FC, useEffect, useState } from "react";
import {
getFormHelpers,
nameValidator,
@@ -17,7 +19,6 @@ import {
FormFooter,
HorizontalForm,
} from "components/Form/Form";
import { makeStyles } from "@mui/styles";
import {
getInitialRichParameterValues,
useValidationSchemaForRichParameters,
@@ -31,7 +32,7 @@ import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Stack } from "components/Stack/Stack";
import { type ExternalAuthPollingState } from "./CreateWorkspacePage";
import { useSearchParams } from "react-router-dom";
import { CreateWSPermissions } from "./permissions";
import type { CreateWSPermissions } from "./permissions";
export interface CreateWorkspacePageViewProps {
error: unknown;
@@ -69,7 +70,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
onSubmit,
onCancel,
}) => {
const styles = useStyles();
const theme = useTheme();
const [owner, setOwner] = useState(defaultOwner);
const { verifyExternalAuth, externalAuthErrors } =
useExternalAuthVerification(externalAuth);
@@ -123,14 +124,14 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
<FormFields>
<SelectedTemplate template={template} />
{versionId && versionId !== template.active_version_id && (
<Stack spacing={1} className={styles.hasDescription}>
<Stack spacing={1} css={styles.hasDescription}>
<TextField
disabled
fullWidth
value={versionId}
label="Version ID"
/>
<span className={styles.description}>
<span css={styles.description}>
This parameter has been preset, and cannot be modified.
</span>
</Stack>
@@ -210,7 +211,16 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
/>
<ImmutableTemplateParametersSection
templateParameters={parameters}
classes={{ root: styles.warningSection }}
classes={{
root: css`
border: 1px solid ${theme.palette.warning.light};
border-radius: 8px;
background-color: ${theme.palette.background.paper};
padding: ${theme.spacing(10)};
margin-left: ${theme.spacing(-10)};
margin-right: ${theme.spacing(-10)};
`,
}}
getInputProps={(parameter, index) => {
return {
...getFieldHelpers(
@@ -279,23 +289,12 @@ const useExternalAuthVerification = (
};
};
const useStyles = makeStyles((theme) => ({
hasDescription: {
const styles = {
hasDescription: (theme) => ({
paddingBottom: theme.spacing(2),
},
description: {
}),
description: (theme) => ({
fontSize: 13,
color: theme.palette.text.secondary,
},
warningText: {
color: theme.palette.warning.light,
},
warningSection: {
border: `1px solid ${theme.palette.warning.light}`,
borderRadius: 8,
backgroundColor: theme.palette.background.paper,
padding: theme.spacing(10),
marginLeft: theme.spacing(-10),
marginRight: theme.spacing(-10),
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,13 +1,14 @@
import { type Interpolation, type Theme } from "@emotion/react";
import Button from "@mui/material/Button";
import { makeStyles, useTheme } from "@mui/styles";
import { useTheme } from "@mui/styles";
import Skeleton from "@mui/material/Skeleton";
import AddIcon from "@mui/icons-material/AddOutlined";
import RefreshIcon from "@mui/icons-material/Refresh";
import { GetLicensesResponse } from "api/api";
import type { GetLicensesResponse } from "api/api";
import { Header } from "components/DeploySettingsLayout/Header";
import { LicenseCard } from "./LicenseCard";
import { Stack } from "components/Stack/Stack";
import { FC } from "react";
import { type FC } from "react";
import Confetti from "react-confetti";
import { Link } from "react-router-dom";
import useWindowSize from "react-use/lib/useWindowSize";
@@ -38,10 +39,8 @@ const LicensesSettingsPageView: FC<Props> = ({
removeLicense,
refreshEntitlements,
}) => {
const styles = useStyles();
const { width, height } = useWindowSize();
const theme = useTheme();
const { width, height } = useWindowSize();
return (
<>
@@ -107,13 +106,11 @@ const LicensesSettingsPageView: FC<Props> = ({
)}
{!isLoading && licenses === null && (
<div className={styles.root}>
<div css={styles.root}>
<Stack alignItems="center" spacing={1}>
<Stack alignItems="center" spacing={0.5}>
<span className={styles.title}>
You don&apos;t have any licenses!
</span>
<span className={styles.description}>
<span css={styles.title}>You don&apos;t have any licenses!</span>
<span css={styles.description}>
You&apos;re missing out on high availability, RBAC, quotas, and
much more. Contact{" "}
<MuiLink href="mailto:sales@coder.com">sales</MuiLink> or{" "}
@@ -130,12 +127,12 @@ const LicensesSettingsPageView: FC<Props> = ({
);
};
const useStyles = makeStyles((theme) => ({
title: {
const styles = {
title: (theme) => ({
fontSize: theme.spacing(2),
},
}),
root: {
root: (theme) => ({
minHeight: theme.spacing(30),
display: "flex",
alignItems: "center",
@@ -143,14 +140,14 @@ const useStyles = makeStyles((theme) => ({
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,
padding: theme.spacing(6),
},
}),
description: {
description: (theme) => ({
color: theme.palette.text.secondary,
textAlign: "center",
maxWidth: theme.spacing(58),
marginTop: theme.spacing(1),
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
export default LicensesSettingsPageView;
+13 -16
View File
@@ -1,6 +1,6 @@
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 Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
@@ -20,10 +20,10 @@ import {
TableRowSkeleton,
} from "components/TableLoader/TableLoader";
import { UserAvatar } from "components/UserAvatar/UserAvatar";
import { FC } from "react";
import { type FC } from "react";
import { Link as RouterLink, useNavigate } from "react-router-dom";
import { Paywall } from "components/Paywall/Paywall";
import { Group } from "api/typesGenerated";
import type { Group } from "api/typesGenerated";
import { GroupAvatar } from "components/GroupAvatar/GroupAvatar";
import { docs } from "utils/docs";
import Skeleton from "@mui/material/Skeleton";
@@ -44,7 +44,6 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
const isLoading = Boolean(groups === undefined);
const isEmpty = Boolean(groups && groups.length === 0);
const navigate = useNavigate();
const styles = useStyles();
return (
<>
@@ -137,7 +136,7 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
navigate(groupPageLink);
}
}}
className={styles.clickableTableRow}
css={styles.clickableTableRow}
>
<TableCell>
<AvatarData
@@ -170,10 +169,8 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
</TableCell>
<TableCell>
<div className={styles.arrowCell}>
<KeyboardArrowRight
className={styles.arrowRight}
/>
<div css={styles.arrowCell}>
<KeyboardArrowRight css={styles.arrowRight} />
</div>
</TableCell>
</TableRow>
@@ -210,8 +207,8 @@ const TableLoader = () => {
);
};
const useStyles = makeStyles((theme) => ({
clickableTableRow: {
const styles = {
clickableTableRow: (theme) => ({
cursor: "pointer",
"&:hover td": {
@@ -223,17 +220,17 @@ const useStyles = makeStyles((theme) => ({
},
"& .MuiTableCell-root:last-child": {
paddingRight: theme.spacing(2),
paddingRight: `${theme.spacing(2)} !important`,
},
},
arrowRight: {
}),
arrowRight: (theme) => ({
color: theme.palette.text.secondary,
width: 20,
height: 20,
},
}),
arrowCell: {
display: "flex",
},
}));
} satisfies Record<string, Interpolation<Theme>>;
export default GroupsPageView;
@@ -1,4 +1,9 @@
import { MockAuthMethods, mockApiError } from "testHelpers/entities";
import {
MockAuthMethodsAll,
MockAuthMethodsExternal,
MockAuthMethodsPasswordOnly,
mockApiError,
} from "testHelpers/entities";
import { LoginPageView } from "./LoginPageView";
import type { Meta, StoryObj } from "@storybook/react";
@@ -12,17 +17,37 @@ type Story = StoryObj<typeof LoginPageView>;
export const Example: Story = {
args: {
authMethods: MockAuthMethods,
authMethods: MockAuthMethodsPasswordOnly,
},
};
export const WithExternalAuthMethods: Story = {
args: {
authMethods: MockAuthMethodsExternal,
},
};
export const WithAllAuthMethods: Story = {
args: {
authMethods: MockAuthMethodsAll,
},
};
export const AuthError: Story = {
args: {
error: mockApiError({
message: "User or password is incorrect",
detail: "Please, try again",
message: "Incorrect email or password.",
}),
authMethods: MockAuthMethods,
authMethods: MockAuthMethodsPasswordOnly,
},
};
export const ExternalAuthError: Story = {
args: {
error: mockApiError({
message: "Incorrect email or password.",
}),
authMethods: MockAuthMethodsAll,
},
};
@@ -35,6 +60,6 @@ export const LoadingAuthMethods: Story = {
export const SigningIn: Story = {
args: {
isSigningIn: true,
authMethods: MockAuthMethods,
authMethods: MockAuthMethodsPasswordOnly,
},
};
@@ -1,5 +1,5 @@
import Button from "@mui/material/Button";
import { makeStyles } from "@mui/styles";
import { useTheme } from "@emotion/react";
import { Loader } from "components/Loader/Loader";
import { Margins } from "components/Margins/Margins";
import { MemoizedMarkdown } from "components/Markdown/Markdown";
@@ -8,13 +8,13 @@ import {
PageHeaderSubtitle,
PageHeaderTitle,
} from "components/PageHeader/PageHeader";
import { FC } from "react";
import { type FC } from "react";
import ViewCodeIcon from "@mui/icons-material/OpenInNewOutlined";
import PlusIcon from "@mui/icons-material/AddOutlined";
import { Stack } from "components/Stack/Stack";
import { Link } from "react-router-dom";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { TemplateExample } from "api/typesGenerated";
import type { TemplateExample } from "api/typesGenerated";
export interface StarterTemplatePageViewProps {
starterTemplate?: TemplateExample;
@@ -25,7 +25,7 @@ export const StarterTemplatePageView: FC<StarterTemplatePageViewProps> = ({
starterTemplate,
error,
}) => {
const styles = useStyles();
const theme = useTheme();
if (error) {
return (
@@ -65,7 +65,19 @@ export const StarterTemplatePageView: FC<StarterTemplatePageViewProps> = ({
}
>
<Stack direction="row" spacing={3} alignItems="center">
<div className={styles.icon}>
<div
css={{
height: theme.spacing(6),
width: theme.spacing(6),
display: "flex",
alignItems: "center",
justifyContent: "center",
"& img": {
width: "100%",
},
}}
>
<img src={starterTemplate.icon} alt="" />
</div>
<div>
@@ -77,39 +89,24 @@ export const StarterTemplatePageView: FC<StarterTemplatePageViewProps> = ({
</Stack>
</PageHeader>
<div className={styles.markdownSection} id="readme">
<div className={styles.markdownWrapper}>
<div
css={{
background: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
}}
id="readme"
>
<div
css={{
padding: theme.spacing(5, 5, 8),
maxWidth: 800,
margin: "auto",
}}
>
<MemoizedMarkdown>{starterTemplate.markdown}</MemoizedMarkdown>
</div>
</div>
</Margins>
);
};
export const useStyles = makeStyles((theme) => {
return {
icon: {
height: theme.spacing(6),
width: theme.spacing(6),
display: "flex",
alignItems: "center",
justifyContent: "center",
"& img": {
width: "100%",
},
},
markdownSection: {
background: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
},
markdownWrapper: {
padding: theme.spacing(5, 5, 8),
maxWidth: 800,
margin: "auto",
},
};
});
@@ -1,4 +1,4 @@
import { makeStyles } from "@mui/styles";
import { type Interpolation, type Theme } from "@emotion/react";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Loader } from "components/Loader/Loader";
import { Margins } from "components/Margins/Margins";
@@ -9,10 +9,9 @@ import {
} from "components/PageHeader/PageHeader";
import { Stack } from "components/Stack/Stack";
import { TemplateExampleCard } from "components/TemplateExampleCard/TemplateExampleCard";
import { FC } from "react";
import { type FC } from "react";
import { Link, useSearchParams } from "react-router-dom";
import { combineClasses } from "utils/combineClasses";
import { StarterTemplatesByTag } from "utils/starterTemplates";
import type { StarterTemplatesByTag } from "utils/starterTemplates";
const getTagLabel = (tag: string) => {
const labelByTag: Record<string, string> = {
@@ -40,7 +39,6 @@ export const StarterTemplatesPageView: FC<StarterTemplatesPageViewProps> = ({
error,
}) => {
const [urlParams] = useSearchParams();
const styles = useStyles();
const tags = starterTemplatesByTag
? selectTags(starterTemplatesByTag)
: undefined;
@@ -64,16 +62,16 @@ export const StarterTemplatesPageView: FC<StarterTemplatesPageViewProps> = ({
<Stack direction="row" spacing={4}>
{starterTemplatesByTag && tags && (
<Stack className={styles.filter}>
<span className={styles.filterCaption}>Filter</span>
<Stack css={styles.filter}>
<span css={styles.filterCaption}>Filter</span>
{tags.map((tag) => (
<Link
key={tag}
to={`?tag=${tag}`}
className={combineClasses({
[styles.tagLink]: true,
[styles.tagLinkActive]: tag === activeTag,
})}
css={[
styles.tagLink,
tag === activeTag && styles.tagLinkActive,
]}
>
{getTagLabel(tag)} ({starterTemplatesByTag[tag].length})
</Link>
@@ -81,7 +79,7 @@ export const StarterTemplatesPageView: FC<StarterTemplatesPageViewProps> = ({
</Stack>
)}
<div className={styles.templates}>
<div css={styles.templates}>
{visibleTemplates &&
visibleTemplates.map((example) => (
<TemplateExampleCard example={example} key={example.id} />
@@ -92,21 +90,21 @@ export const StarterTemplatesPageView: FC<StarterTemplatesPageViewProps> = ({
);
};
const useStyles = makeStyles((theme) => ({
filter: {
const styles = {
filter: (theme) => ({
width: theme.spacing(26),
flexShrink: 0,
},
}),
filterCaption: {
filterCaption: (theme) => ({
textTransform: "uppercase",
fontWeight: 600,
fontSize: 12,
color: theme.palette.text.secondary,
letterSpacing: "0.1em",
},
}),
tagLink: {
tagLink: (theme) => ({
color: theme.palette.text.secondary,
textDecoration: "none",
fontSize: 14,
@@ -115,18 +113,18 @@ const useStyles = makeStyles((theme) => ({
"&:hover": {
color: theme.palette.text.primary,
},
},
}),
tagLinkActive: {
tagLinkActive: (theme) => ({
color: theme.palette.text.primary,
fontWeight: 600,
},
}),
templates: {
templates: (theme) => ({
flex: "1",
display: "grid",
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
gap: theme.spacing(2),
gridAutoRows: "min-content",
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
+69 -62
View File
@@ -1,27 +1,72 @@
import { makeStyles } from "@mui/styles";
import { css } from "@emotion/css";
import {
useTheme,
type CSSObject,
type Interpolation,
type Theme,
} from "@emotion/react";
import ScheduleIcon from "@mui/icons-material/TimerOutlined";
import VariablesIcon from "@mui/icons-material/CodeOutlined";
import { Template } from "api/typesGenerated";
import type { Template } from "api/typesGenerated";
import { Stack } from "components/Stack/Stack";
import { FC, ElementType, PropsWithChildren, ReactNode } from "react";
import {
type FC,
type ElementType,
type PropsWithChildren,
type ReactNode,
} from "react";
import { Link, NavLink } from "react-router-dom";
import { combineClasses } from "utils/combineClasses";
import GeneralIcon from "@mui/icons-material/SettingsOutlined";
import SecurityIcon from "@mui/icons-material/LockOutlined";
import { Avatar } from "components/Avatar/Avatar";
import { combineClasses } from "utils/combineClasses";
const SidebarNavItem: FC<
PropsWithChildren<{ href: string; icon: ReactNode }>
> = ({ children, href, icon }) => {
const styles = useStyles();
const theme = useTheme();
const sidebarNavItemStyles = css`
color: inherit;
display: block;
font-size: 14px;
text-decoration: none;
padding: ${theme.spacing(1.5, 1.5, 1.5, 2)};
border-radius: ${theme.shape.borderRadius / 2}px;
transition: background-color 0.15s ease-in-out;
margin-bottom: 1px;
position: relative;
&:hover {
background-color: ${theme.palette.action.hover};
}
`;
const sidebarNavItemActiveStyles = css`
background-color: ${theme.palette.action.hover};
&:before {
content: "";
display: block;
width: 3px;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-color: ${theme.palette.secondary.dark};
border-top-left-radius: ${theme.shape.borderRadius}px;
border-bottom-left-radius: ${theme.shape.borderRadius}px;
}
`;
return (
<NavLink
end
to={href}
className={({ isActive }) =>
combineClasses([
styles.sidebarNavItem,
isActive ? styles.sidebarNavItemActive : undefined,
sidebarNavItemStyles,
isActive ? sidebarNavItemActiveStyles : undefined,
])
}
>
@@ -36,28 +81,21 @@ const SidebarNavItem: FC<
const SidebarNavItemIcon: React.FC<{ icon: ElementType }> = ({
icon: Icon,
}) => {
const styles = useStyles();
return <Icon className={styles.sidebarNavItemIcon} />;
return <Icon css={styles.sidebarNavItemIcon} />;
};
export const Sidebar: React.FC<{ template: Template }> = ({ template }) => {
const styles = useStyles();
return (
<nav className={styles.sidebar}>
<Stack
direction="row"
alignItems="center"
className={styles.templateInfo}
>
<nav css={styles.sidebar}>
<Stack direction="row" alignItems="center" css={styles.templateInfo}>
<Avatar src={template.icon} variant="square" fitImage />
<Stack spacing={0} className={styles.templateData}>
<Link className={styles.name} to={`/templates/${template.name}`}>
<Stack spacing={0} css={styles.templateData}>
<Link css={styles.name} to={`/templates/${template.name}`}>
{template.display_name !== ""
? template.display_name
: template.name}
</Link>
<span className={styles.secondary}>{template.name}</span>
<span css={styles.secondary}>{template.name}</span>
</Stack>
</Stack>
@@ -86,65 +124,34 @@ export const Sidebar: React.FC<{ template: Template }> = ({ template }) => {
);
};
const useStyles = makeStyles((theme) => ({
const styles = {
sidebar: {
width: 245,
flexShrink: 0,
},
sidebarNavItem: {
color: "inherit",
display: "block",
fontSize: 14,
textDecoration: "none",
padding: theme.spacing(1.5, 1.5, 1.5, 2),
borderRadius: theme.shape.borderRadius / 2,
transition: "background-color 0.15s ease-in-out",
marginBottom: 1,
position: "relative",
"&:hover": {
backgroundColor: theme.palette.action.hover,
},
},
sidebarNavItemActive: {
backgroundColor: theme.palette.action.hover,
"&:before": {
content: '""',
display: "block",
width: 3,
height: "100%",
position: "absolute",
left: 0,
top: 0,
backgroundColor: theme.palette.secondary.dark,
borderTopLeftRadius: theme.shape.borderRadius,
borderBottomLeftRadius: theme.shape.borderRadius,
},
},
sidebarNavItemIcon: {
sidebarNavItemIcon: (theme) => ({
width: theme.spacing(2),
height: theme.spacing(2),
},
templateInfo: {
...theme.typography.body2,
}),
templateInfo: (theme) => ({
...(theme.typography.body2 as CSSObject),
marginBottom: theme.spacing(2),
},
}),
templateData: {
overflow: "hidden",
},
name: {
name: (theme) => ({
fontWeight: 600,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
color: theme.palette.text.primary,
textDecoration: "none",
},
secondary: {
}),
secondary: (theme) => ({
color: theme.palette.text.secondary,
fontSize: 12,
overflow: "hidden",
textOverflow: "ellipsis",
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,5 +1,5 @@
import { type Interpolation, type Theme } from "@emotion/react";
import Button from "@mui/material/Button";
import { makeStyles } from "@mui/styles";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
@@ -7,7 +7,7 @@ import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import AddIcon from "@mui/icons-material/AddOutlined";
import { FC } from "react";
import { type FC } from "react";
import { useNavigate, Link as RouterLink } from "react-router-dom";
import { createDayString } from "utils/createDayString";
import {
@@ -35,8 +35,7 @@ import {
} from "components/HelpTooltip/HelpTooltip";
import { EmptyTemplates } from "./EmptyTemplates";
import { useClickableTableRow } from "hooks/useClickableTableRow";
import { Template, TemplateExample } from "api/typesGenerated";
import { combineClasses } from "utils/combineClasses";
import type { Template, TemplateExample } from "api/typesGenerated";
import { colors } from "theme/colors";
import ArrowForwardOutlined from "@mui/icons-material/ArrowForwardOutlined";
import { Avatar } from "components/Avatar/Avatar";
@@ -80,17 +79,17 @@ const TemplateRow: FC<{ template: Template }> = ({ template }) => {
const templatePageLink = `/templates/${template.name}`;
const hasIcon = template.icon && template.icon !== "";
const navigate = useNavigate();
const styles = useStyles();
const { className: clickableClassName, ...clickableRow } =
useClickableTableRow({ onClick: () => navigate(templatePageLink) });
const { css: clickableCss, ...clickableRow } = useClickableTableRow({
onClick: () => navigate(templatePageLink),
});
return (
<TableRow
key={template.id}
data-testid={`template-${template.id}`}
{...clickableRow}
className={combineClasses([clickableClassName, styles.tableRow])}
css={[clickableCss, styles.tableRow]}
>
<TableCell>
<AvatarData
@@ -106,22 +105,23 @@ const TemplateRow: FC<{ template: Template }> = ({ template }) => {
/>
</TableCell>
<TableCell className={styles.secondary}>
<TableCell css={styles.secondary}>
{Language.developerCount(template.active_user_count)}
</TableCell>
<TableCell className={styles.secondary}>
<TableCell css={styles.secondary}>
{formatTemplateBuildTime(template.build_time_stats.start.P50)}
</TableCell>
<TableCell data-chromatic="ignore" className={styles.secondary}>
<TableCell data-chromatic="ignore" css={styles.secondary}>
{createDayString(template.updated_at)}
</TableCell>
<TableCell className={styles.actionCell}>
<TableCell css={styles.actionCell}>
<Button
size="small"
className={styles.actionButton}
css={styles.actionButton}
className="actionButton"
startIcon={<ArrowForwardOutlined />}
title={`Create a workspace using the ${template.display_name} template`}
onClick={(e) => {
@@ -247,7 +247,7 @@ const TableLoader = () => {
);
};
const useStyles = makeStyles((theme) => ({
const styles = {
templateIconWrapper: {
// Same size then the avatar component
width: 36,
@@ -261,20 +261,20 @@ const useStyles = makeStyles((theme) => ({
actionCell: {
whiteSpace: "nowrap",
},
secondary: {
secondary: (theme) => ({
color: theme.palette.text.secondary,
},
tableRow: {
"&:hover $actionButton": {
}),
tableRow: (theme) => ({
"&:hover .actionButton": {
color: theme.palette.text.primary,
borderColor: colors.gray[11],
"&:hover": {
borderColor: theme.palette.text.primary,
},
},
},
actionButton: {
}),
actionButton: (theme) => ({
color: theme.palette.text.secondary,
transition: "none",
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
+8 -69
View File
@@ -1,5 +1,5 @@
import { makeStyles, useTheme } from "@mui/styles";
import { FC, useCallback, useEffect, useRef, useState } from "react";
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import { type FC, useCallback, useEffect, useRef, useState } from "react";
import { Helmet } from "react-helmet-async";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import { colors } from "theme/colors";
@@ -16,7 +16,7 @@ import { pageTitle } from "utils/page";
import { useProxy } from "contexts/ProxyContext";
import Box from "@mui/material/Box";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { Region } from "api/typesGenerated";
import type { Region } from "api/typesGenerated";
import { getLatencyColor } from "utils/latency";
import { ProxyStatusLatency } from "components/ProxyStatusLatency/ProxyStatusLatency";
import { openMaybePortForwardedURL } from "utils/portForward";
@@ -45,7 +45,6 @@ export const Language = {
const TerminalPage: FC = () => {
const navigate = useNavigate();
const styles = useStyles();
const { proxy } = useProxy();
const params = useParams() as { username: string; workspace: string };
const username = params.username.replace("@", "");
@@ -316,11 +315,7 @@ const TerminalPage: FC = () => {
{lifecycleState === "ready" &&
prevLifecycleState.current === "starting" && <LoadedScriptsAlert />}
{terminalState === "disconnected" && <DisconnectedAlert />}
<div
className={styles.terminal}
ref={xtermRef}
data-testid="terminal"
/>
<div css={styles.terminal} ref={xtermRef} data-testid="terminal" />
{dashboard.experiments.includes("moons") &&
selectedProxy &&
latency && (
@@ -426,35 +421,8 @@ const BottomBar = ({ proxy, latency }: { proxy: Region; latency?: number }) => {
);
};
const useStyles = makeStyles((theme) => ({
overlay: {
position: "absolute",
pointerEvents: "none",
top: 0,
left: 0,
bottom: 0,
right: 0,
zIndex: 1,
alignItems: "center",
justifyContent: "center",
display: "flex",
color: "white",
fontSize: 16,
backgroundColor: "rgba(0, 0, 0, 0.6)",
backdropFilter: "blur(4px)",
"&.connected": {
opacity: 0,
},
},
overlayText: {
fontSize: 16,
fontWeight: 600,
},
overlaySubtext: {
fontSize: 14,
color: theme.palette.text.secondary,
},
terminal: {
const styles = {
terminal: (theme) => ({
width: "100vw",
overflow: "hidden",
backgroundColor: theme.palette.background.paper,
@@ -480,36 +448,7 @@ const useStyles = makeStyles((theme) => ({
minHeight: 20,
backgroundColor: "rgba(255, 255, 255, 0.18)",
},
},
alert: {
display: "flex",
background: theme.palette.background.paperLight,
alignItems: "center",
padding: theme.spacing(2),
gap: theme.spacing(2),
borderBottom: `1px solid ${theme.palette.divider}`,
...theme.typography.body2,
},
alertIcon: {
color: theme.palette.warning.light,
fontSize: theme.spacing(3),
},
alertError: {
"& $alertIcon": {
color: theme.palette.error.light,
},
},
alertTitle: {
fontWeight: 600,
color: theme.palette.text.primary,
},
alertMessage: {
fontSize: 14,
color: theme.palette.text.secondary,
},
alertActions: {
marginLeft: "auto",
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
export default TerminalPage;
@@ -6,10 +6,7 @@ import {
waitForLoaderToBeRemoved,
} from "testHelpers/renderHelpers";
import { SecurityPage } from "./SecurityPage";
import {
MockAuthMethodsWithPasswordType,
mockApiError,
} from "testHelpers/entities";
import { MockAuthMethodsAll, mockApiError } from "testHelpers/entities";
import userEvent from "@testing-library/user-event";
import * as SSO from "./SingleSignOnSection";
import { OAuthConversionResponse } from "api/typesGenerated";
@@ -40,9 +37,7 @@ const fillAndSubmitSecurityForm = () => {
};
beforeEach(() => {
jest
.spyOn(API, "getAuthMethods")
.mockResolvedValue(MockAuthMethodsWithPasswordType);
jest.spyOn(API, "getAuthMethods").mockResolvedValue(MockAuthMethodsAll);
jest.spyOn(API, "getUserLoginType").mockResolvedValue({
login_type: "password",
});
@@ -2,8 +2,8 @@ import type { Meta, StoryObj } from "@storybook/react";
import { SecurityPageView } from "./SecurityPage";
import { action } from "@storybook/addon-actions";
import {
MockAuthMethods,
MockAuthMethodsWithPasswordType,
MockAuthMethodsPasswordOnly,
MockAuthMethodsAll,
} from "testHelpers/entities";
import { ComponentProps } from "react";
import set from "lodash/fp/set";
@@ -22,7 +22,7 @@ const defaultArgs: ComponentProps<typeof SecurityPageView> = {
userLoginType: {
login_type: "password",
},
authMethods: MockAuthMethods,
authMethods: MockAuthMethodsPasswordOnly,
closeConfirmation: action("closeConfirmation"),
confirm: action("confirm"),
error: undefined,
@@ -52,11 +52,7 @@ export const NoOIDCAvailable: Story = {
};
export const UserLoginTypeIsPassword: Story = {
args: set(
"oidc.section.authMethods",
MockAuthMethodsWithPasswordType,
defaultArgs,
),
args: set("oidc.section.authMethods", MockAuthMethodsAll, defaultArgs),
};
export const ConfirmingOIDCConversion: Story = {
@@ -64,7 +60,7 @@ export const ConfirmingOIDCConversion: Story = {
"oidc.section",
{
...defaultArgs.oidc?.section,
authMethods: MockAuthMethodsWithPasswordType,
authMethods: MockAuthMethodsAll,
isConfirming: true,
},
defaultArgs,
@@ -7,7 +7,7 @@ import KeyIcon from "@mui/icons-material/VpnKey";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import { convertToOAUTH } from "api/api";
import {
import type {
AuthMethods,
LoginType,
OIDCAuthMethod,
@@ -19,7 +19,6 @@ import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { getErrorMessage } from "api/errors";
import CheckCircleOutlined from "@mui/icons-material/CheckCircleOutlined";
import { EmptyState } from "components/EmptyState/EmptyState";
import { makeStyles } from "@mui/styles";
import Link from "@mui/material/Link";
import { docs } from "utils/docs";
@@ -101,21 +100,15 @@ export const useSingleSignOnSection = () => {
};
};
const useEmptyStateStyles = makeStyles((theme) => ({
root: {
minHeight: 0,
padding: theme.spacing(6, 4),
backgroundColor: theme.palette.background.paper,
borderRadius: theme.shape.borderRadius,
},
}));
function SSOEmptyState() {
const styles = useEmptyStateStyles();
return (
<EmptyState
className={styles.root}
css={(theme) => ({
minHeight: 0,
padding: theme.spacing(6, 4),
backgroundColor: theme.palette.background.paper,
borderRadius: theme.shape.borderRadius,
})}
message="No SSO Providers"
description="No SSO providers are configured with this Coder deployment."
cta={
@@ -1,12 +1,9 @@
import { FC, PropsWithChildren } from "react";
import { type FC, type PropsWithChildren } from "react";
import { Section } from "components/SettingsLayout/Section";
import { WorkspaceProxyView } from "./WorkspaceProxyView";
import makeStyles from "@mui/styles/makeStyles";
import { useProxy } from "contexts/ProxyContext";
export const WorkspaceProxyPage: FC<PropsWithChildren<unknown>> = () => {
const styles = useStyles();
const description =
"Workspace proxies improve terminal and web app connections to workspaces.";
@@ -22,7 +19,15 @@ export const WorkspaceProxyPage: FC<PropsWithChildren<unknown>> = () => {
return (
<Section
title="Workspace Proxies"
className={styles.section}
css={(theme) => ({
"& code": {
background: theme.palette.divider,
fontSize: 12,
padding: "2px 4px",
color: theme.palette.text.primary,
borderRadius: 2,
},
})}
description={description}
layout="fluid"
>
@@ -38,16 +43,4 @@ export const WorkspaceProxyPage: FC<PropsWithChildren<unknown>> = () => {
);
};
const useStyles = makeStyles((theme) => ({
section: {
"& code": {
background: theme.palette.divider,
fontSize: 12,
padding: "2px 4px",
color: theme.palette.text.primary,
borderRadius: 2,
},
},
}));
export default WorkspaceProxyPage;
@@ -4,7 +4,7 @@ import {
MockUser2,
MockAssignableSiteRoles,
mockApiError,
MockAuthMethods,
MockAuthMethodsPasswordOnly,
} from "testHelpers/entities";
import { UsersPageView } from "./UsersPageView";
import { ComponentProps } from "react";
@@ -37,7 +37,7 @@ const meta: Meta<typeof UsersPageView> = {
count: 2,
canEditUsers: true,
filterProps: defaultFilterProps,
authMethods: MockAuthMethods,
authMethods: MockAuthMethodsPasswordOnly,
},
};
@@ -1,7 +1,8 @@
import { css } from "@emotion/css";
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import IconButton from "@mui/material/IconButton";
import { EditSquare } from "components/Icons/EditSquare";
import { FC } from "react";
import { makeStyles } from "@mui/styles";
import { type FC } from "react";
import { Stack } from "components/Stack/Stack";
import Checkbox from "@mui/material/Checkbox";
import UserIcon from "@mui/icons-material/PersonOutline";
@@ -34,15 +35,13 @@ const Option: React.FC<{
isChecked: boolean;
onChange: (roleName: string) => void;
}> = ({ value, name, description, isChecked, onChange }) => {
const styles = useStyles();
return (
<label htmlFor={name} className={styles.option}>
<label htmlFor={name} css={styles.option}>
<Stack direction="row" alignItems="flex-start">
<Checkbox
id={name}
size="small"
className={styles.checkbox}
css={styles.checkbox}
value={value}
checked={isChecked}
onChange={(e) => {
@@ -51,7 +50,7 @@ const Option: React.FC<{
/>
<Stack spacing={0}>
<strong>{name}</strong>
<span className={styles.optionDescription}>{description}</span>
<span css={styles.optionDescription}>{description}</span>
</Stack>
</Stack>
</label>
@@ -77,7 +76,7 @@ export const EditRolesButton: FC<EditRolesButtonProps> = ({
userLoginType,
oidcRoleSync,
}) => {
const styles = useStyles();
const theme = useTheme();
const handleChange = (roleName: string) => {
if (selectedRoleNames.has(roleName)) {
@@ -108,20 +107,28 @@ export const EditRolesButton: FC<EditRolesButtonProps> = ({
<PopoverTrigger>
<IconButton
size="small"
className={styles.editButton}
css={styles.editButton}
title="Edit user roles"
>
<EditSquare />
</IconButton>
</PopoverTrigger>
<PopoverContent classes={{ paper: styles.popoverPaper }}>
<PopoverContent
classes={{
paper: css`
width: ${theme.spacing(45)};
margin-top: ${theme.spacing(1)};
background: ${theme.palette.background.paperLight};
`,
}}
>
<fieldset
className={styles.fieldset}
css={styles.fieldset}
disabled={isLoading}
title="Available roles"
>
<Stack className={styles.options} spacing={3}>
<Stack css={styles.options} spacing={3}>
{roles.map((role) => (
<Option
key={role.name}
@@ -134,12 +141,12 @@ export const EditRolesButton: FC<EditRolesButtonProps> = ({
))}
</Stack>
</fieldset>
<div className={styles.footer}>
<div css={styles.footer}>
<Stack direction="row" alignItems="flex-start">
<UserIcon className={styles.userIcon} />
<UserIcon css={styles.userIcon} />
<Stack spacing={0}>
<strong>Member</strong>
<span className={styles.optionDescription}>
<span css={styles.optionDescription}>
{roleDescriptions.member}
</span>
</Stack>
@@ -150,8 +157,8 @@ export const EditRolesButton: FC<EditRolesButtonProps> = ({
);
};
const useStyles = makeStyles((theme) => ({
editButton: {
const styles = {
editButton: (theme) => ({
color: theme.palette.text.secondary,
"& .MuiSvgIcon-root": {
@@ -165,12 +172,7 @@ const useStyles = makeStyles((theme) => ({
color: theme.palette.text.primary,
backgroundColor: "transparent",
},
},
popoverPaper: {
width: theme.spacing(45),
marginTop: theme.spacing(1),
background: theme.palette.background.paperLight,
},
}),
fieldset: {
border: 0,
margin: 0,
@@ -180,14 +182,14 @@ const useStyles = makeStyles((theme) => ({
opacity: 0.5,
},
},
options: {
options: (theme) => ({
padding: theme.spacing(3),
},
}),
option: {
cursor: "pointer",
fontSize: 14,
},
checkbox: {
checkbox: (theme) => ({
padding: 0,
position: "relative",
top: 1, // Alignment
@@ -196,21 +198,21 @@ const useStyles = makeStyles((theme) => ({
width: theme.spacing(2.5),
height: theme.spacing(2.5),
},
},
optionDescription: {
}),
optionDescription: (theme) => ({
fontSize: 13,
color: theme.palette.text.secondary,
lineHeight: "160%",
},
footer: {
}),
footer: (theme) => ({
padding: theme.spacing(3),
backgroundColor: theme.palette.background.paper,
borderTop: `1px solid ${theme.palette.divider}`,
fontSize: 14,
},
userIcon: {
}),
userIcon: (theme) => ({
width: theme.spacing(2.5), // Same as the checkbox
height: theme.spacing(2.5),
color: theme.palette.primary.main,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -90,7 +90,7 @@ export function UserGroupsCell({ userGroups }: GroupsCellProps) {
flexFlow: "column nowrap",
fontSize: theme.typography.body2.fontSize,
padding: theme.spacing(0.5, 0.25),
gap: theme.spacing(0),
gap: 0,
}}
>
{userGroups.map((group) => {
@@ -2,7 +2,7 @@ import {
MockUser,
MockUser2,
MockAssignableSiteRoles,
MockAuthMethods,
MockAuthMethodsPasswordOnly,
MockGroup,
} from "testHelpers/entities";
import { UsersTable } from "./UsersTable";
@@ -18,7 +18,7 @@ const meta: Meta<typeof UsersTable> = {
component: UsersTable,
args: {
isNonInitialPage: false,
authMethods: MockAuthMethods,
authMethods: MockAuthMethodsPasswordOnly,
},
};
@@ -1,10 +1,10 @@
import { type Interpolation, type Theme } from "@emotion/react";
import { BuildAvatar } from "components/BuildAvatar/BuildAvatar";
import { FC } from "react";
import { type FC } from "react";
import { ProvisionerJobLog, WorkspaceBuild } from "api/typesGenerated";
import { Loader } from "components/Loader/Loader";
import { Stack } from "components/Stack/Stack";
import { WorkspaceBuildLogs } from "components/WorkspaceBuildLogs/WorkspaceBuildLogs";
import { makeStyles } from "@mui/styles";
import {
FullWidthPageHeader,
PageHeaderTitle,
@@ -48,8 +48,6 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
builds,
activeBuildNumber,
}) => {
const styles = useStyles();
if (!build) {
return <Loader />;
}
@@ -65,9 +63,9 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
</div>
</Stack>
<Stats aria-label="Build details" className={styles.stats}>
<Stats aria-label="Build details" css={styles.stats}>
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label="Workspace"
value={
<Link
@@ -78,22 +76,22 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
}
/>
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label="Template version"
value={build.template_version_name}
/>
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label="Duration"
value={displayWorkspaceBuildDuration(build)}
/>
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label="Started at"
value={new Date(build.created_at).toLocaleString()}
/>
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label="Action"
value={
<Box component="span" sx={{ textTransform: "capitalize" }}>
@@ -240,8 +238,8 @@ const BuildSidebarItemSkeleton = () => {
);
};
const useStyles = makeStyles((theme) => ({
stats: {
const styles = {
stats: (theme) => ({
padding: 0,
border: 0,
gap: theme.spacing(6),
@@ -254,7 +252,7 @@ const useStyles = makeStyles((theme) => ({
alignItems: "flex-start",
gap: theme.spacing(1),
},
},
}),
statsItem: {
flexDirection: "column",
@@ -266,4 +264,4 @@ const useStyles = makeStyles((theme) => ({
fontWeight: 500,
},
},
}));
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,9 +1,8 @@
import MenuItem from "@mui/material/MenuItem";
import Menu from "@mui/material/Menu";
import { makeStyles } from "@mui/styles";
import MoreVertOutlined from "@mui/icons-material/MoreVertOutlined";
import { FC, Fragment, ReactNode, useRef, useState } from "react";
import { Workspace, WorkspaceBuildParameter } from "api/typesGenerated";
import { type FC, Fragment, type ReactNode, useRef, useState } from "react";
import type { Workspace, WorkspaceBuildParameter } from "api/typesGenerated";
import {
ActionLoadingButton,
CancelButton,
@@ -56,7 +55,6 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
isRestarting,
canChangeVersions,
}) => {
const styles = useStyles();
const {
canCancel,
canAcceptJobs,
@@ -115,7 +113,14 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
};
return (
<div className={styles.actions} data-testid="workspace-actions">
<div
css={(theme) => ({
display: "flex",
alignItems: "center",
gap: theme.spacing(1.5),
})}
data-testid="workspace-actions"
>
{canBeUpdated &&
(isUpdating
? buttonMapping[ButtonTypesEnum.updating]
@@ -167,11 +172,3 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
</div>
);
};
const useStyles = makeStyles((theme) => ({
actions: {
display: "flex",
alignItems: "center",
gap: theme.spacing(1.5),
},
}));
@@ -1,8 +1,9 @@
import { css } from "@emotion/css";
import { type Interpolation, type Theme } from "@emotion/react";
import LinearProgress from "@mui/material/LinearProgress";
import makeStyles from "@mui/styles/makeStyles";
import { TransitionStats, Template, Workspace } from "api/typesGenerated";
import dayjs, { Dayjs } from "dayjs";
import { FC, useEffect, useState } from "react";
import type { TransitionStats, Template, Workspace } from "api/typesGenerated";
import dayjs, { type Dayjs } from "dayjs";
import { type FC, useEffect, useState } from "react";
import capitalize from "lodash/capitalize";
import duration from "dayjs/plugin/duration";
@@ -68,7 +69,6 @@ export const WorkspaceBuildProgress: FC<WorkspaceBuildProgressProps> = ({
workspace,
transitionStats: transitionStats,
}) => {
const styles = useStyles();
const job = workspace.latest_build.job;
const [progressValue, setProgressValue] = useState<number | undefined>(0);
const [progressText, setProgressText] = useState<string | undefined>(
@@ -107,7 +107,7 @@ export const WorkspaceBuildProgress: FC<WorkspaceBuildProgressProps> = ({
return <></>;
}
return (
<div className={styles.stack}>
<div css={styles.stack}>
<LinearProgress
data-chromatic="ignore"
value={progressValue !== undefined ? progressValue : 0}
@@ -123,13 +123,17 @@ export const WorkspaceBuildProgress: FC<WorkspaceBuildProgressProps> = ({
// If a transition is set, there is a moment on new load where the
// bar accelerates to progressValue and then rapidly decelerates, which
// is not indicative of true progress.
classes={{ bar: styles.noTransition }}
classes={{
bar: css`
transition: none;
`,
}}
/>
<div className={styles.barHelpers}>
<div className={styles.label}>
<div css={styles.barHelpers}>
<div css={styles.label}>
{capitalize(workspace.latest_build.status)} workspace...
</div>
<div className={styles.label} data-chromatic="ignore">
<div css={styles.label} data-chromatic="ignore">
{progressText}
</div>
</div>
@@ -137,23 +141,20 @@ export const WorkspaceBuildProgress: FC<WorkspaceBuildProgressProps> = ({
);
};
const useStyles = makeStyles((theme) => ({
stack: {
const styles = {
stack: (theme) => ({
paddingLeft: theme.spacing(0.2),
paddingRight: theme.spacing(0.2),
},
noTransition: {
transition: "none",
},
barHelpers: {
}),
barHelpers: (theme) => ({
display: "flex",
justifyContent: "space-between",
marginTop: theme.spacing(0.5),
},
label: {
}),
label: (theme) => ({
fontSize: 12,
display: "block",
fontWeight: 600,
color: theme.palette.text.secondary,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
+63 -66
View File
@@ -1,6 +1,8 @@
import { css } from "@emotion/css";
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import Link from "@mui/material/Link";
import { WorkspaceOutdatedTooltip } from "components/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip";
import { FC } from "react";
import { type FC } from "react";
import { Link as RouterLink } from "react-router-dom";
import { createDayString } from "utils/createDayString";
import {
@@ -9,13 +11,12 @@ import {
isWorkspaceOn,
workspaceUpdatePolicy,
} from "utils/workspace";
import { Workspace } from "api/typesGenerated";
import type { Workspace } from "api/typesGenerated";
import { Stats, StatsItem } from "components/Stats/Stats";
import upperFirst from "lodash/upperFirst";
import { autostartDisplay, autostopDisplay } from "utils/schedule";
import IconButton from "@mui/material/IconButton";
import RemoveIcon from "@mui/icons-material/RemoveOutlined";
import { makeStyles } from "@mui/styles";
import AddIcon from "@mui/icons-material/AddOutlined";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
@@ -70,26 +71,36 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
onDeadlineMinus,
onDeadlinePlus,
}) => {
const theme = useTheme();
const initiatedBy = getDisplayWorkspaceBuildInitiatedBy(
workspace.latest_build,
);
const displayTemplateName = getDisplayWorkspaceTemplateName(workspace);
const styles = useStyles();
const deadlinePlusEnabled = maxDeadlineIncrease >= 1;
const deadlineMinusEnabled = maxDeadlineDecrease >= 1;
const templatePoliciesEnabled = useTemplatePoliciesEnabled();
const paperStyles = css`
padding: ${theme.spacing(3)};
max-width: ${theme.spacing(36)};
margin-top: ${theme.spacing(1)};
border-radius: 4px;
display: flex;
flex-direction: column;
gap: ${theme.spacing(1)};
`;
return (
<>
<Stats aria-label={Language.workspaceDetails} className={styles.stats}>
<Stats aria-label={Language.workspaceDetails} css={styles.stats}>
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label="Status"
value={<WorkspaceStatusText workspace={workspace} />}
/>
<DormantDeletionStat workspace={workspace} />
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label={Language.templateLabel}
value={
<Link
@@ -101,7 +112,7 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
}
/>
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label={Language.versionLabel}
value={
<>
@@ -124,7 +135,7 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
}
/>
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label={Language.lastBuiltLabel}
value={
<>
@@ -135,10 +146,10 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
/>
{shouldDisplayScheduleLabel(workspace) && (
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label={getScheduleLabel(workspace)}
value={
<span className={styles.scheduleValue}>
<span css={styles.scheduleValue}>
<Link
component={RouterLink}
to="settings/schedule"
@@ -149,21 +160,21 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
: autostartDisplay(workspace.autostart_schedule)}
</Link>
{canUpdateWorkspace && canEditDeadline(workspace) && (
<span className={styles.scheduleControls}>
<span css={styles.scheduleControls}>
<Popover>
<PopoverTrigger>
<IconButton
disabled={!deadlineMinusEnabled}
size="small"
title="Subtract hours from deadline"
className={styles.scheduleButton}
css={styles.scheduleButton}
>
<RemoveIcon />
</IconButton>
</PopoverTrigger>
<PopoverContent
id="schedule-sub"
classes={{ paper: styles.timePopoverPaper }}
classes={{ paper: paperStyles }}
horizontal="right"
>
<DecreaseTimeContent
@@ -178,14 +189,14 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
disabled={!deadlinePlusEnabled}
size="small"
title="Add hours to deadline"
className={styles.scheduleButton}
css={styles.scheduleButton}
>
<AddIcon />
</IconButton>
</PopoverTrigger>
<PopoverContent
id="schedule-add"
classes={{ paper: styles.timePopoverPaper }}
classes={{ paper: paperStyles }}
horizontal="right"
>
<AddTimeContent
@@ -202,7 +213,7 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
)}
{workspace.latest_build.daily_cost > 0 && (
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label={Language.costLabel}
value={`${workspace.latest_build.daily_cost} ${
quotaBudget ? `/ ${quotaBudget}` : ""
@@ -212,7 +223,7 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
{templatePoliciesEnabled && (
<Stack direction="row" spacing={0.5}>
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label={Language.updatePolicy}
value={upperFirst(
workspaceUpdatePolicy(workspace, canChangeVersions),
@@ -239,18 +250,17 @@ const AddTimeContent = (props: {
maxDeadlineIncrease: number;
onDeadlinePlus: (value: number) => void;
}) => {
const styles = useStyles();
const popover = usePopover();
return (
<>
<span className={styles.timePopoverTitle}>Add hours to deadline</span>
<span className={styles.timePopoverDescription}>
<span css={styles.timePopoverTitle}>Add hours to deadline</span>
<span css={styles.timePopoverDescription}>
Delay the shutdown of this workspace for a few more hours. This is only
applied once.
</span>
<form
className={styles.timePopoverForm}
css={styles.timePopoverForm}
onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
@@ -264,9 +274,9 @@ const AddTimeContent = (props: {
type="number"
size="small"
fullWidth
className={styles.timePopoverField}
css={styles.timePopoverField}
InputProps={{
className: styles.timePopoverFieldInput,
className: timePopoverFieldInputStyles,
}}
inputProps={{
min: 0,
@@ -276,7 +286,7 @@ const AddTimeContent = (props: {
}}
/>
<Button className={styles.timePopoverButton} type="submit">
<Button css={styles.timePopoverButton} type="submit">
Apply
</Button>
</form>
@@ -288,20 +298,17 @@ export const DecreaseTimeContent = (props: {
onDeadlineMinus: (hours: number) => void;
maxDeadlineDecrease: number;
}) => {
const styles = useStyles();
const popover = usePopover();
return (
<>
<span className={styles.timePopoverTitle}>
Subtract hours to deadline
</span>
<span className={styles.timePopoverDescription}>
<span css={styles.timePopoverTitle}>Subtract hours to deadline</span>
<span css={styles.timePopoverDescription}>
Anticipate the shutdown of this workspace for a few more hours. This is
only applied once.
</span>
<form
className={styles.timePopoverForm}
css={styles.timePopoverForm}
onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
@@ -315,9 +322,9 @@ export const DecreaseTimeContent = (props: {
type="number"
size="small"
fullWidth
className={styles.timePopoverField}
css={styles.timePopoverField}
InputProps={{
className: styles.timePopoverFieldInput,
className: timePopoverFieldInputStyles,
}}
inputProps={{
min: 0,
@@ -327,7 +334,7 @@ export const DecreaseTimeContent = (props: {
}}
/>
<Button className={styles.timePopoverButton} type="submit">
<Button css={styles.timePopoverButton} type="submit">
Apply
</Button>
</form>
@@ -353,8 +360,14 @@ const getScheduleLabel = (workspace: Workspace) => {
return isWorkspaceOn(workspace) ? "Stops at" : "Starts at";
};
const useStyles = makeStyles((theme) => ({
stats: {
const timePopoverFieldInputStyles = css`
font-size: 14px;
padding: 0px;
border-radius: 4px;
`;
const styles = {
stats: (theme) => ({
padding: 0,
border: 0,
gap: theme.spacing(6),
@@ -367,7 +380,7 @@ const useStyles = makeStyles((theme) => ({
alignItems: "flex-start",
gap: theme.spacing(1),
},
},
}),
statsItem: {
flexDirection: "column",
@@ -380,19 +393,19 @@ const useStyles = makeStyles((theme) => ({
},
},
scheduleValue: {
scheduleValue: (theme) => ({
display: "flex",
alignItems: "center",
gap: theme.spacing(1.5),
},
}),
scheduleControls: {
scheduleControls: (theme) => ({
display: "flex",
alignItems: "center",
gap: theme.spacing(0.5),
},
}),
scheduleButton: {
scheduleButton: (theme) => ({
border: `1px solid ${theme.palette.divider}`,
borderRadius: 4,
width: 20,
@@ -402,47 +415,31 @@ const useStyles = makeStyles((theme) => ({
width: theme.spacing(1.5),
height: theme.spacing(1.5),
},
},
timePopoverPaper: {
padding: theme.spacing(3),
maxWidth: theme.spacing(36),
marginTop: theme.spacing(1),
borderRadius: 4,
display: "flex",
flexDirection: "column",
gap: theme.spacing(1),
},
}),
timePopoverTitle: {
fontWeight: 600,
},
timePopoverDescription: {
timePopoverDescription: (theme) => ({
color: theme.palette.text.secondary,
},
}),
timePopoverForm: {
timePopoverForm: (theme) => ({
display: "flex",
alignItems: "center",
gap: theme.spacing(1),
padding: theme.spacing(1, 0),
},
}),
timePopoverField: {
margin: 0,
},
timePopoverFieldInput: {
fontSize: 14,
padding: theme.spacing(0),
borderRadius: 4,
},
timePopoverButton: {
timePopoverButton: (theme) => ({
borderRadius: 4,
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
flexShrink: 0,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
+18 -4
View File
@@ -1254,16 +1254,30 @@ export const MockUserAgent = {
os: "Windows 10",
};
export const MockAuthMethods: TypesGen.AuthMethods = {
export const MockAuthMethodsPasswordOnly: TypesGen.AuthMethods = {
password: { enabled: true },
github: { enabled: false },
oidc: { enabled: false, signInText: "", iconUrl: "" },
};
export const MockAuthMethodsWithPasswordType: TypesGen.AuthMethods = {
...MockAuthMethods,
export const MockAuthMethodsExternal: TypesGen.AuthMethods = {
password: { enabled: false },
github: { enabled: true },
oidc: { enabled: true, signInText: "", iconUrl: "" },
oidc: {
enabled: true,
signInText: "Google",
iconUrl: "/icon/google.svg",
},
};
export const MockAuthMethodsAll: TypesGen.AuthMethods = {
password: { enabled: true },
github: { enabled: true },
oidc: {
enabled: true,
signInText: "Google",
iconUrl: "/icon/google.svg",
},
};
export const MockGitSSHKey: TypesGen.GitSSHKey = {
+1 -1
View File
@@ -182,7 +182,7 @@ export const handlers = [
return res(ctx.status(200), ctx.json(M.MockAPIKey));
}),
rest.get("/api/v2/users/authmethods", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockAuthMethods));
return res(ctx.status(200), ctx.json(M.MockAuthMethodsPasswordOnly));
}),
rest.get("/api/v2/users/roles", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockSiteRoles));