feat: public invite landing page with federation redirect

Make /join/:code a public route with a standalone JoinPage that shows a
space preview and handles authenticated, unauthenticated, and cross-instance
users. Adds GET /api/spaces/invite/:code/preview (no auth) endpoint,
?redirect= param support on login/register, and cleans up dead invite
handling from AppLayout and JoinSpace modal.
This commit is contained in:
Jannis Braun
2026-03-13 03:04:52 +01:00
parent 07ef49eac0
commit c3617de832
9 changed files with 484 additions and 29 deletions
@@ -34,7 +34,7 @@ import { useVoiceStore } from '../../stores/voiceStore';
import { AudioManager } from '../../audio/AudioManager';
export function AppLayout() {
const { spaceId, channelId, inviteCode } = useParams<{ spaceId?: string; channelId?: string; inviteCode?: string }>();
const { spaceId, channelId } = useParams<{ spaceId?: string; channelId?: string }>();
const navigate = useNavigate();
// Global interaction handler to resume AudioContext
@@ -91,7 +91,6 @@ export function AppLayout() {
const loadMessages = useChatStore((s) => s.loadMessages);
const setIsMobile = useUIStore((s) => s.setIsMobile);
const setShowDms = useUIStore((s) => s.setShowDms);
const openModal = useUIStore((s) => s.openModal);
const sidebarOpen = useUIStore((s) => s.sidebarOpen);
const isMobile = useUIStore((s) => s.isMobile);
@@ -200,12 +199,6 @@ export function AppLayout() {
}
}, [spaceId, setCurrentSpace, loadSpaceDetail, setShowDms]);
useEffect(() => {
if (inviteCode) {
openModal('joinSpace');
}
}, [inviteCode, openModal]);
useEffect(() => {
if (channelId) {
setCurrentChannel(channelId);