From b7098627419e6cc2f8eb4ac54aed136ed3d927fc Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:45:50 +0100 Subject: [PATCH] fix: federated leaveSpace 403 and context menu for owner spaces Use getMyUserIdForOrigin() in leaveSpace() so federated spaces send the correct remote user ID instead of the home ID. Show context menu for all spaces with "Invite People" action; "Leave Space" only for non-owners. --- .../src/components/layout/SpaceSidebar.tsx | 56 ++++++++++++++----- packages/web/src/stores/spaceStore.ts | 2 +- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/packages/web/src/components/layout/SpaceSidebar.tsx b/packages/web/src/components/layout/SpaceSidebar.tsx index abc5b9ed..31357841 100644 --- a/packages/web/src/components/layout/SpaceSidebar.tsx +++ b/packages/web/src/components/layout/SpaceSidebar.tsx @@ -169,9 +169,11 @@ function SpaceContextMenu({ spaceId, x, y, onClose }: { spaceId: string; x: numb const space = useSpaceStore((s) => s.spaces.find(sp => sp.id === spaceId)); const currentUserId = useAuthStore((s) => s.user?.id); const leaveSpace = useSpaceStore((s) => s.leaveSpace); + const generateInvite = useSpaceStore((s) => s.generateInvite); const currentSpaceId = useSpaceStore((s) => s.currentSpaceId); const setCurrentSpace = useSpaceStore((s) => s.setCurrentSpace); const setShowDms = useUIStore((s) => s.setShowDms); + const addToast = useUIStore((s) => s.addToast); const navigate = useNavigate(); const isOwner = space?.ownerId === currentUserId; @@ -198,12 +200,25 @@ function SpaceContextMenu({ spaceId, x, y, onClose }: { spaceId: string; x: numb }; }, [onClose]); - // Don't render for owners (no menu items) - if (isOwner || !space) return null; + if (!space) return null; + + const handleInvite = async () => { + try { + const code = await generateInvite(spaceId); + const origin = (space as any)._instanceOrigin || window.location.origin; + const url = `${origin}/invite/${code}`; + await navigator.clipboard.writeText(url); + addToast('Invite link copied to clipboard', 'success', 3000); + } catch { + addToast('Failed to generate invite', 'warning', 3000); + } + onClose(); + }; // Viewport-aware clamping const menuWidth = 180; - const menuHeight = 40; + const itemCount = isOwner ? 1 : 2; + const menuHeight = itemCount * 32 + 8; const clampedX = Math.min(x, window.innerWidth - menuWidth - 8); const clampedY = Math.min(y, window.innerHeight - menuHeight - 8); @@ -214,22 +229,33 @@ function SpaceContextMenu({ spaceId, x, y, onClose }: { spaceId: string; x: numb style={{ left: clampedX, top: clampedY }} > + {!isOwner && ( + + )} , document.body, ); diff --git a/packages/web/src/stores/spaceStore.ts b/packages/web/src/stores/spaceStore.ts index f71b55f0..0caee697 100644 --- a/packages/web/src/stores/spaceStore.ts +++ b/packages/web/src/stores/spaceStore.ts @@ -239,7 +239,7 @@ export const useSpaceStore = create((set, get) => ({ const space = get().spaces.find(s => s.id === spaceId); const origin = (space as TaggedSpace)?._instanceOrigin ?? ''; const targetApi = getApiForOrigin(origin); - const userId = useAuthStore.getState().user?.id; + const userId = getMyUserIdForOrigin(origin); if (!userId) return; await targetApi.spaces.removeMember(spaceId, userId); set((state) => ({