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
+4
View File
@@ -38,6 +38,7 @@ import type {
Role,
SpaceLayoutItem,
SpaceFolder,
InvitePreview,
} from '@backspace/shared';
export class RateLimitError extends Error {
@@ -86,6 +87,7 @@ export class BackspaceApiClient {
ban: (spaceId: string, userId: string, reason?: string) => Promise<{ success: boolean }>;
unban: (spaceId: string, userId: string) => Promise<{ success: boolean }>;
transferOwnership: (spaceId: string, newOwnerId: string) => Promise<Space>;
invitePreview: (code: string) => Promise<InvitePreview>;
};
readonly channels: {
@@ -303,6 +305,8 @@ export class BackspaceApiClient {
request<{ success: boolean }>('DELETE', `/spaces/${spaceId}/bans/${userId}`),
transferOwnership: (spaceId: string, newOwnerId: string) =>
request<Space>('PATCH', `/spaces/${spaceId}/transfer-ownership`, { newOwnerId }),
invitePreview: (code: string) =>
request<InvitePreview>('GET', `/spaces/invite/${encodeURIComponent(code)}/preview`, undefined, false),
};
this.channels = {