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:
Jake Howell
2026-07-28 03:39:42 +00:00
committed by GitHub
parent a3d67507e9
commit 072b101624
3 changed files with 35 additions and 47 deletions
+1 -1
View File
@@ -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} />
);
};
+21 -5
View File
@@ -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>