mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
Merge latest FE changes (email validation, form state persistence)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user