feat: discover people tab, privacy settings, and friend request button fix
- Add "Discover People" section to Add Friend tab with user cards, mutual counts, and inline actions - Add discoverStore for fetching/searching discoverable users across local and federated instances - Add PrivacyPanel to user settings with discoverability toggle - Add is_discoverable column to users table with migration - Fix "Send Friend Request" button vertical alignment using transform centering
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import { create } from 'zustand';
|
||||
import type { DiscoverUser } from '@backspace/shared';
|
||||
import { api } from '../api/client';
|
||||
import { useInstanceStore } from './instanceStore';
|
||||
import { normalizeUserAssets } from '../utils/assetUrls';
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface TaggedDiscoverUser extends DiscoverUser {
|
||||
_instanceOrigin: string; // '' = home instance
|
||||
}
|
||||
|
||||
interface DiscoverState {
|
||||
users: TaggedDiscoverUser[];
|
||||
searchQuery: string;
|
||||
isLoading: boolean;
|
||||
total: number;
|
||||
error: string | null;
|
||||
|
||||
fetchUsers: (query?: string) => Promise<void>;
|
||||
setSearchQuery: (q: string) => void;
|
||||
updateRelationship: (userId: string, origin: string, relationship: DiscoverUser['relationship'], requestId?: string) => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
// ─── Store ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export const useDiscoverStore = create<DiscoverState>((set) => ({
|
||||
users: [],
|
||||
searchQuery: '',
|
||||
isLoading: false,
|
||||
total: 0,
|
||||
error: null,
|
||||
|
||||
fetchUsers: async (query?: string) => {
|
||||
set({ isLoading: true, error: null });
|
||||
|
||||
// Wait for autoConnectAll to finish (same guard as exploreStore)
|
||||
if (!useInstanceStore.getState()._autoConnectDone) {
|
||||
await new Promise<void>((resolve) => {
|
||||
const unsub = useInstanceStore.subscribe((state) => {
|
||||
if (state._autoConnectDone) {
|
||||
unsub();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
if (useInstanceStore.getState()._autoConnectDone) {
|
||||
unsub();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const instances = useInstanceStore.getState().instances;
|
||||
const connectedInstances = instances.filter(i => i.status === 'connected');
|
||||
|
||||
const results = await Promise.allSettled([
|
||||
api.social.discover(query).then(res => ({ ...res, origin: '' })),
|
||||
...connectedInstances.map(inst =>
|
||||
inst.api.social.discover(query).then(res => ({ ...res, origin: inst.origin }))
|
||||
),
|
||||
]);
|
||||
|
||||
const fulfilled = results.filter(r => r.status === 'fulfilled') as PromiseFulfilledResult<{ users: DiscoverUser[]; total: number; origin: string }>[];
|
||||
|
||||
if (fulfilled.length === 0) {
|
||||
set({ isLoading: false, error: 'Failed to reach any instance for discovery' });
|
||||
return;
|
||||
}
|
||||
|
||||
const allUsers: TaggedDiscoverUser[] = [];
|
||||
const seen = new Set<string>();
|
||||
let totalSum = 0;
|
||||
|
||||
for (const result of fulfilled) {
|
||||
const { users, total, origin } = result.value;
|
||||
totalSum += total;
|
||||
|
||||
for (const user of users) {
|
||||
const key = `${user.id}:${origin}`;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
if (origin) normalizeUserAssets(user as unknown as { avatar?: string | null; banner?: string | null }, origin);
|
||||
allUsers.push({ ...user, _instanceOrigin: origin });
|
||||
}
|
||||
}
|
||||
|
||||
set({
|
||||
users: allUsers,
|
||||
total: totalSum,
|
||||
isLoading: false,
|
||||
});
|
||||
} catch (err) {
|
||||
set({
|
||||
isLoading: false,
|
||||
error: err instanceof Error ? err.message : 'Failed to discover users',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setSearchQuery: (q: string) => set({ searchQuery: q }),
|
||||
|
||||
updateRelationship: (userId: string, origin: string, relationship: DiscoverUser['relationship'], requestId?: string) => {
|
||||
set((state) => ({
|
||||
users: state.users.map(u =>
|
||||
u.id === userId && u._instanceOrigin === origin
|
||||
? { ...u, relationship, ...(requestId !== undefined ? { requestId } : {}) }
|
||||
: u
|
||||
),
|
||||
}));
|
||||
},
|
||||
|
||||
reset: () => set({
|
||||
users: [],
|
||||
searchQuery: '',
|
||||
isLoading: false,
|
||||
total: 0,
|
||||
error: null,
|
||||
}),
|
||||
}));
|
||||
Reference in New Issue
Block a user