fix: federated space icon display/upload in settings, restyle settings panels
Fix three federation bugs in Space Settings: icon URL was double-wrapped through home API, upload targeted home instance instead of remote, and updateSpace used home API client. Also restyle settings panels (User, Space, Members, Roles, Connected Instances) with consistent section headers, grouped cards, and improved layout.
This commit is contained in:
@@ -24,21 +24,39 @@ interface CachedInstanceToken {
|
||||
username: string;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'backspace_instances';
|
||||
const STORAGE_KEY_PREFIX = 'backspace_instances';
|
||||
const LEGACY_STORAGE_KEY = 'backspace_instances';
|
||||
|
||||
function storageKey(userId: string): string {
|
||||
return `${STORAGE_KEY_PREFIX}_${userId}`;
|
||||
}
|
||||
|
||||
// ─── localStorage helpers ────────────────────────────────────────────────────
|
||||
|
||||
function loadCachedTokens(): Record<string, CachedInstanceToken> {
|
||||
function loadCachedTokens(userId: string): Record<string, CachedInstanceToken> {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return {};
|
||||
return JSON.parse(raw) as Record<string, CachedInstanceToken>;
|
||||
const scopedKey = storageKey(userId);
|
||||
const raw = localStorage.getItem(scopedKey);
|
||||
if (raw) {
|
||||
return JSON.parse(raw) as Record<string, CachedInstanceToken>;
|
||||
}
|
||||
|
||||
// One-time migration: adopt legacy unscoped key if it exists
|
||||
const legacy = localStorage.getItem(LEGACY_STORAGE_KEY);
|
||||
if (legacy) {
|
||||
const parsed = JSON.parse(legacy) as Record<string, CachedInstanceToken>;
|
||||
localStorage.setItem(scopedKey, legacy);
|
||||
localStorage.removeItem(LEGACY_STORAGE_KEY);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
return {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function saveCachedTokens(instances: ConnectedInstance[]): void {
|
||||
function saveCachedTokens(instances: ConnectedInstance[], userId: string): void {
|
||||
const cache: Record<string, CachedInstanceToken> = {};
|
||||
for (const inst of instances) {
|
||||
// Skip tokenless placeholders — writing an empty token would cause
|
||||
@@ -50,7 +68,7 @@ function saveCachedTokens(instances: ConnectedInstance[]): void {
|
||||
username: inst.username,
|
||||
};
|
||||
}
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(cache));
|
||||
localStorage.setItem(storageKey(userId), JSON.stringify(cache));
|
||||
}
|
||||
|
||||
// ─── Network error detection ────────────────────────────────────────────────
|
||||
@@ -216,7 +234,7 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
||||
|
||||
set((state) => {
|
||||
const updated = [...state.instances, instance];
|
||||
saveCachedTokens(updated);
|
||||
saveCachedTokens(updated, currentUser.id);
|
||||
return { instances: updated, isLoading: false };
|
||||
});
|
||||
|
||||
@@ -255,7 +273,8 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
||||
|
||||
set((state) => {
|
||||
const updated = [...state.instances, instance];
|
||||
saveCachedTokens(updated);
|
||||
const userId = useAuthStore.getState().user?.id;
|
||||
if (userId) saveCachedTokens(updated, userId);
|
||||
return { instances: updated, isLoading: false };
|
||||
});
|
||||
|
||||
@@ -284,7 +303,8 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
||||
|
||||
set((state) => {
|
||||
const updated = state.instances.filter(i => i.origin !== origin);
|
||||
saveCachedTokens(updated);
|
||||
const userId = useAuthStore.getState().user?.id;
|
||||
if (userId) saveCachedTokens(updated, userId);
|
||||
return { instances: updated };
|
||||
});
|
||||
|
||||
@@ -405,7 +425,7 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
||||
return;
|
||||
}
|
||||
|
||||
const cached = loadCachedTokens();
|
||||
const cached = loadCachedTokens(currentUser.id);
|
||||
|
||||
// Split server-known instances into two groups:
|
||||
// - withToken: have a cached token → attempt reconnection
|
||||
@@ -543,7 +563,7 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
||||
|
||||
// Save final state to localStorage — persist ALL instances regardless of status
|
||||
// so disconnected instances survive page reload and can auto-reconnect later
|
||||
saveCachedTokens(get().instances);
|
||||
saveCachedTokens(get().instances, currentUser.id);
|
||||
|
||||
// Mark auto-connect complete so syncInstanceList is now safe to run
|
||||
set({ _autoConnectDone: true });
|
||||
@@ -560,7 +580,7 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
||||
disconnectAllRemote();
|
||||
|
||||
set({ instances: [], isLoading: false, error: null, _autoConnectDone: false });
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
// Token cache preserved — scoped per user, survives logout for seamless reconnect
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -199,7 +199,14 @@ export const useSpaceStore = create<SpaceState>((set, get) => ({
|
||||
},
|
||||
|
||||
updateSpace: async (spaceId: string, data: UpdateSpaceRequest) => {
|
||||
const updated = await api.spaces.update(spaceId, data);
|
||||
const space = get().spaces.find(s => s.id === spaceId);
|
||||
const origin = space?._instanceOrigin ?? '';
|
||||
const client = getApiForOrigin(origin);
|
||||
const updated = await client.spaces.update(spaceId, data);
|
||||
// Normalize remote asset URL so the icon displays correctly in-app
|
||||
if (origin && updated.icon) {
|
||||
updated.icon = resolveAssetUrl(updated.icon, origin) ?? updated.icon;
|
||||
}
|
||||
set((state) => ({
|
||||
spaces: state.spaces.map(s => s.id === spaceId ? { ...s, ...updated } : s),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user