revert(ui): keep the onboarding link modal on antd

The invitation dialog opens over the still-antd Invite User modal. Lifting
only the shadcn dialog content above antd's mask leaves its own backdrop
underneath, so an outside click reaches the wrong modal. Adding a second
backdrop stops that but does not restore dismissal, and the same hazard
already ships in three guardrails modals, so the stacking needs one shared
fix rather than a fourth local workaround.
This commit is contained in:
Yuneng Jiang 2026-08-14 07:10:13 -07:00
parent 15a331f6df
commit 4544f7dbad
No known key found for this signature in database
3 changed files with 42 additions and 117 deletions

View file

@ -2848,6 +2848,9 @@
"src/components/onboarding_link.tsx": {
"local/filename-pascal-case": {
"count": 1
},
"no-restricted-imports": {
"count": 2
}
},
"src/components/organisms/RegenerateKeyModal.tsx": {

View file

@ -1,22 +1,5 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import OnboardingModal, { buildOnboardingUrl, InvitationLink } from "./onboarding_link";
vi.mock("./molecules/notifications_manager", () => ({ default: { success: vi.fn() } }));
const invitation: InvitationLink = {
id: "inv-123",
user_id: "user-abc",
is_accepted: false,
accepted_at: null,
expires_at: new Date("2026-09-01"),
created_at: new Date("2026-08-01"),
created_by: "admin",
updated_at: new Date("2026-08-01"),
updated_by: "admin",
has_user_setup_sso: false,
};
import { describe, it, expect } from "vitest";
import { buildOnboardingUrl } from "./onboarding_link";
describe("buildOnboardingUrl", () => {
it("points the invitation link at the dedicated /ui/onboarding route", () => {
@ -85,73 +68,3 @@ describe("buildOnboardingUrl", () => {
).toBe("");
});
});
describe("OnboardingModal", () => {
it("renders nothing until it is opened", () => {
render(
<OnboardingModal
isInvitationLinkModalVisible={false}
setIsInvitationLinkModalVisible={vi.fn()}
baseUrl="http://localhost:4000/"
invitationLinkData={invitation}
/>,
);
expect(screen.queryByText("http://localhost:4000/ui/onboarding?invitation_id=inv-123")).not.toBeInTheDocument();
});
it("shows the invitation url, the user id and an invitation-flavoured copy button", async () => {
render(
<OnboardingModal
isInvitationLinkModalVisible
setIsInvitationLinkModalVisible={vi.fn()}
baseUrl="http://localhost:4000/"
invitationLinkData={invitation}
/>,
);
expect(await screen.findByText("http://localhost:4000/ui/onboarding?invitation_id=inv-123")).toBeInTheDocument();
expect(screen.getByText("user-abc")).toBeInTheDocument();
expect(screen.getAllByText("Invitation Link").length).toBeGreaterThan(0);
expect(screen.getByRole("button", { name: "Copy invitation link" })).toBeInTheDocument();
expect(screen.getByText(/Copy and send the generated link to onboard this user/)).toBeInTheDocument();
});
it("switches every label and the url to the reset-password flow", async () => {
render(
<OnboardingModal
isInvitationLinkModalVisible
setIsInvitationLinkModalVisible={vi.fn()}
baseUrl="http://localhost:4000/"
invitationLinkData={invitation}
modalType="resetPassword"
/>,
);
expect(
await screen.findByText("http://localhost:4000/ui/onboarding?invitation_id=inv-123&action=reset_password"),
).toBeInTheDocument();
expect(screen.getAllByText("Reset Password Link").length).toBeGreaterThan(0);
expect(screen.getByRole("button", { name: "Copy password reset link" })).toBeInTheDocument();
expect(
screen.getByText(/Copy and send the generated link to the user to reset their password/),
).toBeInTheDocument();
});
it("closes through setIsInvitationLinkModalVisible when the close control is used", async () => {
const user = userEvent.setup();
const setVisible = vi.fn();
render(
<OnboardingModal
isInvitationLinkModalVisible
setIsInvitationLinkModalVisible={setVisible}
baseUrl="http://localhost:4000/"
invitationLinkData={invitation}
/>,
);
await user.click(await screen.findByRole("button", { name: /close/i }));
expect(setVisible).toHaveBeenCalledWith(false);
});
});

View file

@ -1,7 +1,7 @@
import React from "react";
import { Button, Modal, Typography } from "antd";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Text } from "@tremor/react";
import NotificationsManager from "./molecules/notifications_manager";
export interface InvitationLink {
@ -58,7 +58,10 @@ export default function OnboardingModal({
invitationLinkData,
modalType = "invitation",
}: OnboardingProps) {
const linkLabel = modalType === "invitation" ? "Invitation Link" : "Reset Password Link";
const { Paragraph } = Typography;
const handleInvitationOk = () => {
setIsInvitationLinkModalVisible(false);
};
const handleInvitationCancel = () => {
setIsInvitationLinkModalVisible(false);
@ -73,30 +76,36 @@ export default function OnboardingModal({
});
return (
<Dialog open={isInvitationLinkModalVisible} onOpenChange={(open) => !open && handleInvitationCancel()}>
<DialogContent className="z-[1100] max-h-[calc(100dvh-2rem)] overflow-y-auto sm:max-w-[800px]">
<DialogHeader>
<DialogTitle>{linkLabel}</DialogTitle>
</DialogHeader>
<p>
{modalType === "invitation"
? "Copy and send the generated link to onboard this user to the proxy."
: "Copy and send the generated link to the user to reset their password."}
</p>
<div className="flex justify-between pt-5 pb-2">
<span className="text-base">User ID</span>
<span className="min-w-0 break-words">{invitationLinkData?.user_id}</span>
</div>
<div className="flex justify-between pt-5 pb-2">
<span>{linkLabel}</span>
<span className="min-w-0 break-words">{getInvitationUrl()}</span>
</div>
<div className="flex justify-end mt-5">
<CopyToClipboard text={getInvitationUrl()} onCopy={() => NotificationsManager.success("Copied!")}>
<Button>{modalType === "invitation" ? "Copy invitation link" : "Copy password reset link"}</Button>
</CopyToClipboard>
</div>
</DialogContent>
</Dialog>
<Modal
title={modalType === "invitation" ? "Invitation Link" : "Reset Password Link"}
open={isInvitationLinkModalVisible}
width={800}
footer={null}
onOk={handleInvitationOk}
onCancel={handleInvitationCancel}
>
<Paragraph>
{modalType === "invitation"
? "Copy and send the generated link to onboard this user to the proxy."
: "Copy and send the generated link to the user to reset their password."}
</Paragraph>
<div className="flex justify-between pt-5 pb-2">
<Text className="text-base">User ID</Text>
<Text>{invitationLinkData?.user_id}</Text>
</div>
<div className="flex justify-between pt-5 pb-2">
<Text>{modalType === "invitation" ? "Invitation Link" : "Reset Password Link"}</Text>
<Text>
<Text>{getInvitationUrl()}</Text>
</Text>
</div>
<div className="flex justify-end mt-5">
<CopyToClipboard text={getInvitationUrl()} onCopy={() => NotificationsManager.success("Copied!")}>
<Button type="primary">
{modalType === "invitation" ? "Copy invitation link" : "Copy password reset link"}
</Button>
</CopyToClipboard>
</div>
</Modal>
);
}