feat(frontend): Display toast for first time saas users (#8279)

This commit is contained in:
sp.wack
2025-05-05 19:40:28 +04:00
committed by GitHub
parent 24290ebd7d
commit 27878a2200
4 changed files with 63 additions and 36 deletions

View File

@@ -1,12 +1,16 @@
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { createRoutesStub } from "react-router";
import { screen, waitFor, within } from "@testing-library/react";
import { renderWithProviders } from "test-utils";
import {
createAxiosNotFoundErrorObject,
renderWithProviders,
} from "test-utils";
import userEvent from "@testing-library/user-event";
import MainApp from "#/routes/root-layout";
import i18n from "#/i18n";
import * as CaptureConsent from "#/utils/handle-capture-consent";
import OpenHands from "#/api/open-hands";
import * as ToastHandlers from "#/utils/custom-toast-handlers";
describe("frontend/routes/_oh", () => {
const RouteStub = createRoutesStub([{ Component: MainApp, path: "/" }]);
@@ -172,4 +176,32 @@ describe("frontend/routes/_oh", () => {
// expect(logoutCleanupSpy).toHaveBeenCalled();
expect(localStorage.getItem("ghToken")).toBeNull();
});
it("should render a you're in toast if it is a new user and in saas mode", async () => {
const getConfigSpy = vi.spyOn(OpenHands, "getConfig");
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
const displaySuccessToastSpy = vi.spyOn(
ToastHandlers,
"displaySuccessToast",
);
getConfigSpy.mockResolvedValue({
APP_MODE: "saas",
GITHUB_CLIENT_ID: "test-id",
POSTHOG_CLIENT_KEY: "test-key",
FEATURE_FLAGS: {
ENABLE_BILLING: false,
HIDE_LLM_SETTINGS: false,
},
});
getSettingsSpy.mockRejectedValue(createAxiosNotFoundErrorObject());
renderWithProviders(<RouteStub />);
await waitFor(() => {
expect(displaySuccessToastSpy).toHaveBeenCalledWith("BILLING$YOURE_IN");
expect(displaySuccessToastSpy).toHaveBeenCalledOnce();
});
});
});

View File

@@ -4,30 +4,13 @@ import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import userEvent from "@testing-library/user-event";
import { createRoutesStub } from "react-router";
import { Provider } from "react-redux";
import { setupStore } from "test-utils";
import { AxiosError } from "axios";
import { createAxiosNotFoundErrorObject, setupStore } from "test-utils";
import HomeScreen from "#/routes/home";
import { AuthProvider } from "#/context/auth-context";
import { GitRepository } from "#/types/git";
import OpenHands from "#/api/open-hands";
import MainApp from "#/routes/root-layout";
const createAxiosNotFoundErrorObject = () =>
new AxiosError(
"Request failed with status code 404",
"ERR_BAD_REQUEST",
undefined,
undefined,
{
status: 404,
statusText: "Not Found",
data: { message: "Settings not found" },
headers: {},
// @ts-expect-error - we only need the response object for this test
config: {},
},
);
const RouterStub = createRoutesStub([
{
Component: MainApp,

View File

@@ -5,7 +5,6 @@ import {
Outlet,
useNavigate,
useLocation,
useSearchParams,
} from "react-router";
import { useTranslation } from "react-i18next";
import { I18nKey } from "#/i18n/declaration";
@@ -60,9 +59,8 @@ export default function MainApp() {
const navigate = useNavigate();
const { pathname } = useLocation();
const tosPageStatus = useIsOnTosPage();
const [searchParams] = useSearchParams();
const { data: settings } = useSettings();
const { error, isFetching } = useBalance();
const { error } = useBalance();
const { migrateUserConsent } = useMigrateUserConsent();
const { t } = useTranslation();
@@ -114,21 +112,18 @@ export default function MainApp() {
}, [tosPageStatus]);
React.useEffect(() => {
// Don't do any redirects when on TOS page
if (!tosPageStatus) {
// Don't allow users to use the app if it 402s
if (error?.status === 402 && pathname !== "/") {
navigate("/");
} else if (
!isFetching &&
searchParams.get("free_credits") === "success"
) {
displaySuccessToast(t(I18nKey.BILLING$YOURE_IN));
searchParams.delete("free_credits");
navigate("/");
}
if (settings?.IS_NEW_USER && config.data?.APP_MODE === "saas") {
displaySuccessToast(t(I18nKey.BILLING$YOURE_IN));
}
}, [error?.status, pathname, isFetching, tosPageStatus]);
}, [settings?.IS_NEW_USER, config.data?.APP_MODE]);
React.useEffect(() => {
// Don't do any redirects when on TOS page
// Don't allow users to use the app if it 402s
if (!tosPageStatus && error?.status === 402 && pathname !== "/") {
navigate("/");
}
}, [error?.status, pathname, tosPageStatus]);
// When on TOS page, we don't make any API calls, so we need to handle this case
const userIsAuthed = tosPageStatus ? false : !!isAuthed && !authError;

View File

@@ -8,6 +8,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { I18nextProvider, initReactI18next } from "react-i18next";
import i18n from "i18next";
import { vi } from "vitest";
import { AxiosError } from "axios";
import { AppStore, RootState, rootReducer } from "./src/store";
import { AuthProvider } from "#/context/auth-context";
import { ConversationProvider } from "#/context/conversation-context";
@@ -83,3 +84,19 @@ export function renderWithProviders(
}
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}
export const createAxiosNotFoundErrorObject = () =>
new AxiosError(
"Request failed with status code 404",
"ERR_BAD_REQUEST",
undefined,
undefined,
{
status: 404,
statusText: "Not Found",
data: { message: "Settings not found" },
headers: {},
// @ts-expect-error - we only need the response object for this test
config: {},
},
);