feat: add frontend Rich Presence — activityStore, ActivityCard, sidebar upgrades, settings toggle

- Task 7: Create activityStore (Zustand) with Map<userId, Activity[]>, showActivity toggle,
  debounced pushActivities, and reset on logout
- Task 8: Wire WS integration — presence_update propagates activities to store,
  ready payload initializes userActivities and showActivity (via setState to avoid side effects)
- Task 9: Create ActivityCard component with compact/full modes, type-colored labels,
  elapsed time display, asset images, and fallback to customStatus
- Task 10: Upgrade ActivityPanel to three groups — active friends (full cards, no header),
  online friends (compact), offline friends — using getPrimaryActivity for grouping
- Task 11: Upgrade MemberSidebar to show compact ActivityCards instead of raw customStatus
- Task 12: Add showActivity toggle in AccountPanel settings (Activity Status section)
  with API persistence and store sync
This commit is contained in:
Jannis Braun
2026-03-21 01:55:17 +01:00
parent 9403ded8da
commit 1f4e149ee1
7 changed files with 285 additions and 11 deletions
+12
View File
@@ -11,6 +11,7 @@ import { broadcastVoiceStatus, broadcastDeafenViaLiveKit } from '../utils/voice'
import { registerSelfId } from '../utils/identity';
import { getActiveRoom } from './useLiveKit';
import { useUIStore } from '../stores/uiStore';
import { useActivityStore } from '../stores/activityStore';
// ─── Connection state ─────────────────────────────────────────────────────────
@@ -205,6 +206,14 @@ function handleEvent(origin: string, event: ServerEvent): void {
setVoiceUsers(channelId, userIds);
}
}
// Initialize activity data from ready payload
if (event.userActivities) {
useActivityStore.getState().initActivities(event.userActivities);
}
if (event.user.showActivity !== undefined) {
useActivityStore.setState({ showActivity: event.user.showActivity });
}
// Populate voice user statuses (mute/deafen/camera/screenshare) from server
if (event.voiceUserStates) {
for (const [uid, status] of Object.entries(event.voiceUserStates)) {
@@ -369,6 +378,9 @@ function handleEvent(origin: string, event: ServerEvent): void {
case 'presence_update':
updateMemberPresence(event.userId, event.status);
useSocialStore.getState().updateFriendPresence(event.userId, event.status);
if (event.activities) {
useActivityStore.getState().setUserActivities(event.userId, event.activities);
}
break;
case 'user_updated': {