mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
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
This commit is contained in:
199
site/src/pages/CreateUserPage/CreateUserForm.tsx
Normal file
199
site/src/pages/CreateUserPage/CreateUserForm.tsx
Normal file
@ -0,0 +1,199 @@
|
||||
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",
|
||||
},
|
||||
}))
|
Reference in New Issue
Block a user