fix onboarding modal

This commit is contained in:
Arthur Belleville 2026-04-25 10:28:29 +02:00
parent 3fd0f3dcde
commit 0476a87afd
No known key found for this signature in database
3 changed files with 79 additions and 5 deletions

View file

@ -1 +1 @@
{"root":["./src/app.tsx","./src/envproduction.test.ts","./src/i18n.test.ts","./src/i18n.ts","./src/main.tsx","./src/maincss.test.ts","./src/routes.tsx","./src/setuptests.ts","./src/vite-env.d.ts","./src/viteconfig.test.ts","./src/components/clientauthgate.tsx","./src/components/clientlayout.test.tsx","./src/components/clientlayout.tsx","./src/lib/supabase.ts","./src/pages/authcallback.tsx","./src/pages/clienttablolistpage.tsx","./src/pages/clienttablopage.test.tsx","./src/pages/clienttablopage.tsx","./src/pages/loginpage.test.tsx","./src/pages/loginpage.tsx","./src/pages/resetpasswordpage.test.tsx","./src/pages/resetpasswordpage.tsx","./src/pages/setpasswordpage.test.tsx","./src/pages/setpasswordpage.tsx","./src/test/testhelpers.test.tsx","./src/test/testhelpers.tsx"],"version":"5.9.3"}
{"root":["./src/app.tsx","./src/envproduction.test.ts","./src/i18n.test.ts","./src/i18n.ts","./src/main.tsx","./src/maincss.test.ts","./src/routes.tsx","./src/setuptests.ts","./src/vite-env.d.ts","./src/viteconfig.test.ts","./src/components/clientauthgate.tsx","./src/components/clientlayout.test.tsx","./src/components/clientlayout.tsx","./src/lib/rum.ts","./src/lib/supabase.ts","./src/pages/authcallback.tsx","./src/pages/clienttablolistpage.tsx","./src/pages/clienttablopage.test.tsx","./src/pages/clienttablopage.tsx","./src/pages/loginpage.test.tsx","./src/pages/loginpage.tsx","./src/pages/resetpasswordpage.test.tsx","./src/pages/resetpasswordpage.tsx","./src/pages/setpasswordpage.test.tsx","./src/pages/setpasswordpage.tsx","./src/test/testhelpers.test.tsx","./src/test/testhelpers.tsx"],"version":"5.9.3"}

View file

@ -1,11 +1,55 @@
import { fireEvent, screen } from "@testing-library/react";
import { Layout } from "@ui/components/Layout";
import { beforeEach } from "vitest";
import { beforeEach, vi } from "vitest";
import { renderWithProviders } from "../utils/testHelpers";
vi.mock("../hooks/organization", async (importOriginal) => {
const actual = await importOriginal<typeof import("../hooks/organization")>();
return {
...actual,
useOrganization: vi.fn(),
};
});
vi.mock("./OnboardingModal", () => ({
OnboardingModal: ({ open }: { open: boolean }) => (
<div data-testid="onboarding-modal">{open ? "open" : "closed"}</div>
),
}));
import { useOrganization } from "../hooks/organization";
const mockUseOrganization = vi.mocked(useOrganization);
const baseOrganizationData = {
organization: {
id: 1,
name: "Org",
plan: "none",
member_count: 1,
tablo_count: 0,
logo_url: null,
},
members: [],
invites_sent: [],
trial_starts_at: "2026-01-01",
trial_ends_at: "2026-02-01",
is_trial_expired: false,
required_plan: "solo" as const,
required_team_quantity: 1,
active_subscription_plan: null,
active_subscription_quantity: 0,
is_billing_owner: true,
};
describe("Layout", () => {
beforeEach(() => {
localStorage.setItem("xtablo-onboarding-completed", "true");
mockUseOrganization.mockReturnValue({
data: baseOrganizationData,
isLoading: false,
error: null,
} as ReturnType<typeof useOrganization>);
});
it("renders the layout with children", () => {
@ -80,4 +124,28 @@ describe("Layout", () => {
// Should have transition and mobile/desktop behavior
expect(navParent).toHaveClass("fixed", "md:relative", "transition-transform");
});
it("keeps onboarding closed when there is no paid plan", () => {
localStorage.removeItem("xtablo-onboarding-completed");
renderWithProviders(<Layout />);
expect(screen.getByTestId("onboarding-modal")).toHaveTextContent("closed");
});
it("opens onboarding when there is an active paid plan and onboarding is incomplete", () => {
localStorage.removeItem("xtablo-onboarding-completed");
mockUseOrganization.mockReturnValue({
data: {
...baseOrganizationData,
active_subscription_plan: "solo",
},
isLoading: false,
error: null,
} as ReturnType<typeof useOrganization>);
renderWithProviders(<Layout />);
expect(screen.getByTestId("onboarding-modal")).toHaveTextContent("open");
});
});

View file

@ -3,6 +3,7 @@ import { MenuIcon, XIcon } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { Outlet, useLocation } from "react-router-dom";
import { twMerge } from "tailwind-merge";
import { useOrganization } from "../hooks/organization";
import { SideNavigation } from "./NavigationBar";
import { OnboardingModal } from "./OnboardingModal";
import { TopBar } from "./TopBar";
@ -13,14 +14,19 @@ export function Layout() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [showOnboarding, setShowOnboarding] = useState(false);
const location = useLocation();
const { data: organizationData } = useOrganization();
useEffect(() => {
// Check if user has completed onboarding
const hasCompletedOnboarding = localStorage.getItem(ONBOARDING_STORAGE_KEY);
if (!hasCompletedOnboarding) {
const hasPaidPlan = Boolean(organizationData?.active_subscription_plan);
if (!hasCompletedOnboarding && hasPaidPlan) {
setShowOnboarding(true);
return;
}
}, []);
setShowOnboarding(false);
}, [organizationData?.active_subscription_plan]);
// Close mobile menu on route change
useEffect(() => {