import React, { useEffect, useMemo } from 'react'; import { useSocialStore } from '../../stores/socialStore'; import { useUIStore } from '../../stores/uiStore'; import { useActivityStore } from '../../stores/activityStore'; import { Avatar } from '../ui/Avatar'; import { Username } from '../ui/Username'; import { ActivityCard, hasRichActivity, getActivityAccentClass } from '../ui/ActivityCard'; import type { Friend, Activity, User } from '@backspace/shared'; import { getPrimaryActivity } from '@backspace/shared/src/activities.js'; import { parseFederatedUsername, isFederationGlobeApplicable } from '../../utils/identity'; import { useCanonicalUserView } from '../../utils/userViewLookup'; function ActivityFriendRow({ friend, isOffline, activities, isRichActivity, accentClass, onClickFriend, }: { friend: Friend; isOffline: boolean; activities: Activity[]; isRichActivity: boolean; accentClass: string; onClickFriend: (e: React.MouseEvent, friend: Friend) => void; }) { const canonical = useCanonicalUserView(friend as unknown as User); const { baseName } = parseFederatedUsername(canonical.username); const friendDisplayName = canonical.displayName ?? baseName; const rowClass = isRichActivity ? `flex items-center gap-2.5 px-2.5 py-2 rounded-[10px] mb-1 cursor-pointer transition-colors glass-pill border-l-2 ${accentClass}` : 'flex items-center gap-2.5 px-2 py-1.5 rounded-[4px] hover:bg-interactive-hover cursor-pointer group transition-colors'; return (
onClickFriend(e, friend)} className={rowClass} >
{!isOffline && isFederationGlobeApplicable(canonical) && (
@{parseFederatedUsername(canonical.username).domain}
)} {!isOffline && ( )}
); } export function ActivityPanel() { const friends = useSocialStore((s) => s.friends); const loadFriends = useSocialStore((s) => s.loadFriends); const memberListOpen = useUIStore((s) => s.memberListOpen); const openUserProfile = useUIStore((s) => s.openUserProfile); const userActivities = useActivityStore((s) => s.userActivities); useEffect(() => { loadFriends(); }, [loadFriends]); const { activeFriends, onlineFriends, offlineFriends } = useMemo(() => { const active: Friend[] = []; const online: Friend[] = []; const offline: Friend[] = []; for (const f of friends) { if (f.status === 'offline') { offline.push(f); continue; } const activities = userActivities.get(f.homeUserId ?? f.id) ?? []; const primary = getPrimaryActivity(activities); // Active = has a non-custom activity (playing, listening, watching, streaming) if (primary && primary.type !== 'custom') { active.push(f); } else { online.push(f); } } return { activeFriends: active, onlineFriends: online, offlineFriends: offline }; }, [friends, userActivities]); if (!memberListOpen) return null; const handleFriendClick = (e: React.MouseEvent, friend: Friend) => { e.stopPropagation(); openUserProfile( { id: friend.id, username: friend.username, displayName: friend.displayName, avatar: friend.avatar, banner: friend.banner, accentColor: friend.accentColor, avatarColor: friend.avatarColor, bio: friend.bio, status: friend.status, customStatus: friend.customStatus, createdAt: friend.createdAt, homeUserId: friend.homeUserId, homeInstance: friend.homeInstance, isAdmin: false, replicatedInstances: [], }, e.currentTarget.getBoundingClientRect(), 'left', ); }; const renderFriend = (friend: Friend, isOffline = false) => { const activities = userActivities.get(friend.homeUserId ?? friend.id) ?? []; const isRichActivity = !isOffline && hasRichActivity(activities); const primary = getPrimaryActivity(activities); const accentClass = primary ? getActivityAccentClass(primary.type) : ''; return ( ); }; return (

Active Now

{activeFriends.length === 0 && onlineFriends.length === 0 && offlineFriends.length === 0 ? (
It's quiet for now...
When a friend starts an activity—like playing a game or hanging out on voice—we'll show it here!
) : ( <> {activeFriends.length > 0 && (
{activeFriends.map(f => renderFriend(f))}
)} {onlineFriends.length > 0 && (

ONLINE — {onlineFriends.length}

{onlineFriends.map(f => renderFriend(f))}
)} {offlineFriends.length > 0 && (

OFFLINE — {offlineFriends.length}

{offlineFriends.map(f => renderFriend(f, true))}
)} )}
); }