Files
coder/site/src/pages/CreateUserPage/CreateUserForm.tsx
Bruno Quaresma b15bfa41c2 chore(site): move components close to where they are used (#9552)
* Move AppLink

* Move AuditLogRow

* Move UserDropdown

* Move BuildsTable

* Remove CodeBlock

* Move CreateUserForm

* Move DeploymentBanner

* Move ResetPassworDialog

* Move EditRolesButton

* Move EnterpriseSnackbar

* Move GitAuth

* Move LicenseBanner

* Move Logs

* Move MultiTextField

* Move Navbar

* Remove PasswordField

* Move RuntimeErrorState

* Remove Section

* Move SectionAction

* Move ServiceBanner

* Move SettingsAccountForm

* Move LicenseCard

* Move SettingsSecurityForm

* Move SignInForm

* Remove TabPanel and TabSidebar

* Move TemplateStats and TemplateVariableField

* Move TemplateEditor

* Move TerminalLink

* Move SSH Button

* Move many tooltips

* Move UsersTable

* Move VersionsTable

* Move VSCodeDesktopButton

* Remove WarningAlert

* Move Workspace

* Move WorkspaceActions

* Move WorkspaceBuildProgress

* Move WorkspaceDeletedBanner

* Move WorkspaceScheduleForm

* Move WorkspaceSection

* Move WorkspaceStats

* Fix imports

* Flat CreateUserForm

* Flat GitAuth

* Transform WorkspaceSection into ChartSection

* Flat AccountForm

* Flat ConfirmDeleteDialog

* Flat ResetPasswordDialog

* Flat BuildsTable
2023-09-06 18:06:08 +00:00

200 lines
6.0 KiB
TypeScript

import TextField from "@mui/material/TextField"
import { FormikContextType, useFormik } from "formik"
import { FC } from "react"
import * as Yup from "yup"
import * as TypesGen from "api/typesGenerated"
import { getFormHelpers, nameValidator, onChangeTrimmed } from "utils/formUtils"
import { FormFooter } from "components/FormFooter/FormFooter"
import { FullPageForm } from "components/FullPageForm/FullPageForm"
import { Stack } from "components/Stack/Stack"
import { ErrorAlert } from "components/Alert/ErrorAlert"
import { hasApiFieldErrors, isApiError } from "api/errors"
import MenuItem from "@mui/material/MenuItem"
import { makeStyles } from "@mui/styles"
import { Theme } from "@mui/material/styles"
import Link from "@mui/material/Link"
export const Language = {
emailLabel: "Email",
passwordLabel: "Password",
usernameLabel: "Username",
emailInvalid: "Please enter a valid email address.",
emailRequired: "Please enter an email address.",
passwordRequired: "Please enter a password.",
createUser: "Create",
cancel: "Cancel",
}
export const authMethodLanguage = {
password: {
displayName: "Password",
description: "Use an email address and password to login",
},
oidc: {
displayName: "OpenID Connect",
description: "Use an OpenID Connect provider for authentication",
},
github: {
displayName: "Github",
description: "Use Github OAuth for authentication",
},
none: {
displayName: "None",
description: (
<>
Disable authentication for this user (See the{" "}
<Link
target="_blank"
rel="noopener"
href="https://coder.com/docs/v2/latest/admin/auth#disable-built-in-authentication"
>
documentation
</Link>{" "}
for more details)
</>
),
},
}
export interface CreateUserFormProps {
onSubmit: (user: TypesGen.CreateUserRequest) => void
onCancel: () => void
error?: unknown
isLoading: boolean
myOrgId: string
authMethods?: TypesGen.AuthMethods
}
const validationSchema = Yup.object({
email: Yup.string()
.trim()
.email(Language.emailInvalid)
.required(Language.emailRequired),
password: Yup.string().when("login_type", {
is: "password",
then: (schema) => schema.required(Language.passwordRequired),
otherwise: (schema) => schema,
}),
username: nameValidator(Language.usernameLabel),
login_type: Yup.string().oneOf(Object.keys(authMethodLanguage)),
})
export const CreateUserForm: FC<
React.PropsWithChildren<CreateUserFormProps>
> = ({ onSubmit, onCancel, error, isLoading, myOrgId, authMethods }) => {
const form: FormikContextType<TypesGen.CreateUserRequest> =
useFormik<TypesGen.CreateUserRequest>({
initialValues: {
email: "",
password: "",
username: "",
organization_id: myOrgId,
disable_login: false,
login_type: "",
},
validationSchema,
onSubmit,
})
const getFieldHelpers = getFormHelpers<TypesGen.CreateUserRequest>(
form,
error,
)
const styles = useStyles()
const methods = [
authMethods?.password.enabled && "password",
authMethods?.oidc.enabled && "oidc",
authMethods?.github.enabled && "github",
"none",
].filter(Boolean) as Array<keyof typeof authMethodLanguage>
return (
<FullPageForm title="Create user">
{isApiError(error) && !hasApiFieldErrors(error) && (
<ErrorAlert error={error} sx={{ mb: 4 }} />
)}
<form onSubmit={form.handleSubmit} autoComplete="off">
<Stack spacing={2.5}>
<TextField
{...getFieldHelpers("username")}
onChange={onChangeTrimmed(form)}
autoComplete="username"
autoFocus
fullWidth
label={Language.usernameLabel}
/>
<TextField
{...getFieldHelpers("email")}
onChange={onChangeTrimmed(form)}
autoComplete="email"
fullWidth
label={Language.emailLabel}
/>
<TextField
{...getFieldHelpers(
"login_type",
"Authentication method for this user",
)}
select
id="login_type"
data-testid="login-type-input"
value={form.values.login_type}
label="Login Type"
onChange={async (e) => {
if (e.target.value !== "password") {
await form.setFieldValue("password", "")
}
await form.setFieldValue("login_type", e.target.value)
}}
SelectProps={{
renderValue: (selected: unknown) =>
authMethodLanguage[selected as keyof typeof authMethodLanguage]
?.displayName ?? "",
}}
>
{methods.map((value) => {
const language = authMethodLanguage[value]
return (
<MenuItem key={value} id={"item-" + value} value={value}>
<Stack spacing={0} maxWidth={400}>
{language.displayName}
<span className={styles.labelDescription}>
{language.description}
</span>
</Stack>
</MenuItem>
)
})}
</TextField>
<TextField
{...getFieldHelpers(
"password",
form.values.login_type === "password"
? ""
: "No password required for this login type",
)}
autoComplete="current-password"
fullWidth
id="password"
data-testid="password-input"
disabled={form.values.login_type !== "password"}
label={Language.passwordLabel}
type="password"
/>
</Stack>
<FormFooter onCancel={onCancel} isLoading={isLoading} />
</form>
</FullPageForm>
)
}
const useStyles = makeStyles<Theme>((theme) => ({
labelDescription: {
fontSize: 14,
color: theme.palette.text.secondary,
wordWrap: "normal",
whiteSpace: "break-spaces",
},
}))