From f6bf223100b550db25ccbc727941f8d010b09970 Mon Sep 17 00:00:00 2001 From: IsmailofficialGithub Date: Fri, 21 Aug 2026 18:53:24 +0500 Subject: [PATCH] fix(web): show toast and email mismatch UI for workspace invites Surface API errors when accepting an invite, guide users signed in with the wrong email, and prevent double-submit. Fixes #9660 --- .../app/(all)/workspace-invitations/page.tsx | 121 ++++++++++++++---- 1 file changed, 95 insertions(+), 26 deletions(-) diff --git a/apps/web/app/(all)/workspace-invitations/page.tsx b/apps/web/app/(all)/workspace-invitations/page.tsx index d6267d1888c..cfdfac5893d 100644 --- a/apps/web/app/(all)/workspace-invitations/page.tsx +++ b/apps/web/app/(all)/workspace-invitations/page.tsx @@ -4,11 +4,13 @@ * See the LICENSE file for details. */ +import { useState } from "react"; import { observer } from "mobx-react"; import { useSearchParams } from "next/navigation"; import useSWR from "swr"; -import { Boxes, Share2, Star, User2 } from "lucide-react"; +import { Boxes, LogOut, Share2, Star, User2 } from "lucide-react"; import { CheckIcon, CloseIcon } from "@plane/propel/icons"; +import { TOAST_TYPE, setToast } from "@plane/propel/toast"; // components import { LogoSpinner } from "@/components/common/logo-spinner"; import { EmptySpace, EmptySpaceItem } from "@/components/ui/empty-space"; @@ -27,7 +29,16 @@ import { WorkspaceService } from "@/services/workspace.service"; // service initialization const workspaceService = new WorkspaceService(); +const getJoinErrorMessage = (err: unknown) => { + if (err && typeof err === "object" && "error" in err && typeof (err as { error?: unknown }).error === "string") { + return (err as { error: string }).error; + } + return "Something went wrong. Please try again."; +}; + function WorkspaceInvitationPage() { + // states + const [isSubmitting, setIsSubmitting] = useState(false); // router const router = useAppRouter(); // query params @@ -36,7 +47,7 @@ function WorkspaceInvitationPage() { const slug = searchParams.get("slug"); const token = searchParams.get("token"); // store hooks - const { data: currentUser } = useUser(); + const { data: currentUser, signOut } = useUser(); const { data: invitationDetail, error } = useSWR( invitation_id && slug && WORKSPACE_INVITATION(invitation_id.toString()), @@ -45,34 +56,80 @@ function WorkspaceInvitationPage() { : null ); - const handleAccept = () => { - if (!invitationDetail) return; - workspaceService - .joinWorkspace(invitationDetail.workspace.slug, invitationDetail.id, { + const invitationEmail = invitationDetail?.email?.toLowerCase(); + const currentEmail = currentUser?.email?.toLowerCase(); + const isEmailMismatch = Boolean(invitationEmail && currentEmail && invitationEmail !== currentEmail); + + const handleAccept = async () => { + if (!invitationDetail || isSubmitting) return; + if (isEmailMismatch) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Wrong account", + message: `This invitation was sent to ${invitationDetail.email}. Sign in with that email to accept.`, + }); + return; + } + + setIsSubmitting(true); + try { + await workspaceService.joinWorkspace(invitationDetail.workspace.slug, invitationDetail.id, { accepted: true, token: token, - }) - .then(() => { - if (invitationDetail.email === currentUser?.email) { - router.push(`/${invitationDetail.workspace.slug}`); - } else { - router.push("/"); - } - }) - .catch((err: unknown) => console.error(err)); + }); + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Invitation accepted", + message: `You joined ${invitationDetail.workspace.name}.`, + }); + router.push(`/${invitationDetail.workspace.slug}`); + } catch (err: unknown) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Could not accept invitation", + message: getJoinErrorMessage(err), + }); + } finally { + setIsSubmitting(false); + } }; - const handleReject = () => { - if (!invitationDetail || !token) return; - void workspaceService - .joinWorkspace(invitationDetail.workspace.slug, invitationDetail.id, { + const handleReject = async () => { + if (!invitationDetail || !token || isSubmitting) return; + setIsSubmitting(true); + try { + await workspaceService.joinWorkspace(invitationDetail.workspace.slug, invitationDetail.id, { accepted: false, token: token, - }) - .then(() => { - router.push("/"); - }) - .catch((err: unknown) => console.error(err)); + }); + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Invitation declined", + message: "You declined this workspace invitation.", + }); + router.push("/"); + } catch (err: unknown) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Could not decline invitation", + message: getJoinErrorMessage(err), + }); + } finally { + setIsSubmitting(false); + } + }; + + const handleSwitchAccount = async () => { + try { + await signOut(); + router.push(`/?next_path=${encodeURIComponent(window.location.pathname + window.location.search)}`); + } catch { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error", + message: "Could not sign out. Please try again.", + }); + } }; return ( @@ -83,13 +140,25 @@ function WorkspaceInvitationPage() {

INVITATION NOT FOUND

+ ) : isEmailMismatch ? ( + + + + ) : ( - - + + ) ) : error || invitationDetail?.responded_at ? (