mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
feat(frontend): redirect form submission to login page with modal
- Change login-cta Learn More button to navigate to /onboarding/information-request - Redirect form submission to /login instead of homepage - Move RequestSubmittedModal from home.tsx to login.tsx - Update tests to follow conventions (use createRoutesStub) Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { createRoutesStub } from "react-router";
|
||||
import { LoginCTA } from "#/components/features/auth/login-cta";
|
||||
|
||||
// Mock useTracking hook
|
||||
@@ -16,8 +17,23 @@ describe("LoginCTA", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const renderWithRouter = () => {
|
||||
const Stub = createRoutesStub([
|
||||
{
|
||||
path: "/",
|
||||
Component: LoginCTA,
|
||||
},
|
||||
{
|
||||
path: "/onboarding/information-request",
|
||||
Component: () => <div data-testid="information-request-page" />,
|
||||
},
|
||||
]);
|
||||
|
||||
return render(<Stub initialEntries={["/"]} />);
|
||||
};
|
||||
|
||||
it("should render enterprise CTA with title and description", () => {
|
||||
render(<LoginCTA />);
|
||||
renderWithRouter();
|
||||
|
||||
expect(screen.getByTestId("login-cta")).toBeInTheDocument();
|
||||
expect(screen.getByText("CTA$ENTERPRISE")).toBeInTheDocument();
|
||||
@@ -25,7 +41,7 @@ describe("LoginCTA", () => {
|
||||
});
|
||||
|
||||
it("should render all enterprise feature list items", () => {
|
||||
render(<LoginCTA />);
|
||||
renderWithRouter();
|
||||
|
||||
expect(screen.getByText("CTA$FEATURE_ON_PREMISES")).toBeInTheDocument();
|
||||
expect(screen.getByText("CTA$FEATURE_DATA_CONTROL")).toBeInTheDocument();
|
||||
@@ -33,31 +49,18 @@ describe("LoginCTA", () => {
|
||||
expect(screen.getByText("CTA$FEATURE_SUPPORT")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render Learn More as a link with correct href and target", () => {
|
||||
render(<LoginCTA />);
|
||||
|
||||
const learnMoreLink = screen.getByRole("link", {
|
||||
name: "CTA$LEARN_MORE",
|
||||
});
|
||||
expect(learnMoreLink).toHaveAttribute(
|
||||
"href",
|
||||
"https://openhands.dev/enterprise/",
|
||||
);
|
||||
expect(learnMoreLink).toHaveAttribute("target", "_blank");
|
||||
expect(learnMoreLink).toHaveAttribute("rel", "noopener noreferrer");
|
||||
});
|
||||
|
||||
it("should call trackSaasSelfhostedInquiry with location 'login_page' when Learn More is clicked", async () => {
|
||||
it("should track and navigate to information request page when Learn More is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<LoginCTA />);
|
||||
renderWithRouter();
|
||||
|
||||
const learnMoreLink = screen.getByRole("link", {
|
||||
const learnMoreButton = screen.getByRole("button", {
|
||||
name: "CTA$LEARN_MORE",
|
||||
});
|
||||
await user.click(learnMoreLink);
|
||||
await user.click(learnMoreButton);
|
||||
|
||||
expect(mockTrackSaasSelfhostedInquiry).toHaveBeenCalledWith({
|
||||
location: "login_page",
|
||||
});
|
||||
expect(screen.getByTestId("information-request-page")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -208,7 +208,7 @@ describe("InformationRequestForm", () => {
|
||||
expect(mockTrackEnterpriseLeadFormSubmitted).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should navigate to homepage with modal state when form is submitted with all fields filled", async () => {
|
||||
it("should navigate to login page with modal state when form is submitted with all fields filled", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithRouter();
|
||||
|
||||
@@ -220,7 +220,7 @@ describe("InformationRequestForm", () => {
|
||||
const submitButton = screen.getByRole("button", { name: "ENTERPRISE$FORM_SUBMIT" });
|
||||
await user.click(submitButton);
|
||||
|
||||
expect(mockNavigate).toHaveBeenCalledWith("/", {
|
||||
expect(mockNavigate).toHaveBeenCalledWith("/login", {
|
||||
state: { showRequestSubmittedModal: true },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router";
|
||||
import { Card } from "#/ui/card";
|
||||
import { CardTitle } from "#/ui/card-title";
|
||||
import { Typography } from "#/ui/typography";
|
||||
@@ -9,10 +10,12 @@ import { useTracking } from "#/hooks/use-tracking";
|
||||
|
||||
export function LoginCTA() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { trackSaasSelfhostedInquiry } = useTracking();
|
||||
|
||||
const handleLearnMoreClick = () => {
|
||||
trackSaasSelfhostedInquiry({ location: "login_page" });
|
||||
navigate("/onboarding/information-request");
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -44,10 +47,8 @@ export function LoginCTA() {
|
||||
</ul>
|
||||
|
||||
<div className={cn("h-10 flex justify-start")}>
|
||||
<a
|
||||
href="https://openhands.dev/enterprise/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLearnMoreClick}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center",
|
||||
@@ -58,7 +59,7 @@ export function LoginCTA() {
|
||||
)}
|
||||
>
|
||||
{t(I18nKey.CTA$LEARN_MORE)}
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -62,8 +62,8 @@ export function InformationRequestForm({
|
||||
message: formData.message.trim(),
|
||||
});
|
||||
|
||||
// Navigate to homepage with state to show confirmation modal
|
||||
navigate("/", { state: { showRequestSubmittedModal: true } });
|
||||
// Navigate to login page with state to show confirmation modal
|
||||
navigate("/login", { state: { showRequestSubmittedModal: true } });
|
||||
};
|
||||
|
||||
const isSaas = requestType === "saas";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { PrefetchPageLinks, useLocation, useNavigate } from "react-router";
|
||||
import { PrefetchPageLinks } from "react-router";
|
||||
import { HomeHeader } from "#/components/features/home/home-header/home-header";
|
||||
import { RepoConnector } from "#/components/features/home/repo-connector";
|
||||
import { TaskSuggestions } from "#/components/features/home/tasks/task-suggestions";
|
||||
@@ -7,22 +7,14 @@ import { GitRepository } from "#/types/git";
|
||||
import { NewConversation } from "#/components/features/home/new-conversation/new-conversation";
|
||||
import { RecentConversations } from "#/components/features/home/recent-conversations/recent-conversations";
|
||||
import { HomepageCTA } from "#/components/features/home/homepage-cta";
|
||||
import { RequestSubmittedModal } from "#/components/features/onboarding/request-submitted-modal";
|
||||
import { isCTADismissed } from "#/utils/local-storage";
|
||||
import { useConfig } from "#/hooks/query/use-config";
|
||||
import { ENABLE_PROJ_USER_JOURNEY } from "#/utils/feature-flags";
|
||||
|
||||
<PrefetchPageLinks page="/conversations/:conversationId" />;
|
||||
|
||||
interface LocationState {
|
||||
showRequestSubmittedModal?: boolean;
|
||||
}
|
||||
|
||||
function HomeScreen() {
|
||||
const { data: config } = useConfig();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const locationState = location.state as LocationState | null;
|
||||
|
||||
const [selectedRepo, setSelectedRepo] = React.useState<GitRepository | null>(
|
||||
null,
|
||||
@@ -32,18 +24,8 @@ function HomeScreen() {
|
||||
() => !isCTADismissed("homepage"),
|
||||
);
|
||||
|
||||
const [showRequestModal, setShowRequestModal] = React.useState(
|
||||
() => locationState?.showRequestSubmittedModal ?? false,
|
||||
);
|
||||
|
||||
const isSaasMode = config?.app_mode === "saas";
|
||||
|
||||
const handleModalClose = () => {
|
||||
setShowRequestModal(false);
|
||||
// Clear the state from location to prevent modal showing on refresh
|
||||
navigate(location.pathname, { replace: true, state: {} });
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="home-screen"
|
||||
@@ -76,8 +58,6 @@ function HomeScreen() {
|
||||
<HomepageCTA setShouldShowCTA={setShouldShowCTA} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showRequestModal && <RequestSubmittedModal onClose={handleModalClose} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router";
|
||||
import { useNavigate, useSearchParams, useLocation } from "react-router";
|
||||
import { useIsAuthed } from "#/hooks/query/use-is-authed";
|
||||
import { useConfig } from "#/hooks/query/use-config";
|
||||
import { useGitHubAuthUrl } from "#/hooks/use-github-auth-url";
|
||||
@@ -7,11 +7,18 @@ import { useEmailVerification } from "#/hooks/use-email-verification";
|
||||
import { useInvitation } from "#/hooks/use-invitation";
|
||||
import { LoginContent } from "#/components/features/auth/login-content";
|
||||
import { EmailVerificationModal } from "#/components/features/waitlist/email-verification-modal";
|
||||
import { RequestSubmittedModal } from "#/components/features/onboarding/request-submitted-modal";
|
||||
|
||||
interface LocationState {
|
||||
showRequestSubmittedModal?: boolean;
|
||||
}
|
||||
|
||||
export default function LoginPage() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const returnTo = searchParams.get("returnTo") || "/";
|
||||
const locationState = location.state as LocationState | null;
|
||||
|
||||
const config = useConfig();
|
||||
const { data: isAuthed, isLoading: isAuthLoading } = useIsAuthed();
|
||||
@@ -32,6 +39,15 @@ export default function LoginPage() {
|
||||
authUrl: config.data?.auth_url,
|
||||
});
|
||||
|
||||
const [showRequestModal, setShowRequestModal] = React.useState(
|
||||
() => locationState?.showRequestSubmittedModal ?? false,
|
||||
);
|
||||
|
||||
const handleRequestModalClose = () => {
|
||||
setShowRequestModal(false);
|
||||
navigate(location.pathname, { replace: true, state: {} });
|
||||
};
|
||||
|
||||
// Redirect OSS mode users to home
|
||||
React.useEffect(() => {
|
||||
if (!config.isLoading && config.data?.app_mode === "oss") {
|
||||
@@ -94,6 +110,10 @@ export default function LoginPage() {
|
||||
wasRateLimited={wasRateLimited}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showRequestModal && (
|
||||
<RequestSubmittedModal onClose={handleRequestModalClose} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user