Merge latest FE changes (email validation, form state persistence)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
openhands
2026-03-19 22:38:26 +00:00
4 changed files with 36 additions and 14 deletions

View File

@@ -2,9 +2,11 @@ import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi, beforeEach } from "vitest";
import { createRoutesStub } from "react-router";
import { useState } from "react";
import {
InformationRequestForm,
RequestType,
FormData,
} from "#/components/features/onboarding/information-request-form";
// Mock useTracking
@@ -15,6 +17,12 @@ vi.mock("#/hooks/use-tracking", () => ({
}),
}));
// Wrapper to manage form state (needed since component is controlled)
function StatefulForm({ requestType, onBack }: { requestType: RequestType; onBack: () => void }) {
const [formData, setFormData] = useState<FormData>({ name: "", company: "", email: "", message: "" });
return <InformationRequestForm requestType={requestType} formData={formData} onFormDataChange={setFormData} onBack={onBack} />;
}
describe("InformationRequestForm", () => {
const defaultProps = {
requestType: "saas" as RequestType,
@@ -29,7 +37,7 @@ describe("InformationRequestForm", () => {
const Stub = createRoutesStub([
{
path: "/",
Component: () => <InformationRequestForm {...props} />,
Component: () => <StatefulForm {...props} />,
},
{
path: "/login",

View File

@@ -1,5 +1,7 @@
export const isValidEmail = (email: string): boolean =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/.test(
email,
);
interface FormInputProps {
id: string;

View File

@@ -13,25 +13,30 @@ import StackedIcon from "#/icons/stacked.svg?react";
export type RequestType = "saas" | "self-hosted";
export interface FormData {
name: string;
company: string;
email: string;
message: string;
}
interface InformationRequestFormProps {
requestType: RequestType;
formData: FormData;
onFormDataChange: (data: FormData) => void;
onBack: () => void;
}
export function InformationRequestForm({
requestType,
formData,
onFormDataChange,
onBack,
}: InformationRequestFormProps) {
const { t } = useTranslation();
const navigate = useNavigate();
const { trackEnterpriseLeadFormSubmitted } = useTracking();
const submitEnterpriseLead = useSubmitEnterpriseLead();
const [formData, setFormData] = useState({
name: "",
company: "",
email: "",
message: "",
});
const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState(false);
const isSubmitting = submitEnterpriseLead.isPending;
@@ -135,9 +140,7 @@ export function InformationRequestForm({
placeholder={t(I18nKey.ENTERPRISE$FORM_NAME_PLACEHOLDER)}
required
showError={hasAttemptedSubmit}
onChange={(value) =>
setFormData((prev) => ({ ...prev, name: value }))
}
onChange={(value) => onFormDataChange({ ...formData, name: value })}
/>
<FormInput
@@ -148,7 +151,7 @@ export function InformationRequestForm({
required
showError={hasAttemptedSubmit}
onChange={(value) =>
setFormData((prev) => ({ ...prev, company: value }))
onFormDataChange({ ...formData, company: value })
}
/>
@@ -161,7 +164,7 @@ export function InformationRequestForm({
required
showError={hasAttemptedSubmit}
onChange={(value) =>
setFormData((prev) => ({ ...prev, email: value }))
onFormDataChange({ ...formData, email: value })
}
/>
@@ -174,7 +177,7 @@ export function InformationRequestForm({
required
showError={hasAttemptedSubmit}
onChange={(value) =>
setFormData((prev) => ({ ...prev, message: value }))
onFormDataChange({ ...formData, message: value })
}
/>

View File

@@ -8,6 +8,7 @@ import { BrandButton } from "#/components/features/settings/brand-button";
import {
InformationRequestForm,
RequestType,
FormData,
} from "#/components/features/onboarding/information-request-form";
import OpenHandsLogoWhite from "#/assets/branding/openhands-logo-white.svg?react";
import CloudIcon from "#/icons/cloud-minimal.svg?react";
@@ -74,6 +75,12 @@ export default function InformationRequest() {
const navigate = useNavigate();
const [selectedRequestType, setSelectedRequestType] =
useState<RequestType | null>(null);
const [formData, setFormData] = useState<FormData>({
name: "",
company: "",
email: "",
message: "",
});
const handleBack = () => {
navigate("/login");
@@ -110,6 +117,8 @@ export default function InformationRequest() {
>
<InformationRequestForm
requestType={selectedRequestType}
formData={formData}
onFormDataChange={setFormData}
onBack={handleFormBack}
/>
</div>