@linktr.ee/messaging-react 3.5.0-rc-1782792813 → 3.5.0-rc-1782793221

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/{Card-BnGeMt4f.js → Card-49AvU29H.js} +2 -2
  2. package/dist/{Card-BnGeMt4f.js.map → Card-49AvU29H.js.map} +1 -1
  3. package/dist/{Card-DpCwDviq.cjs → Card-Cse19FAO.cjs} +2 -2
  4. package/dist/{Card-DpCwDviq.cjs.map → Card-Cse19FAO.cjs.map} +1 -1
  5. package/dist/{Card-qrDaFHtJ.cjs → Card-DBIVUdM5.cjs} +2 -2
  6. package/dist/{Card-qrDaFHtJ.cjs.map → Card-DBIVUdM5.cjs.map} +1 -1
  7. package/dist/{Card-BWWBaNKF.js → Card-DS8zNUkL.js} +2 -2
  8. package/dist/{Card-BWWBaNKF.js.map → Card-DS8zNUkL.js.map} +1 -1
  9. package/dist/{Card-Bj4YaHav.cjs → Card-DgPx-6aq.cjs} +2 -2
  10. package/dist/{Card-Bj4YaHav.cjs.map → Card-DgPx-6aq.cjs.map} +1 -1
  11. package/dist/{Card-DkXzpFLA.js → Card-q0zqEbga.js} +3 -3
  12. package/dist/{Card-DkXzpFLA.js.map → Card-q0zqEbga.js.map} +1 -1
  13. package/dist/{LockedThumbnail-DhXEk-NX.cjs → LockedThumbnail-BFgw9F0g.cjs} +2 -2
  14. package/dist/{LockedThumbnail-DhXEk-NX.cjs.map → LockedThumbnail-BFgw9F0g.cjs.map} +1 -1
  15. package/dist/{LockedThumbnail-DJR--PAi.js → LockedThumbnail-CRBDhB49.js} +2 -2
  16. package/dist/{LockedThumbnail-DJR--PAi.js.map → LockedThumbnail-CRBDhB49.js.map} +1 -1
  17. package/dist/index-BcmwckcI.cjs +2 -0
  18. package/dist/index-BcmwckcI.cjs.map +1 -0
  19. package/dist/{index-ubCRSgQk.js → index-DxzkXLSW.js} +1150 -1172
  20. package/dist/index-DxzkXLSW.js.map +1 -0
  21. package/dist/index.cjs +1 -1
  22. package/dist/index.d.ts +0 -12
  23. package/dist/index.js +1 -1
  24. package/package.json +2 -2
  25. package/src/components/ChannelList/ChannelList.stories.tsx +37 -0
  26. package/src/components/ChannelList/CustomChannelPreview.stories.tsx +0 -110
  27. package/src/components/ChannelList/CustomChannelPreview.test.tsx +8 -58
  28. package/src/components/ChannelList/CustomChannelPreview.tsx +12 -7
  29. package/src/components/ChannelView.stories.tsx +7 -38
  30. package/src/components/ChannelView.test.tsx +27 -44
  31. package/src/components/ChannelView.tsx +6 -4
  32. package/src/hooks/useChannelStar.ts +6 -9
  33. package/src/stream-custom-data.ts +1 -16
  34. package/dist/index-DzOA7Yu8.cjs +0 -2
  35. package/dist/index-DzOA7Yu8.cjs.map +0 -1
  36. package/dist/index-ubCRSgQk.js.map +0 -1
  37. package/src/components/ParticipantBadges.tsx +0 -42
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-DzOA7Yu8.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageVoteButtons=e.MessageVoteButtons;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-BcmwckcI.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageVoteButtons=e.MessageVoteButtons;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.ts CHANGED
@@ -1501,13 +1501,6 @@ declare module 'stream-chat' {
1501
1501
  */
1502
1502
  chatbot_paused?: boolean;
1503
1503
  }
1504
- interface CustomMemberData {
1505
- /**
1506
- * Whether the member should be identified as a customer in participant UI.
1507
- * Stored on the channel member so it is scoped to this conversation.
1508
- */
1509
- customer?: boolean;
1510
- }
1511
1504
  interface CustomMessageData {
1512
1505
  /**
1513
1506
  * When true, hides the date timestamp in system messages.
@@ -1525,10 +1518,5 @@ declare module 'stream-chat' {
1525
1518
  */
1526
1519
  dm_agent_system_type?: DmAgentSystemType;
1527
1520
  }
1528
- interface CustomUserData {
1529
- is_account?: boolean;
1530
- is_follower?: boolean;
1531
- username?: string;
1532
- }
1533
1521
  }
1534
1522
 
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, b as t, C as i, c as o, d as n, e as m, F as g, f as r, L as l, h as M, M as u, i as L, j as c, k as h, l as d, m as p, n as C, o as v, p as A, q as k, s as F, t as b, u as f, v as x, w as y, x as P, y as S, z as q, B as z, D as B, E as D } from "./index-ubCRSgQk.js";
1
+ import { a as e, b as t, C as i, c as o, d as n, e as m, F as g, f as r, L as l, h as M, M as u, i as L, j as c, k as h, l as d, m as p, n as C, o as v, p as A, q as k, s as F, t as b, u as f, v as x, w as y, x as P, y as S, z as q, B as z, D as B, E as D } from "./index-DxzkXLSW.js";
2
2
  export {
3
3
  e as ActionButton,
4
4
  t as Avatar,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "3.5.0-rc-1782792813",
3
+ "version": "3.5.0-rc-1782793221",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@linktr.ee/component-library": "11.8.6",
41
- "@linktr.ee/messaging-core": "2.3.0-rc-1782792813",
41
+ "@linktr.ee/messaging-core": "2.3.0-rc-1782793221",
42
42
  "@phosphor-icons/react": "^2.1.10"
43
43
  },
44
44
  "devDependencies": {
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryFn } from '@storybook/react'
2
2
  import React from 'react'
3
+ import type { ChannelPreviewUIComponentProps } from 'stream-chat-react'
3
4
 
4
5
  import { MockChatProviderWithChannels } from '../../stories/mocks'
5
6
 
@@ -37,6 +38,35 @@ const Template: StoryFn<ComponentProps> = (args) => {
37
38
  )
38
39
  }
39
40
 
41
+ const HostChannelPreview: React.FC<ChannelPreviewUIComponentProps> = ({
42
+ channel,
43
+ unread,
44
+ }) => {
45
+ const participant = Object.values(channel?.state?.members || {}).find(
46
+ (member) => member.user?.id !== channel?._client?.userID
47
+ )
48
+
49
+ return (
50
+ <div className="mx-3 mt-3 rounded-[20px] border border-[#e0dfdb] bg-white p-4 shadow-[0_4px_16px_rgba(0,0,0,0.06)]">
51
+ <div className="flex items-center justify-between">
52
+ <div className="min-w-0">
53
+ <p className="truncate text-sm font-semibold text-[#191918]">
54
+ {participant?.user?.name ?? 'Unknown visitor'}
55
+ </p>
56
+ <p className="mt-1 line-clamp-1 text-xs text-[#717070]">
57
+ Host-owned channel preview
58
+ </p>
59
+ </div>
60
+ {Boolean(unread) && (
61
+ <span className="ml-3 rounded-full bg-[#7f22fe] px-2 py-1 text-xs font-medium text-white">
62
+ {unread}
63
+ </span>
64
+ )}
65
+ </div>
66
+ </div>
67
+ )
68
+ }
69
+
40
70
  export const Default: StoryFn<ComponentProps> = Template.bind({})
41
71
  Default.args = {
42
72
  filters: { type: 'messaging' },
@@ -49,4 +79,11 @@ WithSelection.args = {
49
79
  onChannelSelect: (channel) => console.log('Channel selected:', channel.id),
50
80
  }
51
81
 
82
+ export const Custom: StoryFn<ComponentProps> = Template.bind({})
83
+ Custom.args = {
84
+ filters: { type: 'messaging' },
85
+ onChannelSelect: (channel) => console.log('Channel selected:', channel.id),
86
+ channelPreview: HostChannelPreview,
87
+ }
88
+
52
89
 
@@ -6,7 +6,6 @@ import {
6
6
  LocalMessage,
7
7
  StreamChat,
8
8
  } from 'stream-chat'
9
- import type { ChannelPreviewUIComponentProps } from 'stream-chat-react'
10
9
 
11
10
  import {
12
11
  daysAgo,
@@ -15,12 +14,9 @@ import {
15
14
  now,
16
15
  secondsAgo,
17
16
  } from '../../stories/decorators/storyTime'
18
- import { MockChatProviderWithChannels } from '../../stories/mocks'
19
17
 
20
18
  import CustomChannelPreview from './CustomChannelPreview'
21
19
 
22
- import { ChannelList } from '.'
23
-
24
20
  type ComponentProps = React.ComponentProps<typeof CustomChannelPreview>
25
21
 
26
22
  const meta: Meta<ComponentProps> = {
@@ -59,8 +55,6 @@ const createMockChannel = (options: {
59
55
  thumb_url?: string
60
56
  }>
61
57
  lastMessageMetadata?: CustomMessageData['metadata']
62
- isCustomer?: boolean
63
- isStarred?: boolean
64
58
  }): Channel => {
65
59
  const {
66
60
  id,
@@ -72,8 +66,6 @@ const createMockChannel = (options: {
72
66
  unreadCount = 0,
73
67
  lastMessageAttachments,
74
68
  lastMessageMetadata,
75
- isCustomer = false,
76
- isStarred = false,
77
69
  } = options
78
70
 
79
71
  const eventListeners = new Map<string, Set<(...args: unknown[]) => void>>()
@@ -97,12 +89,8 @@ const createMockChannel = (options: {
97
89
  image: participantImage,
98
90
  },
99
91
  user_id: participantId,
100
- ...(isCustomer ? { customer: true } : {}),
101
92
  },
102
93
  },
103
- membership: {
104
- pinned_at: isStarred ? now().toISOString() : null,
105
- },
106
94
  messages:
107
95
  lastMessageText || lastMessageAttachments || lastMessageMetadata
108
96
  ? ([
@@ -261,61 +249,6 @@ ChatbotMessage.args = {
261
249
  }),
262
250
  }
263
251
 
264
- export const ParticipantBadges: StoryFn = () => {
265
- const channels = [
266
- createMockChannel({
267
- id: 'channel-customer',
268
- participantName: 'Alice Customer',
269
- participantId: 'participant-customer',
270
- participantImage: 'https://i.pravatar.cc/150?img=40',
271
- lastMessageText: 'I bought this from your Linktree',
272
- lastMessageTime: minutesAgo(2),
273
- isCustomer: true,
274
- }),
275
- createMockChannel({
276
- id: 'channel-starred',
277
- participantName: 'Bob Starred',
278
- participantId: 'participant-starred',
279
- participantImage: 'https://i.pravatar.cc/150?img=41',
280
- lastMessageText: 'Following up on this thread',
281
- lastMessageTime: minutesAgo(8),
282
- isStarred: true,
283
- }),
284
- createMockChannel({
285
- id: 'channel-customer-starred',
286
- participantName: 'Carol Customer',
287
- participantId: 'participant-customer-starred',
288
- participantImage: 'https://i.pravatar.cc/150?img=42',
289
- lastMessageText: 'Thanks for the quick reply',
290
- lastMessageTime: minutesAgo(15),
291
- isCustomer: true,
292
- isStarred: true,
293
- }),
294
- ]
295
-
296
- return (
297
- <div className="w-[360px] bg-chalk border border-sand rounded-lg overflow-hidden">
298
- {channels.map((channel) => (
299
- <CustomChannelPreview
300
- key={channel.id}
301
- channel={channel}
302
- onChannelSelect={(channel) => {
303
- console.log('Channel selected:', channel.id)
304
- }}
305
- />
306
- ))}
307
- </div>
308
- )
309
- }
310
- ParticipantBadges.parameters = {
311
- docs: {
312
- description: {
313
- story:
314
- 'Shows the built-in customer badge from participant.member customer data and the starred badge from the current user membership pinned_at value.',
315
- },
316
- },
317
- }
318
-
319
252
  export const TipMessage: StoryFn<ComponentProps> = Template.bind({})
320
253
  TipMessage.args = {
321
254
  channel: createMockChannel({
@@ -866,46 +799,3 @@ WithLongAttachmentUrl.args = {
866
799
  }),
867
800
  }
868
801
  WithLongAttachmentUrl.parameters = skipInChromatic
869
-
870
- const HostChannelPreview: React.FC<ChannelPreviewUIComponentProps> = ({
871
- channel,
872
- unread,
873
- }) => {
874
- const participant = Object.values(channel?.state?.members || {}).find(
875
- (member) => member.user?.id !== channel?._client?.userID
876
- )
877
-
878
- return (
879
- <div className="mx-3 mt-3 rounded-[20px] border border-[#e0dfdb] bg-white p-4 shadow-[0_4px_16px_rgba(0,0,0,0.06)]">
880
- <div className="flex items-center justify-between">
881
- <div className="min-w-0">
882
- <p className="truncate text-sm font-semibold text-[#191918]">
883
- {participant?.user?.name ?? 'Unknown visitor'}
884
- </p>
885
- <p className="mt-1 line-clamp-1 text-xs text-[#717070]">
886
- Host-owned channel preview
887
- </p>
888
- </div>
889
- {Boolean(unread) && (
890
- <span className="ml-3 rounded-full bg-[#7f22fe] px-2 py-1 text-xs font-medium text-white">
891
- {unread}
892
- </span>
893
- )}
894
- </div>
895
- </div>
896
- )
897
- }
898
-
899
- export const Custom: StoryFn = () => (
900
- <MockChatProviderWithChannels>
901
- <div className="h-screen w-full md:w-[360px] bg-chalk">
902
- <ChannelList
903
- filters={{ type: 'messaging' }}
904
- onChannelSelect={(channel) => {
905
- console.log('Channel selected:', channel.id)
906
- }}
907
- channelPreview={HostChannelPreview}
908
- />
909
- </div>
910
- </MockChatProviderWithChannels>
911
- )
@@ -12,15 +12,12 @@ const mockUser = {
12
12
  }
13
13
 
14
14
  type MockChannel = Channel & {
15
- emitMemberUpdated: (member?: {
16
- pinned_at?: string | null
17
- user_id?: string
18
- }) => void
15
+ emitMemberUpdated: (member?: { pinned_at?: string | null }) => void
19
16
  }
20
17
 
21
18
  const createMockChannel = (messages: Partial<LocalMessage>[]): MockChannel => {
22
19
  const memberUpdatedListeners = new Set<
23
- (event: { member?: { pinned_at?: string | null; user_id?: string } }) => void
20
+ (event: { member?: { pinned_at?: string | null } }) => void
24
21
  >()
25
22
 
26
23
  return {
@@ -41,34 +38,24 @@ const createMockChannel = (messages: Partial<LocalMessage>[]): MockChannel => {
41
38
  on: vi.fn(
42
39
  (
43
40
  eventName: string,
44
- listener: (event: {
45
- member?: { pinned_at?: string | null; user_id?: string }
46
- }) => void
41
+ listener: (event: { member?: { pinned_at?: string | null } }) => void
47
42
  ) => {
48
43
  if (eventName === 'member.updated') {
49
44
  memberUpdatedListeners.add(listener)
50
45
  }
51
- return {
52
- unsubscribe: () => memberUpdatedListeners.delete(listener),
53
- }
54
46
  }
55
47
  ),
56
48
  off: vi.fn(
57
49
  (
58
50
  eventName: string,
59
- listener: (event: {
60
- member?: { pinned_at?: string | null; user_id?: string }
61
- }) => void
51
+ listener: (event: { member?: { pinned_at?: string | null } }) => void
62
52
  ) => {
63
53
  if (eventName === 'member.updated') {
64
54
  memberUpdatedListeners.delete(listener)
65
55
  }
66
56
  }
67
57
  ),
68
- emitMemberUpdated: (member?: {
69
- pinned_at?: string | null
70
- user_id?: string
71
- }) => {
58
+ emitMemberUpdated: (member?: { pinned_at?: string | null }) => {
72
59
  memberUpdatedListeners.forEach((listener) => listener({ member }))
73
60
  },
74
61
  } as unknown as MockChannel
@@ -266,21 +253,6 @@ describe('CustomChannelPreview', () => {
266
253
  expect(screen.getByText('No messages yet')).toBeInTheDocument()
267
254
  })
268
255
 
269
- it('shows a customer badge from participant member data', () => {
270
- const channel = createMockChannel([])
271
- channel.state.members['participant-1'] = {
272
- user: { id: 'participant-1', name: 'Alice' },
273
- user_id: 'participant-1',
274
- customer: true,
275
- }
276
-
277
- renderWithProviders(
278
- <CustomChannelPreview {...defaultProps} channel={channel} />
279
- )
280
-
281
- expect(screen.getByRole('img', { name: 'Customer' })).toBeInTheDocument()
282
- })
283
-
284
256
  it('updates starred state when the channel membership changes', () => {
285
257
  const channel = createMockChannel([])
286
258
 
@@ -289,37 +261,15 @@ describe('CustomChannelPreview', () => {
289
261
  )
290
262
 
291
263
  expect(
292
- screen.queryByRole('img', { name: 'Starred conversation' })
264
+ screen.queryByRole('heading', { name: /starred conversation/i })
293
265
  ).not.toBeInTheDocument()
294
266
 
295
267
  act(() => {
296
- channel.emitMemberUpdated({
297
- pinned_at: new Date().toISOString(),
298
- user_id: mockUser.id,
299
- })
268
+ channel.emitMemberUpdated({ pinned_at: new Date().toISOString() })
300
269
  })
301
270
 
302
271
  expect(
303
- screen.getByRole('img', { name: 'Starred conversation' })
272
+ screen.getByRole('heading', { name: /starred conversation/i })
304
273
  ).toBeInTheDocument()
305
274
  })
306
-
307
- it('ignores starred updates for other members', () => {
308
- const channel = createMockChannel([])
309
-
310
- renderWithProviders(
311
- <CustomChannelPreview {...defaultProps} channel={channel} />
312
- )
313
-
314
- act(() => {
315
- channel.emitMemberUpdated({
316
- pinned_at: new Date().toISOString(),
317
- user_id: 'participant-1',
318
- })
319
- })
320
-
321
- expect(
322
- screen.queryByRole('img', { name: 'Starred conversation' })
323
- ).not.toBeInTheDocument()
324
- })
325
275
  })
@@ -2,12 +2,12 @@ import classNames from 'classnames'
2
2
  import React from 'react'
3
3
  import { ChannelPreviewUIComponentProps } from 'stream-chat-react'
4
4
 
5
+ import { useChannelStar } from '../../hooks/useChannelStar'
5
6
  import { formatRelativeTime } from '../../utils/formatRelativeTime'
6
7
  import { getMessageDisplayText } from '../../utils/getMessageDisplayText'
7
8
  import { resolveParticipantDisplayName } from '../../utils/resolveParticipantDisplayName'
8
9
  import { Avatar } from '../Avatar'
9
10
  import { isChatbotMessage } from '../CustomMessage/MessageTag'
10
- import { ParticipantBadges } from '../ParticipantBadges'
11
11
 
12
12
  import { useChannelListContext } from './ChannelListContext'
13
13
 
@@ -105,6 +105,7 @@ const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
105
105
  const messagePreview = renderMessagePreview
106
106
  ? renderMessagePreview(lastMessage, lastMessageText)
107
107
  : `${isLastMessageFromChatbot ? '✨ ' : ''}${lastMessageText}`
108
+ const isChannelStarred = useChannelStar(channel)
108
109
 
109
110
  // Use the unread prop passed by Stream Chat (reactive and updates automatically)
110
111
  const unreadCount = unread ?? 0
@@ -141,18 +142,22 @@ const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
141
142
  name={participantName}
142
143
  image={participantImage}
143
144
  size={48}
145
+ starred={isChannelStarred}
144
146
  />
145
147
 
146
148
  {/* Content column */}
147
149
  <div className="flex-1 min-w-0 flex flex-col gap-1">
148
150
  {/* Name and timestamp row */}
149
151
  <div className="flex items-center justify-between gap-2">
150
- <h3 className="flex min-w-0 items-center gap-1 text-sm font-medium text-[#191918]">
151
- <span className="min-w-0 truncate">{participantName}</span>
152
- <ParticipantBadges
153
- channel={channel}
154
- participant={participant}
155
- />
152
+ <h3
153
+ className={classNames(
154
+ 'text-sm font-medium truncate text-[#191918]'
155
+ )}
156
+ >
157
+ {isChannelStarred && (
158
+ <span className="sr-only">Starred conversation. </span>
159
+ )}
160
+ {participantName}
156
161
  </h3>
157
162
  {lastMessageTime && (
158
163
  <span className="text-xs text-[#717070] flex-shrink-0">
@@ -41,14 +41,7 @@ const createMockChannel = async (
41
41
  client: StreamChat,
42
42
  hasMessages = true,
43
43
  followerStatus?: string | boolean,
44
- isFrozen = false,
45
- {
46
- isCustomer = false,
47
- isStarred = false,
48
- }: {
49
- isCustomer?: boolean
50
- isStarred?: boolean
51
- } = {}
44
+ isFrozen = false
52
45
  ) => {
53
46
  const participant = mockParticipants[0]
54
47
 
@@ -149,12 +142,8 @@ const createMockChannel = async (
149
142
  user_id: participant.id,
150
143
  role: 'member',
151
144
  is_account: true,
152
- ...(isCustomer ? { customer: true } : {}),
153
145
  },
154
146
  }
155
- channel.state.membership = {
156
- pinned_at: isStarred ? now().toISOString() : null,
157
- } as ChannelMemberResponse
158
147
  return {
159
148
  channel: channelData,
160
149
  members: [],
@@ -182,8 +171,6 @@ const createMockChannel = async (
182
171
  type TemplateProps = ComponentProps & {
183
172
  followerStatus?: string | boolean
184
173
  isFrozen?: boolean
185
- isCustomer?: boolean
186
- isStarred?: boolean
187
174
  typingUser?: {
188
175
  id: string
189
176
  name?: string
@@ -195,8 +182,6 @@ const Template: StoryFn<TemplateProps> = (args) => {
195
182
  const {
196
183
  followerStatus,
197
184
  isFrozen = false,
198
- isCustomer = false,
199
- isStarred = false,
200
185
  typingUser,
201
186
  ...channelViewProps
202
187
  } = args
@@ -212,13 +197,12 @@ const Template: StoryFn<TemplateProps> = (args) => {
212
197
  const [channel, setChannel] = React.useState<ChannelType | null>(null)
213
198
 
214
199
  useEffect(() => {
215
- createMockChannel(client, true, followerStatus, isFrozen, {
216
- isCustomer,
217
- isStarred,
218
- }).then((mockChannel) => {
219
- setChannel(mockChannel)
220
- })
221
- }, [client, followerStatus, isCustomer, isFrozen, isStarred])
200
+ createMockChannel(client, true, followerStatus, isFrozen).then(
201
+ (mockChannel) => {
202
+ setChannel(mockChannel)
203
+ }
204
+ )
205
+ }, [client, followerStatus, isFrozen])
222
206
 
223
207
  useEffect(() => {
224
208
  if (!channel || !typingUser) {
@@ -325,21 +309,6 @@ WithHeaderTitleBadges.parameters = {
325
309
  },
326
310
  }
327
311
 
328
- export const ParticipantBadges: StoryFn<TemplateProps> = Template.bind({})
329
- ParticipantBadges.args = {
330
- showBackButton: false,
331
- isCustomer: true,
332
- isStarred: true,
333
- }
334
- ParticipantBadges.parameters = {
335
- docs: {
336
- description: {
337
- story:
338
- 'Shows the built-in customer badge from participant.customer and the starred badge from the current user membership pinned_at value in the channel header.',
339
- },
340
- },
341
- }
342
-
343
312
  export const RestrictedOfficialChannel: StoryFn<TemplateProps> = Template.bind(
344
313
  {}
345
314
  )
@@ -84,9 +84,13 @@ vi.mock('./Avatar', () => ({
84
84
  },
85
85
  }))
86
86
 
87
+ let mockIsStarred = false
88
+ vi.mock('../hooks/useChannelStar', () => ({
89
+ useChannelStar: () => mockIsStarred,
90
+ }))
91
+
87
92
  const createChannel = ({
88
93
  currentUserId = 'visitor-1',
89
- currentUserPinnedAt,
90
94
  currentUserRole = 'owner',
91
95
  otherUser = {
92
96
  id: 'linker-1' as const,
@@ -94,12 +98,9 @@ const createChannel = ({
94
98
  username: undefined as string | undefined,
95
99
  is_account: true,
96
100
  },
97
- otherMemberCustomer = false,
98
101
  }: {
99
102
  currentUserId?: 'visitor-1' | 'linker-1'
100
- currentUserPinnedAt?: string
101
103
  currentUserRole?: 'owner' | 'member'
102
- otherMemberCustomer?: boolean
103
104
  otherUser?: {
104
105
  id: string
105
106
  name?: string
@@ -132,15 +133,12 @@ const createChannel = ({
132
133
  otherUser.is_account ?? currentUserId === 'visitor-1',
133
134
  },
134
135
  role: currentUserRole === 'owner' ? 'member' : 'owner',
135
- customer: otherMemberCustomer,
136
136
  },
137
137
  },
138
- membership: {
139
- pinned_at: currentUserPinnedAt,
140
- },
138
+ membership: {},
141
139
  messages: [],
142
140
  },
143
- on: vi.fn(() => ({ unsubscribe: vi.fn() })),
141
+ on: vi.fn(),
144
142
  off: vi.fn(),
145
143
  sendMessage: vi.fn(),
146
144
  hide: vi.fn(),
@@ -155,6 +153,7 @@ describe('ChannelView', () => {
155
153
  avatarRenderCalls.length = 0
156
154
  channelActionsMenuProps.length = 0
157
155
  messageInputProps.length = 0
156
+ mockIsStarred = false
158
157
  })
159
158
 
160
159
  const lastActionsMenuProps = () =>
@@ -308,54 +307,38 @@ describe('ChannelView', () => {
308
307
  expect(screen.queryByTestId('conversation-footer')).not.toBeInTheDocument()
309
308
  })
310
309
 
311
- it('does not pass starred to avatar when channel is starred', () => {
312
- const channel = createChannel({
313
- currentUserPinnedAt: '2026-01-01T00:00:00.000Z',
314
- })
310
+ it('does not pass starred to avatar when showStarButton is false', () => {
311
+ mockIsStarred = true
312
+ const channel = createChannel()
315
313
 
316
314
  renderWithProviders(
317
- <ChannelView channel={channel} showStarButton={true} />
315
+ <ChannelView channel={channel} showStarButton={false} />
318
316
  )
319
317
 
320
318
  expect(avatarRenderCalls.length).toBeGreaterThan(0)
321
- expect(avatarRenderCalls.every((call) => !('starred' in call))).toBe(true)
319
+ expect(avatarRenderCalls.every((call) => call.starred === false)).toBe(true)
322
320
  })
323
321
 
324
- it('renders built-in customer badges from participant member data', () => {
322
+ it('passes starred to avatar when showStarButton is true and channel is starred', () => {
323
+ mockIsStarred = true
324
+ const channel = createChannel()
325
+
325
326
  renderWithProviders(
326
- <ChannelView
327
- channel={createChannel({ otherMemberCustomer: true })}
328
- renderHeaderTitleBadges={() => (
329
- <span data-testid="custom-header-badge">VIP</span>
330
- )}
331
- />
327
+ <ChannelView channel={channel} showStarButton={true} />
332
328
  )
333
329
 
334
- const customerBadges = screen.getAllByRole('img', { name: 'Customer' })
335
- const customBadges = screen.getAllByTestId('custom-header-badge')
336
-
337
- expect(customerBadges.length).toBeGreaterThan(0)
338
- expect(customBadges.length).toBe(customerBadges.length)
339
- customerBadges.forEach((badge, index) => {
340
- expect(
341
- badge.compareDocumentPosition(customBadges[index]) &
342
- Node.DOCUMENT_POSITION_FOLLOWING
343
- ).toBeTruthy()
344
- })
330
+ expect(avatarRenderCalls.length).toBeGreaterThan(0)
331
+ expect(avatarRenderCalls.every((call) => call.starred === true)).toBe(true)
345
332
  })
346
333
 
347
- it('renders built-in starred badges from channel star state', () => {
348
- renderWithProviders(
349
- <ChannelView
350
- channel={createChannel({
351
- currentUserPinnedAt: '2026-01-01T00:00:00.000Z',
352
- })}
353
- />
354
- )
334
+ it('does not pass starred to avatar by default (showStarButton defaults to false)', () => {
335
+ mockIsStarred = true
336
+ const channel = createChannel()
355
337
 
356
- expect(
357
- screen.getAllByRole('img', { name: 'Starred conversation' }).length
358
- ).toBeGreaterThan(0)
338
+ renderWithProviders(<ChannelView channel={channel} />)
339
+
340
+ expect(avatarRenderCalls.length).toBeGreaterThan(0)
341
+ expect(avatarRenderCalls.every((call) => call.starred === false)).toBe(true)
359
342
  })
360
343
 
361
344
  it('passes dmAgentEnabled to avatar when dmAgentEnabled is true for visitor view', () => {