From fed2a64d3fbaae7f73c03f96d25e8cfbfb5be36f Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Fri, 13 Mar 2026 03:58:28 +0100 Subject: [PATCH] fix: JoinPage graceful already-member handling + "I use another instance" for auth'd users - Add spaceId to InvitePreview so the client can navigate to the space - Detect "already a member" error and show green success card with auto-redirect - Add "I use another instance" link for authenticated users alongside "Not you? Log in" --- packages/server/src/routes/spaces.ts | 1 + packages/shared/src/types.ts | 1 + packages/web/src/components/JoinPage.tsx | 64 ++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/packages/server/src/routes/spaces.ts b/packages/server/src/routes/spaces.ts index 5ad31e77..f9be826f 100644 --- a/packages/server/src/routes/spaces.ts +++ b/packages/server/src/routes/spaces.ts @@ -1103,6 +1103,7 @@ export async function spaceRoutes(app: FastifyInstance): Promise { const instanceName = settings?.instanceName ?? 'Backspace'; return reply.code(200).send({ + spaceId: space.id, spaceName: space.name, description: space.description ?? null, icon: space.icon ?? null, diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 64435542..2205fa0b 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -53,6 +53,7 @@ export interface Space { } export interface InvitePreview { + spaceId: string; spaceName: string; description: string | null; icon: string | null; diff --git a/packages/web/src/components/JoinPage.tsx b/packages/web/src/components/JoinPage.tsx index b8cd763a..a7cbc40f 100644 --- a/packages/web/src/components/JoinPage.tsx +++ b/packages/web/src/components/JoinPage.tsx @@ -8,7 +8,7 @@ import { parseInviteInput } from '../utils/inviteParser'; import { Avatar } from './ui/Avatar'; import type { InvitePreview } from '@backspace/shared'; -type JoinPhase = 'preview' | 'connect' | 'fallback' | 'other-instance'; +type JoinPhase = 'preview' | 'connect' | 'fallback' | 'other-instance' | 'already-member'; export function JoinPage() { const { inviteCode: rawInviteCode } = useParams<{ inviteCode: string }>(); @@ -106,7 +106,13 @@ export function JoinPage() { setPhase('connect'); setError(''); } else { - setError(err instanceof Error ? err.message : 'Failed to join space'); + const msg = err instanceof Error ? err.message : 'Failed to join space'; + if (msg.toLowerCase().includes('already a member')) { + setPhase('already-member'); + setError(''); + } else { + setError(msg); + } } } finally { setIsJoining(false); @@ -130,7 +136,13 @@ export function JoinPage() { setFallbackPassword(''); setError(''); } else { - setError(err instanceof Error ? err.message : 'Failed to connect'); + const msg = err instanceof Error ? err.message : 'Failed to connect'; + if (msg.toLowerCase().includes('already a member')) { + setPhase('already-member'); + setError(''); + } else { + setError(msg); + } } } finally { setIsJoining(false); @@ -148,7 +160,13 @@ export function JoinPage() { const space = await joinByCode(parsed.code, parsed.origin); navigate(`/channels/${space.id}`); } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to log in'); + const msg = err instanceof Error ? err.message : 'Failed to log in'; + if (msg.toLowerCase().includes('already a member')) { + setPhase('already-member'); + setError(''); + } else { + setError(msg); + } } finally { setIsJoining(false); } @@ -295,6 +313,14 @@ export function JoinPage() { > Log in + {' · '} +

) : token ? ( @@ -431,6 +457,11 @@ export function JoinPage() { )} + {/* Phase: already-member — green success with auto-redirect */} + {phase === 'already-member' && preview && ( + + )} + {/* Phase: fallback — different password on remote */} {phase === 'fallback' && (
@@ -484,3 +515,28 @@ export function JoinPage() { ); } + +function AlreadyMemberCard({ spaceName, spaceId, navigate }: { spaceName: string; spaceId: string; navigate: (path: string) => void }) { + useEffect(() => { + const timer = setTimeout(() => navigate(`/channels/${spaceId}`), 2000); + return () => clearTimeout(timer); + }, [spaceId, navigate]); + + return ( +
+
+ + + +
+

You're already in {spaceName}!

+

Redirecting you now...

+ +
+ ); +}