mirror of
https://github.com/coder/coder.git
synced 2026-09-22 21:22:17 +08:00
chore: setup page cleanup (#24649)
This pull-request addresses a few things that may have made this page less accurate than we would have liked. * Disable the setup form when it is submitting things to the backend. * Migrate `<PasswordField />` over to being backed by `<FormField />` * Don't make use of `<strong />` in the header. Rely on the `font-semibold` as per other headings in the codebase.
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
import type { ComponentPropsWithRef, FC } from "react";
|
||||
import { cn } from "#/utils/cn";
|
||||
|
||||
export type InputProps = ComponentPropsWithRef<"input">;
|
||||
type InputProps = ComponentPropsWithRef<"input">;
|
||||
|
||||
export const Input: FC<InputProps> = ({ className, type, ...props }) => {
|
||||
return (
|
||||
|
||||
@@ -1,27 +1,17 @@
|
||||
import type { FC } from "react";
|
||||
import type { ComponentProps, FC } from "react";
|
||||
import { keepPreviousData, useQuery } from "react-query";
|
||||
import { API } from "#/api/api";
|
||||
import { Input, type InputProps } from "#/components/Input/Input";
|
||||
import { Label } from "#/components/Label/Label";
|
||||
import { FormField } from "#/components/FormField/FormField";
|
||||
import { useDebouncedValue } from "#/hooks/debounce";
|
||||
import { cn } from "#/utils/cn";
|
||||
import type { FormHelpers } from "#/utils/formUtils";
|
||||
|
||||
type PasswordFieldProps = InputProps & {
|
||||
label: string;
|
||||
field: FormHelpers;
|
||||
};
|
||||
type PasswordFieldProps = ComponentProps<typeof FormField>;
|
||||
|
||||
/**
|
||||
* A password field component that validates the password against the API with
|
||||
* debounced calls. It uses a debounced value to minimize the number of API
|
||||
* calls and displays validation errors.
|
||||
*/
|
||||
export const PasswordField: FC<PasswordFieldProps> = ({
|
||||
label,
|
||||
field,
|
||||
...props
|
||||
}) => {
|
||||
export const PasswordField: FC<PasswordFieldProps> = ({ field, ...props }) => {
|
||||
const value = field.value === undefined ? "" : String(field.value);
|
||||
const debouncedValue = useDebouncedValue(value, 500);
|
||||
const validatePasswordQuery = useQuery({
|
||||
@@ -30,35 +20,17 @@ export const PasswordField: FC<PasswordFieldProps> = ({
|
||||
placeholderData: keepPreviousData,
|
||||
enabled: debouncedValue.length > 0,
|
||||
});
|
||||
const valid = validatePasswordQuery.data?.valid ?? true;
|
||||
|
||||
const displayHelper = !valid
|
||||
? validatePasswordQuery.data?.details
|
||||
const invalidPassword = validatePasswordQuery.data?.valid === false;
|
||||
const helperText = invalidPassword
|
||||
? (validatePasswordQuery.data?.details ?? field.helperText)
|
||||
: field.helperText;
|
||||
const mergedField = {
|
||||
...field,
|
||||
error: field.error || invalidPassword,
|
||||
helperText,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<Label htmlFor={field.id}>{label}</Label>
|
||||
<Input
|
||||
id={field.id}
|
||||
type="password"
|
||||
name={field.name}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
{...props}
|
||||
aria-invalid={!valid || undefined}
|
||||
/>
|
||||
{displayHelper && (
|
||||
<span
|
||||
className={cn(
|
||||
"text-xs text-left",
|
||||
valid ? "text-content-secondary" : "text-content-destructive",
|
||||
)}
|
||||
>
|
||||
{displayHelper}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<FormField {...props} type={props.type ?? "password"} field={mergedField} />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -114,6 +114,7 @@ type SelectFieldProps = FormHelpers & {
|
||||
onValueChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
children: ReactNode;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const SelectField: FC<SelectFieldProps> = ({
|
||||
@@ -126,6 +127,7 @@ const SelectField: FC<SelectFieldProps> = ({
|
||||
onValueChange,
|
||||
placeholder,
|
||||
children,
|
||||
disabled,
|
||||
}) => (
|
||||
<Field
|
||||
label={label}
|
||||
@@ -134,7 +136,11 @@ const SelectField: FC<SelectFieldProps> = ({
|
||||
helperText={helperText}
|
||||
className={className}
|
||||
>
|
||||
<Select value={String(value ?? "")} onValueChange={onValueChange}>
|
||||
<Select
|
||||
value={String(value ?? "")}
|
||||
onValueChange={onValueChange}
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectTrigger id={id}>
|
||||
<SelectValue placeholder={placeholder} />
|
||||
</SelectTrigger>
|
||||
@@ -193,9 +199,7 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
|
||||
<div className="flex flex-col w-full max-w-[500px] px-4">
|
||||
<header className="mb-8">
|
||||
<ProductLogo />
|
||||
<h1 className="text-2xl font-normal mt-4 mb-0">
|
||||
Welcome to <strong>Coder</strong>
|
||||
</h1>
|
||||
<h1 className="text-2xl font-semibold mt-4 mb-0">Welcome to Coder</h1>
|
||||
<p className="mt-3 mb-0 text-sm text-content-secondary font-normal">
|
||||
Set up your admin account and start building secure, reproducible
|
||||
dev environments.
|
||||
@@ -229,13 +233,15 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
|
||||
onChange={onChangeTrimmed(form, (email) => {
|
||||
form.setFieldValue("username", usernameFromEmail(email));
|
||||
})}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
|
||||
{/* Password */}
|
||||
<PasswordField
|
||||
field={getFieldHelpers("password")}
|
||||
label="Password"
|
||||
field={getFieldHelpers("password")}
|
||||
autoComplete="new-password"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
|
||||
{/* Premium trial toggle */}
|
||||
@@ -252,6 +258,7 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
|
||||
}
|
||||
data-testid="trial"
|
||||
className="mt-0.5"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<div className="flex flex-col items-start gap-0.5">
|
||||
<span className="text-sm font-semibold">
|
||||
@@ -279,10 +286,12 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
|
||||
<FormField
|
||||
label="First name"
|
||||
field={getFieldHelpers("trial_info.first_name")}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<FormField
|
||||
label="Last name"
|
||||
field={getFieldHelpers("trial_info.last_name")}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -290,6 +299,7 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
|
||||
<FormField
|
||||
label="Company"
|
||||
field={getFieldHelpers("trial_info.company_name")}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<SelectField
|
||||
label="Number of developers"
|
||||
@@ -298,6 +308,7 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
|
||||
form.setFieldValue("trial_info.developers", value)
|
||||
}
|
||||
placeholder="Select..."
|
||||
disabled={isLoading}
|
||||
>
|
||||
{numberOfDevelopersOptions.map((opt) => (
|
||||
<SelectItem key={opt} value={opt}>
|
||||
@@ -309,12 +320,14 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
|
||||
<FormField
|
||||
label="Job title"
|
||||
field={getFieldHelpers("trial_info.job_title")}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<FormField
|
||||
label="Phone number"
|
||||
field={getFieldHelpers("trial_info.phone_number")}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<SelectField
|
||||
label="Country"
|
||||
@@ -323,6 +336,7 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
|
||||
form.setFieldValue("trial_info.country", value)
|
||||
}
|
||||
placeholder="Select..."
|
||||
disabled={isLoading}
|
||||
>
|
||||
{countries.map((c) => (
|
||||
<SelectItem key={c.name} value={c.name}>
|
||||
@@ -354,6 +368,7 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
|
||||
)
|
||||
}
|
||||
data-testid="onboarding_info.newsletter_releases"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<div className="flex flex-col text-sm">
|
||||
<span className="font-medium">Release notes & updates</span>
|
||||
@@ -379,6 +394,7 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
|
||||
)
|
||||
}
|
||||
data-testid="onboarding_info.newsletter_marketing"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<div className="flex flex-col text-sm">
|
||||
<span className="font-medium">Monthly Coder newsletter</span>
|
||||
|
||||
Reference in New Issue
Block a user