5.6 KiB
5.6 KiB
Discord-like Client Design
Context
The go-socket backend has a full real-time messaging API (REST + WebSocket) but the React client is nearly empty — only AuthPage and an empty MainApp placeholder exist. This spec covers building the main app UI: a Discord-inspired layout with a connections/DM sidebar and chat panel, wired to the real backend.
Layout
Two-column layout, full viewport height:
┌──────────────────────────────────────────────────────┐
│ [280px sidebar] │ [flex chat area] │
│ │ │
│ ○ ○ ○ + ← hub row │ Contact Name │
│ ───────────────── │ ──────────────── │
│ Search... │ │
│ ▸ Contact A [3] │ [message history] │
│ ▸ Contact B │ │
│ ▸ ... │ ────────────────────── │
│ ───────────────── │ [ type a message... ] ➤ │
│ [avatar] You ⚙ │ │
└──────────────────────────────────────────────────────┘
Sidebar (280px):
- Hub row: horizontal strip of small icon circles + a
+stub button (empty for now, layout ready for hubs) - Search input to filter the connections list
- Scrollable connections list: colored avatar circle (initial fallback), name, unread badge, last-message preview
- Bottom bar: current user avatar + username + settings gear (stub — no action yet)
Chat area:
- Header bar: contact name + pronouns
- Scrollable message history (newest at bottom)
- Message input + send button
Components
src/
context/
AppContext.jsx — connections, selectedId, messages, currentUser, ws ref
components/
MainApp.jsx — root layout, mounts context provider
Sidebar.jsx — hub row + search + connection list + user bar
HubRow.jsx — stub hub icons + add button
ConnectionList.jsx — filtered, sorted list of ConnectionItem
ConnectionItem.jsx — single row: avatar, name, unread badge, last message
UserBar.jsx — bottom of sidebar: own avatar, name, settings gear stub
ChatArea.jsx — header + message list + input bar
MessageList.jsx — scrollable history, auto-scrolls to bottom on new message
MessageItem.jsx — single message bubble: sender avatar, content, timestamp
MessageInput.jsx — textarea + send button, Enter to send
api/
http.js — thin fetch wrapper; reads token from localStorage; sends token header
ws.js — WebSocket singleton: connect, send, onMessage dispatcher
Data Flow
On MainApp mount:
- Read
token+userIdfrom localStorage (App.jsx already guards this route) GET /connections→ load sidebar listGET /connections/unreadmessages?connections=<ids>→ overlay unread counts- Open WebSocket
ws://localhost:8080/ws, on open send{"token": "..."}
On connection selected:
GET /connection/messages?connectionid=<id>→ render history- Clear unread badge locally for that connection
Sending a message:
POST /connection/messagebody:connectionid,msgContent- Optimistically append to message list
Incoming WebSocket events:
| Type | Action |
|---|---|
| 1 — DirectMessage | Append to open chat if matches; else bump unread badge |
| 2 — ConnectionCreated | Prepend new connection to sidebar |
| 3 — ConnectionDeleted | Remove from sidebar; clear chat if open |
| 4/5 — Elevated/DeElevated | Update connection state field (Friend/Stranger cosmetic label) |
| 6/7/8 — Profile/Avatar/BgChange | Refresh contact display data |
API Reference
All requests send token as a plain header (not Bearer). Token and userId stored in localStorage from login.
| Endpoint | Usage |
|---|---|
GET /connections |
Load sidebar list on mount |
GET /connections/unreadmessages?connections=UUID,UUID |
Unread counts array |
GET /connection/messages?connectionid=UUID |
Load history for selected DM |
POST /connection/message |
Send message (body: connectionid, msgContent) |
WS /ws → send {"token":"..."} |
Authenticate WebSocket |
Design Aesthetic
- Dark theme (
bg-gray-900base, matching existingindex.css) - Natural, human-designed feel — no generic AI look
- User-assigned color (RGBA from server) used as avatar background and accent
- Subtle hover/active states, no heavy borders
- Tailwind utility classes, no CSS files
Out of Scope (this spec)
- Hub channels and hub management
- User profile/customization UI (gear stub present, no action)
- File attachments
- Friend request flow (elevate/deelevate)
- Connection creation UI
Verification
pnpm devinclient/— app loads at localhost:5173- Log in with existing credentials → redirects to MainApp
- Sidebar shows real connections from the server
- Clicking a connection loads message history
- Sending a message appears in the chat
- Open a second browser tab as another user — send a message → first tab receives it via WebSocket without refresh
- Unread badges update when a message arrives for a non-selected connection