From c1f6698ec5407a61b0d2e0a2ebb82f8949bc2809 Mon Sep 17 00:00:00 2001 From: le-czs Date: Wed, 22 Jul 2026 01:22:17 +0800 Subject: [PATCH] fix(ui/invite): make pending-approval admin guidance non-clickable (#6786) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies, and humans onboard into companies via invite links. > - Some invite types (`company_join` with `requires_company_admin_approval`) require an admin to approve the join request after the invitee submits it. > - While the requester waits, the invitee sees the `AwaitingJoinApprovalPanel` in `InviteLanding`, which describes where the admin needs to go to approve the request. > - That panel rendered the destination — "Company Settings → Access" — as two clickable `` links, even though the surrounding copy is plainly addressed to the admin ("Ask **them** to visit ..."), not the requester. > - First-time invitees naturally click the only underlined link on the screen, are sent to `/company/settings/access`, hit the "No company access" panel (they have no membership yet), and conclude the invite flow is broken. > - This PR removes the navigation by rendering both "Company Settings → Access" references as plain styled text (`

` / ``), so the guidance stays visible but cannot be followed by the requester. > - The benefit is that the post-submit invite experience matches the copy's intent — guidance for the admin, not navigation for the requester. Fixes #6784. ## What Changed - `ui/src/pages/InviteLanding.tsx` — In `AwaitingJoinApprovalPanel`, replace the two `Company Settings → Access` elements with `

` and `` containing the same text. Remove the now-unused `approvalUrl` constant. - `ui/src/pages/InviteLanding.test.tsx` — Update the existing "pending approval page" test: it previously asserted two anchor tags pointing at `/company/settings/access`; it now asserts **zero** anchors while the text "Company Settings → Access" still appears twice (in the "Approval page" box and inline in the "Ask them to visit ..." sentence). Renamed the test description from "...linked access instructions" to "...non-clickable access instructions" to reflect the contract. ## Verification ``` pnpm vitest run ui/src/pages/InviteLanding.test.tsx ``` Result: 8 / 8 pass, including the updated "shows the pending approval page with the company icon and non-clickable access instructions" case. Manual reproduction (master @ `242a2c2f`, `deploymentMode=authenticated`, `bind=lan`, embedded Postgres): 1. As instance admin, generate a `company_join` invite that requires admin approval. 2. In a fresh browser profile, open the invite link. 3. Fill **Create your account** and submit. 4. The "Request to join \" panel appears. 5. Hover the "Company Settings → Access" mentions — no underline, no link cursor; clicking does nothing. The text is still readable and the surrounding copy ("Ask them to visit ...") still conveys the instruction to the requester. Before / after screenshots: see issue #6784 — the "before" state lands users on `/company/settings/access` which renders "No company access". After this PR the guidance is informational only. ## Risks Low. UI-only change confined to one function in `InviteLanding.tsx` plus its matching test. No API contracts, routes, or data shapes are modified. The removed `approvalUrl` constant was only referenced by the two anchor elements. ## Model Used - Anthropic Claude Opus 4.7 (`claude-opus-4-7`, 1M context, extended thinking enabled). - Tools: file editing, Bash, Playwright reproduction against a self-hosted Paperclip instance running master @ `242a2c2f`, and the Paperclip monorepo's own Vitest suite for verifying the test update. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots (will attach in PR thread) - [x] I have updated relevant documentation to reflect my changes (no docs files needed updating) - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --- ui/src/pages/InviteLanding.test.tsx | 18 +++++++++++------- ui/src/pages/InviteLanding.tsx | 10 ++-------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/ui/src/pages/InviteLanding.test.tsx b/ui/src/pages/InviteLanding.test.tsx index c26174de7e..8dd88e1723 100644 --- a/ui/src/pages/InviteLanding.test.tsx +++ b/ui/src/pages/InviteLanding.test.tsx @@ -506,7 +506,7 @@ describe("InviteLandingPage", () => { }); }); - it("shows the pending approval page with the company icon and linked access instructions", async () => { + it("shows the pending approval page with the company icon and non-clickable access instructions", async () => { acceptInviteMock.mockResolvedValue({ id: "join-1", companyId: "company-1", @@ -553,14 +553,18 @@ describe("InviteLandingPage", () => { expect(container.querySelector('img[alt="Acme Robotics logo"]')).not.toBeNull(); expect(container.textContent).not.toContain("http://localhost/company/settings/members"); - const approvalLinks = Array.from(container.querySelectorAll("a")).filter( + // The "Company Settings → Members" guidance addresses the company admin, + // not the requester. It must render as plain text so the requester cannot + // navigate themselves to /company/settings/members — a route they have no + // permission to view, which renders a misleading "No company access" + // panel and makes the invite flow look broken. See #6784. + const approvalAnchors = Array.from(container.querySelectorAll("a")).filter( (link) => link.textContent === "Company Settings → Members", ); - expect(approvalLinks).toHaveLength(2); - const expectedApprovalUrl = `${window.location.origin}/company/settings/members`; - for (const link of approvalLinks) { - expect(link.getAttribute("href")).toBe(expectedApprovalUrl); - } + expect(approvalAnchors).toHaveLength(0); + const approvalMentions = + container.textContent?.match(/Company Settings → Members/g) ?? []; + expect(approvalMentions).toHaveLength(2); await act(async () => { root.unmount(); diff --git a/ui/src/pages/InviteLanding.tsx b/ui/src/pages/InviteLanding.tsx index b540fc7528..f732085361 100644 --- a/ui/src/pages/InviteLanding.tsx +++ b/ui/src/pages/InviteLanding.tsx @@ -160,7 +160,6 @@ function AwaitingJoinApprovalPanel({ claimApiKeyPath = null, onboardingTextUrl = null, }: AwaitingJoinApprovalPanelProps) { - const approvalUrl = `${window.location.origin}/company/settings/members`; const approverLabel = invitedByUserName ?? "A company admin"; return ( @@ -181,15 +180,10 @@ function AwaitingJoinApprovalPanel({

Approval page

- - Company Settings → Members - +

Company Settings → Members

- Ask them to visit Company Settings → Members to approve your request. + Ask them to visit Company Settings → Members to approve your request.

Refresh this page after you've been approved — you'll be redirected automatically.