chore: remove <CircularProgress />
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import Link from "@mui/material/Link";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Link } from "#/components/Link/Link";
|
||||
import { FileUpload } from "./FileUpload";
|
||||
|
||||
const meta: Meta<typeof FileUpload> = {
|
||||
@@ -10,8 +10,10 @@ const meta: Meta<typeof FileUpload> = {
|
||||
description: (
|
||||
<>
|
||||
The template has to be a .tar or .zip file. You can also use our{" "}
|
||||
<Link href="/starter-templates">starter templates</Link> to getting
|
||||
started with Coder.
|
||||
<Link href="/starter-templates" showExternalIcon={false}>
|
||||
starter templates
|
||||
</Link>{" "}
|
||||
to getting started with Coder.
|
||||
</>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { css, type Interpolation, type Theme } from "@emotion/react";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import { CloudUploadIcon, FolderIcon, TrashIcon } from "lucide-react";
|
||||
import { type DragEvent, type FC, type ReactNode, useRef } from "react";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import { Stack } from "#/components/Stack/Stack";
|
||||
import { useClickable } from "#/hooks/useClickable";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { Spinner } from "../Spinner/Spinner";
|
||||
|
||||
interface FileUploadProps {
|
||||
isUploading: boolean;
|
||||
@@ -35,16 +34,11 @@ export const FileUpload: FC<FileUploadProps> = ({
|
||||
|
||||
if (!isUploading && file) {
|
||||
return (
|
||||
<Stack
|
||||
css={styles.file}
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Stack direction="row" alignItems="center">
|
||||
<div className="flex flex-row items-center justify-between gap-4 rounded-lg border border-border bg-surface-secondary p-4">
|
||||
<div className="flex flex-row items-center gap-4">
|
||||
<FolderIcon className="size-icon-sm" />
|
||||
<span>{file.name}</span>
|
||||
</Stack>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="subtle"
|
||||
@@ -54,7 +48,7 @@ export const FileUpload: FC<FileUploadProps> = ({
|
||||
>
|
||||
<TrashIcon className="size-icon-sm" />
|
||||
</Button>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,31 +56,36 @@ export const FileUpload: FC<FileUploadProps> = ({
|
||||
<>
|
||||
<div
|
||||
data-testid="drop-zone"
|
||||
css={[styles.root, isUploading && styles.disabled]}
|
||||
className={cn(
|
||||
"flex cursor-pointer items-center justify-center rounded-lg border-2 border-dashed border-border p-12 hover:bg-surface-secondary",
|
||||
isUploading && "pointer-events-none opacity-75",
|
||||
)}
|
||||
{...clickable}
|
||||
{...fileDrop}
|
||||
>
|
||||
<Stack alignItems="center" spacing={1}>
|
||||
<div css={styles.iconWrapper}>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="flex size-16 items-center justify-center">
|
||||
{isUploading ? (
|
||||
<CircularProgress size={32} />
|
||||
<Spinner size="lg" loading />
|
||||
) : (
|
||||
<CloudUploadIcon className="size-16" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Stack alignItems="center" spacing={0.5}>
|
||||
<span css={styles.title}>{title}</span>
|
||||
<span css={styles.description}>{description}</span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<span className="text-base leading-none">{title}</span>
|
||||
<span className="mt-1 max-w-md text-center text-sm leading-normal text-content-secondary">
|
||||
{description}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
data-testid="file-upload"
|
||||
ref={inputRef}
|
||||
css={styles.input}
|
||||
className="hidden"
|
||||
accept={extensions?.map((ext) => `.${ext}`).join(",")}
|
||||
onChange={(event) => {
|
||||
const file = event.currentTarget.files?.[0];
|
||||
@@ -139,58 +138,3 @@ const useFileDrop = (
|
||||
onDrop,
|
||||
};
|
||||
};
|
||||
|
||||
const styles = {
|
||||
root: (theme) => css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
border: 2px dashed ${theme.palette.divider};
|
||||
padding: 48px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: ${theme.palette.background.paper};
|
||||
}
|
||||
`,
|
||||
|
||||
disabled: {
|
||||
pointerEvents: "none",
|
||||
opacity: 0.75,
|
||||
},
|
||||
|
||||
// Used to maintain the size of icon and spinner
|
||||
iconWrapper: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
|
||||
title: {
|
||||
fontSize: 16,
|
||||
lineHeight: "1",
|
||||
},
|
||||
|
||||
description: (theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
textAlign: "center",
|
||||
maxWidth: 400,
|
||||
fontSize: 14,
|
||||
lineHeight: "1.5",
|
||||
marginTop: 4,
|
||||
}),
|
||||
|
||||
input: {
|
||||
display: "none",
|
||||
},
|
||||
|
||||
file: (theme) => ({
|
||||
borderRadius: 8,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
padding: 16,
|
||||
background: theme.palette.background.paper,
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import Link from "@mui/material/Link";
|
||||
import { isAxiosError } from "axios";
|
||||
import { ExternalLinkIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
@@ -8,6 +5,9 @@ import type { ApiErrorResponse } from "#/api/errors";
|
||||
import type { ExternalAuthDevice } from "#/api/typesGenerated";
|
||||
import { Alert, AlertDescription, AlertTitle } from "#/components/Alert/Alert";
|
||||
import { CopyButton } from "#/components/CopyButton/CopyButton";
|
||||
import { Link } from "#/components/Link/Link";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { Spinner } from "../Spinner/Spinner";
|
||||
|
||||
interface GitDeviceAuthProps {
|
||||
externalAuthDevice?: ExternalAuthDevice;
|
||||
@@ -72,8 +72,12 @@ export const GitDeviceAuth: FC<GitDeviceAuthProps> = ({
|
||||
deviceExchangeError,
|
||||
}) => {
|
||||
let status = (
|
||||
<p css={styles.status}>
|
||||
<CircularProgress size={16} color="secondary" data-chromatic="ignore" />
|
||||
<p
|
||||
className={cn(
|
||||
"m-0 flex items-center justify-center gap-2 text-content-disabled",
|
||||
)}
|
||||
>
|
||||
<Spinner size="sm" loading />
|
||||
Checking for authentication...
|
||||
</p>
|
||||
);
|
||||
@@ -126,30 +130,33 @@ export const GitDeviceAuth: FC<GitDeviceAuthProps> = ({
|
||||
}
|
||||
|
||||
if (!externalAuthDevice) {
|
||||
return <CircularProgress />;
|
||||
return <Spinner size="sm" loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p css={styles.text}>
|
||||
<div className="m-0 text-center text-base leading-[160%] text-content-secondary">
|
||||
Copy your one-time code:
|
||||
<div css={styles.copyCode}>
|
||||
<span css={styles.code}>{externalAuthDevice.user_code}</span>
|
||||
<span className="inline-flex items-center">
|
||||
<span className="font-bold text-content-primary">
|
||||
{externalAuthDevice.user_code}
|
||||
</span>
|
||||
{" "}
|
||||
<CopyButton
|
||||
text={externalAuthDevice.user_code}
|
||||
label="Copy user code"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
<br />
|
||||
Then open the link below and paste it:
|
||||
</p>
|
||||
<div css={styles.links}>
|
||||
</div>
|
||||
<div className="my-4 flex flex-col gap-1">
|
||||
<Link
|
||||
css={styles.link}
|
||||
className="flex items-center justify-center gap-2 text-base"
|
||||
href={externalAuthDevice.verification_uri}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
showExternalIcon={false}
|
||||
>
|
||||
<ExternalLinkIcon className="size-icon-xs" />
|
||||
Open and Paste
|
||||
@@ -160,46 +167,3 @@ export const GitDeviceAuth: FC<GitDeviceAuthProps> = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
text: (theme) => ({
|
||||
fontSize: 16,
|
||||
color: theme.palette.text.secondary,
|
||||
textAlign: "center",
|
||||
lineHeight: "160%",
|
||||
margin: 0,
|
||||
}),
|
||||
|
||||
copyCode: {
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
|
||||
code: (theme) => ({
|
||||
fontWeight: "bold",
|
||||
color: theme.palette.text.primary,
|
||||
}),
|
||||
|
||||
links: {
|
||||
display: "flex",
|
||||
gap: 4,
|
||||
margin: 16,
|
||||
flexDirection: "column",
|
||||
},
|
||||
|
||||
link: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: 16,
|
||||
gap: 8,
|
||||
},
|
||||
|
||||
status: (theme) => ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 8,
|
||||
color: theme.palette.text.disabled,
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import CircularProgress, {
|
||||
type CircularProgressProps,
|
||||
} from "@mui/material/CircularProgress";
|
||||
import { type FC, type ReactNode, useMemo } from "react";
|
||||
import { cva } from "class-variance-authority";
|
||||
import type { FC, ReactNode } from "react";
|
||||
import type { ThemeRole } from "#/theme/roles";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { Spinner, type SpinnerProps } from "../Spinner/Spinner";
|
||||
|
||||
type PillProps = React.ComponentPropsWithRef<"div"> & {
|
||||
icon?: ReactNode;
|
||||
@@ -11,36 +10,88 @@ type PillProps = React.ComponentPropsWithRef<"div"> & {
|
||||
size?: "md" | "lg";
|
||||
};
|
||||
|
||||
const themeStyles = (type: ThemeRole) => (theme: Theme) => {
|
||||
const palette = theme.roles[type];
|
||||
return {
|
||||
backgroundColor: palette.background,
|
||||
borderColor: palette.outline,
|
||||
};
|
||||
};
|
||||
const pillRoleVariants = cva("text-content-primary", {
|
||||
variants: {
|
||||
type: {
|
||||
error: "border-border-destructive bg-surface-red",
|
||||
warning: "border-border-warning bg-surface-orange",
|
||||
notice: "border-border-pending bg-surface-sky",
|
||||
info: "border-border bg-surface-quaternary",
|
||||
success: "border-border-green bg-surface-green",
|
||||
active: "border-border-pending bg-surface-sky",
|
||||
inactive: "border-border bg-surface-secondary",
|
||||
danger: "border-border-warning bg-surface-orange",
|
||||
preview: "border-border-purple bg-surface-purple",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
type: "inactive",
|
||||
},
|
||||
});
|
||||
|
||||
const PILL_HEIGHT = 24;
|
||||
const PILL_ICON_SIZE = 14;
|
||||
const PILL_ICON_SPACING = (PILL_HEIGHT - PILL_ICON_SIZE) / 2;
|
||||
const pillLayoutVariants = cva(
|
||||
"inline-flex cursor-default items-center whitespace-nowrap border border-solid font-normal [&_svg]:size-[14px]",
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
md: "h-6 rounded-full text-xs leading-none",
|
||||
lg: "rounded-full py-3.5 pl-4 pr-4 text-sm leading-none",
|
||||
},
|
||||
withIcon: {
|
||||
true: "",
|
||||
false: "",
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
size: "md",
|
||||
withIcon: false,
|
||||
class: "gap-[5px] px-3",
|
||||
},
|
||||
{
|
||||
size: "md",
|
||||
withIcon: true,
|
||||
class: "gap-[5px] pr-3 pl-[5px]",
|
||||
},
|
||||
{
|
||||
size: "lg",
|
||||
withIcon: false,
|
||||
class: "gap-2.5",
|
||||
},
|
||||
{
|
||||
size: "lg",
|
||||
withIcon: true,
|
||||
class: "gap-2.5 pl-2.5 pr-4",
|
||||
},
|
||||
],
|
||||
defaultVariants: {
|
||||
size: "md",
|
||||
withIcon: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
type PillSpinnerProps = SpinnerProps;
|
||||
|
||||
export const PillSpinner: FC<PillSpinnerProps> = ({ size = "sm" }) => {
|
||||
return <Spinner size={size} loading />;
|
||||
};
|
||||
|
||||
export const Pill: FC<PillProps> = ({
|
||||
icon,
|
||||
type = "inactive",
|
||||
children,
|
||||
size = "md",
|
||||
className,
|
||||
...divProps
|
||||
}) => {
|
||||
const typeStyles = useMemo(() => themeStyles(type), [type]);
|
||||
|
||||
return (
|
||||
<div
|
||||
css={[
|
||||
styles.pill,
|
||||
Boolean(icon) && size === "md" && styles.pillWithIcon,
|
||||
size === "lg" && styles.pillLg,
|
||||
Boolean(icon) && size === "lg" && styles.pillLgWithIcon,
|
||||
typeStyles,
|
||||
]}
|
||||
className={cn(
|
||||
pillLayoutVariants({ size, withIcon: Boolean(icon) }),
|
||||
pillRoleVariants({ type }),
|
||||
className,
|
||||
)}
|
||||
{...divProps}
|
||||
>
|
||||
{icon}
|
||||
@@ -48,55 +99,3 @@ export const Pill: FC<PillProps> = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const PillSpinner: FC<CircularProgressProps> = (props) => {
|
||||
return (
|
||||
<CircularProgress size={PILL_ICON_SIZE} css={styles.spinner} {...props} />
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
pill: (theme) => ({
|
||||
fontSize: 12,
|
||||
color: theme.experimental.l1.text,
|
||||
cursor: "default",
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
whiteSpace: "nowrap",
|
||||
fontWeight: 400,
|
||||
borderWidth: 1,
|
||||
borderStyle: "solid",
|
||||
borderRadius: 99999,
|
||||
lineHeight: 1,
|
||||
height: PILL_HEIGHT,
|
||||
gap: PILL_ICON_SPACING,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
|
||||
"& svg": {
|
||||
width: PILL_ICON_SIZE,
|
||||
height: PILL_ICON_SIZE,
|
||||
},
|
||||
}),
|
||||
|
||||
pillWithIcon: {
|
||||
paddingLeft: PILL_ICON_SPACING,
|
||||
},
|
||||
|
||||
pillLg: {
|
||||
gap: PILL_ICON_SPACING * 2,
|
||||
padding: "14px 16px",
|
||||
},
|
||||
|
||||
pillLgWithIcon: {
|
||||
paddingLeft: PILL_ICON_SPACING * 2,
|
||||
},
|
||||
|
||||
spinner: (theme) => ({
|
||||
color: theme.experimental.l1.text,
|
||||
// It is necessary to align it with the MUI Icons internal padding
|
||||
"& svg": {
|
||||
transform: "scale(.75)",
|
||||
},
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -23,7 +23,7 @@ const spinnerVariants = cva("", {
|
||||
},
|
||||
});
|
||||
|
||||
type SpinnerProps = React.SVGProps<SVGSVGElement> &
|
||||
export type SpinnerProps = React.SVGProps<SVGSVGElement> &
|
||||
VariantProps<typeof spinnerVariants> & {
|
||||
children?: ReactNode;
|
||||
loading?: boolean;
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import { css } from "@emotion/css";
|
||||
import Autocomplete from "@mui/material/Autocomplete";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
import { type FC, useState } from "react";
|
||||
import { type FC, useMemo, useState } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { templateVersions } from "#/api/queries/templates";
|
||||
import type { TemplateVersion, Workspace } from "#/api/typesGenerated";
|
||||
import { Alert, AlertTitle } from "#/components/Alert/Alert";
|
||||
import { Avatar } from "#/components/Avatar/Avatar";
|
||||
import { AvatarData } from "#/components/Avatar/AvatarData";
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxButton,
|
||||
ComboboxContent,
|
||||
ComboboxEmpty,
|
||||
ComboboxInput,
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
ComboboxTrigger,
|
||||
} from "#/components/Combobox/Combobox";
|
||||
import { ConfirmDialog } from "#/components/Dialogs/ConfirmDialog/ConfirmDialog";
|
||||
import type { DialogProps } from "#/components/Dialogs/Dialog";
|
||||
import type { SelectFilterOption } from "#/components/Filter/SelectFilter";
|
||||
import { FormFields } from "#/components/Form/Form";
|
||||
import { Loader } from "#/components/Loader/Loader";
|
||||
import { Pill } from "#/components/Pill/Pill";
|
||||
import { Stack } from "#/components/Stack/Stack";
|
||||
import { TemplateUpdateMessage } from "#/modules/templates/TemplateUpdateMessage";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { createDayString } from "#/utils/createDayString";
|
||||
|
||||
type ChangeWorkspaceVersionDialogProps = DialogProps & {
|
||||
@@ -32,14 +39,30 @@ export const ChangeWorkspaceVersionDialog: FC<
|
||||
...templateVersions(workspace.template_id),
|
||||
select: (data) => [...data].reverse(),
|
||||
});
|
||||
const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false);
|
||||
const [newVersion, setNewVersion] = useState<TemplateVersion>();
|
||||
const currentVersion = versions?.find(
|
||||
(v) => workspace.latest_build.template_version_id === v.id,
|
||||
);
|
||||
const [newVersion, setNewVersion] = useState<TemplateVersion>();
|
||||
const validVersions = versions?.filter((v) => v.job.status === "succeeded");
|
||||
const selectedVersion = newVersion || currentVersion;
|
||||
|
||||
const selectedOption: SelectFilterOption | undefined = useMemo(() => {
|
||||
if (!selectedVersion) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
value: selectedVersion.id,
|
||||
label: selectedVersion.name,
|
||||
startIcon: (
|
||||
<Avatar
|
||||
size="sm"
|
||||
src={selectedVersion.created_by.avatar_url}
|
||||
fallback={selectedVersion.name}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}, [selectedVersion]);
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
{...dialogProps}
|
||||
@@ -55,87 +78,79 @@ export const ChangeWorkspaceVersionDialog: FC<
|
||||
confirmText="Change"
|
||||
title="Change version"
|
||||
description={
|
||||
<Stack>
|
||||
<div className="flex flex-col gap-4">
|
||||
<p>You are about to change the version of this workspace.</p>
|
||||
{validVersions ? (
|
||||
<>
|
||||
<FormFields>
|
||||
<Autocomplete
|
||||
disableClearable
|
||||
options={validVersions}
|
||||
defaultValue={selectedVersion}
|
||||
id="template-version-autocomplete"
|
||||
open={isAutocompleteOpen}
|
||||
onChange={(_, newTemplateVersion) => {
|
||||
setNewVersion(newTemplateVersion);
|
||||
<Combobox
|
||||
value={selectedVersion?.id}
|
||||
onValueChange={(id) => {
|
||||
if (!id) {
|
||||
setNewVersion(undefined);
|
||||
return;
|
||||
}
|
||||
const next = validVersions.find((v) => v.id === id);
|
||||
setNewVersion(next);
|
||||
}}
|
||||
onOpen={() => {
|
||||
setIsAutocompleteOpen(true);
|
||||
}}
|
||||
onClose={() => {
|
||||
setIsAutocompleteOpen(false);
|
||||
}}
|
||||
isOptionEqualToValue={(
|
||||
option: TemplateVersion,
|
||||
value: TemplateVersion,
|
||||
) => option.id === value.id}
|
||||
getOptionLabel={(option) => option.name}
|
||||
renderOption={(props, option: TemplateVersion) => (
|
||||
<li {...props}>
|
||||
<AvatarData
|
||||
avatar={
|
||||
<Avatar
|
||||
src={option.created_by.avatar_url}
|
||||
fallback={option.name}
|
||||
>
|
||||
<ComboboxTrigger asChild>
|
||||
<ComboboxButton
|
||||
id="template-version-autocomplete"
|
||||
aria-label="Template version"
|
||||
selectedOption={selectedOption}
|
||||
placeholder="Template version name"
|
||||
className="w-full min-w-0 pl-3.5"
|
||||
/>
|
||||
</ComboboxTrigger>
|
||||
<ComboboxContent
|
||||
className="max-w-none min-w-[min(100%,320px)]"
|
||||
align="start"
|
||||
>
|
||||
<ComboboxInput placeholder="Search versions…" />
|
||||
<ComboboxList>
|
||||
{validVersions.map((option) => (
|
||||
<ComboboxItem
|
||||
key={option.id}
|
||||
value={option.id}
|
||||
keywords={[option.name]}
|
||||
className={cn(
|
||||
"px-3 py-2 font-normal",
|
||||
"data-[selected=true]:bg-surface-tertiary",
|
||||
)}
|
||||
>
|
||||
<AvatarData
|
||||
avatar={
|
||||
<Avatar
|
||||
src={option.created_by.avatar_url}
|
||||
fallback={option.name}
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<div className="flex w-full flex-row items-center justify-between gap-2">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
{option.name}
|
||||
{option.message && (
|
||||
<InfoIcon
|
||||
aria-hidden="true"
|
||||
className="size-icon-xs"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{workspace.template_active_version_id ===
|
||||
option.id && (
|
||||
<Pill type="success">Active</Pill>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
subtitle={createDayString(option.created_at)}
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
>
|
||||
{option.name}
|
||||
{option.message && (
|
||||
<InfoIcon
|
||||
aria-hidden="true"
|
||||
className="size-icon-xs"
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
{workspace.template_active_version_id ===
|
||||
option.id && <Pill type="success">Active</Pill>}
|
||||
</Stack>
|
||||
}
|
||||
subtitle={createDayString(option.created_at)}
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
renderInput={(params) => (
|
||||
<>
|
||||
<TextField
|
||||
{...params}
|
||||
fullWidth
|
||||
placeholder="Template version name"
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<>
|
||||
{!versions && <CircularProgress size={16} />}
|
||||
{params.InputProps.endAdornment}
|
||||
</>
|
||||
),
|
||||
classes: { root: classNames.root },
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</ComboboxItem>
|
||||
))}
|
||||
</ComboboxList>
|
||||
<ComboboxEmpty>No template versions found</ComboboxEmpty>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
</FormFields>
|
||||
{selectedVersion && (
|
||||
<>
|
||||
@@ -155,15 +170,8 @@ export const ChangeWorkspaceVersionDialog: FC<
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const classNames = {
|
||||
// Same `padding-left` as input
|
||||
root: css`
|
||||
padding-left: 14px !important;
|
||||
`,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import kebabCase from "lodash/fp/kebabCase";
|
||||
import { BellOffIcon, RotateCcwIcon } from "lucide-react";
|
||||
import { type FC, Suspense } from "react";
|
||||
@@ -9,6 +8,7 @@ import type { HealthSeverity } from "#/api/typesGenerated";
|
||||
import { ErrorAlert } from "#/components/Alert/ErrorAlert";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import { Loader } from "#/components/Loader/Loader";
|
||||
import { Spinner } from "#/components/Spinner/Spinner";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -92,7 +92,7 @@ export const HealthLayout: FC = () => {
|
||||
}}
|
||||
>
|
||||
{isRefreshing ? (
|
||||
<CircularProgress size={16} />
|
||||
<Spinner size="sm" loading />
|
||||
) : (
|
||||
<RotateCcwIcon className="size-5" />
|
||||
)}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import type { FC } from "react";
|
||||
import type { GitSSHKey } from "#/api/typesGenerated";
|
||||
import { ErrorAlert } from "#/components/Alert/ErrorAlert";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import { CodeExample } from "#/components/CodeExample/CodeExample";
|
||||
import { Stack } from "#/components/Stack/Stack";
|
||||
import { Spinner } from "#/components/Spinner/Spinner";
|
||||
|
||||
interface SSHKeysPageViewProps {
|
||||
isLoading: boolean;
|
||||
@@ -20,43 +18,27 @@ export const SSHKeysPageView: FC<SSHKeysPageViewProps> = ({
|
||||
sshKey,
|
||||
onRegenerateClick,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="p-8">
|
||||
<CircularProgress size={26} />
|
||||
<Spinner size="lg" loading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Regenerating the key is not an option if getSSHKey fails.
|
||||
Only one of the error messages will exist at a single time */}
|
||||
{Boolean(getSSHKeyError) && <ErrorAlert error={getSSHKeyError} />}
|
||||
|
||||
{sshKey && (
|
||||
<>
|
||||
<p
|
||||
css={{
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.secondary,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
<p className="m-0 text-sm text-content-secondary">
|
||||
The following public key is used to authenticate Git in workspaces.
|
||||
You may add it to Git services (such as GitHub) that you need to
|
||||
access from your workspace. Coder configures authentication via{" "}
|
||||
<code
|
||||
css={{
|
||||
background: theme.palette.divider,
|
||||
fontSize: 12,
|
||||
padding: "2px 4px",
|
||||
color: theme.palette.text.primary,
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<code className="rounded-sm border border-border bg-surface-secondary px-1 py-0.5 text-xs text-content-primary">
|
||||
$GIT_SSH_COMMAND
|
||||
</code>
|
||||
.
|
||||
@@ -73,6 +55,6 @@ export const SSHKeysPageView: FC<SSHKeysPageViewProps> = ({
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -165,7 +165,6 @@ export default defineConfig({
|
||||
"@mui/material/CardActionArea",
|
||||
"@mui/material/CardContent",
|
||||
"@mui/material/Checkbox",
|
||||
"@mui/material/CircularProgress",
|
||||
"@mui/material/Collapse",
|
||||
"@mui/material/CssBaseline",
|
||||
"@mui/material/Dialog",
|
||||
|
||||
Reference in New Issue
Block a user