chore: use emotion for styling (pt. 8) (#10447)

This commit is contained in:
Kayla Washburn
2023-11-01 14:43:42 -04:00
committed by GitHub
parent b3e6a461ed
commit 7f70a23844
22 changed files with 512 additions and 599 deletions

View File

@ -1,40 +1,28 @@
import Box from "@mui/material/Box"
import { makeStyles } from "@mui/styles"
import { ComponentProps, FC, PropsWithChildren } from "react"
import { combineClasses } from "utils/combineClasses"
import { type CSSObject, type Interpolation, type Theme } from "@emotion/react";
import Box from "@mui/material/Box";
import { type ComponentProps, type FC } from "react";
export const Stats: FC<ComponentProps<typeof Box>> = (props) => {
const styles = useStyles()
return (
<Box
{...props}
className={combineClasses([styles.stats, props.className])}
/>
)
}
return <Box {...props} css={styles.stats} />;
};
export const StatsItem: FC<
{
label: string
value: string | number | JSX.Element
label: string;
value: string | number | JSX.Element;
} & ComponentProps<typeof Box>
> = ({ label, value, ...divProps }) => {
const styles = useStyles()
return (
<Box
{...divProps}
className={combineClasses([styles.statItem, divProps.className])}
>
<span className={styles.statsLabel}>{label}:</span>
<span className={styles.statsValue}>{value}</span>
<Box {...divProps} css={styles.statItem}>
<span css={styles.statsLabel}>{label}:</span>
<span css={styles.statsValue}>{value}</span>
</Box>
)
}
);
};
const useStyles = makeStyles((theme) => ({
stats: {
...theme.typography.body2,
const styles = {
stats: (theme) => ({
...(theme.typography.body2 as CSSObject),
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
borderRadius: theme.shape.borderRadius,
@ -49,9 +37,9 @@ const useStyles = makeStyles((theme) => ({
display: "block",
padding: theme.spacing(2),
},
},
}),
statItem: {
statItem: (theme) => ({
padding: theme.spacing(1.75),
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
@ -62,14 +50,14 @@ const useStyles = makeStyles((theme) => ({
[theme.breakpoints.down("md")]: {
padding: theme.spacing(1),
},
},
}),
statsLabel: {
display: "block",
wordWrap: "break-word",
},
statsValue: {
statsValue: (theme) => ({
marginTop: theme.spacing(0.25),
display: "flex",
wordWrap: "break-word",
@ -85,5 +73,5 @@ const useStyles = makeStyles((theme) => ({
textDecoration: "underline",
},
},
},
}))
}),
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,10 +1,9 @@
import { makeStyles } from "@mui/styles";
import { LogLevel } from "api/typesGenerated";
import { type Interpolation, type Theme } from "@emotion/react";
import type { LogLevel } from "api/typesGenerated";
import dayjs from "dayjs";
import { FC, useMemo } from "react";
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
import { combineClasses } from "utils/combineClasses";
import { type FC, type ReactNode, useMemo } from "react";
import AnsiToHTML from "ansi-to-html";
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
export interface Line {
time: string;
@ -24,19 +23,17 @@ export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({
lines,
className = "",
}) => {
const styles = useStyles();
return (
<div className={combineClasses([className, styles.root])}>
<div className={styles.scrollWrapper}>
<div css={styles.root} className={className}>
<div css={styles.scrollWrapper}>
{lines.map((line, idx) => (
<div className={combineClasses([styles.line, line.level])} key={idx}>
<div css={styles.line} className={line.level} key={idx}>
{!hideTimestamps && (
<>
<span className={styles.time}>
<span css={styles.time}>
{dayjs(line.time).format(`HH:mm:ss.SSS`)}
</span>
<span className={styles.space} />
<span css={styles.space} />
</>
)}
<span>{line.output}</span>
@ -56,10 +53,9 @@ export const LogLine: FC<{
hideTimestamp?: boolean;
number?: number;
style?: React.CSSProperties;
sourceIcon?: JSX.Element;
sourceIcon?: ReactNode;
maxNumber?: number;
}> = ({ line, hideTimestamp, number, maxNumber, sourceIcon, style }) => {
const styles = useStyles();
const output = useMemo(() => {
return convert.toHtml(line.output.split(/\r/g).pop() as string);
}, [line.output]);
@ -67,28 +63,22 @@ export const LogLine: FC<{
return (
<div
className={combineClasses([
styles.line,
line.level,
isUsingLineNumber && styles.lineNumber,
])}
css={[styles.line, isUsingLineNumber && styles.lineNumber]}
className={line.level}
style={style}
>
{sourceIcon}
{!hideTimestamp && (
<>
<span
className={combineClasses([
styles.time,
isUsingLineNumber && styles.number,
])}
css={[styles.time, isUsingLineNumber && styles.number]}
style={{
minWidth: `${maxNumber ? maxNumber.toString().length - 1 : 0}em`,
}}
>
{number ? number : dayjs(line.time).format(`HH:mm:ss.SSS`)}
</span>
<span className={styles.space} />
<span css={styles.space} />
</>
)}
<span
@ -100,8 +90,8 @@ export const LogLine: FC<{
);
};
const useStyles = makeStyles((theme) => ({
root: {
const styles = {
root: (theme) => ({
minHeight: 156,
padding: theme.spacing(1, 0),
borderRadius: theme.shape.borderRadius,
@ -112,11 +102,11 @@ const useStyles = makeStyles((theme) => ({
borderBottom: `1px solid ${theme.palette.divider}`,
borderRadius: 0,
},
},
}),
scrollWrapper: {
minWidth: "fit-content",
},
line: {
line: (theme) => ({
wordBreak: "break-all",
display: "flex",
alignItems: "center",
@ -139,24 +129,24 @@ const useStyles = makeStyles((theme) => ({
"&.warn": {
backgroundColor: theme.palette.warning.dark,
},
},
lineNumber: {
}),
lineNumber: (theme) => ({
paddingLeft: theme.spacing(2),
},
space: {
}),
space: (theme) => ({
userSelect: "none",
width: theme.spacing(3),
display: "block",
flexShrink: 0,
},
time: {
}),
time: (theme) => ({
userSelect: "none",
whiteSpace: "pre",
display: "inline-block",
color: theme.palette.text.secondary,
},
number: {
}),
number: (theme) => ({
width: theme.spacing(4),
textAlign: "right",
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,13 +1,25 @@
import { makeStyles } from "@mui/styles";
import Typography from "@mui/material/Typography";
import { FC } from "react";
import { type FC } from "react";
export const NotFoundPage: FC = () => {
const styles = useStyles();
return (
<div className={styles.root}>
<div className={styles.headingContainer}>
<div
css={{
width: "100%",
height: "100%",
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
}}
>
<div
css={(theme) => ({
margin: theme.spacing(1),
padding: theme.spacing(1),
borderRight: theme.palette.divider,
})}
>
<Typography variant="h4">404</Typography>
</div>
<Typography variant="body2">This page could not be found.</Typography>
@ -15,20 +27,4 @@ export const NotFoundPage: FC = () => {
);
};
const useStyles = makeStyles((theme) => ({
root: {
width: "100%",
height: "100%",
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
},
headingContainer: {
margin: theme.spacing(1),
padding: theme.spacing(1),
borderRight: theme.palette.divider,
},
}));
export default NotFoundPage;

View File

@ -1,9 +1,8 @@
import { makeStyles } from "@mui/styles";
import { AuditLog } from "api/typesGenerated";
import { type Interpolation, type Theme } from "@emotion/react";
import { type FC } from "react";
import type { AuditLog } from "api/typesGenerated";
import { colors } from "theme/colors";
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
import { combineClasses } from "utils/combineClasses";
import { FC } from "react";
const getDiffValue = (value: unknown): string => {
if (typeof value === "string") {
@ -23,43 +22,32 @@ const getDiffValue = (value: unknown): string => {
};
export const AuditLogDiff: FC<{ diff: AuditLog["diff"] }> = ({ diff }) => {
const styles = useStyles();
const diffEntries = Object.entries(diff);
return (
<div className={styles.diff}>
<div className={combineClasses([styles.diffColumn, styles.diffOld])}>
<div css={styles.diff}>
<div css={[styles.diffColumn, styles.diffOld]}>
{diffEntries.map(([attrName, valueDiff], index) => (
<div key={attrName} className={styles.diffRow}>
<div className={styles.diffLine}>{index + 1}</div>
<div className={styles.diffIcon}>-</div>
<div key={attrName} css={styles.diffRow}>
<div css={styles.diffLine}>{index + 1}</div>
<div css={styles.diffIcon}>-</div>
<div>
{attrName}:{" "}
<span
className={combineClasses([
styles.diffValue,
styles.diffValueOld,
])}
>
<span css={[styles.diffValue, styles.diffValueOld]}>
{valueDiff.secret ? "••••••••" : getDiffValue(valueDiff.old)}
</span>
</div>
</div>
))}
</div>
<div className={combineClasses([styles.diffColumn, styles.diffNew])}>
<div css={[styles.diffColumn, styles.diffNew]}>
{diffEntries.map(([attrName, valueDiff], index) => (
<div key={attrName} className={styles.diffRow}>
<div className={styles.diffLine}>{index + 1}</div>
<div className={styles.diffIcon}>+</div>
<div key={attrName} css={styles.diffRow}>
<div css={styles.diffLine}>{index + 1}</div>
<div css={styles.diffIcon}>+</div>
<div>
{attrName}:{" "}
<span
className={combineClasses([
styles.diffValue,
styles.diffValueNew,
])}
>
<span css={[styles.diffValue, styles.diffValueNew]}>
{valueDiff.secret ? "••••••••" : getDiffValue(valueDiff.new)}
</span>
</div>
@ -70,8 +58,8 @@ export const AuditLogDiff: FC<{ diff: AuditLog["diff"] }> = ({ diff }) => {
);
};
const useStyles = makeStyles((theme) => ({
diff: {
const styles = {
diff: (theme) => ({
display: "flex",
alignItems: "flex-start",
fontSize: theme.typography.body2.fontSize,
@ -79,9 +67,9 @@ const useStyles = makeStyles((theme) => ({
fontFamily: MONOSPACE_FONT_FAMILY,
position: "relative",
zIndex: 2,
},
}),
diffColumn: {
diffColumn: (theme) => ({
flex: 1,
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2.5),
@ -89,41 +77,41 @@ const useStyles = makeStyles((theme) => ({
lineHeight: "160%",
alignSelf: "stretch",
overflowWrap: "anywhere",
},
}),
diffOld: {
diffOld: (theme) => ({
backgroundColor: theme.palette.error.dark,
color: theme.palette.error.contrastText,
},
}),
diffRow: {
display: "flex",
alignItems: "baseline",
},
diffLine: {
diffLine: (theme) => ({
opacity: 0.5,
width: theme.spacing(6),
textAlign: "right",
flexShrink: 0,
},
}),
diffIcon: {
diffIcon: (theme) => ({
width: theme.spacing(4),
textAlign: "center",
fontSize: theme.typography.body1.fontSize,
flexShrink: 0,
},
}),
diffNew: {
diffNew: (theme) => ({
backgroundColor: theme.palette.success.dark,
color: theme.palette.success.contrastText,
},
}),
diffValue: {
diffValue: (theme) => ({
padding: 1,
borderRadius: theme.shape.borderRadius / 2,
},
}),
diffValueOld: {
backgroundColor: colors.red[12],
@ -132,4 +120,4 @@ const useStyles = makeStyles((theme) => ({
diffValueNew: {
backgroundColor: colors.green[12],
},
}));
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,7 +1,7 @@
import { type CSSObject, type Interpolation, type Theme } from "@emotion/react";
import Collapse from "@mui/material/Collapse";
import { makeStyles } from "@mui/styles";
import TableCell from "@mui/material/TableCell";
import { AuditLog } from "api/typesGenerated";
import type { AuditLog } from "api/typesGenerated";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { Pill, type PillType } from "components/Pill/Pill";
import { Stack } from "components/Stack/Stack";
@ -40,7 +40,6 @@ export const AuditLogRow: React.FC<AuditLogRowProps> = ({
auditLog,
defaultIsDiffOpen = false,
}) => {
const styles = useStyles();
const [isDiffOpen, setIsDiffOpen] = useState(defaultIsDiffOpen);
const diffs = Object.entries(auditLog.diff);
const shouldDisplayDiff = diffs.length > 0;
@ -65,11 +64,11 @@ export const AuditLogRow: React.FC<AuditLogRowProps> = ({
data-testid={`audit-log-row-${auditLog.id}`}
clickable={shouldDisplayDiff}
>
<TableCell className={styles.auditLogCell}>
<TableCell css={styles.auditLogCell}>
<Stack
direction="row"
alignItems="center"
className={styles.auditLogHeader}
css={styles.auditLogHeader}
tabIndex={0}
onClick={toggle}
onKeyDown={(event) => {
@ -81,13 +80,9 @@ export const AuditLogRow: React.FC<AuditLogRowProps> = ({
<Stack
direction="row"
alignItems="center"
className={styles.auditLogHeaderInfo}
css={styles.auditLogHeaderInfo}
>
<Stack
direction="row"
alignItems="center"
className={styles.fullWidth}
>
<Stack direction="row" alignItems="center" css={styles.fullWidth}>
<UserAvatar
username={auditLog.user?.username ?? "?"}
avatarURL={auditLog.user?.avatar_url}
@ -95,23 +90,23 @@ export const AuditLogRow: React.FC<AuditLogRowProps> = ({
<Stack
alignItems="baseline"
className={styles.fullWidth}
css={styles.fullWidth}
justifyContent="space-between"
direction="row"
>
<Stack
className={styles.auditLogSummary}
css={styles.auditLogSummary}
direction="row"
alignItems="baseline"
spacing={1}
>
<AuditLogDescription auditLog={auditLog} />
{auditLog.is_deleted && (
<span className={styles.deletedLabel}>
<span css={styles.deletedLabel}>
<>(deleted)</>
</span>
)}
<span className={styles.auditLogTime}>
<span css={styles.auditLogTime}>
{new Date(auditLog.time).toLocaleTimeString()}
</span>
</Stack>
@ -119,19 +114,19 @@ export const AuditLogRow: React.FC<AuditLogRowProps> = ({
<Stack direction="row" alignItems="center">
<Stack direction="row" spacing={1} alignItems="baseline">
{auditLog.ip && (
<span className={styles.auditLogInfo}>
<span css={styles.auditLogInfo}>
<>IP: </>
<strong>{auditLog.ip}</strong>
</span>
)}
{os.name && (
<span className={styles.auditLogInfo}>
<span css={styles.auditLogInfo}>
<>OS: </>
<strong>{os.name}</strong>
</span>
)}
{browser.name && (
<span className={styles.auditLogInfo}>
<span css={styles.auditLogInfo}>
<>Browser: </>
<strong>
{browser.name} {browser.version}
@ -141,7 +136,7 @@ export const AuditLogRow: React.FC<AuditLogRowProps> = ({
</Stack>
<Pill
className={styles.httpStatusPill}
css={styles.httpStatusPill}
type={httpStatusColor(auditLog.status_code)}
text={auditLog.status_code.toString()}
/>
@ -153,7 +148,7 @@ export const AuditLogRow: React.FC<AuditLogRowProps> = ({
{shouldDisplayDiff ? (
<div> {<DropdownArrow close={isDiffOpen} />}</div>
) : (
<div className={styles.columnWithoutDiff}></div>
<div css={styles.columnWithoutDiff}></div>
)}
</Stack>
@ -167,37 +162,37 @@ export const AuditLogRow: React.FC<AuditLogRowProps> = ({
);
};
const useStyles = makeStyles((theme) => ({
const styles = {
auditLogCell: {
padding: "0 !important",
border: 0,
},
auditLogHeader: {
auditLogHeader: (theme) => ({
padding: theme.spacing(2, 4),
},
}),
auditLogHeaderInfo: {
flex: 1,
},
auditLogSummary: {
...theme.typography.body1,
auditLogSummary: (theme) => ({
...(theme.typography.body1 as CSSObject),
fontFamily: "inherit",
},
}),
auditLogTime: {
auditLogTime: (theme) => ({
color: theme.palette.text.secondary,
fontSize: 12,
},
}),
auditLogInfo: {
...theme.typography.body2,
auditLogInfo: (theme) => ({
...(theme.typography.body2 as CSSObject),
fontSize: 12,
fontFamily: "inherit",
color: theme.palette.text.secondary,
display: "block",
},
}),
// offset the absence of the arrow icon on diff-less logs
columnWithoutDiff: {
@ -216,8 +211,8 @@ const useStyles = makeStyles((theme) => ({
fontWeight: 600,
},
deletedLabel: {
...theme.typography.caption,
deletedLabel: (theme) => ({
...(theme.typography.caption as CSSObject),
color: theme.palette.text.secondary,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,10 +1,10 @@
import Button from "@mui/material/Button";
import { makeStyles } from "@mui/styles";
import { useTheme } from "@emotion/react";
import { type FC } from "react";
import { Link as RouterLink } from "react-router-dom";
import { CodeExample } from "components/CodeExample/CodeExample";
import { SignInLayout } from "components/SignInLayout/SignInLayout";
import { Welcome } from "components/Welcome/Welcome";
import { FC } from "react";
import { Link as RouterLink } from "react-router-dom";
import { FullScreenLoader } from "components/Loader/FullScreenLoader";
export interface CliAuthPageViewProps {
@ -12,7 +12,7 @@ export interface CliAuthPageViewProps {
}
export const CliAuthPageView: FC<CliAuthPageViewProps> = ({ sessionToken }) => {
const styles = useStyles();
const theme = useTheme();
if (!sessionToken) {
return <FullScreenLoader />;
@ -22,14 +22,35 @@ export const CliAuthPageView: FC<CliAuthPageViewProps> = ({ sessionToken }) => {
<SignInLayout>
<Welcome message="Session token" />
<p className={styles.text}>
<p
css={{
fontSize: 16,
color: theme.palette.text.secondary,
marginBottom: theme.spacing(4),
textAlign: "center",
lineHeight: "160%",
}}
>
Copy the session token below and{" "}
<strong className={styles.lineBreak}>paste it in your terminal</strong>.
<strong
css={{
whiteSpace: "nowrap",
}}
>
paste it in your terminal
</strong>
.
</p>
<CodeExample code={sessionToken} password />
<div className={styles.links}>
<div
css={{
display: "flex",
justifyContent: "flex-end",
paddingTop: theme.spacing(1),
}}
>
<Button component={RouterLink} size="large" to="/workspaces" fullWidth>
Go to workspaces
</Button>
@ -37,30 +58,3 @@ export const CliAuthPageView: FC<CliAuthPageViewProps> = ({ sessionToken }) => {
</SignInLayout>
);
};
const useStyles = makeStyles((theme) => ({
title: {
fontSize: theme.spacing(4),
fontWeight: 400,
lineHeight: "140%",
margin: 0,
},
text: {
fontSize: 16,
color: theme.palette.text.secondary,
marginBottom: theme.spacing(4),
textAlign: "center",
lineHeight: "160%",
},
lineBreak: {
whiteSpace: "nowrap",
},
links: {
display: "flex",
justifyContent: "flex-end",
paddingTop: theme.spacing(1),
},
}));

View File

@ -1,28 +1,26 @@
import { type Interpolation, type Theme } from "@emotion/react";
import FormControlLabel from "@mui/material/FormControlLabel";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import { makeStyles } from "@mui/styles";
import TextField from "@mui/material/TextField";
import { Stack } from "components/Stack/Stack";
import { FC } from "react";
import { TemplateVersionVariable } from "api/typesGenerated";
import { type FC } from "react";
import type { TemplateVersionVariable } from "api/typesGenerated";
const isBoolean = (variable: TemplateVersionVariable) => {
return variable.type === "bool";
};
const VariableLabel: React.FC<{ variable: TemplateVersionVariable }> = ({
const VariableLabel: FC<{ variable: TemplateVersionVariable }> = ({
variable,
}) => {
const styles = useStyles();
return (
<label htmlFor={variable.name}>
<span className={styles.labelName}>
<span css={styles.labelName}>
var.{variable.name}
{!variable.required && " (optional)"}
</span>
<span className={styles.labelDescription}>{variable.description}</span>
<span css={styles.labelDescription}>{variable.description}</span>
</label>
);
};
@ -40,12 +38,10 @@ export const VariableInput: FC<VariableInputProps> = ({
variable,
defaultValue,
}) => {
const styles = useStyles();
return (
<Stack direction="column" spacing={0.75}>
<VariableLabel variable={variable} />
<div className={styles.input}>
<div css={styles.input}>
<VariableField
disabled={disabled}
onChange={onChange}
@ -113,26 +109,21 @@ const VariableField: React.FC<VariableInputProps> = ({
);
};
const useStyles = makeStyles((theme) => ({
labelName: {
const styles = {
labelName: (theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
display: "block",
marginBottom: theme.spacing(0.5),
},
labelDescription: {
}),
labelDescription: (theme) => ({
fontSize: 16,
color: theme.palette.text.primary,
display: "block",
fontWeight: 600,
},
}),
input: {
display: "flex",
flexDirection: "column",
},
checkbox: {
display: "flex",
alignItems: "center",
gap: theme.spacing(1),
},
}));
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,4 +1,4 @@
import { FC, useState } from "react";
import { type FC, useState } from "react";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page";
import { FullPageHorizontalForm } from "components/FullPageForm/FullPageHorizontalForm";
@ -12,7 +12,6 @@ import { CreateTokenForm } from "./CreateTokenForm";
import { NANO_HOUR, CreateTokenData } from "./utils";
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { CodeExample } from "components/CodeExample/CodeExample";
import { makeStyles } from "@mui/styles";
import { ErrorAlert } from "components/Alert/ErrorAlert";
const initialValues: CreateTokenData = {
@ -21,7 +20,6 @@ const initialValues: CreateTokenData = {
};
export const CreateTokenPage: FC = () => {
const styles = useStyles();
const navigate = useNavigate();
const {
@ -72,7 +70,15 @@ export const CreateTokenPage: FC = () => {
const tokenDescription = (
<>
<p>Make sure you copy the below token before proceeding:</p>
<CodeExample code={newToken?.key ?? ""} className={styles.codeExample} />
<CodeExample
code={newToken?.key ?? ""}
css={(theme) => ({
minHeight: "auto",
userSelect: "all",
width: "100%",
marginTop: theme.spacing(3),
})}
/>
</>
);
@ -114,13 +120,4 @@ export const CreateTokenPage: FC = () => {
);
};
const useStyles = makeStyles((theme) => ({
codeExample: {
minHeight: "auto",
userSelect: "all",
width: "100%",
marginTop: theme.spacing(3),
},
}));
export default CreateTokenPage;

View File

@ -12,16 +12,15 @@ import { getFormHelpers } from "utils/formUtils";
import Button from "@mui/material/Button";
import FormControlLabel from "@mui/material/FormControlLabel";
import { BlockPicker } from "react-color";
import makeStyles from "@mui/styles/makeStyles";
import Switch from "@mui/material/Switch";
import TextField from "@mui/material/TextField";
import { UpdateAppearanceConfig } from "api/typesGenerated";
import type { UpdateAppearanceConfig } from "api/typesGenerated";
import { Stack } from "components/Stack/Stack";
import { useFormik } from "formik";
import { useTheme } from "@mui/styles";
import Link from "@mui/material/Link";
import { colors } from "theme/colors";
import { hslToHex } from "utils/colors";
import { useTheme } from "@emotion/react";
export type AppearanceSettingsPageViewProps = {
appearance: UpdateAppearanceConfig;
@ -39,7 +38,6 @@ export const AppearanceSettingsPageView = ({
isEntitled,
onSaveAppearance,
}: AppearanceSettingsPageViewProps): JSX.Element => {
const styles = useStyles();
const theme = useTheme();
const applicationNameForm = useFormik<{
@ -133,7 +131,17 @@ export const AppearanceSettingsPageView = ({
disabled={!isEntitled}
InputProps={{
endAdornment: (
<InputAdornment position="end" className={styles.logoAdornment}>
<InputAdornment
position="end"
css={{
width: theme.spacing(3),
height: theme.spacing(3),
"& img": {
maxWidth: "100%",
},
}}
>
<img
alt=""
src={logoForm.values.logo_url}
@ -264,17 +272,3 @@ export const AppearanceSettingsPageView = ({
</>
);
};
const useStyles = makeStyles((theme) => ({
form: {
maxWidth: "500px",
},
logoAdornment: {
width: theme.spacing(3),
height: theme.spacing(3),
"& img": {
maxWidth: "100%",
},
},
}));

View File

@ -1,11 +1,11 @@
import { makeStyles } from "@mui/styles";
import { css, useTheme } from "@emotion/react";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import { DeploymentValues, ExternalAuthConfig } from "api/typesGenerated";
import type { DeploymentValues, ExternalAuthConfig } from "api/typesGenerated";
import { Alert } from "components/Alert/Alert";
import { EnterpriseBadge } from "components/DeploySettingsLayout/Badges";
import { Header } from "components/DeploySettingsLayout/Header";
@ -18,7 +18,7 @@ export type ExternalAuthSettingsPageViewProps = {
export const ExternalAuthSettingsPageView = ({
config,
}: ExternalAuthSettingsPageViewProps): JSX.Element => {
const styles = useStyles();
const theme = useTheme();
return (
<>
@ -40,7 +40,12 @@ export const ExternalAuthSettingsPageView = ({
}}
/>
<div className={styles.description}>
<div
css={{
marginTop: theme.spacing(3),
marginBottom: theme.spacing(3),
}}
>
<Alert severity="info" actions={<EnterpriseBadge key="enterprise" />}>
Integrating with multiple External authentication providers is an
Enterprise feature.
@ -48,7 +53,19 @@ export const ExternalAuthSettingsPageView = ({
</div>
<TableContainer>
<Table className={styles.table}>
<Table
css={css`
& td {
padding-top: ${theme.spacing(3)};
padding-bottom: ${theme.spacing(3)};
}
& td:last-child,
& th:last-child {
padding-left: ${theme.spacing(4)};
}
`}
>
<TableHead>
<TableRow>
<TableCell width="25%">ID</TableCell>
@ -61,7 +78,7 @@ export const ExternalAuthSettingsPageView = ({
config.external_auth?.length === 0) && (
<TableRow>
<TableCell colSpan={999}>
<div className={styles.empty}>
<div css={{ textAlign: "center" }}>
No providers have been configured!
</div>
</TableCell>
@ -83,23 +100,3 @@ export const ExternalAuthSettingsPageView = ({
</>
);
};
const useStyles = makeStyles((theme) => ({
table: {
"& td": {
paddingTop: theme.spacing(3),
paddingBottom: theme.spacing(3),
},
"& td:last-child, & th:last-child": {
paddingLeft: theme.spacing(4),
},
},
description: {
marginTop: theme.spacing(3),
marginBottom: theme.spacing(3),
},
empty: {
textAlign: "center",
},
}));

View File

@ -1,7 +1,11 @@
import { useTheme } from "@emotion/react";
import Paper from "@mui/material/Paper";
import { makeStyles } from "@mui/styles";
import { HTMLProps, ReactNode, FC, PropsWithChildren } from "react";
import { combineClasses } from "utils/combineClasses";
import {
type HTMLProps,
type ReactNode,
type FC,
type PropsWithChildren,
} from "react";
export interface ChartSectionProps {
/**
@ -18,44 +22,46 @@ export const ChartSection: FC<PropsWithChildren<ChartSectionProps>> = ({
contentsProps,
title,
}) => {
const styles = useStyles();
const theme = useTheme();
return (
<Paper className={styles.root} elevation={0}>
<Paper
css={{
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
}}
elevation={0}
>
{title && (
<div className={styles.header}>
<h6 className={styles.title}>{title}</h6>
<div
css={{
alignItems: "center",
display: "flex",
justifyContent: "space-between",
padding: theme.spacing(1.5, 2),
}}
>
<h6
css={{
margin: 0,
fontSize: 14,
fontWeight: 600,
}}
>
{title}
</h6>
{action && <div>{action}</div>}
</div>
)}
<div
{...contentsProps}
className={combineClasses([styles.contents, contentsProps?.className])}
css={{
margin: theme.spacing(2),
}}
>
{children}
</div>
</Paper>
);
};
const useStyles = makeStyles((theme) => ({
root: {
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
},
contents: {
margin: theme.spacing(2),
},
header: {
alignItems: "center",
display: "flex",
justifyContent: "space-between",
padding: theme.spacing(1.5, 2),
},
title: {
margin: 0,
fontSize: 14,
fontWeight: 600,
},
}));

View File

@ -1,16 +1,16 @@
import { useTheme } from "@emotion/react";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import { makeStyles } from "@mui/styles";
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
import { type FC } from "react";
import { Link as RouterLink } from "react-router-dom";
import { Fieldset } from "components/DeploySettingsLayout/Fieldset";
import { Header } from "components/DeploySettingsLayout/Header";
import { FileUpload } from "components/FileUpload/FileUpload";
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
import { displayError } from "components/GlobalSnackbar/utils";
import { Stack } from "components/Stack/Stack";
import { DividerWithText } from "pages/DeploySettingsPage/LicensesSettingsPage/DividerWithText";
import { FC } from "react";
import { Link as RouterLink } from "react-router-dom";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { DividerWithText } from "pages/DeploySettingsPage/LicensesSettingsPage/DividerWithText";
type AddNewLicenseProps = {
onSaveLicenseKey: (license: string) => void;
@ -23,7 +23,7 @@ export const AddNewLicensePageView: FC<AddNewLicenseProps> = ({
isSavingLicense,
savingLicenseError,
}) => {
const styles = useStyles();
const theme = useTheme();
function handleFileUploaded(files: File[]) {
const fileReader = new FileReader();
@ -76,7 +76,7 @@ export const AddNewLicensePageView: FC<AddNewLicenseProps> = ({
description="Select a text file that contains your license key."
/>
<Stack className={styles.main}>
<Stack css={{ paddingTop: theme.spacing(5) }}>
<DividerWithText>or</DividerWithText>
<Fieldset
@ -109,9 +109,3 @@ export const AddNewLicensePageView: FC<AddNewLicenseProps> = ({
</>
);
};
const useStyles = makeStyles((theme) => ({
main: {
paddingTop: theme.spacing(5),
},
}));

View File

@ -1,32 +1,31 @@
import { makeStyles } from "@mui/styles";
import { FC, PropsWithChildren } from "react";
import { type Interpolation, type Theme } from "@emotion/react";
import { type FC, type PropsWithChildren } from "react";
export const DividerWithText: FC<PropsWithChildren> = ({ children }) => {
const classes = useStyles();
return (
<div className={classes.container}>
<div className={classes.border} />
<span className={classes.content}>{children}</span>
<div className={classes.border} />
<div css={styles.container}>
<div css={styles.border} />
<span css={styles.content}>{children}</span>
<div css={styles.border} />
</div>
);
};
const useStyles = makeStyles((theme) => ({
const styles = {
container: {
display: "flex",
alignItems: "center",
},
border: {
border: (theme) => ({
borderBottom: `2px solid ${theme.palette.divider}`,
width: "100%",
},
content: {
}),
content: (theme) => ({
paddingTop: theme.spacing(0.5),
paddingBottom: theme.spacing(0.5),
paddingRight: theme.spacing(2),
paddingLeft: theme.spacing(2),
fontSize: theme.typography.h5.fontSize,
color: theme.palette.text.secondary,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,13 +1,13 @@
import { type CSSObject, type Interpolation, type Theme } from "@emotion/react";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
import { makeStyles } from "@mui/styles";
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { Stack } from "components/Stack/Stack";
import dayjs from "dayjs";
import { useState } from "react";
import { Pill } from "components/Pill/Pill";
import { compareAsc } from "date-fns";
import { GetLicensesResponse } from "api/api";
import { type GetLicensesResponse } from "api/api";
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { Pill } from "components/Pill/Pill";
import { Stack } from "components/Stack/Stack";
type LicenseCardProps = {
license: GetLicensesResponse;
@ -24,8 +24,6 @@ export const LicenseCard = ({
onRemove,
isRemoving,
}: LicenseCardProps) => {
const styles = useStyles();
const [licenseIDMarkedForRemoval, setLicenseIDMarkedForRemoval] = useState<
number | undefined
>(undefined);
@ -34,7 +32,7 @@ export const LicenseCard = ({
license.claims.features["user_limit"] || userLimitLimit;
return (
<Paper key={license.id} elevation={2} className={styles.licenseCard}>
<Paper key={license.id} elevation={2} css={styles.licenseCard}>
<ConfirmDialog
type="delete"
hideCancel={false}
@ -55,12 +53,12 @@ export const LicenseCard = ({
<Stack
direction="row"
spacing={2}
className={styles.cardContent}
css={styles.cardContent}
justifyContent="left"
alignItems="center"
>
<span className={styles.licenseId}>#{license.id}</span>
<span className={styles.accountType}>
<span css={styles.licenseId}>#{license.id}</span>
<span css={styles.accountType}>
{license.claims.trial ? "Trial" : "Enterprise"}
</span>
<Stack
@ -73,8 +71,8 @@ export const LicenseCard = ({
}}
>
<Stack direction="column" spacing={0} alignItems="center">
<span className={styles.secondaryMaincolor}>Users</span>
<span className={styles.userLimit}>
<span css={styles.secondaryMaincolor}>Users</span>
<span css={styles.userLimit}>
{userLimitActual} {` / ${currentUserLimit || "Unlimited"}`}
</span>
</Stack>
@ -88,15 +86,11 @@ export const LicenseCard = ({
new Date(license.claims.license_expires * 1000),
new Date(),
) < 1 ? (
<Pill
className={styles.expiredBadge}
text="Expired"
type="error"
/>
<Pill css={styles.expiredBadge} text="Expired" type="error" />
) : (
<span className={styles.secondaryMaincolor}>Valid Until</span>
<span css={styles.secondaryMaincolor}>Valid Until</span>
)}
<span className={styles.licenseExpires}>
<span css={styles.licenseExpires}>
{dayjs
.unix(license.claims.license_expires)
.format("MMMM D, YYYY")}
@ -104,7 +98,7 @@ export const LicenseCard = ({
</Stack>
<Stack spacing={2}>
<Button
className={styles.removeButton}
css={styles.removeButton}
variant="contained"
size="small"
onClick={() => setLicenseIDMarkedForRemoval(license.id)}
@ -118,39 +112,39 @@ export const LicenseCard = ({
);
};
const useStyles = makeStyles((theme) => ({
userLimit: {
const styles = {
userLimit: (theme) => ({
color: theme.palette.text.primary,
},
licenseCard: {
...theme.typography.body2,
}),
licenseCard: (theme) => ({
...(theme.typography.body2 as CSSObject),
padding: theme.spacing(2),
},
}),
cardContent: {},
licenseId: {
licenseId: (theme) => ({
color: theme.palette.secondary.main,
fontSize: 18,
fontWeight: 600,
},
}),
accountType: {
fontWeight: 600,
fontSize: 18,
alignItems: "center",
textTransform: "capitalize",
},
licenseExpires: {
licenseExpires: (theme) => ({
color: theme.palette.text.secondary,
},
expiredBadge: {
}),
expiredBadge: (theme) => ({
marginBottom: theme.spacing(0.5),
},
secondaryMaincolor: {
}),
secondaryMaincolor: (theme) => ({
color: theme.palette.text.secondary,
},
removeButton: {
}),
removeButton: (theme) => ({
color: theme.palette.error.main,
"&:hover": {
backgroundColor: "transparent",
},
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,17 +1,17 @@
import { type Interpolation, type Theme } from "@emotion/react";
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
import RefreshIcon from "@mui/icons-material/Refresh";
import CircularProgress from "@mui/material/CircularProgress";
import Link from "@mui/material/Link";
import Tooltip from "@mui/material/Tooltip";
import { makeStyles } from "@mui/styles";
import { ApiErrorResponse } from "api/errors";
import { ExternalAuth, ExternalAuthDevice } from "api/typesGenerated";
import type { ApiErrorResponse } from "api/errors";
import type { ExternalAuth, ExternalAuthDevice } from "api/typesGenerated";
import { Alert } from "components/Alert/Alert";
import { Avatar } from "components/Avatar/Avatar";
import { CopyButton } from "components/CopyButton/CopyButton";
import { SignInLayout } from "components/SignInLayout/SignInLayout";
import { Welcome } from "components/Welcome/Welcome";
import { type FC } from "react";
import { type FC, type ReactNode } from "react";
export interface ExternalAuthPageViewProps {
externalAuth: ExternalAuth;
@ -30,8 +30,6 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
onReauthenticate,
viewExternalAuthConfig,
}) => {
const styles = useStyles();
if (!externalAuth.authenticated) {
return (
<SignInLayout>
@ -50,7 +48,7 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
const hasInstallations = externalAuth.installations.length > 0;
// We only want to wrap this with a link if an install URL is available!
let installTheApp: React.ReactNode = `install the ${externalAuth.display_name} App`;
let installTheApp: ReactNode = `install the ${externalAuth.display_name} App`;
if (externalAuth.app_install_url) {
installTheApp = (
<Link
@ -68,7 +66,7 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
<Welcome
message={`You've authenticated with ${externalAuth.display_name}!`}
/>
<p className={styles.text}>
<p css={styles.text}>
{externalAuth.user?.login && `Hey @${externalAuth.user?.login}! 👋 `}
{(!externalAuth.app_installable ||
externalAuth.installations.length > 0) &&
@ -76,7 +74,7 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
</p>
{externalAuth.installations.length > 0 && (
<div className={styles.authorizedInstalls}>
<div css={styles.authorizedInstalls}>
{externalAuth.installations.map((install) => {
if (!install.account) {
return;
@ -105,9 +103,9 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
</div>
)}
<div className={styles.links}>
<div css={styles.links}>
{!hasInstallations && externalAuth.app_installable && (
<Alert severity="warning" className={styles.installAlert}>
<Alert severity="warning" css={styles.installAlert}>
You must {installTheApp} to clone private repositories. Accounts
will appear here once authorized.
</Alert>
@ -120,7 +118,7 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
href={externalAuth.app_install_url}
target="_blank"
rel="noreferrer"
className={styles.link}
css={styles.link}
>
<OpenInNewIcon fontSize="small" />
{externalAuth.installations.length > 0
@ -130,7 +128,7 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
</Link>
)}
<Link
className={styles.link}
css={styles.link}
href="#"
onClick={() => {
onReauthenticate();
@ -147,10 +145,8 @@ const GitDeviceAuth: FC<{
externalAuthDevice?: ExternalAuthDevice;
deviceExchangeError?: ApiErrorResponse;
}> = ({ externalAuthDevice, deviceExchangeError }) => {
const styles = useStyles();
let status = (
<p className={styles.status}>
<p css={styles.status}>
<CircularProgress size={16} color="secondary" data-chromatic="ignore" />
Checking for authentication...
</p>
@ -189,18 +185,18 @@ const GitDeviceAuth: FC<{
return (
<div>
<p className={styles.text}>
<p css={styles.text}>
Copy your one-time code:&nbsp;
<div className={styles.copyCode}>
<span className={styles.code}>{externalAuthDevice.user_code}</span>
<div css={styles.copyCode}>
<span css={styles.code}>{externalAuthDevice.user_code}</span>
&nbsp; <CopyButton text={externalAuthDevice.user_code} />
</div>
<br />
Then open the link below and paste it:
</p>
<div className={styles.links}>
<div css={styles.links}>
<Link
className={styles.link}
css={styles.link}
href={externalAuthDevice.verification_uri}
target="_blank"
rel="noreferrer"
@ -217,56 +213,56 @@ const GitDeviceAuth: FC<{
export default ExternalAuthPageView;
const useStyles = makeStyles((theme) => ({
text: {
const styles = {
text: (theme) => ({
fontSize: 16,
color: theme.palette.text.secondary,
textAlign: "center",
lineHeight: "160%",
margin: 0,
},
}),
copyCode: {
display: "inline-flex",
alignItems: "center",
},
code: {
code: (theme) => ({
fontWeight: "bold",
color: theme.palette.text.primary,
},
}),
installAlert: {
installAlert: (theme) => ({
margin: theme.spacing(2),
},
}),
links: {
links: (theme) => ({
display: "flex",
gap: theme.spacing(0.5),
margin: theme.spacing(2),
flexDirection: "column",
},
}),
link: {
link: (theme) => ({
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 16,
gap: theme.spacing(1),
},
}),
status: {
status: (theme) => ({
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: theme.spacing(1),
color: theme.palette.text.disabled,
},
}),
authorizedInstalls: {
authorizedInstalls: (theme) => ({
display: "flex",
gap: 4,
color: theme.palette.text.disabled,
margin: theme.spacing(4),
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -8,7 +8,7 @@ import TableRow from "@mui/material/TableRow";
import DeleteOutline from "@mui/icons-material/DeleteOutline";
import PersonAdd from "@mui/icons-material/PersonAdd";
import SettingsOutlined from "@mui/icons-material/SettingsOutlined";
import { Group, User } from "api/typesGenerated";
import type { Group, User } from "api/typesGenerated";
import { AvatarData } from "components/AvatarData/AvatarData";
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
import { EmptyState } from "components/EmptyState/EmptyState";
@ -26,7 +26,6 @@ import { type FC, useState } from "react";
import { Helmet } from "react-helmet-async";
import { Link as RouterLink, useNavigate, useParams } from "react-router-dom";
import { pageTitle } from "utils/page";
import { makeStyles } from "@mui/styles";
import {
PaginationStatus,
TableToolbar,
@ -60,7 +59,6 @@ export const GroupPage: FC = () => {
const [isDeletingGroup, setIsDeletingGroup] = useState(false);
const isLoading = !groupData || !permissions;
const canUpdateGroup = permissions ? permissions.canUpdateGroup : false;
const styles = useStyles();
const helmet = (
<Helmet>
@ -103,7 +101,7 @@ export const GroupPage: FC = () => {
setIsDeletingGroup(true);
}}
startIcon={<DeleteOutline />}
className={styles.removeButton}
css={styles.removeButton}
>
Delete&hellip;
</Button>
@ -213,7 +211,6 @@ const AddGroupMember: React.FC<{
onSubmit: (user: User, reset: () => void) => void;
}> = ({ isLoading, onSubmit }) => {
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const styles = useStyles();
const resetValues = () => {
setSelectedUser(null);
@ -231,7 +228,7 @@ const AddGroupMember: React.FC<{
>
<Stack direction="row" alignItems="center" spacing={1}>
<UserAutocomplete
className={styles.autoComplete}
css={styles.autoComplete}
value={selectedUser}
onChange={(newValue) => {
setSelectedUser(newValue);
@ -312,19 +309,16 @@ const GroupMemberRow = (props: {
);
};
const useStyles = makeStyles((theme) => ({
const styles = {
autoComplete: {
width: 300,
},
removeButton: {
removeButton: (theme) => ({
color: theme.palette.error.main,
"&:hover": {
backgroundColor: "transparent",
},
},
}));
const styles = {
}),
status: {
textTransform: "capitalize",
},

View File

@ -1,3 +1,4 @@
import { type Interpolation, type Theme } from "@emotion/react";
import Box from "@mui/material/Box";
import { useQuery } from "react-query";
import { getHealth } from "api/api";
@ -16,7 +17,6 @@ import {
PageHeaderSubtitle,
} from "components/PageHeader/FullWidthPageHeader";
import { Stats, StatsItem } from "components/Stats/Stats";
import { makeStyles } from "@mui/styles";
import { createDayString } from "utils/createDayString";
import { DashboardFullPage } from "components/Dashboard/DashboardLayout";
@ -57,8 +57,6 @@ export function HealthPageView({
healthStatus: Awaited<ReturnType<typeof getHealth>>;
tab: ReturnType<typeof useTab>;
}) {
const styles = useStyles();
return (
<DashboardFullPage>
<FullWidthPageHeader sticky={false}>
@ -93,14 +91,14 @@ export function HealthPageView({
</div>
</Stack>
<Stats aria-label="Deployment details" className={styles.stats}>
<Stats aria-label="Deployment details" css={styles.stats}>
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label="Last check"
value={createDayString(healthStatus.time)}
/>
<StatsItem
className={styles.statsItem}
css={styles.statsItem}
label="Coder version"
value={healthStatus.coder_version}
/>
@ -212,8 +210,8 @@ export function HealthPageView({
);
}
const useStyles = makeStyles((theme) => ({
stats: {
const styles = {
stats: (theme) => ({
padding: 0,
border: 0,
gap: theme.spacing(6),
@ -226,7 +224,7 @@ const useStyles = makeStyles((theme) => ({
alignItems: "flex-start",
gap: theme.spacing(1),
},
},
}),
statsItem: {
flexDirection: "column",
@ -238,4 +236,4 @@ const useStyles = makeStyles((theme) => ({
fontWeight: 500,
},
},
}));
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,11 +1,11 @@
import { makeStyles } from "@mui/styles";
import { FC } from "react";
import { type Interpolation, type Theme } from "@emotion/react";
import { type FC } from "react";
import { useLocation } from "react-router-dom";
import { SignInForm } from "./SignInForm";
import { retrieveRedirect } from "utils/redirect";
import { CoderIcon } from "components/Icons/CoderIcon";
import { getApplicationName, getLogoURL } from "utils/appearance";
import { AuthMethods } from "api/typesGenerated";
import type { AuthMethods } from "api/typesGenerated";
export interface LoginPageViewProps {
authMethods: AuthMethods | undefined;
@ -22,7 +22,6 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
}) => {
const location = useLocation();
const redirectTo = retrieveRedirect(location.search);
const styles = useStyles();
// This allows messages to be displayed at the top of the sign in form.
// Helpful for any redirects that want to inform the user of something.
const info = new URLSearchParams(location.search).get("info") || undefined;
@ -41,12 +40,12 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
}}
/>
) : (
<CoderIcon fill="white" opacity={1} className={styles.icon} />
<CoderIcon fill="white" opacity={1} css={styles.icon} />
);
return (
<div className={styles.root}>
<div className={styles.container}>
<div css={styles.root}>
<div css={styles.container}>
{applicationLogo}
<SignInForm
authMethods={authMethods}
@ -56,7 +55,7 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
info={info}
onSubmit={onSignIn}
/>
<footer className={styles.footer}>
<footer css={styles.footer}>
Copyright © {new Date().getFullYear()} Coder Technologies, Inc.
</footer>
</div>
@ -64,32 +63,32 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
);
};
const useStyles = makeStyles((theme) => ({
root: {
const styles = {
root: (theme) => ({
padding: theme.spacing(3),
display: "flex",
alignItems: "center",
justifyContent: "center",
minHeight: "100%",
textAlign: "center",
},
}),
container: {
container: (theme) => ({
width: "100%",
maxWidth: 385,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: theme.spacing(2),
},
}),
icon: {
icon: (theme) => ({
fontSize: theme.spacing(8),
},
}),
footer: {
footer: (theme) => ({
fontSize: 12,
color: theme.palette.text.secondary,
marginTop: theme.spacing(3),
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -3,10 +3,10 @@ import Button from "@mui/material/Button";
import GitHubIcon from "@mui/icons-material/GitHub";
import KeyIcon from "@mui/icons-material/VpnKey";
import Box from "@mui/material/Box";
import { type FC } from "react";
import { useTheme } from "@emotion/react";
import { Language } from "./SignInForm";
import { AuthMethods } from "api/typesGenerated";
import { FC } from "react";
import { makeStyles } from "@mui/styles";
import { type AuthMethods } from "api/typesGenerated";
type OAuthSignInFormProps = {
isSigningIn: boolean;
@ -14,19 +14,17 @@ type OAuthSignInFormProps = {
authMethods?: AuthMethods;
};
const useStyles = makeStyles((theme) => ({
buttonIcon: {
width: theme.spacing(2),
height: theme.spacing(2),
},
}));
export const OAuthSignInForm: FC<OAuthSignInFormProps> = ({
isSigningIn,
redirectTo,
authMethods,
}) => {
const styles = useStyles();
const theme = useTheme();
const iconStyles = {
width: theme.spacing(2),
height: theme.spacing(2),
};
return (
<Box display="grid" gap="16px">
@ -37,7 +35,7 @@ export const OAuthSignInForm: FC<OAuthSignInFormProps> = ({
)}`}
>
<Button
startIcon={<GitHubIcon className={styles.buttonIcon} />}
startIcon={<GitHubIcon css={iconStyles} />}
disabled={isSigningIn}
fullWidth
type="submit"
@ -61,10 +59,10 @@ export const OAuthSignInForm: FC<OAuthSignInFormProps> = ({
<img
alt="Open ID Connect icon"
src={authMethods.oidc.iconUrl}
className={styles.buttonIcon}
css={iconStyles}
/>
) : (
<KeyIcon className={styles.buttonIcon} />
<KeyIcon css={iconStyles} />
)
}
disabled={isSigningIn}

View File

@ -1,10 +1,10 @@
import { makeStyles } from "@mui/styles";
import { FormikTouched } from "formik";
import { FC, useState } from "react";
import { AuthMethods } from "api/typesGenerated";
import { type Interpolation, type Theme } from "@emotion/react";
import { type FormikTouched } from "formik";
import { type FC, useState } from "react";
import type { AuthMethods } from "api/typesGenerated";
import { PasswordSignInForm } from "./PasswordSignInForm";
import { OAuthSignInForm } from "./OAuthSignInForm";
import { BuiltInAuthFormValues } from "./SignInForm.types";
import { type BuiltInAuthFormValues } from "./SignInForm.types";
import Button from "@mui/material/Button";
import EmailIcon from "@mui/icons-material/EmailOutlined";
import { Alert } from "components/Alert/Alert";
@ -21,11 +21,11 @@ export const Language = {
oidcSignIn: "OpenID Connect",
};
const useStyles = makeStyles((theme) => ({
const styles = {
root: {
width: "100%",
},
title: {
title: (theme) => ({
fontSize: theme.spacing(4),
fontWeight: 400,
margin: 0,
@ -35,34 +35,34 @@ const useStyles = makeStyles((theme) => ({
"& strong": {
fontWeight: 600,
},
},
alert: {
}),
alert: (theme) => ({
marginBottom: theme.spacing(4),
},
divider: {
}),
divider: (theme) => ({
paddingTop: theme.spacing(3),
paddingBottom: theme.spacing(3),
display: "flex",
alignItems: "center",
gap: theme.spacing(2),
},
dividerLine: {
}),
dividerLine: (theme) => ({
width: "100%",
height: 1,
backgroundColor: theme.palette.divider,
},
dividerLabel: {
}),
dividerLabel: (theme) => ({
flexShrink: 0,
color: theme.palette.text.secondary,
textTransform: "uppercase",
fontSize: 12,
letterSpacing: 1,
},
icon: {
}),
icon: (theme) => ({
width: theme.spacing(2),
height: theme.spacing(2),
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
export interface SignInFormProps {
isSigningIn: boolean;
@ -90,23 +90,22 @@ export const SignInForm: FC<React.PropsWithChildren<SignInFormProps>> = ({
const passwordEnabled = authMethods?.password.enabled ?? true;
// Hide password auth by default if any OAuth method is enabled
const [showPasswordAuth, setShowPasswordAuth] = useState(!oAuthEnabled);
const styles = useStyles();
const applicationName = getApplicationName();
return (
<div className={styles.root}>
<h1 className={styles.title}>
<div css={styles.root}>
<h1 css={styles.title}>
Sign in to <strong>{applicationName}</strong>
</h1>
{Boolean(error) && (
<div className={styles.alert}>
<div css={styles.alert}>
<ErrorAlert error={error} />
</div>
)}
{Boolean(info) && Boolean(error) && (
<div className={styles.alert}>
<div css={styles.alert}>
<Alert severity="info">{info}</Alert>
</div>
)}
@ -120,10 +119,10 @@ export const SignInForm: FC<React.PropsWithChildren<SignInFormProps>> = ({
)}
{passwordEnabled && showPasswordAuth && oAuthEnabled && (
<div className={styles.divider}>
<div className={styles.dividerLine} />
<div className={styles.dividerLabel}>Or</div>
<div className={styles.dividerLine} />
<div css={styles.divider}>
<div css={styles.dividerLine} />
<div css={styles.dividerLabel}>Or</div>
<div css={styles.dividerLine} />
</div>
)}
@ -141,17 +140,17 @@ export const SignInForm: FC<React.PropsWithChildren<SignInFormProps>> = ({
{passwordEnabled && !showPasswordAuth && (
<>
<div className={styles.divider}>
<div className={styles.dividerLine} />
<div className={styles.dividerLabel}>Or</div>
<div className={styles.dividerLine} />
<div css={styles.divider}>
<div css={styles.dividerLine} />
<div css={styles.dividerLabel}>Or</div>
<div css={styles.dividerLine} />
</div>
<Button
fullWidth
size="large"
onClick={() => setShowPasswordAuth(true)}
startIcon={<EmailIcon className={styles.icon} />}
startIcon={<EmailIcon css={styles.icon} />}
>
Email and password
</Button>

View File

@ -1,13 +1,13 @@
import { makeStyles } from "@mui/styles";
import { MemoizedMarkdown } from "components/Markdown/Markdown";
import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout";
import { useTheme } from "@emotion/react";
import frontMatter from "front-matter";
import { Helmet } from "react-helmet-async";
import { MemoizedMarkdown } from "components/Markdown/Markdown";
import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout";
import { pageTitle } from "utils/page";
export default function TemplateDocsPage() {
const { template, activeVersion } = useTemplateLayoutContext();
const styles = useStyles();
const theme = useTheme();
const readme = frontMatter(activeVersion.readme);
@ -17,35 +17,34 @@ export default function TemplateDocsPage() {
<title>{pageTitle(`${template.name} · Documentation`)}</title>
</Helmet>
<div className={styles.markdownSection} id="readme">
<div className={styles.readmeLabel}>README.md</div>
<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={{
color: theme.palette.text.secondary,
fontWeight: 600,
padding: theme.spacing(2, 3),
borderBottom: `1px solid ${theme.palette.divider}`,
}}
>
README.md
</div>
<div
css={{
padding: theme.spacing(0, 3, 5),
maxWidth: 800,
margin: "auto",
}}
>
<MemoizedMarkdown>{readme.body}</MemoizedMarkdown>
</div>
</div>
</>
);
}
export const useStyles = makeStyles((theme) => {
return {
markdownSection: {
background: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
},
readmeLabel: {
color: theme.palette.text.secondary,
fontWeight: 600,
padding: theme.spacing(2, 3),
borderBottom: `1px solid ${theme.palette.divider}`,
},
markdownWrapper: {
padding: theme.spacing(0, 3, 5),
maxWidth: 800,
margin: "auto",
},
};
});

View File

@ -1,8 +1,19 @@
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 { Workspace } from "api/typesGenerated";
import type { Workspace } from "api/typesGenerated";
import { Stack } from "components/Stack/Stack";
import { FC, ElementType, PropsWithChildren, ReactNode } from "react";
import {
type FC,
type ComponentType,
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";
@ -12,16 +23,47 @@ import { Avatar } from "components/Avatar/Avatar";
const SidebarNavItem: FC<
PropsWithChildren<{ href: string; icon: ReactNode }>
> = ({ children, href, icon }) => {
const styles = useStyles();
const theme = useTheme();
const linkStyles = css({
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,
},
});
const activeLinkStyles = css({
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,
},
});
return (
<NavLink
end
to={href}
className={({ isActive }) =>
combineClasses([
styles.sidebarNavItem,
isActive ? styles.sidebarNavItemActive : undefined,
])
combineClasses([linkStyles, isActive ? activeLinkStyles : undefined])
}
>
<Stack alignItems="center" spacing={1.5} direction="row">
@ -32,32 +74,32 @@ const SidebarNavItem: FC<
);
};
const SidebarNavItemIcon: React.FC<{ icon: ElementType }> = ({
icon: Icon,
}) => {
const styles = useStyles();
return <Icon className={styles.sidebarNavItemIcon} />;
const SidebarNavItemIcon: FC<{
icon: ComponentType<{ className?: string }>;
}> = ({ icon: Icon }) => {
return (
<Icon
css={(theme) => ({
width: theme.spacing(2),
height: theme.spacing(2),
})}
/>
);
};
export const Sidebar: React.FC<{ username: string; workspace: Workspace }> = ({
username,
workspace,
}) => {
const styles = useStyles();
return (
<nav className={styles.sidebar}>
<Stack
direction="row"
alignItems="center"
className={styles.workspaceInfo}
>
<nav css={styles.sidebar}>
<Stack direction="row" alignItems="center" css={styles.workspaceInfo}>
<Avatar src={workspace.template_icon} variant="square" fitImage />
<Stack spacing={0} className={styles.workspaceData}>
<Link className={styles.name} to={`/@${username}/${workspace.name}`}>
<Stack spacing={0} css={styles.workspaceData}>
<Link css={styles.name} to={`/@${username}/${workspace.name}`}>
{workspace.name}
</Link>
<span className={styles.secondary}>
<span css={styles.secondary}>
{workspace.template_display_name ?? workspace.template_name}
</span>
</Stack>
@ -82,65 +124,30 @@ export const Sidebar: React.FC<{ username: string; workspace: Workspace }> = ({
);
};
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: {
width: theme.spacing(2),
height: theme.spacing(2),
},
workspaceInfo: {
...theme.typography.body2,
workspaceInfo: (theme) => ({
...(theme.typography.body2 as CSSObject),
marginBottom: theme.spacing(2),
},
}),
workspaceData: {
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>>;