feat: add cross-team member name extraction and badge component
- Introduced a new utility function to extract member names from cross-team recipient strings. - Added a CrossTeamTeamBadge component for displaying team names with consistent styling. - Updated ActivityItem to utilize the new member name extraction and badge component, enhancing the display of cross-team messages. - Enhanced tests to validate the functionality of the new member name extraction method.
This commit is contained in:
parent
0feb5e650f
commit
c40c61f099
2 changed files with 44 additions and 36 deletions
|
|
@ -91,6 +91,21 @@ export function getCrossTeamSentTarget(
|
||||||
return recipient.teamName;
|
return recipient.teamName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCrossTeamSentMemberName(value: string | undefined): string | null {
|
||||||
|
return parseQualifiedRecipient(value)?.memberName ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CrossTeamTeamBadge({ teamName }: { teamName: string }): React.JSX.Element {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium tracking-wide"
|
||||||
|
style={{ backgroundColor: 'rgba(168, 85, 247, 0.15)', color: '#c084fc' }}
|
||||||
|
>
|
||||||
|
{teamName}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface ActivityItemProps {
|
interface ActivityItemProps {
|
||||||
message: InboxMessage;
|
message: InboxMessage;
|
||||||
teamName: string;
|
teamName: string;
|
||||||
|
|
@ -319,6 +334,10 @@ export const ActivityItem = ({
|
||||||
() => getCrossTeamSentTarget(message.to, teamName, localMemberNames),
|
() => getCrossTeamSentTarget(message.to, teamName, localMemberNames),
|
||||||
[message.to, teamName, localMemberNames]
|
[message.to, teamName, localMemberNames]
|
||||||
);
|
);
|
||||||
|
const crossTeamSentMemberName = useMemo(
|
||||||
|
() => getCrossTeamSentMemberName(message.to),
|
||||||
|
[message.to]
|
||||||
|
);
|
||||||
const isCrossTeam = message.source === CROSS_TEAM_SOURCE || parsedCrossTeamPrefix !== null;
|
const isCrossTeam = message.source === CROSS_TEAM_SOURCE || parsedCrossTeamPrefix !== null;
|
||||||
const isCrossTeamSent =
|
const isCrossTeamSent =
|
||||||
message.source === CROSS_TEAM_SENT_SOURCE ||
|
message.source === CROSS_TEAM_SENT_SOURCE ||
|
||||||
|
|
@ -344,6 +363,10 @@ export const ActivityItem = ({
|
||||||
if (dot <= 0) return message.to;
|
if (dot <= 0) return message.to;
|
||||||
return message.to.substring(0, dot);
|
return message.to.substring(0, dot);
|
||||||
}, [crossTeamSentTarget, isCrossTeamSent, message.to, qualifiedRecipient]);
|
}, [crossTeamSentTarget, isCrossTeamSent, message.to, qualifiedRecipient]);
|
||||||
|
const senderName = crossTeamOrigin ? crossTeamOrigin.memberName : message.from;
|
||||||
|
const senderColor = crossTeamOrigin ? undefined : (memberColor ?? message.color);
|
||||||
|
const senderHideAvatar =
|
||||||
|
message.from === 'user' || message.from === 'system' || crossTeamOrigin?.memberName === 'user';
|
||||||
|
|
||||||
// Strip agent-only blocks + normalize escape sequences (before linkification)
|
// Strip agent-only blocks + normalize escape sequences (before linkification)
|
||||||
const strippedText = useMemo(() => {
|
const strippedText = useMemo(() => {
|
||||||
|
|
@ -477,15 +500,13 @@ export const ActivityItem = ({
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* Sender avatar + name badge */}
|
{/* Sender avatar + name badge */}
|
||||||
|
{crossTeamOrigin ? <CrossTeamTeamBadge teamName={crossTeamOrigin.teamName} /> : null}
|
||||||
<MemberBadge
|
<MemberBadge
|
||||||
name={crossTeamOrigin ? crossTeamOrigin.memberName : message.from}
|
name={senderName}
|
||||||
color={isCrossTeamAny ? 'purple' : (memberColor ?? message.color)}
|
color={senderColor}
|
||||||
hideAvatar={
|
hideAvatar={senderHideAvatar}
|
||||||
message.from === 'user' ||
|
|
||||||
message.from === 'system' ||
|
|
||||||
crossTeamOrigin?.memberName === 'user'
|
|
||||||
}
|
|
||||||
onClick={onMemberNameClick}
|
onClick={onMemberNameClick}
|
||||||
|
disableHoverCard={crossTeamOrigin != null}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Role */}
|
{/* Role */}
|
||||||
|
|
@ -517,26 +538,6 @@ export const ActivityItem = ({
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* Cross-team origin badge */}
|
|
||||||
{isCrossTeam && crossTeamOrigin ? (
|
|
||||||
<span
|
|
||||||
className="inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium tracking-wide"
|
|
||||||
style={{ backgroundColor: 'rgba(168, 85, 247, 0.15)', color: '#c084fc' }}
|
|
||||||
>
|
|
||||||
from {crossTeamOrigin.teamName}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{/* Cross-team sent badge */}
|
|
||||||
{isCrossTeamSent && crossTeamTarget ? (
|
|
||||||
<span
|
|
||||||
className="inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium tracking-wide"
|
|
||||||
style={{ backgroundColor: 'rgba(168, 85, 247, 0.15)', color: '#c084fc' }}
|
|
||||||
>
|
|
||||||
to {crossTeamTarget}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{/* Rate limit warning badge */}
|
{/* Rate limit warning badge */}
|
||||||
{rateLimited ? (
|
{rateLimited ? (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full bg-red-500/20 px-1.5 py-0.5 text-[10px] font-medium text-red-400">
|
<span className="inline-flex items-center gap-1 rounded-full bg-red-500/20 px-1.5 py-0.5 text-[10px] font-medium text-red-400">
|
||||||
|
|
@ -559,15 +560,19 @@ export const ActivityItem = ({
|
||||||
<span style={{ color: CARD_ICON_MUTED }} className="text-[10px]">
|
<span style={{ color: CARD_ICON_MUTED }} className="text-[10px]">
|
||||||
→
|
→
|
||||||
</span>
|
</span>
|
||||||
<MemberBadge
|
{crossTeamTarget ? <CrossTeamTeamBadge teamName={crossTeamTarget} /> : null}
|
||||||
name={qualifiedRecipient?.memberName ?? crossTeamSentTarget ?? message.to}
|
{crossTeamSentMemberName || !crossTeamTarget ? (
|
||||||
color={crossTeamSentTarget ? 'purple' : recipientColor}
|
<MemberBadge
|
||||||
hideAvatar={
|
name={crossTeamSentMemberName ?? qualifiedRecipient?.memberName ?? message.to}
|
||||||
crossTeamSentTarget !== null ||
|
color={crossTeamTarget ? undefined : recipientColor}
|
||||||
(qualifiedRecipient?.memberName ?? message.to) === 'user'
|
hideAvatar={
|
||||||
}
|
(crossTeamSentMemberName ?? qualifiedRecipient?.memberName ?? message.to) ===
|
||||||
onClick={onMemberNameClick}
|
'user'
|
||||||
/>
|
}
|
||||||
|
onClick={onMemberNameClick}
|
||||||
|
disableHoverCard={crossTeamTarget != null}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
getCrossTeamSentMemberName,
|
||||||
getCrossTeamSentTarget,
|
getCrossTeamSentTarget,
|
||||||
getSystemMessageLabel,
|
getSystemMessageLabel,
|
||||||
isQualifiedExternalRecipient,
|
isQualifiedExternalRecipient,
|
||||||
|
|
@ -37,5 +38,7 @@ describe('ActivityItem legacy system message fallback', () => {
|
||||||
expect(getCrossTeamSentTarget('team-best.user', 'my-team', new Set(['ops.bot']))).toBe(
|
expect(getCrossTeamSentTarget('team-best.user', 'my-team', new Set(['ops.bot']))).toBe(
|
||||||
'team-best'
|
'team-best'
|
||||||
);
|
);
|
||||||
|
expect(getCrossTeamSentMemberName('team-best.user')).toBe('user');
|
||||||
|
expect(getCrossTeamSentMemberName('cross-team:team-best')).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue