feat(activity): show the current activity on the profile card
The activity pipeline was already complete end to end — Activity type, store, WS broadcast, server validation, presence relay, and an ActivityCard used by four list surfaces — but the profile card rendered none of it, which is the 'Listening to Spotify' block the design calls for. Add ProfileActivity: richer than ActivityCard because the card has room for artwork, track and artist, so it reads details/state/assets. All optional, so it degrades to the bare name that today's process-based detector supplies. Also scheme-check activity image assets server-side. activity.url was already restricted to http(s) but assets.largeImage/smallImage were only length-checked — an asymmetry that was harmless while nothing rendered them, and is not once they become <img src>: a client could point them at a host it controls and harvest the IP of everyone opening that profile.
This commit is contained in:
@@ -3,6 +3,8 @@ import { useNavigate } from 'react-router-dom';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import type { User } from '@backspace/shared';
|
||||
import { Avatar } from '../ui/Avatar';
|
||||
import { ProfileActivity } from '../ui/ProfileActivity';
|
||||
import { useActivityStore } from '../../stores/activityStore';
|
||||
import { Username } from '../ui/Username';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
import { useSpaceStore, getApiForOrigin, resolveUserOrigin } from '../../stores/spaceStore';
|
||||
@@ -149,6 +151,13 @@ export function UserProfileModal() {
|
||||
|
||||
// Banner — use correct API client for remote users
|
||||
const profileApi = getApiForOrigin(userOrigin);
|
||||
// Keyed by home id, matching every other activity consumer (ActivityPanel,
|
||||
// MemberSidebar), so federated users resolve to the same record. The `?? []`
|
||||
// stays OUTSIDE the selector: building it inside would hand zustand a fresh
|
||||
// array reference every render and spin.
|
||||
const activityList = useActivityStore((s) => s.userActivities.get(user.homeUserId ?? user.id));
|
||||
const activities = activityList ?? [];
|
||||
|
||||
const bannerSrc = user.banner
|
||||
? (user.banner.startsWith('http') ? user.banner : profileApi.uploads.url(user.banner))
|
||||
: null;
|
||||
@@ -353,6 +362,9 @@ export function UserProfileModal() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Current activity — the "Listening to Spotify" block */}
|
||||
<ProfileActivity activities={activities} />
|
||||
|
||||
{/* Member Since */}
|
||||
<div>
|
||||
<span className="text-[11px] uppercase tracking-wide font-semibold text-txt-tertiary">
|
||||
|
||||
Reference in New Issue
Block a user