mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
feat: Add selected template link at the template select field (#1918)
This commit is contained in:
@ -7,6 +7,7 @@ import * as TypesGen from "../../api/typesGenerated"
|
||||
import { getFormHelpers, nameValidator, onChangeTrimmed } from "../../util/formUtils"
|
||||
import { FormFooter } from "../FormFooter/FormFooter"
|
||||
import { FullPageForm } from "../FullPageForm/FullPageForm"
|
||||
import { Stack } from "../Stack/Stack"
|
||||
|
||||
export const Language = {
|
||||
emailLabel: "Email",
|
||||
@ -57,32 +58,34 @@ export const CreateUserForm: FC<CreateUserFormProps> = ({
|
||||
return (
|
||||
<FullPageForm title="Create user" onCancel={onCancel}>
|
||||
<form onSubmit={form.handleSubmit}>
|
||||
<TextField
|
||||
{...getFieldHelpers("username")}
|
||||
onChange={onChangeTrimmed(form)}
|
||||
autoComplete="username"
|
||||
autoFocus
|
||||
fullWidth
|
||||
label={Language.usernameLabel}
|
||||
variant="outlined"
|
||||
/>
|
||||
<TextField
|
||||
{...getFieldHelpers("email")}
|
||||
onChange={onChangeTrimmed(form)}
|
||||
autoComplete="email"
|
||||
fullWidth
|
||||
label={Language.emailLabel}
|
||||
variant="outlined"
|
||||
/>
|
||||
<TextField
|
||||
{...getFieldHelpers("password")}
|
||||
autoComplete="current-password"
|
||||
fullWidth
|
||||
id="password"
|
||||
label={Language.passwordLabel}
|
||||
type="password"
|
||||
variant="outlined"
|
||||
/>
|
||||
<Stack spacing={1}>
|
||||
<TextField
|
||||
{...getFieldHelpers("username")}
|
||||
onChange={onChangeTrimmed(form)}
|
||||
autoComplete="username"
|
||||
autoFocus
|
||||
fullWidth
|
||||
label={Language.usernameLabel}
|
||||
variant="outlined"
|
||||
/>
|
||||
<TextField
|
||||
{...getFieldHelpers("email")}
|
||||
onChange={onChangeTrimmed(form)}
|
||||
autoComplete="email"
|
||||
fullWidth
|
||||
label={Language.emailLabel}
|
||||
variant="outlined"
|
||||
/>
|
||||
<TextField
|
||||
{...getFieldHelpers("password")}
|
||||
autoComplete="current-password"
|
||||
fullWidth
|
||||
id="password"
|
||||
label={Language.passwordLabel}
|
||||
type="password"
|
||||
variant="outlined"
|
||||
/>
|
||||
</Stack>
|
||||
{error && <FormHelperText error>{error}</FormHelperText>}
|
||||
<FormFooter onCancel={onCancel} isLoading={isLoading} />
|
||||
</form>
|
||||
|
@ -14,16 +14,17 @@ export interface FormFooterProps {
|
||||
submitLabel?: string
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
footer: {
|
||||
display: "flex",
|
||||
flex: "0",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
gap: theme.spacing(1.5),
|
||||
alignItems: "center",
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
button: {
|
||||
margin: "1em",
|
||||
width: "100%",
|
||||
},
|
||||
}))
|
||||
|
||||
|
@ -9,9 +9,8 @@ export interface FormTitleProps {
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
title: {
|
||||
textAlign: "center",
|
||||
marginTop: theme.spacing(5),
|
||||
marginBottom: theme.spacing(5),
|
||||
marginTop: theme.spacing(6),
|
||||
marginBottom: theme.spacing(4),
|
||||
|
||||
"& h3": {
|
||||
marginBottom: theme.spacing(1),
|
||||
|
@ -23,7 +23,7 @@ export const FullPageForm: FC<FullPageFormProps> = ({ title, detail, onCancel, c
|
||||
const styles = useStyles()
|
||||
return (
|
||||
<main className={styles.root}>
|
||||
<Margins>
|
||||
<Margins size="small">
|
||||
<FormTitle title={title} detail={detail} />
|
||||
<FormCloseButton onClose={onCancel} />
|
||||
|
||||
|
@ -1,18 +1,30 @@
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { FC } from "react"
|
||||
import { maxWidth, sidePadding } from "../../theme/constants"
|
||||
import { containerWidth, sidePadding } from "../../theme/constants"
|
||||
|
||||
type Size = "regular" | "medium" | "small"
|
||||
|
||||
const widthBySize: Record<Size, number> = {
|
||||
regular: containerWidth,
|
||||
medium: containerWidth / 2,
|
||||
small: containerWidth / 3,
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
margins: {
|
||||
margin: "0 auto",
|
||||
maxWidth,
|
||||
padding: `0 ${sidePadding}`,
|
||||
maxWidth: ({ maxWidth }: { maxWidth: number }) => maxWidth,
|
||||
padding: `0 ${sidePadding}px`,
|
||||
flex: 1,
|
||||
width: "100%",
|
||||
},
|
||||
}))
|
||||
|
||||
export const Margins: FC = ({ children }) => {
|
||||
const styles = useStyles()
|
||||
interface MarginsProps {
|
||||
size?: Size
|
||||
}
|
||||
|
||||
export const Margins: FC<MarginsProps> = ({ children, size = "regular" }) => {
|
||||
const styles = useStyles({ maxWidth: widthBySize[size] })
|
||||
return <div className={styles.margins}>{children}</div>
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel"
|
||||
import Paper from "@material-ui/core/Paper"
|
||||
import Radio from "@material-ui/core/Radio"
|
||||
import RadioGroup from "@material-ui/core/RadioGroup"
|
||||
import { lighten, makeStyles } from "@material-ui/core/styles"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import TextField from "@material-ui/core/TextField"
|
||||
import { FC } from "react"
|
||||
import { ParameterSchema } from "../../api/typesGenerated"
|
||||
@ -17,7 +16,7 @@ export interface ParameterInputProps {
|
||||
export const ParameterInput: FC<ParameterInputProps> = ({ disabled, onChange, schema }) => {
|
||||
const styles = useStyles()
|
||||
return (
|
||||
<Paper className={styles.paper}>
|
||||
<div className={styles.root}>
|
||||
<div className={styles.title}>
|
||||
<h2>var.{schema.name}</h2>
|
||||
{schema.description && <span>{schema.description}</span>}
|
||||
@ -25,7 +24,7 @@ export const ParameterInput: FC<ParameterInputProps> = ({ disabled, onChange, sc
|
||||
<div className={styles.input}>
|
||||
<ParameterField disabled={disabled} onChange={onChange} schema={schema} />
|
||||
</div>
|
||||
</Paper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -67,28 +66,26 @@ const ParameterField: React.FC<ParameterInputProps> = ({ disabled, onChange, sch
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
paper: {
|
||||
root: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||
paddingTop: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(2),
|
||||
},
|
||||
title: {
|
||||
background: lighten(theme.palette.background.default, 0.1),
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
padding: theme.spacing(3),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
"& h2": {
|
||||
margin: 0,
|
||||
},
|
||||
"& span": {
|
||||
paddingTop: theme.spacing(2),
|
||||
paddingTop: theme.spacing(1),
|
||||
},
|
||||
},
|
||||
input: {
|
||||
padding: theme.spacing(3),
|
||||
marginTop: theme.spacing(2),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
maxWidth: 480,
|
||||
},
|
||||
}))
|
||||
|
@ -167,8 +167,8 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
|
||||
|
||||
return (
|
||||
<FullPageForm onCancel={onCancel} title="Workspace Schedule">
|
||||
<form className={styles.form} onSubmit={form.handleSubmit}>
|
||||
<Stack className={styles.stack}>
|
||||
<form onSubmit={form.handleSubmit} className={styles.form}>
|
||||
<Stack>
|
||||
<TextField
|
||||
{...formHelpers("startTime", Language.startTimeHelperText)}
|
||||
disabled={form.isSubmitting || isLoading}
|
||||
@ -177,7 +177,6 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
|
||||
}}
|
||||
label={Language.startTimeLabel}
|
||||
type="time"
|
||||
variant="standard"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
@ -195,7 +194,6 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
|
||||
shrink: true,
|
||||
}}
|
||||
label={Language.timezoneLabel}
|
||||
variant="standard"
|
||||
/>
|
||||
|
||||
<FormControl component="fieldset" error={Boolean(form.errors.monday)}>
|
||||
@ -212,6 +210,9 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
|
||||
disabled={!form.values.startTime || form.isSubmitting || isLoading}
|
||||
onChange={form.handleChange}
|
||||
name={checkbox.name}
|
||||
color="primary"
|
||||
size="small"
|
||||
disableRipple
|
||||
/>
|
||||
}
|
||||
key={checkbox.name}
|
||||
@ -229,7 +230,6 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
|
||||
inputProps={{ min: 0, step: 1 }}
|
||||
label={Language.ttlLabel}
|
||||
type="number"
|
||||
variant="standard"
|
||||
/>
|
||||
|
||||
<FormFooter onCancel={onCancel} isLoading={form.isSubmitting || isLoading} />
|
||||
@ -241,21 +241,10 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
|
||||
|
||||
const useStyles = makeStyles({
|
||||
form: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
|
||||
"& input": {
|
||||
colorScheme: "dark",
|
||||
},
|
||||
},
|
||||
stack: {
|
||||
// REMARK: 360 is 'arbitrary' in that it gives the helper text enough room
|
||||
// to render on one line. If we change the text, we might want to
|
||||
// adjust these. Without constraining the width, the date picker
|
||||
// and number inputs aren't visually appealing or maximally usable.
|
||||
maxWidth: 360,
|
||||
minWidth: 360,
|
||||
},
|
||||
daysOfWeekLabel: {
|
||||
fontSize: 12,
|
||||
},
|
||||
|
@ -1,13 +1,16 @@
|
||||
import Link from "@material-ui/core/Link"
|
||||
import MenuItem from "@material-ui/core/MenuItem"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import TextField, { TextFieldProps } from "@material-ui/core/TextField"
|
||||
import OpenInNewIcon from "@material-ui/icons/OpenInNew"
|
||||
import { FormikContextType, useFormik } from "formik"
|
||||
import { FC, useState } from "react"
|
||||
import { Link as RouterLink } from "react-router-dom"
|
||||
import * as Yup from "yup"
|
||||
import * as TypesGen from "../../api/typesGenerated"
|
||||
import { FormFooter } from "../../components/FormFooter/FormFooter"
|
||||
import { FullPageForm } from "../../components/FullPageForm/FullPageForm"
|
||||
import { Loader } from "../../components/Loader/Loader"
|
||||
import { Margins } from "../../components/Margins/Margins"
|
||||
import { ParameterInput } from "../../components/ParameterInput/ParameterInput"
|
||||
import { Stack } from "../../components/Stack/Stack"
|
||||
import { getFormHelpers, nameValidator, onChangeTrimmed } from "../../util/formUtils"
|
||||
@ -35,6 +38,7 @@ export const validationSchema = Yup.object({
|
||||
|
||||
export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props) => {
|
||||
const [parameterValues, setParameterValues] = useState<Record<string, string>>({})
|
||||
const styles = useStyles()
|
||||
const form: FormikContextType<TypesGen.CreateWorkspaceRequest> = useFormik<TypesGen.CreateWorkspaceRequest>({
|
||||
initialValues: {
|
||||
name: "",
|
||||
@ -67,6 +71,10 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props)
|
||||
},
|
||||
})
|
||||
const getFieldHelpers = getFormHelpers<TypesGen.CreateWorkspaceRequest>(form)
|
||||
const selectedTemplate =
|
||||
props.templates &&
|
||||
form.values.template_id &&
|
||||
props.templates.find((template) => template.id === form.values.template_id)
|
||||
|
||||
const handleTemplateChange: TextFieldProps["onChange"] = (event) => {
|
||||
if (!props.templates) {
|
||||
@ -85,67 +93,90 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props)
|
||||
}
|
||||
|
||||
return (
|
||||
<Margins>
|
||||
<FullPageForm title="Create workspace" onCancel={props.onCancel}>
|
||||
<form onSubmit={form.handleSubmit}>
|
||||
{props.loadingTemplates && <Loader />}
|
||||
<FullPageForm title="Create workspace" onCancel={props.onCancel}>
|
||||
<form onSubmit={form.handleSubmit}>
|
||||
{props.loadingTemplates && <Loader />}
|
||||
|
||||
<Stack>
|
||||
{props.templates && (
|
||||
<Stack>
|
||||
{props.templates && (
|
||||
<TextField
|
||||
{...getFieldHelpers("template_id")}
|
||||
disabled={form.isSubmitting}
|
||||
onChange={handleTemplateChange}
|
||||
autoFocus
|
||||
fullWidth
|
||||
label={Language.templateLabel}
|
||||
variant="outlined"
|
||||
select
|
||||
helperText={
|
||||
selectedTemplate && (
|
||||
<Link
|
||||
className={styles.readMoreLink}
|
||||
component={RouterLink}
|
||||
to={`/templates/${selectedTemplate.name}`}
|
||||
target="_blank"
|
||||
>
|
||||
Read more about this template <OpenInNewIcon />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
>
|
||||
{props.templates.map((template) => (
|
||||
<MenuItem key={template.id} value={template.id}>
|
||||
{template.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
)}
|
||||
|
||||
{props.selectedTemplate && props.templateSchema && (
|
||||
<>
|
||||
<TextField
|
||||
{...getFieldHelpers("template_id")}
|
||||
{...getFieldHelpers("name")}
|
||||
disabled={form.isSubmitting}
|
||||
onChange={handleTemplateChange}
|
||||
onChange={onChangeTrimmed(form)}
|
||||
autoFocus
|
||||
fullWidth
|
||||
label={Language.templateLabel}
|
||||
label={Language.nameLabel}
|
||||
variant="outlined"
|
||||
select
|
||||
>
|
||||
{props.templates.map((template) => (
|
||||
<MenuItem key={template.id} value={template.id}>
|
||||
{template.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
)}
|
||||
/>
|
||||
|
||||
{props.selectedTemplate && props.templateSchema && (
|
||||
<>
|
||||
<TextField
|
||||
{...getFieldHelpers("name")}
|
||||
disabled={form.isSubmitting}
|
||||
onChange={onChangeTrimmed(form)}
|
||||
autoFocus
|
||||
fullWidth
|
||||
label={Language.nameLabel}
|
||||
variant="outlined"
|
||||
/>
|
||||
{props.templateSchema.length > 0 && (
|
||||
<Stack>
|
||||
{props.templateSchema.map((schema) => (
|
||||
<ParameterInput
|
||||
disabled={form.isSubmitting}
|
||||
key={schema.id}
|
||||
onChange={(value) => {
|
||||
setParameterValues({
|
||||
...parameterValues,
|
||||
[schema.name]: value,
|
||||
})
|
||||
}}
|
||||
schema={schema}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{props.templateSchema.length > 0 && (
|
||||
<Stack>
|
||||
{props.templateSchema.map((schema) => (
|
||||
<ParameterInput
|
||||
disabled={form.isSubmitting}
|
||||
key={schema.id}
|
||||
onChange={(value) => {
|
||||
setParameterValues({
|
||||
...parameterValues,
|
||||
[schema.name]: value,
|
||||
})
|
||||
}}
|
||||
schema={schema}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<FormFooter onCancel={props.onCancel} isLoading={props.creatingWorkspace} />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</form>
|
||||
</FullPageForm>
|
||||
</Margins>
|
||||
<FormFooter onCancel={props.onCancel} isLoading={props.creatingWorkspace} />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</form>
|
||||
</FullPageForm>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
readMoreLink: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
|
||||
"& svg": {
|
||||
width: 12,
|
||||
height: 12,
|
||||
marginLeft: theme.spacing(0.5),
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
@ -7,8 +7,8 @@ export const BODY_FONT_FAMILY = `"Inter", sans-serif`
|
||||
export const lightButtonShadow = "0 2px 2px rgba(0, 23, 121, 0.08)"
|
||||
export const emptyBoxShadow = "none"
|
||||
export const navHeight = 62
|
||||
export const maxWidth = 1380
|
||||
export const sidePadding = "50px"
|
||||
export const containerWidth = 1380
|
||||
export const sidePadding = 24
|
||||
export const TitleIconSize = 48
|
||||
export const CardRadius = 2
|
||||
export const CardPadding = 20
|
||||
|
@ -95,5 +95,15 @@ export const getOverrides = (palette: PaletteOptions) => {
|
||||
border: `1px solid ${palette.divider}`,
|
||||
},
|
||||
},
|
||||
MuiFormHelperText: {
|
||||
contained: {
|
||||
marginLeft: 0,
|
||||
marginRight: 0,
|
||||
},
|
||||
|
||||
marginDense: {
|
||||
marginTop: 8,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user