@linktr.ee/messaging-react 3.4.2-rc-1782395074 → 3.4.2-rc-1782453771
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.
- package/dist/{Card-mpVcQ8-W.js → Card-52LnCirC.js} +3 -3
- package/dist/{Card-mpVcQ8-W.js.map → Card-52LnCirC.js.map} +1 -1
- package/dist/{Card-V-bNUxcw.cjs → Card-BIa-47Ne.cjs} +2 -2
- package/dist/{Card-V-bNUxcw.cjs.map → Card-BIa-47Ne.cjs.map} +1 -1
- package/dist/{Card-CphU_jG-.js → Card-BaXLsSK2.js} +2 -2
- package/dist/{Card-CphU_jG-.js.map → Card-BaXLsSK2.js.map} +1 -1
- package/dist/{Card-DFy_ZvFH.cjs → Card-BkVj6kGN.cjs} +2 -2
- package/dist/{Card-DFy_ZvFH.cjs.map → Card-BkVj6kGN.cjs.map} +1 -1
- package/dist/{Card-DxroYNrS.cjs → Card-CcTyYY9N.cjs} +2 -2
- package/dist/{Card-DxroYNrS.cjs.map → Card-CcTyYY9N.cjs.map} +1 -1
- package/dist/{Card-BKalDyyu.js → Card-ql9pImRa.js} +2 -2
- package/dist/{Card-BKalDyyu.js.map → Card-ql9pImRa.js.map} +1 -1
- package/dist/{LockedThumbnail-BD7qtcso.js → LockedThumbnail-C3YOnS-o.js} +2 -2
- package/dist/{LockedThumbnail-BD7qtcso.js.map → LockedThumbnail-C3YOnS-o.js.map} +1 -1
- package/dist/{LockedThumbnail-B3W7upHs.cjs → LockedThumbnail-D0ASm7LP.cjs} +2 -2
- package/dist/{LockedThumbnail-B3W7upHs.cjs.map → LockedThumbnail-D0ASm7LP.cjs.map} +1 -1
- package/dist/{index-CjwQ2Hj3.js → index-CbWEp3Zs.js} +1136 -1093
- package/dist/index-CbWEp3Zs.js.map +1 -0
- package/dist/index-DkqM2z_j.cjs +2 -0
- package/dist/index-DkqM2z_j.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +13 -25
- package/dist/index.js +13 -14
- package/package.json +2 -2
- package/src/components/ChannelList/CustomChannelPreview.stories.tsx +63 -0
- package/src/components/ChannelList/CustomChannelPreview.test.tsx +58 -8
- package/src/components/ChannelList/CustomChannelPreview.tsx +7 -10
- package/src/components/ChannelView.stories.tsx +38 -7
- package/src/components/ChannelView.test.tsx +29 -15
- package/src/components/ChannelView.tsx +19 -9
- package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +0 -34
- package/src/components/CustomMessageInput/index.tsx +1 -23
- package/src/components/MessagingShell/index.tsx +0 -2
- package/src/components/ParticipantBadges.tsx +39 -0
- package/src/hooks/useChannelStar.ts +9 -6
- package/src/index.ts +0 -1
- package/src/stream-custom-data.ts +16 -1
- package/src/types.ts +0 -18
- package/dist/index-CjwQ2Hj3.js.map +0 -1
- package/dist/index-tI-4cxV1.cjs +0 -2
- package/dist/index-tI-4cxV1.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-DkqM2z_j.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.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.ts
CHANGED
|
@@ -149,7 +149,7 @@ export declare const ChannelView: default_2.NamedExoticComponent<ChannelViewProp
|
|
|
149
149
|
* Props that MessagingShell passes through to ChannelView.
|
|
150
150
|
* ChannelViewProps is the source of truth for these props.
|
|
151
151
|
*/
|
|
152
|
-
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'onMessageSent' | 'chatbotVotingEnabled' | 'viewerLanguage' | 'renderHeaderTitleBadges' | 'renderChannelBanner' | 'customChannelActions' | 'renderChannelActions' | 'onParticipantNameClick' | 'renderMessage' | 'onMessageLinkClick' | 'showChannelInfo'
|
|
152
|
+
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'onMessageSent' | 'chatbotVotingEnabled' | 'viewerLanguage' | 'renderHeaderTitleBadges' | 'renderChannelBanner' | 'customChannelActions' | 'renderChannelActions' | 'onParticipantNameClick' | 'renderMessage' | 'onMessageLinkClick' | 'showChannelInfo'>;
|
|
153
153
|
|
|
154
154
|
/**
|
|
155
155
|
* ChannelView component props
|
|
@@ -351,22 +351,6 @@ export declare interface ChannelViewProps {
|
|
|
351
351
|
* Use this to render staged paid or media attachments inside the input.
|
|
352
352
|
*/
|
|
353
353
|
attachmentPreviewList?: ComponentType;
|
|
354
|
-
/**
|
|
355
|
-
* Replace the entire inner composer — the textarea + send arrangement — with
|
|
356
|
-
* a host-owned component. Passed to Stream's `<MessageInput>` as the `Input`
|
|
357
|
-
* component, so it runs inside the message-input context and reuses the
|
|
358
|
-
* library's send pipeline (`doSendMessageRequest`, message metadata, DM-agent
|
|
359
|
-
* suppression), attachments, and previews via the Stream hooks
|
|
360
|
-
* (`useMessageInputContext`, `useMessageComposerHasSendableData`,
|
|
361
|
-
* `TextareaComposer`, …). Use it when a surface needs a different composer
|
|
362
|
-
* layout (e.g. a full-width textarea above a toolbar row) than the default.
|
|
363
|
-
*
|
|
364
|
-
* The outer wrapper — frozen handling and the `renderMessageInputActions` /
|
|
365
|
-
* `renderMessageInputFooter` slots — is unchanged. Read the exported
|
|
366
|
-
* `useComposerLocked()` hook to honour the frozen state. Defaults to the
|
|
367
|
-
* built-in composer when omitted.
|
|
368
|
-
*/
|
|
369
|
-
composerInput?: ComponentType;
|
|
370
354
|
/**
|
|
371
355
|
* Fired when the participant's name in the channel header is clicked. When
|
|
372
356
|
* provided, the name renders as a button with a trailing caret (an affordance
|
|
@@ -1392,14 +1376,6 @@ export declare interface SentCardProps extends LockedAttachmentBaseProps {
|
|
|
1392
1376
|
onFetchSource?: () => Promise<LockedAttachmentSource | void>;
|
|
1393
1377
|
}
|
|
1394
1378
|
|
|
1395
|
-
/**
|
|
1396
|
-
* Read the composer's locked (frozen-channel) state from inside a custom
|
|
1397
|
-
* composer supplied via `composerInput`. Returns true when the channel is
|
|
1398
|
-
* frozen; mirror the default composer by rendering the textarea read-only and
|
|
1399
|
-
* disabling the send control.
|
|
1400
|
-
*/
|
|
1401
|
-
export declare const useComposerLocked: () => boolean;
|
|
1402
|
-
|
|
1403
1379
|
export declare function useCustomMessage<K extends keyof CustomMessageRegistry>(key: K): CustomMessageRegistry[K];
|
|
1404
1380
|
|
|
1405
1381
|
/**
|
|
@@ -1495,6 +1471,13 @@ declare module 'stream-chat' {
|
|
|
1495
1471
|
*/
|
|
1496
1472
|
chatbot_paused?: boolean;
|
|
1497
1473
|
}
|
|
1474
|
+
interface CustomMemberData {
|
|
1475
|
+
/**
|
|
1476
|
+
* Whether the member should be identified as a customer in participant UI.
|
|
1477
|
+
* Stored on the channel member so it is scoped to this conversation.
|
|
1478
|
+
*/
|
|
1479
|
+
customer?: boolean;
|
|
1480
|
+
}
|
|
1498
1481
|
interface CustomMessageData {
|
|
1499
1482
|
/**
|
|
1500
1483
|
* When true, hides the date timestamp in system messages.
|
|
@@ -1512,5 +1495,10 @@ declare module 'stream-chat' {
|
|
|
1512
1495
|
*/
|
|
1513
1496
|
dm_agent_system_type?: DmAgentSystemType;
|
|
1514
1497
|
}
|
|
1498
|
+
interface CustomUserData {
|
|
1499
|
+
is_account?: boolean;
|
|
1500
|
+
is_follower?: boolean;
|
|
1501
|
+
username?: string;
|
|
1502
|
+
}
|
|
1515
1503
|
}
|
|
1516
1504
|
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { a as e, b as t, C as i, c as
|
|
1
|
+
import { a as e, b as t, C as i, c as n, d as o, e as m, F as g, f as l, L as r, h as M, M as u, i as L, j as c, k as h, l as d, m as p, n as v, o as A, p as C, q as F, s as k, 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 } from "./index-CbWEp3Zs.js";
|
|
2
2
|
export {
|
|
3
3
|
e as ActionButton,
|
|
4
4
|
t as Avatar,
|
|
5
5
|
i as ChannelEmptyState,
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
n as ChannelList,
|
|
7
|
+
o as ChannelView,
|
|
8
8
|
m as CustomMessageProvider,
|
|
9
9
|
g as FaqList,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
l as FaqListItem,
|
|
11
|
+
r as LinkAttachment,
|
|
12
12
|
M as LockedAttachment,
|
|
13
13
|
u as MediaMessage,
|
|
14
14
|
L as MessageAttachment,
|
|
@@ -16,20 +16,19 @@ export {
|
|
|
16
16
|
h as MessagingProvider,
|
|
17
17
|
d as MessagingShell,
|
|
18
18
|
p as buildCompactMetaLabel,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
v as formatFileSize,
|
|
20
|
+
A as formatRelativeTime,
|
|
21
|
+
C as getFileExtensionLabel,
|
|
22
|
+
F as getMessageDisplayText,
|
|
23
|
+
k as isLinkAttachment,
|
|
24
24
|
b as isUuidLike,
|
|
25
25
|
f as messageAttachmentGroupPositionFromStream,
|
|
26
26
|
x as normalizeLanguageCode,
|
|
27
27
|
y as resolveLinkAttachment,
|
|
28
28
|
P as resolveMediaFromMessage,
|
|
29
29
|
S as resolveParticipantDisplayName,
|
|
30
|
-
q as
|
|
31
|
-
z as
|
|
32
|
-
B as
|
|
33
|
-
D as useMessaging
|
|
30
|
+
q as useCustomMessage,
|
|
31
|
+
z as useMessageVote,
|
|
32
|
+
B as useMessaging
|
|
34
33
|
};
|
|
35
34
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/messaging-react",
|
|
3
|
-
"version": "3.4.2-rc-
|
|
3
|
+
"version": "3.4.2-rc-1782453771",
|
|
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-
|
|
41
|
+
"@linktr.ee/messaging-core": "2.3.0-rc-1782453771",
|
|
42
42
|
"@phosphor-icons/react": "^2.1.10"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
@@ -55,6 +55,8 @@ const createMockChannel = (options: {
|
|
|
55
55
|
thumb_url?: string
|
|
56
56
|
}>
|
|
57
57
|
lastMessageMetadata?: CustomMessageData['metadata']
|
|
58
|
+
isCustomer?: boolean
|
|
59
|
+
isStarred?: boolean
|
|
58
60
|
}): Channel => {
|
|
59
61
|
const {
|
|
60
62
|
id,
|
|
@@ -66,6 +68,8 @@ const createMockChannel = (options: {
|
|
|
66
68
|
unreadCount = 0,
|
|
67
69
|
lastMessageAttachments,
|
|
68
70
|
lastMessageMetadata,
|
|
71
|
+
isCustomer = false,
|
|
72
|
+
isStarred = false,
|
|
69
73
|
} = options
|
|
70
74
|
|
|
71
75
|
const eventListeners = new Map<string, Set<(...args: unknown[]) => void>>()
|
|
@@ -89,8 +93,12 @@ const createMockChannel = (options: {
|
|
|
89
93
|
image: participantImage,
|
|
90
94
|
},
|
|
91
95
|
user_id: participantId,
|
|
96
|
+
...(isCustomer ? { customer: true } : {}),
|
|
92
97
|
},
|
|
93
98
|
},
|
|
99
|
+
membership: {
|
|
100
|
+
pinned_at: isStarred ? now().toISOString() : null,
|
|
101
|
+
},
|
|
94
102
|
messages:
|
|
95
103
|
lastMessageText || lastMessageAttachments || lastMessageMetadata
|
|
96
104
|
? ([
|
|
@@ -249,6 +257,61 @@ ChatbotMessage.args = {
|
|
|
249
257
|
}),
|
|
250
258
|
}
|
|
251
259
|
|
|
260
|
+
export const ParticipantBadges: StoryFn = () => {
|
|
261
|
+
const channels = [
|
|
262
|
+
createMockChannel({
|
|
263
|
+
id: 'channel-customer',
|
|
264
|
+
participantName: 'Alice Customer',
|
|
265
|
+
participantId: 'participant-customer',
|
|
266
|
+
participantImage: 'https://i.pravatar.cc/150?img=40',
|
|
267
|
+
lastMessageText: 'I bought this from your Linktree',
|
|
268
|
+
lastMessageTime: minutesAgo(2),
|
|
269
|
+
isCustomer: true,
|
|
270
|
+
}),
|
|
271
|
+
createMockChannel({
|
|
272
|
+
id: 'channel-starred',
|
|
273
|
+
participantName: 'Bob Starred',
|
|
274
|
+
participantId: 'participant-starred',
|
|
275
|
+
participantImage: 'https://i.pravatar.cc/150?img=41',
|
|
276
|
+
lastMessageText: 'Following up on this thread',
|
|
277
|
+
lastMessageTime: minutesAgo(8),
|
|
278
|
+
isStarred: true,
|
|
279
|
+
}),
|
|
280
|
+
createMockChannel({
|
|
281
|
+
id: 'channel-customer-starred',
|
|
282
|
+
participantName: 'Carol Customer',
|
|
283
|
+
participantId: 'participant-customer-starred',
|
|
284
|
+
participantImage: 'https://i.pravatar.cc/150?img=42',
|
|
285
|
+
lastMessageText: 'Thanks for the quick reply',
|
|
286
|
+
lastMessageTime: minutesAgo(15),
|
|
287
|
+
isCustomer: true,
|
|
288
|
+
isStarred: true,
|
|
289
|
+
}),
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div className="w-[360px] bg-chalk border border-sand rounded-lg overflow-hidden">
|
|
294
|
+
{channels.map((channel) => (
|
|
295
|
+
<CustomChannelPreview
|
|
296
|
+
key={channel.id}
|
|
297
|
+
channel={channel}
|
|
298
|
+
onChannelSelect={(channel) => {
|
|
299
|
+
console.log('Channel selected:', channel.id)
|
|
300
|
+
}}
|
|
301
|
+
/>
|
|
302
|
+
))}
|
|
303
|
+
</div>
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
ParticipantBadges.parameters = {
|
|
307
|
+
docs: {
|
|
308
|
+
description: {
|
|
309
|
+
story:
|
|
310
|
+
'Shows the built-in customer badge from participant.member customer data and the starred badge from the current user membership pinned_at value.',
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
}
|
|
314
|
+
|
|
252
315
|
export const TipMessage: StoryFn<ComponentProps> = Template.bind({})
|
|
253
316
|
TipMessage.args = {
|
|
254
317
|
channel: createMockChannel({
|
|
@@ -12,12 +12,15 @@ const mockUser = {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
type MockChannel = Channel & {
|
|
15
|
-
emitMemberUpdated: (member?: {
|
|
15
|
+
emitMemberUpdated: (member?: {
|
|
16
|
+
pinned_at?: string | null
|
|
17
|
+
user_id?: string
|
|
18
|
+
}) => void
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
const createMockChannel = (messages: Partial<LocalMessage>[]): MockChannel => {
|
|
19
22
|
const memberUpdatedListeners = new Set<
|
|
20
|
-
(event: { member?: { pinned_at?: string | null } }) => void
|
|
23
|
+
(event: { member?: { pinned_at?: string | null; user_id?: string } }) => void
|
|
21
24
|
>()
|
|
22
25
|
|
|
23
26
|
return {
|
|
@@ -38,24 +41,34 @@ const createMockChannel = (messages: Partial<LocalMessage>[]): MockChannel => {
|
|
|
38
41
|
on: vi.fn(
|
|
39
42
|
(
|
|
40
43
|
eventName: string,
|
|
41
|
-
listener: (event: {
|
|
44
|
+
listener: (event: {
|
|
45
|
+
member?: { pinned_at?: string | null; user_id?: string }
|
|
46
|
+
}) => void
|
|
42
47
|
) => {
|
|
43
48
|
if (eventName === 'member.updated') {
|
|
44
49
|
memberUpdatedListeners.add(listener)
|
|
45
50
|
}
|
|
51
|
+
return {
|
|
52
|
+
unsubscribe: () => memberUpdatedListeners.delete(listener),
|
|
53
|
+
}
|
|
46
54
|
}
|
|
47
55
|
),
|
|
48
56
|
off: vi.fn(
|
|
49
57
|
(
|
|
50
58
|
eventName: string,
|
|
51
|
-
listener: (event: {
|
|
59
|
+
listener: (event: {
|
|
60
|
+
member?: { pinned_at?: string | null; user_id?: string }
|
|
61
|
+
}) => void
|
|
52
62
|
) => {
|
|
53
63
|
if (eventName === 'member.updated') {
|
|
54
64
|
memberUpdatedListeners.delete(listener)
|
|
55
65
|
}
|
|
56
66
|
}
|
|
57
67
|
),
|
|
58
|
-
emitMemberUpdated: (member?: {
|
|
68
|
+
emitMemberUpdated: (member?: {
|
|
69
|
+
pinned_at?: string | null
|
|
70
|
+
user_id?: string
|
|
71
|
+
}) => {
|
|
59
72
|
memberUpdatedListeners.forEach((listener) => listener({ member }))
|
|
60
73
|
},
|
|
61
74
|
} as unknown as MockChannel
|
|
@@ -253,6 +266,21 @@ describe('CustomChannelPreview', () => {
|
|
|
253
266
|
expect(screen.getByText('No messages yet')).toBeInTheDocument()
|
|
254
267
|
})
|
|
255
268
|
|
|
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
|
+
|
|
256
284
|
it('updates starred state when the channel membership changes', () => {
|
|
257
285
|
const channel = createMockChannel([])
|
|
258
286
|
|
|
@@ -261,15 +289,37 @@ describe('CustomChannelPreview', () => {
|
|
|
261
289
|
)
|
|
262
290
|
|
|
263
291
|
expect(
|
|
264
|
-
screen.queryByRole('
|
|
292
|
+
screen.queryByRole('img', { name: 'Starred conversation' })
|
|
265
293
|
).not.toBeInTheDocument()
|
|
266
294
|
|
|
267
295
|
act(() => {
|
|
268
|
-
channel.emitMemberUpdated({
|
|
296
|
+
channel.emitMemberUpdated({
|
|
297
|
+
pinned_at: new Date().toISOString(),
|
|
298
|
+
user_id: mockUser.id,
|
|
299
|
+
})
|
|
269
300
|
})
|
|
270
301
|
|
|
271
302
|
expect(
|
|
272
|
-
screen.getByRole('
|
|
303
|
+
screen.getByRole('img', { name: 'Starred conversation' })
|
|
273
304
|
).toBeInTheDocument()
|
|
274
305
|
})
|
|
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
|
+
})
|
|
275
325
|
})
|
|
@@ -8,6 +8,7 @@ import { getMessageDisplayText } from '../../utils/getMessageDisplayText'
|
|
|
8
8
|
import { resolveParticipantDisplayName } from '../../utils/resolveParticipantDisplayName'
|
|
9
9
|
import { Avatar } from '../Avatar'
|
|
10
10
|
import { isChatbotMessage } from '../CustomMessage/MessageTag'
|
|
11
|
+
import { ParticipantBadges } from '../ParticipantBadges'
|
|
11
12
|
|
|
12
13
|
import { useChannelListContext } from './ChannelListContext'
|
|
13
14
|
|
|
@@ -139,22 +140,18 @@ const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
|
|
|
139
140
|
name={participantName}
|
|
140
141
|
image={participantImage}
|
|
141
142
|
size={48}
|
|
142
|
-
starred={isChannelStarred}
|
|
143
143
|
/>
|
|
144
144
|
|
|
145
145
|
{/* Content column */}
|
|
146
146
|
<div className="flex-1 min-w-0 flex flex-col gap-1">
|
|
147
147
|
{/* Name and timestamp row */}
|
|
148
148
|
<div className="flex items-center justify-between gap-2">
|
|
149
|
-
<h3
|
|
150
|
-
className={
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
<span className="sr-only">Starred conversation. </span>
|
|
156
|
-
)}
|
|
157
|
-
{participantName}
|
|
149
|
+
<h3 className="flex min-w-0 items-center gap-1 text-sm font-medium text-[#191918]">
|
|
150
|
+
<span className="min-w-0 truncate">{participantName}</span>
|
|
151
|
+
<ParticipantBadges
|
|
152
|
+
participant={participant}
|
|
153
|
+
isStarred={isChannelStarred}
|
|
154
|
+
/>
|
|
158
155
|
</h3>
|
|
159
156
|
{lastMessageTime && (
|
|
160
157
|
<span className="text-xs text-[#717070] flex-shrink-0">
|
|
@@ -41,7 +41,14 @@ const createMockChannel = async (
|
|
|
41
41
|
client: StreamChat,
|
|
42
42
|
hasMessages = true,
|
|
43
43
|
followerStatus?: string | boolean,
|
|
44
|
-
isFrozen = false
|
|
44
|
+
isFrozen = false,
|
|
45
|
+
{
|
|
46
|
+
isCustomer = false,
|
|
47
|
+
isStarred = false,
|
|
48
|
+
}: {
|
|
49
|
+
isCustomer?: boolean
|
|
50
|
+
isStarred?: boolean
|
|
51
|
+
} = {}
|
|
45
52
|
) => {
|
|
46
53
|
const participant = mockParticipants[0]
|
|
47
54
|
|
|
@@ -142,8 +149,12 @@ const createMockChannel = async (
|
|
|
142
149
|
user_id: participant.id,
|
|
143
150
|
role: 'member',
|
|
144
151
|
is_account: true,
|
|
152
|
+
...(isCustomer ? { customer: true } : {}),
|
|
145
153
|
},
|
|
146
154
|
}
|
|
155
|
+
channel.state.membership = {
|
|
156
|
+
pinned_at: isStarred ? now().toISOString() : null,
|
|
157
|
+
} as ChannelMemberResponse
|
|
147
158
|
return {
|
|
148
159
|
channel: channelData,
|
|
149
160
|
members: [],
|
|
@@ -171,6 +182,8 @@ const createMockChannel = async (
|
|
|
171
182
|
type TemplateProps = ComponentProps & {
|
|
172
183
|
followerStatus?: string | boolean
|
|
173
184
|
isFrozen?: boolean
|
|
185
|
+
isCustomer?: boolean
|
|
186
|
+
isStarred?: boolean
|
|
174
187
|
typingUser?: {
|
|
175
188
|
id: string
|
|
176
189
|
name?: string
|
|
@@ -182,6 +195,8 @@ const Template: StoryFn<TemplateProps> = (args) => {
|
|
|
182
195
|
const {
|
|
183
196
|
followerStatus,
|
|
184
197
|
isFrozen = false,
|
|
198
|
+
isCustomer = false,
|
|
199
|
+
isStarred = false,
|
|
185
200
|
typingUser,
|
|
186
201
|
...channelViewProps
|
|
187
202
|
} = args
|
|
@@ -197,12 +212,13 @@ const Template: StoryFn<TemplateProps> = (args) => {
|
|
|
197
212
|
const [channel, setChannel] = React.useState<ChannelType | null>(null)
|
|
198
213
|
|
|
199
214
|
useEffect(() => {
|
|
200
|
-
createMockChannel(client, true, followerStatus, isFrozen
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
215
|
+
createMockChannel(client, true, followerStatus, isFrozen, {
|
|
216
|
+
isCustomer,
|
|
217
|
+
isStarred,
|
|
218
|
+
}).then((mockChannel) => {
|
|
219
|
+
setChannel(mockChannel)
|
|
220
|
+
})
|
|
221
|
+
}, [client, followerStatus, isCustomer, isFrozen, isStarred])
|
|
206
222
|
|
|
207
223
|
useEffect(() => {
|
|
208
224
|
if (!channel || !typingUser) {
|
|
@@ -307,6 +323,21 @@ WithHeaderTitleBadges.parameters = {
|
|
|
307
323
|
},
|
|
308
324
|
}
|
|
309
325
|
|
|
326
|
+
export const ParticipantBadges: StoryFn<TemplateProps> = Template.bind({})
|
|
327
|
+
ParticipantBadges.args = {
|
|
328
|
+
showBackButton: false,
|
|
329
|
+
isCustomer: true,
|
|
330
|
+
isStarred: true,
|
|
331
|
+
}
|
|
332
|
+
ParticipantBadges.parameters = {
|
|
333
|
+
docs: {
|
|
334
|
+
description: {
|
|
335
|
+
story:
|
|
336
|
+
'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.',
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
}
|
|
340
|
+
|
|
310
341
|
export const RestrictedOfficialChannel: StoryFn<TemplateProps> = Template.bind(
|
|
311
342
|
{}
|
|
312
343
|
)
|
|
@@ -98,9 +98,11 @@ const createChannel = ({
|
|
|
98
98
|
username: undefined as string | undefined,
|
|
99
99
|
is_account: true,
|
|
100
100
|
},
|
|
101
|
+
otherMemberCustomer = false,
|
|
101
102
|
}: {
|
|
102
103
|
currentUserId?: 'visitor-1' | 'linker-1'
|
|
103
104
|
currentUserRole?: 'owner' | 'member'
|
|
105
|
+
otherMemberCustomer?: boolean
|
|
104
106
|
otherUser?: {
|
|
105
107
|
id: string
|
|
106
108
|
name?: string
|
|
@@ -133,6 +135,7 @@ const createChannel = ({
|
|
|
133
135
|
otherUser.is_account ?? currentUserId === 'visitor-1',
|
|
134
136
|
},
|
|
135
137
|
role: currentUserRole === 'owner' ? 'member' : 'owner',
|
|
138
|
+
customer: otherMemberCustomer,
|
|
136
139
|
},
|
|
137
140
|
},
|
|
138
141
|
membership: {},
|
|
@@ -287,38 +290,49 @@ describe('ChannelView', () => {
|
|
|
287
290
|
expect(screen.queryByTestId('conversation-footer')).not.toBeInTheDocument()
|
|
288
291
|
})
|
|
289
292
|
|
|
290
|
-
it('does not pass starred to avatar when
|
|
293
|
+
it('does not pass starred to avatar when channel is starred', () => {
|
|
291
294
|
mockIsStarred = true
|
|
292
295
|
const channel = createChannel()
|
|
293
296
|
|
|
294
297
|
renderWithProviders(
|
|
295
|
-
<ChannelView channel={channel} showStarButton={
|
|
298
|
+
<ChannelView channel={channel} showStarButton={true} />
|
|
296
299
|
)
|
|
297
300
|
|
|
298
301
|
expect(avatarRenderCalls.length).toBeGreaterThan(0)
|
|
299
|
-
expect(avatarRenderCalls.every((call) =>
|
|
302
|
+
expect(avatarRenderCalls.every((call) => !('starred' in call))).toBe(true)
|
|
300
303
|
})
|
|
301
304
|
|
|
302
|
-
it('
|
|
303
|
-
mockIsStarred = true
|
|
304
|
-
const channel = createChannel()
|
|
305
|
-
|
|
305
|
+
it('renders built-in customer badges from participant member data', () => {
|
|
306
306
|
renderWithProviders(
|
|
307
|
-
<ChannelView
|
|
307
|
+
<ChannelView
|
|
308
|
+
channel={createChannel({ otherMemberCustomer: true })}
|
|
309
|
+
renderHeaderTitleBadges={() => (
|
|
310
|
+
<span data-testid="custom-header-badge">VIP</span>
|
|
311
|
+
)}
|
|
312
|
+
/>
|
|
308
313
|
)
|
|
309
314
|
|
|
310
|
-
|
|
311
|
-
|
|
315
|
+
const customerBadges = screen.getAllByRole('img', { name: 'Customer' })
|
|
316
|
+
const customBadges = screen.getAllByTestId('custom-header-badge')
|
|
317
|
+
|
|
318
|
+
expect(customerBadges.length).toBeGreaterThan(0)
|
|
319
|
+
expect(customBadges.length).toBe(customerBadges.length)
|
|
320
|
+
customerBadges.forEach((badge, index) => {
|
|
321
|
+
expect(
|
|
322
|
+
badge.compareDocumentPosition(customBadges[index]) &
|
|
323
|
+
Node.DOCUMENT_POSITION_FOLLOWING
|
|
324
|
+
).toBeTruthy()
|
|
325
|
+
})
|
|
312
326
|
})
|
|
313
327
|
|
|
314
|
-
it('
|
|
328
|
+
it('renders built-in starred badges from channel star state', () => {
|
|
315
329
|
mockIsStarred = true
|
|
316
|
-
const channel = createChannel()
|
|
317
330
|
|
|
318
|
-
renderWithProviders(<ChannelView channel={
|
|
331
|
+
renderWithProviders(<ChannelView channel={createChannel()} />)
|
|
319
332
|
|
|
320
|
-
expect(
|
|
321
|
-
|
|
333
|
+
expect(
|
|
334
|
+
screen.getAllByRole('img', { name: 'Starred conversation' }).length
|
|
335
|
+
).toBeGreaterThan(0)
|
|
322
336
|
})
|
|
323
337
|
|
|
324
338
|
it('passes dmAgentEnabled to avatar when dmAgentEnabled is true for visitor view', () => {
|