feat: refactor space settings into panel components with roles management

Restructure SpaceSettings modal from 778-line monolith into thin
orchestrator with extracted panel components. Add full role CRUD with
permission editor (create, edit name/color/permissions, delete).

Backend: expose role permissions in GET response, accept permissions
in POST/PATCH role endpoints with BigInt validation, broadcast
pushReadyPayload to all space members on role mutations.
This commit is contained in:
Jannis Braun
2026-03-09 00:37:28 +01:00
parent c3bf635fc9
commit 31db1a7bd3
6 changed files with 954 additions and 281 deletions
+16
View File
@@ -30,6 +30,7 @@ import type {
VerifyPasswordResponse,
ExploreSpace,
JoinRequest,
Role,
} from '@backspace/shared';
export class BackspaceApiClient {
@@ -117,6 +118,12 @@ export class BackspaceApiClient {
info: () => Promise<InstanceInfoResponse>;
};
readonly roles: {
create: (spaceId: string, data: { name: string; color?: string; permissions?: string }) => Promise<Role>;
update: (spaceId: string, roleId: string, data: { name?: string; color?: string; position?: number; permissions?: string }) => Promise<Role>;
delete: (spaceId: string, roleId: string) => Promise<{ success: boolean }>;
};
readonly explore: {
list: (q?: string, limit?: number, offset?: number) => Promise<{ spaces: ExploreSpace[]; total: number; totalAll: number; discoveryEnabled: boolean }>;
publicJoin: (spaceId: string) => Promise<SpaceWithChannelsAndMembers>;
@@ -299,6 +306,15 @@ export class BackspaceApiClient {
info: () => request<InstanceInfoResponse>('GET', '/instance/info', undefined, false),
};
this.roles = {
create: (spaceId: string, data: { name: string; color?: string; permissions?: string }) =>
request<Role>('POST', `/spaces/${spaceId}/roles`, data),
update: (spaceId: string, roleId: string, data: { name?: string; color?: string; position?: number; permissions?: string }) =>
request<Role>('PATCH', `/spaces/${spaceId}/roles/${roleId}`, data),
delete: (spaceId: string, roleId: string) =>
request<{ success: boolean }>('DELETE', `/spaces/${spaceId}/roles/${roleId}`),
};
this.explore = {
list: (q?: string, limit = 50, offset = 0) => {
const params = new URLSearchParams();