diff --git a/desktop/src/features/channels/ui/ChannelPane.tsx b/desktop/src/features/channels/ui/ChannelPane.tsx index fea8c48604..07479a2b3a 100644 --- a/desktop/src/features/channels/ui/ChannelPane.tsx +++ b/desktop/src/features/channels/ui/ChannelPane.tsx @@ -203,7 +203,7 @@ export const ChannelPane = React.memo(function ChannelPane({ ); const [isMainDeferredEditPending, setMainDeferredEditPending] = React.useState(false); const isMarketObserver = useMarketObserver(agentPubkeys, currentPubkey); - const { isMarketChannel, messageFooters: marketMessageFooters, messages: marketTimelineMessages } = useMarketChannelTimeline(messages); + const { isMarketChannel, messageFooters: marketMessageFooters, messages: marketTimelineMessages } = useMarketChannelTimeline(messages, profiles); const isNonMemberView = activeChannel !== null && !activeChannel.isMember && @@ -369,7 +369,7 @@ export const ChannelPane = React.memo(function ChannelPane({ onOpenMembers, onWelcomeAddAgent: onAddAgent ? handleWelcomeAddAgent : undefined, }); - const channelIntro = isHuddleTranscript ? null : standardChannelIntro; + const channelIntro = isHuddleTranscript || isMarketChannel ? null : standardChannelIntro; const { mainTimelineEntries, recentMentions, visibleMessages } = useChannelPaneMessages({ activeChannel, diff --git a/desktop/src/features/market/lib/marketBidIdentity.test.mjs b/desktop/src/features/market/lib/marketBidIdentity.test.mjs new file mode 100644 index 0000000000..57480b5c5b --- /dev/null +++ b/desktop/src/features/market/lib/marketBidIdentity.test.mjs @@ -0,0 +1,32 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { resolveMarketBidIdentity } from "./marketBidIdentity.ts"; + +const BIDDER = "a".repeat(64); +const bid = { actorName: "Fizz envelope", bidderPubkey: BIDDER }; + +test("market bids use the signed bidder's profile identity", () => { + assert.deepEqual( + resolveMarketBidIdentity(bid, { + [BIDDER]: { + avatarUrl: "https://relay.example/fizz.png", + displayName: "Fizz", + nip05Handle: null, + ownerPubkey: null, + isAgent: true, + }, + }), + { + avatarUrl: "https://relay.example/fizz.png", + displayName: "Fizz", + }, + ); +}); + +test("market bids fall back to the signed envelope name without a profile", () => { + assert.deepEqual(resolveMarketBidIdentity(bid), { + avatarUrl: null, + displayName: "Fizz envelope", + }); +}); diff --git a/desktop/src/features/market/lib/marketBidIdentity.ts b/desktop/src/features/market/lib/marketBidIdentity.ts new file mode 100644 index 0000000000..6fb0d75604 --- /dev/null +++ b/desktop/src/features/market/lib/marketBidIdentity.ts @@ -0,0 +1,14 @@ +import type { MarketBid } from "@/features/market/lib/marketProtocol"; +import type { UserProfileLookup } from "@/features/profile/lib/identity"; +import { normalizePubkey } from "@/shared/lib/pubkey"; + +export function resolveMarketBidIdentity( + bid: Pick, + profiles?: UserProfileLookup, +) { + const profile = profiles?.[normalizePubkey(bid.bidderPubkey)]; + return { + avatarUrl: profile?.avatarUrl ?? null, + displayName: profile?.displayName?.trim() || bid.actorName, + }; +} diff --git a/desktop/src/features/market/lib/useMarketChannelTimeline.tsx b/desktop/src/features/market/lib/useMarketChannelTimeline.tsx index 8760bcfbaa..63fbae229e 100644 --- a/desktop/src/features/market/lib/useMarketChannelTimeline.tsx +++ b/desktop/src/features/market/lib/useMarketChannelTimeline.tsx @@ -8,8 +8,12 @@ import { } from "@/features/market/lib/marketTimeline"; import { MarketChannelIntro } from "@/features/market/ui/MarketChannelIntro"; import type { TimelineMessage } from "@/features/messages/types"; +import type { UserProfileLookup } from "@/features/profile/lib/identity"; -export function useMarketChannelTimeline(messages: TimelineMessage[]) { +export function useMarketChannelTimeline( + messages: TimelineMessage[], + profiles?: UserProfileLookup, +) { const projection = useMarketChannel(); const timelineMessages = useMarketTimelineMessages(messages); const anchor = React.useMemo( @@ -24,11 +28,12 @@ export function useMarketChannelTimeline(messages: TimelineMessage[]) { ), } : undefined, - [anchor, projection?.bids], + [anchor, profiles, projection?.bids], ); return { diff --git a/desktop/src/features/market/ui/MarketChannelIntro.tsx b/desktop/src/features/market/ui/MarketChannelIntro.tsx index cdaf52e35f..0c622e93dc 100644 --- a/desktop/src/features/market/ui/MarketChannelIntro.tsx +++ b/desktop/src/features/market/ui/MarketChannelIntro.tsx @@ -4,6 +4,7 @@ import { useAppNavigation } from "@/app/navigation/useAppNavigation"; import { MessageCircle } from "lucide-react"; import { useMarketChannel } from "@/features/market/lib/MarketChannelContext"; +import { resolveMarketBidIdentity } from "@/features/market/lib/marketBidIdentity"; import type { MarketBid } from "@/features/market/lib/marketProtocol"; import { isMarketProtocolMessage, @@ -11,15 +12,18 @@ import { } from "@/features/market/lib/marketTimeline"; import { MarketContractCard } from "@/features/market/ui/MarketContractCard"; import type { TimelineMessage } from "@/features/messages/types"; +import type { UserProfileLookup } from "@/features/profile/lib/identity"; import { Button } from "@/shared/ui/button"; import { UserAvatar } from "@/shared/ui/UserAvatar"; function BidList({ bids, channelId, + profiles, }: { bids: MarketBid[]; channelId: string; + profiles?: UserProfileLookup; }) { const { goChannel } = useAppNavigation(); return ( @@ -34,46 +38,52 @@ function BidList({

No bids yet.

) : (
- {bids.map((bid) => ( -
- -
-
- {bid.actorName} - - {bid.amountSats != null - ? `${bid.amountSats} sats per unit` - : "Terms in thread"} - {` · ${bid.quantity} ${bid.quantity === 1 ? "unit" : "units"}`} - -
-

- {bid.message} -

-
- -
- ))} + +
+
+ {displayName} + + {bid.amountSats != null + ? `${bid.amountSats} sats per unit` + : "Terms in thread"} + {` · ${bid.quantity} ${bid.quantity === 1 ? "unit" : "units"}`} + +
+

+ {bid.message} +

+
+ +
+ ); + })} )} @@ -83,9 +93,11 @@ function BidList({ export function MarketChannelIntro({ anchorMessage, bids, + profiles, }: { anchorMessage: Pick; bids: MarketBid[]; + profiles?: UserProfileLookup; }): React.ReactNode { const projection = useMarketChannel(); if (!projection || isMarketProtocolMessage(anchorMessage)) return undefined; @@ -93,7 +105,11 @@ export function MarketChannelIntro({ return (
- +
); }