From f22a0332ab04c74b83dab3c1e07b086d26dade04 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 26 Sep 2025 21:38:05 +0100 Subject: [PATCH] feat: switch to workspace on invite success --- apps/web/src/providers/workspace.tsx | 13 ++- apps/web/src/views/invite/index.tsx | 147 ++++++++++++++++----------- 2 files changed, 95 insertions(+), 65 deletions(-) diff --git a/apps/web/src/providers/workspace.tsx b/apps/web/src/providers/workspace.tsx index b5be9686..686e139f 100644 --- a/apps/web/src/providers/workspace.tsx +++ b/apps/web/src/providers/workspace.tsx @@ -1,5 +1,5 @@ import type { ReactNode } from "react"; -import { useRouter } from "next/navigation"; +import { useRouter, useSearchParams } from "next/navigation"; import React, { createContext, useContext, useEffect, useState } from "react"; import { api } from "~/utils/api"; @@ -46,6 +46,8 @@ export const WorkspaceProvider: React.FC<{ children: ReactNode }> = ({ ); const [hasLoaded, setHasLoaded] = useState(false); + const workspacePublicId = useSearchParams().get("workspacePublicId"); + const { data, isLoading } = api.workspace.all.useQuery(); const utils = api.useUtils(); @@ -67,7 +69,7 @@ export const WorkspaceProvider: React.FC<{ children: ReactNode }> = ({ } const storedWorkspaceId: string | null = - localStorage.getItem("workspacePublicId"); + workspacePublicId ?? localStorage.getItem("workspacePublicId"); if (data.length) { const workspaces = data.map(({ workspace, role }) => ({ @@ -99,6 +101,11 @@ export const WorkspaceProvider: React.FC<{ children: ReactNode }> = ({ description: selectedWorkspace.workspace.description, role: selectedWorkspace.role, }); + + if (workspacePublicId) { + router.push(`/boards`); + localStorage.setItem("workspacePublicId", workspacePublicId); + } } else { const primaryWorkspace = data[0]?.workspace; const primaryWorkspaceRole = data[0]?.role; @@ -114,7 +121,7 @@ export const WorkspaceProvider: React.FC<{ children: ReactNode }> = ({ role: primaryWorkspaceRole, }); } - }, [data, isLoading]); + }, [data, isLoading, workspacePublicId, router]); return ( (null); - const [invitationInfo, setInvitationInfo] = useState(null); const { data: session, isPending: isSessionLoading } = authClient.useSession(); - const { switchWorkspace } = useWorkspace(); + + const isCloudEnv = env("NEXT_PUBLIC_KAN_ENV") === "cloud"; + + const inviteCode = Array.isArray(code) ? code[0] : code; const acceptInviteMutation = api.member.acceptInviteLink.useMutation({ onSuccess: (result) => { if (result.success) { - router.push(`/boards`); + return router.push( + `/boards?workspacePublicId=${result.workspacePublicId}`, + ); } }, onError: (error) => { - setError(error.message || t`Failed to accept invitation`); + if (error.data?.code === "CONFLICT") { + return router.push(`/boards`); + } + + setError( + error.message || + t`Failed to accept invitation. Please try again later, or contact customer support.`, + ); setIsProcessing(false); }, }); @@ -36,40 +48,32 @@ export default function InvitePage() { data: inviteInfo, isLoading: isInviteInfoLoading, isError: isInviteInfoError, - error: inviteInfoError, - } = api.member.getInviteByCode.useQuery({ inviteCode: code ?? "" }); - - // Set invite info when loaded - useEffect(() => { - if (inviteInfo) { - setInvitationInfo(inviteInfo); - } else if (isInviteInfoError) { - setError(t`Invalid or expired invitation link`); - } - }, [inviteInfo, isInviteInfoError]); + } = api.member.getInviteByCode.useQuery( + { inviteCode: inviteCode ?? "" }, + { + enabled: !!inviteCode, + retry: false, + }, + ); // Auto accept invite if user is logged in useEffect(() => { - if (session?.user.id && code && typeof code === "string" && inviteInfo) { - handleAcceptInvite(); + if (session?.user.id && inviteCode && inviteInfo && !error) { + setIsProcessing(true); + setError(null); + + acceptInviteMutation.mutate({ + inviteCode, + }); } - }, [session, code, inviteInfo]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [session?.user.id, inviteCode, inviteInfo, error]); - const handleAcceptInvite = async () => { - if (!code || typeof code !== "string" || !session?.user.id) { - return; - } - - setIsProcessing(true); - setError(null); - - acceptInviteMutation.mutate({ - inviteCode: code, - userId: session.user.id, - }); - }; - - if (session?.user.id || isInviteInfoLoading || isSessionLoading) { + if ( + !isInviteInfoError && + !error && + (session?.user.id || isInviteInfoLoading || isSessionLoading) + ) { return ( <> @@ -81,13 +85,21 @@ export default function InvitePage() { ); } - if (isInviteInfoError || !inviteInfo) { + const PageWrapper = ({ children }: { children: React.ReactNode }) => { return ( <> - + + {children} + + ); + }; + + if (isInviteInfoError || (!isInviteInfoLoading && !inviteInfo)) { + return ( +
-
+

{t`Invalid invitation`} @@ -103,46 +115,57 @@ export default function InvitePage() {

- + ); } return ( - <> - - +
-
+

{t`Join workspace`}

-

- {t`You've been invited to join ${invitationInfo?.workspaceName || "a workspace"} on kan.bn`} -

+ {!error ? ( +

+ {isCloudEnv + ? t`You've been invited to join a workspace on kan.bn.` + : t`You've been invited to join a workspace.`} +

+ ) : ( +

{error}

+ )}
-
- - + {session?.user.id ? ( + + ) : ( + <> + + + + )}
- + ); }