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) => ({