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))}
)}
>
)}
);
}