Files
backspace/packages/web/src/stores/socialStore.ts
T
Jannis Braun 5ef502f2e3 fix: repair invite links, social features, messaging + Discord UI overhaul
Phase 1 - Feature Repair:
- Fix member kick/leave: add missing db.delete() call in servers.ts
- Stabilize invite codes: return existing code instead of regenerating
- Fix user search: use LIKE instead of exact match in social.ts
- Wire DM button on FriendsPage to create/navigate to DM channels
- Add cancel outgoing friend request (DELETE endpoint + frontend)
- Add accept/decline friend request actions with WS real-time events
- Fix replyToId persistence in message creation
- Hydrate reactions and replyTo in message queries
- Add joinByCode to API client and serverStore
- Add friend_request_received/accepted WebSocket events

Phase 2 - Discord UI Overhaul:
- Remove stray borders between layout columns
- Replace shadow-sm with shadow-header on content headers
- Replace all bg-gray-*/text-gray-* with Discord color tokens
- Ensure flat color contrast (#1E1F22, #2B2D31, #313338)

Testing:
- Set up vitest + @testing-library/react + jsdom
- Add 17 tests across InviteModal, JoinServer, FriendsPage (all passing)
- Fix vite resolve.extensions to prefer .tsx over stale .js files
2026-02-18 05:34:45 +01:00

125 lines
3.6 KiB
TypeScript

import { create } from 'zustand';
import type { Friend, FriendRequest, User } from '@opencord/shared';
import { api } from '../api/client';
interface SocialState {
friends: Friend[];
requests: FriendRequest[];
isLoading: boolean;
error: string | null;
loadFriends: () => Promise<void>;
loadRequests: () => Promise<void>;
sendFriendRequest: (username: string) => Promise<void>;
updateFriendRequest: (id: string, status: 'accepted' | 'declined') => Promise<void>;
cancelFriendRequest: (id: string) => Promise<void>;
removeFriend: (id: string) => Promise<void>;
searchUsers: (query: string) => Promise<User[]>;
addIncomingRequest: (request: FriendRequest) => void;
addFriendFromAccepted: (friend: Friend, requestId: string) => void;
}
export const useSocialStore = create<SocialState>((set, get) => ({
friends: [],
requests: [],
isLoading: false,
error: null,
loadFriends: async () => {
set({ isLoading: true, error: null });
try {
const friends = await api.social.friends();
set({ friends, isLoading: false });
} catch (err) {
set({ error: (err as Error).message, isLoading: false });
}
},
loadRequests: async () => {
set({ isLoading: true, error: null });
try {
const requests = await api.social.requests();
set({ requests, isLoading: false });
} catch (err) {
set({ error: (err as Error).message, isLoading: false });
}
},
sendFriendRequest: async (username: string) => {
set({ isLoading: true, error: null });
try {
await api.social.sendRequest(username);
await get().loadRequests();
} catch (err) {
set({ error: (err as Error).message, isLoading: false });
throw err;
}
},
updateFriendRequest: async (id: string, status: 'accepted' | 'declined') => {
set({ isLoading: true, error: null });
try {
await api.social.updateRequest(id, status);
await get().loadRequests();
if (status === 'accepted') {
await get().loadFriends();
}
} catch (err) {
set({ error: (err as Error).message, isLoading: false });
throw err;
}
},
cancelFriendRequest: async (id: string) => {
set({ isLoading: true, error: null });
try {
await api.social.cancelRequest(id);
set((state) => ({
requests: state.requests.filter(r => r.id !== id),
isLoading: false,
}));
} catch (err) {
set({ error: (err as Error).message, isLoading: false });
throw err;
}
},
removeFriend: async (id: string) => {
set({ isLoading: true, error: null });
try {
await api.social.removeFriend(id);
set((state) => ({
friends: state.friends.filter((f) => f.id !== id),
isLoading: false,
}));
} catch (err) {
set({ error: (err as Error).message, isLoading: false });
throw err;
}
},
searchUsers: async (query: string) => {
try {
return await api.social.search(query);
} catch (err) {
console.error('Failed to search users:', err);
return [];
}
},
// Called from WS handler when another user sends you a friend request
addIncomingRequest: (request: FriendRequest) => {
set((state) => {
if (state.requests.find(r => r.id === request.id)) return state;
return { requests: [...state.requests, request] };
});
},
// Called from WS handler when someone accepts your friend request
addFriendFromAccepted: (friend: Friend, requestId: string) => {
set((state) => ({
friends: state.friends.find(f => f.id === friend.id) ? state.friends : [...state.friends, friend],
requests: state.requests.filter(r => r.id !== requestId),
}));
},
}));