Add Discord collectibles to profiles, messages, and settings.
All checks were successful
Trigger cinny-mobile / dispatch (push) Successful in 1s

Integrate profile effects, nameplates, and avatar decorations with live catalog browsing, Matrix profile storage, and rendering across user heroes, DMs, messages, and the sidebar avatar.
This commit is contained in:
2026-08-24 00:54:48 +10:00
parent 6cb1e14632
commit 0cbc5d9a1c
25 changed files with 2473 additions and 174 deletions

View File

@@ -2,7 +2,7 @@ import { Box, Button, color, config, Text, toRem } from 'folds';
import { Icon, Icons } from '../icons';
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { UserHero, UserHeroName } from './UserHero';
import { UserHero } from './UserHero';
import { getMxIdServer, mxcUrlToHttp } from '../../utils/matrix';
import { getMemberAvatarMxc, getMemberDisplayName } from '../../utils/room';
import { useMatrixClient } from '../../hooks/useMatrixClient';
@@ -94,53 +94,55 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
avatarUrl={avatarUrl}
avatarMxc={avatarMxc}
presence={presence && presence.lastActiveTs !== 0 ? presence : undefined}
/>
<Box
direction="Column"
gap="200"
alignItems="Center"
style={{
padding: config.space.S400,
paddingTop: `calc(${config.space.S200} + ${toRem(36)})`,
marginTop: toRem(-36),
textAlign: 'center',
}}
>
{/* Display Name */}
<Text
size="H4"
className={classNames(BreakWord, LineClamp3)}
style={{ color: getMemberDisplayName(room, userId) !== userId ? profileColor : undefined, textShadow: profileTextShadow }}
<Box
direction="Column"
gap="200"
alignItems="Center"
style={{
padding: config.space.S400,
paddingTop: `calc(${config.space.S200} + ${toRem(36)})`,
marginTop: toRem(-36),
}}
>
{getMemberDisplayName(room, userId)}
</Text>
{/* Username */}
<Text
size="T200"
className={BreakWord}
style={{ color: profileColor, textShadow: profileTextShadow }}
>
{userId}
</Text>
{/* Status Pill */}
{presence?.status && (
<Box
<Text
size="H4"
className={classNames(BreakWord, LineClamp3)}
style={{
backgroundColor: color.Surface.Container,
border: `${toRem(1)} solid ${color.Surface.ContainerLine}`,
padding: `${toRem(6)} ${toRem(10)}`,
borderRadius: toRem(16),
maxWidth: toRem(250),
color: getMemberDisplayName(room, userId) !== userId ? profileColor : undefined,
textShadow: profileTextShadow,
}}
>
<Text size="T300" className={BreakWord}>
{presence.status}
</Text>
</Box>
)}
{getMemberDisplayName(room, userId)}
</Text>
<Text
size="T200"
className={BreakWord}
style={{ color: profileColor, textShadow: profileTextShadow }}
>
{userId}
</Text>
{presence?.status && (
<Box
style={{
backgroundColor: color.Surface.Container,
border: `${toRem(1)} solid ${color.Surface.ContainerLine}`,
padding: `${toRem(6)} ${toRem(10)}`,
borderRadius: toRem(16),
maxWidth: toRem(250),
}}
>
<Text size="T300" className={BreakWord}>
{presence.status}
</Text>
</Box>
)}
</Box>
</UserHero>
<Box direction="Column" gap="200" alignItems="Center" style={{ padding: config.space.S400, textAlign: 'center' }}>
{/* Chips Row */}
<Box alignItems="Center" gap="200" wrap="Wrap" justifyContent="Center">
{server && <ServerChip server={server} />}
@@ -170,7 +172,8 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
</Box>
)}
</Box>
{hasBottomContent && <Box direction="Column" gap="400" style={{ padding: config.space.S400 }}>
{hasBottomContent && (
<Box direction="Column" gap="400" style={{ padding: config.space.S400 }}>
{ignored && <IgnoredUserAlert />}
{member && membership === Membership.Ban && (
<UserBanAlert
@@ -206,7 +209,8 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
canKick={canKickUser && membership === Membership.Join}
canBan={canBanUser && membership !== Membership.Ban}
/>
</Box>}
</Box>
)}
</Box>
);
}