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
This commit is contained in:
Jannis Braun
2026-02-18 05:34:45 +01:00
parent 4fd17084a5
commit 5ef502f2e3
82 changed files with 4906 additions and 552 deletions
@@ -0,0 +1,90 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import type { User } from '@opencord/shared';
import { Avatar } from '../ui/Avatar';
import { api } from '../../api/client';
import { useServerStore } from '../../stores/serverStore';
import { useChatStore } from '../../stores/chatStore';
interface UserProfilePopoutProps {
user: User;
onClose: () => void;
position?: { top: number; left: number };
}
export function UserProfilePopout({ user, onClose, position }: UserProfilePopoutProps) {
const navigate = useNavigate();
const addDmChannel = useServerStore((s) => s.addDmChannel);
const setCurrentChannel = useChatStore((s) => s.setCurrentChannel);
const displayName = user.displayName ?? user.username;
const handleSendMessage = async () => {
try {
const channel = await api.dm.create({ userId: user.id });
addDmChannel(channel);
setCurrentChannel(channel.id);
onClose();
navigate(`/channels/@me/${channel.id}`);
} catch (err) {
console.error('Failed to create DM channel:', err);
}
};
return (
<div
className="fixed z-50 w-[300px] bg-discord-bg-floating rounded-[8px] shadow-elevation-high overflow-hidden animate-fade-in select-none"
style={position ? { top: position.top, left: position.left } : { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }}
>
{/* Banner */}
<div className="h-[60px] bg-discord-blurple" />
{/* Avatar Container */}
<div className="px-4 pb-4 relative">
<div className="absolute -top-8 left-4 rounded-full border-[6px] border-discord-bg-floating bg-discord-bg-floating">
<Avatar
src={user.avatar}
name={displayName}
size={80}
status={user.status as any}
/>
</div>
{/* Content */}
<div className="mt-12 bg-discord-bg-tertiary rounded-[8px] p-3">
<div className="text-[20px] font-bold text-discord-text-header leading-tight mb-1">
{displayName}
</div>
<div className="text-[14px] text-discord-text-normal font-medium mb-3">
@{user.username}
</div>
<div className="w-full h-[1px] bg-discord-modifier-accent mb-3" />
<div className="mb-3">
<div className="text-[12px] font-bold text-discord-text-header uppercase mb-1">Opencord Member Since</div>
<div className="text-[12px] text-discord-text-normal font-medium">
{new Date(user.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}
</div>
</div>
{user.customStatus && (
<div className="mb-3">
<div className="text-[12px] font-bold text-discord-text-header uppercase mb-1">Status</div>
<div className="text-[14px] text-discord-text-normal">{user.customStatus}</div>
</div>
)}
</div>
</div>
{/* Footer / Actions */}
<div className="px-4 pb-4">
<button
onClick={handleSendMessage}
className="w-full py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-[14px] font-medium rounded-[4px] transition-colors"
>
Send Message
</button>
</div>
</div>
);
}