Skip to content

Commit 805e084

Browse files
committed
fix(web): surface invitation errors instead of swallowing them
Both invitation responses ended in `.catch(console.error)`, so every rejection logged to the console and rendered nothing. A refused click looked exactly like a click that never registered — which is how a 403 presents to somebody clicking Accept. The common case is worth naming rather than reporting generically: the session belongs to somebody other than the invitee. Plane requires the two to match, because an invitation is not transferable — anyone holding the link could otherwise take the membership (GHSA-4vj8-p63v-8p24). The toast now names both addresses, which turns a dead end into an instruction. Anything else falls back to the server's own message. Also adds the missing `!token` guard to accept, matching reject: without a token the request could only ever come back 403. Claude-Session: https://claude.ai/code/session_01XvVRm84RrH9APR25g8pFXt
1 parent 7292904 commit 805e084

1 file changed

Lines changed: 36 additions & 14 deletions

File tree

  • apps/web/app/(all)/workspace-invitations

apps/web/app/(all)/workspace-invitations/page.tsx

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useSearchParams } from "next/navigation";
99
import useSWR from "swr";
1010
import { Boxes, Share2, Star, User2 } from "lucide-react";
1111
import { CheckIcon, CloseIcon } from "@plane/propel/icons";
12+
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
1213
// components
1314
import { LogoSpinner } from "@/components/common/logo-spinner";
1415
import { EmptySpace, EmptySpaceItem } from "@/components/ui/empty-space";
@@ -45,21 +46,44 @@ function WorkspaceInvitationPage() {
4546
: null
4647
);
4748

49+
/**
50+
* Both responses used to end in `.catch(console.error)`, which meant every
51+
* rejection — most often a 403 — logged to the console and rendered nothing.
52+
* A refused click was indistinguishable from a click that never registered.
53+
*/
54+
const handleInvitationError = (err: unknown) => {
55+
const serverMessage =
56+
typeof err === "object" && err !== null && "error" in err ? String((err as { error: unknown }).error) : undefined;
57+
58+
// By far the most common cause, and the one the server's generic wording
59+
// does not explain: the session belongs to somebody other than the invitee.
60+
// Plane requires the two to match — an invitation is not transferable, or
61+
// anyone holding the link could take the membership (GHSA-4vj8-p63v-8p24).
62+
// Naming both addresses turns a dead end into an instruction.
63+
const invitedEmail = invitationDetail?.email;
64+
const isWrongAccount =
65+
!!currentUser?.email && !!invitedEmail && currentUser.email.toLowerCase() !== invitedEmail.toLowerCase();
66+
67+
setToast({
68+
type: TOAST_TYPE.ERROR,
69+
title: isWrongAccount ? "Signed in as a different account" : "Could not respond to invitation",
70+
message: isWrongAccount
71+
? `This invitation is for ${invitedEmail}, but you are signed in as ${currentUser?.email}. Sign in as ${invitedEmail} to accept it.`
72+
: (serverMessage ?? "Something went wrong. Please try again."),
73+
});
74+
};
75+
4876
const handleAccept = () => {
49-
if (!invitationDetail) return;
50-
workspaceService
77+
if (!invitationDetail || !token) return;
78+
void workspaceService
5179
.joinWorkspace(invitationDetail.workspace.slug, invitationDetail.id, {
5280
accepted: true,
5381
token: token,
5482
})
55-
.then(() => {
56-
if (invitationDetail.email === currentUser?.email) {
57-
router.push(`/${invitationDetail.workspace.slug}`);
58-
} else {
59-
router.push("/");
60-
}
61-
})
62-
.catch((err: unknown) => console.error(err));
83+
.then(() =>
84+
router.push(invitationDetail.email === currentUser?.email ? `/${invitationDetail.workspace.slug}` : "/")
85+
)
86+
.catch(handleInvitationError);
6387
};
6488

6589
const handleReject = () => {
@@ -69,10 +93,8 @@ function WorkspaceInvitationPage() {
6993
accepted: false,
7094
token: token,
7195
})
72-
.then(() => {
73-
router.push("/");
74-
})
75-
.catch((err: unknown) => console.error(err));
96+
.then(() => router.push("/"))
97+
.catch(handleInvitationError);
7698
};
7799

78100
return (

0 commit comments

Comments
 (0)