mirror of
https://github.com/coder/coder.git
synced 2025-07-21 01:28:49 +00:00
chore: use emotion for styling (pt. 8) (#10447)
This commit is contained in:
@ -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>>;
|
||||
|
@ -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>>;
|
||||
|
@ -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;
|
||||
|
@ -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>>;
|
||||
|
@ -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>>;
|
||||
|
@ -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),
|
||||
},
|
||||
}));
|
||||
|
@ -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>>;
|
||||
|
@ -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;
|
||||
|
@ -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%",
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
@ -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",
|
||||
},
|
||||
}));
|
||||
|
@ -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,
|
||||
},
|
||||
}));
|
||||
|
@ -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),
|
||||
},
|
||||
}));
|
||||
|
@ -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>>;
|
||||
|
@ -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>>;
|
||||
|
@ -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:
|
||||
<div className={styles.copyCode}>
|
||||
<span className={styles.code}>{externalAuthDevice.user_code}</span>
|
||||
<div css={styles.copyCode}>
|
||||
<span css={styles.code}>{externalAuthDevice.user_code}</span>
|
||||
<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>>;
|
||||
|
@ -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…
|
||||
</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",
|
||||
},
|
||||
|
@ -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>>;
|
||||
|
@ -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>>;
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -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>>;
|
||||
|
Reference in New Issue
Block a user