@linktr.ee/messaging-react 4.2.0-rc-1787083791 → 4.2.0-rc-1787181113
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-B4lMv-0o.js → Card-BOudQBd6.js} +2 -2
- package/dist/{Card-B4lMv-0o.js.map → Card-BOudQBd6.js.map} +1 -1
- package/dist/{Card-DWL5I62S.cjs → Card-k2959zNX.cjs} +2 -2
- package/dist/{Card-DWL5I62S.cjs.map → Card-k2959zNX.cjs.map} +1 -1
- package/dist/assets/index.css +1 -1
- package/dist/{index-C93LTGLc.js → index-6RdyZh6F.js} +1809 -1912
- package/dist/index-6RdyZh6F.js.map +1 -0
- package/dist/index-BcU18EjV.cjs +3 -0
- package/dist/index-BcU18EjV.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +78 -59
- package/dist/index.js +30 -26
- package/dist/testing.cjs +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +2 -2
- package/dist/testing.js.map +1 -1
- package/package.json +2 -2
- package/src/components/AttachmentCard/MediaPlayer.test.tsx +346 -0
- package/src/components/AttachmentCard/Thumbnail.test.tsx +215 -0
- package/src/components/AttachmentCard/index.test.tsx +160 -0
- package/src/components/Avatar/Avatar.test.tsx +126 -0
- package/src/components/Avatar/getAvatarEmoji.test.ts +39 -0
- package/src/components/ChannelList/ChannelListContext.test.tsx +87 -0
- package/src/components/ChannelList/CustomChannelPreview.test.tsx +410 -3
- package/src/components/ChannelList/index.test.tsx +132 -1
- package/src/components/CustomMessage/CustomMessage.stories.tsx +6 -314
- package/src/components/CustomMessage/CustomMessage.test.tsx +56 -2
- package/src/components/CustomMessage/Locked.stories.tsx +108 -0
- package/src/components/CustomMessage/MessageTag.stories.tsx +17 -53
- package/src/components/CustomMessage/MessageTag.test.tsx +7 -40
- package/src/components/CustomMessage/MessageTag.tsx +7 -39
- package/src/components/CustomMessage/PaidMessage.stories.tsx +42 -0
- package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +8 -7
- package/src/components/CustomMessage/SentMessageDeliveryStatus.tsx +2 -1
- package/src/components/CustomMessage/StreamAttachmentMessage.test.tsx +0 -33
- package/src/components/CustomMessage/StreamAttachmentMessage.tsx +0 -6
- package/src/components/CustomMessage/Tip.stories.tsx +48 -0
- package/src/components/CustomMessage/TipMessage/TipMessage.stories.tsx +51 -0
- package/src/components/CustomMessage/TipMessage/TipMessage.test.tsx +65 -0
- package/src/components/CustomMessage/TipMessage/index.tsx +27 -0
- package/src/components/CustomMessage/index.tsx +22 -19
- package/src/components/FaqList/FaqList.test.tsx +204 -0
- package/src/components/LinkAttachment/LinkAttachment.test.tsx +327 -0
- package/src/components/LinkAttachment/components/_shared/BubbleTail.test.tsx +76 -0
- package/src/components/LinkAttachment/components/_shared/CardBody.test.tsx +248 -0
- package/src/components/LinkAttachment/components/_shared/CardCta.test.tsx +123 -0
- package/src/components/LinkAttachment/components/_shared/CardShell.test.tsx +193 -0
- package/src/components/LinkAttachment/components/_shared/CardThumbnail.test.tsx +242 -0
- package/src/components/LinkAttachment/components/_shared/hex.test.ts +37 -0
- package/src/components/LinkAttachment/components/_shared/normalizeExternalHref.test.ts +72 -0
- package/src/components/LinkAttachment/components/_shared/useChinPalette.test.ts +162 -0
- package/src/components/MessageAttachment/Audio/AudioAttachment.stories.tsx +58 -165
- package/src/components/MessageAttachment/Audio/index.tsx +77 -71
- package/src/components/MessageAttachment/MessageAttachment.test.tsx +74 -176
- package/src/components/MessageAttachment/index.tsx +9 -25
- package/src/components/MessageAttachment/types.ts +9 -19
- package/src/components/MessagingShell/MessagingShell.test.tsx +20 -0
- package/src/components/MessagingShell/index.tsx +2 -0
- package/src/index.ts +4 -0
- package/src/logging/index.test.tsx +136 -0
- package/src/logging/index.tsx +48 -0
- package/src/providers/MessagingProvider.test.tsx +113 -1
- package/src/providers/MessagingProvider.tsx +25 -15
- package/src/stories/customMessageStoryTemplate.tsx +199 -0
- package/src/styles.css +10 -121
- package/src/testing/createMockMessagingClient.ts +2 -2
- package/src/types.ts +9 -0
- package/dist/index-B-9LkWep.cjs +0 -3
- package/dist/index-B-9LkWep.cjs.map +0 -1
- package/dist/index-C93LTGLc.js.map +0 -1
- package/src/components/MessageAttachment/Audio/WaveformAudioRow.tsx +0 -335
|
@@ -2,11 +2,7 @@ import '../../stream-custom-data'
|
|
|
2
2
|
|
|
3
3
|
import type { Meta, StoryFn } from '@storybook/react'
|
|
4
4
|
import React, { useEffect } from 'react'
|
|
5
|
-
import {
|
|
6
|
-
Channel as ChannelType,
|
|
7
|
-
QueryChannelAPIResponse,
|
|
8
|
-
StreamChat,
|
|
9
|
-
} from 'stream-chat'
|
|
5
|
+
import { Channel as ChannelType } from 'stream-chat'
|
|
10
6
|
import {
|
|
11
7
|
Channel,
|
|
12
8
|
Chat,
|
|
@@ -16,18 +12,20 @@ import {
|
|
|
16
12
|
WithComponents,
|
|
17
13
|
} from 'stream-chat-react'
|
|
18
14
|
|
|
19
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
Template,
|
|
17
|
+
createMockChannel,
|
|
18
|
+
type TemplateProps,
|
|
19
|
+
} from '../../stories/customMessageStoryTemplate'
|
|
20
20
|
import {
|
|
21
21
|
currentUserArgType,
|
|
22
22
|
StoryUser,
|
|
23
23
|
storyUsers,
|
|
24
24
|
} from '../../stories/decorators/storyUser'
|
|
25
25
|
import { createMockStreamChatClient } from '../../testing/createMockStreamChatClient'
|
|
26
|
-
import type { OutboundResolver } from '../../types'
|
|
27
26
|
import CustomTypingIndicator from '../CustomTypingIndicator'
|
|
28
27
|
|
|
29
28
|
import { CustomMessageActions } from './CustomMessageActions'
|
|
30
|
-
import { OutboundContext, buildOutboundsByRecordId } from './OutboundContext'
|
|
31
29
|
|
|
32
30
|
import { CustomMessage } from './index'
|
|
33
31
|
|
|
@@ -42,180 +40,6 @@ const meta: Meta = {
|
|
|
42
40
|
}
|
|
43
41
|
export default meta
|
|
44
42
|
|
|
45
|
-
const createMockChannel = async (
|
|
46
|
-
client: StreamChat,
|
|
47
|
-
messages: TemplateProps['messages']
|
|
48
|
-
) => {
|
|
49
|
-
const mockMessages = messages.map((msg, index) => ({
|
|
50
|
-
...msg,
|
|
51
|
-
type: msg.type ?? ('regular' as const),
|
|
52
|
-
created_at: minutesAgo(messages.length - index),
|
|
53
|
-
updated_at: minutesAgo(messages.length - index),
|
|
54
|
-
attachments: msg.attachments ?? [],
|
|
55
|
-
latest_reactions: [],
|
|
56
|
-
own_reactions: [],
|
|
57
|
-
reaction_counts: {},
|
|
58
|
-
reaction_scores: {},
|
|
59
|
-
reply_count: 0,
|
|
60
|
-
status: 'received',
|
|
61
|
-
cid: 'messaging:storybook-channel-1',
|
|
62
|
-
mentioned_users: msg.mentioned_users ?? [],
|
|
63
|
-
}))
|
|
64
|
-
|
|
65
|
-
const channelData = {
|
|
66
|
-
members: [storyUsers.creator.id, storyUsers.visitor.id],
|
|
67
|
-
// Required for useUserRole: canDelete needs delete-own-message, canFlag needs flag-message
|
|
68
|
-
own_capabilities: ['delete-own-message', 'flag-message'],
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const channel = client.channel(
|
|
72
|
-
'messaging',
|
|
73
|
-
'storybook-channel-1',
|
|
74
|
-
channelData
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
channel.watch = async () => {
|
|
78
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
-
channel.state.messages = mockMessages as unknown as any[]
|
|
80
|
-
channel.state.members = {
|
|
81
|
-
[storyUsers.creator.id]: {
|
|
82
|
-
user: storyUsers.creator,
|
|
83
|
-
user_id: storyUsers.creator.id,
|
|
84
|
-
},
|
|
85
|
-
[storyUsers.visitor.id]: {
|
|
86
|
-
user: storyUsers.visitor,
|
|
87
|
-
user_id: storyUsers.visitor.id,
|
|
88
|
-
},
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
channel: channelData,
|
|
92
|
-
members: [],
|
|
93
|
-
messages: mockMessages,
|
|
94
|
-
watchers: [],
|
|
95
|
-
pinned_messages: [],
|
|
96
|
-
duration: '0ms',
|
|
97
|
-
} as unknown as QueryChannelAPIResponse
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
await channel.watch()
|
|
102
|
-
} catch {
|
|
103
|
-
// Ignore errors in mock mode
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return channel
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
interface TemplateProps {
|
|
110
|
-
currentUser: StoryUser
|
|
111
|
-
typingUser?: StoryUser
|
|
112
|
-
resolveOutbound?: OutboundResolver
|
|
113
|
-
messages: Array<{
|
|
114
|
-
id: string
|
|
115
|
-
text: string
|
|
116
|
-
user: StoryUser
|
|
117
|
-
mentioned_users?: StoryUser[]
|
|
118
|
-
type?: 'regular' | 'system'
|
|
119
|
-
attachments?: Array<Record<string, unknown>>
|
|
120
|
-
metadata?: {
|
|
121
|
-
custom_type?:
|
|
122
|
-
| 'MESSAGE_TIP'
|
|
123
|
-
| 'MESSAGE_PAID'
|
|
124
|
-
| 'MESSAGE_CHATBOT'
|
|
125
|
-
| 'MESSAGE_ATTACHMENT'
|
|
126
|
-
| 'MESSAGE_OUTBOUND'
|
|
127
|
-
payment_status?: 'pending' | 'paid' | 'failed' | 'refunded'
|
|
128
|
-
amount_text?: string
|
|
129
|
-
attachment_title?: string
|
|
130
|
-
attachment_mime_type?: string
|
|
131
|
-
attachment_thumbnail?: string
|
|
132
|
-
attachment_detail?: string
|
|
133
|
-
attachment_content_type?: 'text' | 'media'
|
|
134
|
-
outbound_id?: string
|
|
135
|
-
}
|
|
136
|
-
}>
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const TemplateInner: React.FC<{
|
|
140
|
-
currentUser: StoryUser
|
|
141
|
-
typingUser?: StoryUser
|
|
142
|
-
messages: TemplateProps['messages']
|
|
143
|
-
resolveOutbound?: OutboundResolver
|
|
144
|
-
}> = ({ currentUser, typingUser, messages, resolveOutbound }) => {
|
|
145
|
-
const [client] = React.useState(() => createMockStreamChatClient(currentUser))
|
|
146
|
-
|
|
147
|
-
const [channel, setChannel] = React.useState<ChannelType | null>(null)
|
|
148
|
-
|
|
149
|
-
useEffect(() => {
|
|
150
|
-
createMockChannel(client, messages).then(setChannel)
|
|
151
|
-
}, [client, messages])
|
|
152
|
-
|
|
153
|
-
useEffect(() => {
|
|
154
|
-
if (!channel || !typingUser) {
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Stream Channel UI only updates typing context from typing.start/typing.stop events.
|
|
159
|
-
const timer = setTimeout(() => {
|
|
160
|
-
client.dispatchEvent({
|
|
161
|
-
type: 'typing.start',
|
|
162
|
-
cid: channel.cid,
|
|
163
|
-
user: typingUser,
|
|
164
|
-
})
|
|
165
|
-
}, 0)
|
|
166
|
-
|
|
167
|
-
return () => clearTimeout(timer)
|
|
168
|
-
}, [channel, client, typingUser])
|
|
169
|
-
|
|
170
|
-
const outboundsByRecordId = React.useMemo(
|
|
171
|
-
() => buildOutboundsByRecordId(messages, resolveOutbound),
|
|
172
|
-
[messages, resolveOutbound]
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
const MessageComponent = React.useMemo(() => {
|
|
176
|
-
return function CustomMessageComponent(props: MessageUIComponentProps) {
|
|
177
|
-
return <CustomMessage {...props} onOutboundClick={() => undefined} />
|
|
178
|
-
}
|
|
179
|
-
}, [])
|
|
180
|
-
|
|
181
|
-
if (!channel) {
|
|
182
|
-
return <div className="p-4">Loading...</div>
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return (
|
|
186
|
-
<OutboundContext.Provider value={outboundsByRecordId}>
|
|
187
|
-
<Chat client={client}>
|
|
188
|
-
<div className="h-screen w-full bg-white">
|
|
189
|
-
<Channel
|
|
190
|
-
channel={channel}
|
|
191
|
-
Message={MessageComponent}
|
|
192
|
-
TypingIndicator={CustomTypingIndicator}
|
|
193
|
-
>
|
|
194
|
-
<Window>
|
|
195
|
-
<MessageList />
|
|
196
|
-
</Window>
|
|
197
|
-
</Channel>
|
|
198
|
-
</div>
|
|
199
|
-
</Chat>
|
|
200
|
-
</OutboundContext.Provider>
|
|
201
|
-
)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const Template: StoryFn<TemplateProps> = ({
|
|
205
|
-
currentUser = storyUsers.creator,
|
|
206
|
-
typingUser,
|
|
207
|
-
messages,
|
|
208
|
-
resolveOutbound,
|
|
209
|
-
}) => (
|
|
210
|
-
<TemplateInner
|
|
211
|
-
key={currentUser.id}
|
|
212
|
-
currentUser={currentUser}
|
|
213
|
-
typingUser={typingUser}
|
|
214
|
-
messages={messages}
|
|
215
|
-
resolveOutbound={resolveOutbound}
|
|
216
|
-
/>
|
|
217
|
-
)
|
|
218
|
-
|
|
219
43
|
export const Default: StoryFn<TemplateProps> = Template.bind({})
|
|
220
44
|
Default.args = {
|
|
221
45
|
messages: [
|
|
@@ -342,28 +166,6 @@ OutboundStatusResolved.args = {
|
|
|
342
166
|
}),
|
|
343
167
|
}
|
|
344
168
|
|
|
345
|
-
export const WithTipTag: StoryFn<TemplateProps> = Template.bind({})
|
|
346
|
-
WithTipTag.args = {
|
|
347
|
-
messages: [
|
|
348
|
-
{
|
|
349
|
-
id: 'msg-1',
|
|
350
|
-
text: 'Love your content!',
|
|
351
|
-
user: storyUsers.visitor,
|
|
352
|
-
},
|
|
353
|
-
{
|
|
354
|
-
id: 'msg-2',
|
|
355
|
-
text: "Here's a tip for you! Keep up the great work.",
|
|
356
|
-
user: storyUsers.visitor,
|
|
357
|
-
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$5.50' },
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
id: 'msg-3',
|
|
361
|
-
text: 'Thank you so much! 🙏',
|
|
362
|
-
user: storyUsers.creator,
|
|
363
|
-
},
|
|
364
|
-
],
|
|
365
|
-
}
|
|
366
|
-
|
|
367
169
|
export const ThreeConsecutiveSentMessages: StoryFn<TemplateProps> =
|
|
368
170
|
Template.bind({})
|
|
369
171
|
ThreeConsecutiveSentMessages.args = {
|
|
@@ -391,28 +193,6 @@ ThreeConsecutiveSentMessages.args = {
|
|
|
391
193
|
],
|
|
392
194
|
}
|
|
393
195
|
|
|
394
|
-
export const TipOnly: StoryFn<TemplateProps> = Template.bind({})
|
|
395
|
-
TipOnly.args = {
|
|
396
|
-
messages: [
|
|
397
|
-
{
|
|
398
|
-
id: 'msg-1',
|
|
399
|
-
text: 'Hello!',
|
|
400
|
-
user: storyUsers.visitor,
|
|
401
|
-
},
|
|
402
|
-
{
|
|
403
|
-
id: 'msg-2',
|
|
404
|
-
text: '',
|
|
405
|
-
user: storyUsers.visitor,
|
|
406
|
-
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$50.00' },
|
|
407
|
-
},
|
|
408
|
-
{
|
|
409
|
-
id: 'msg-3',
|
|
410
|
-
text: 'Wow, thank you for the tip!',
|
|
411
|
-
user: storyUsers.creator,
|
|
412
|
-
},
|
|
413
|
-
],
|
|
414
|
-
}
|
|
415
|
-
|
|
416
196
|
export const MixedTags: StoryFn<TemplateProps> = Template.bind({})
|
|
417
197
|
MixedTags.args = {
|
|
418
198
|
messages: [
|
|
@@ -468,94 +248,6 @@ MixedTags.args = {
|
|
|
468
248
|
],
|
|
469
249
|
}
|
|
470
250
|
|
|
471
|
-
export const LockedAttachment: StoryFn<TemplateProps> = Template.bind({})
|
|
472
|
-
LockedAttachment.args = {
|
|
473
|
-
messages: [
|
|
474
|
-
{
|
|
475
|
-
id: 'msg-1',
|
|
476
|
-
text: 'Can I have your workout plan?',
|
|
477
|
-
user: storyUsers.visitor,
|
|
478
|
-
},
|
|
479
|
-
{
|
|
480
|
-
id: 'msg-2',
|
|
481
|
-
text: 'Yes, of course!',
|
|
482
|
-
user: storyUsers.creator,
|
|
483
|
-
},
|
|
484
|
-
{
|
|
485
|
-
id: 'msg-3',
|
|
486
|
-
text: 'let me know if you have any questions!',
|
|
487
|
-
user: storyUsers.creator,
|
|
488
|
-
metadata: {
|
|
489
|
-
custom_type: 'MESSAGE_ATTACHMENT',
|
|
490
|
-
amount_text: 'AU$9.99',
|
|
491
|
-
attachment_title: "Alicia's Workout Plan",
|
|
492
|
-
attachment_mime_type: 'video/mp4',
|
|
493
|
-
attachment_thumbnail: '/video-thumbnail-blurred.jpg',
|
|
494
|
-
attachment_detail: '1:20',
|
|
495
|
-
},
|
|
496
|
-
},
|
|
497
|
-
{
|
|
498
|
-
id: 'msg-4',
|
|
499
|
-
text: 'Looks amazing, unlocking now!',
|
|
500
|
-
user: storyUsers.visitor,
|
|
501
|
-
},
|
|
502
|
-
],
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
export const LockedTextAttachment: StoryFn<TemplateProps> = Template.bind({})
|
|
506
|
-
LockedTextAttachment.args = {
|
|
507
|
-
messages: [
|
|
508
|
-
{
|
|
509
|
-
id: 'msg-1',
|
|
510
|
-
text: 'Can you send me the details privately?',
|
|
511
|
-
user: storyUsers.visitor,
|
|
512
|
-
},
|
|
513
|
-
{
|
|
514
|
-
id: 'msg-2',
|
|
515
|
-
// Empty text: the real content is redacted server-side until purchase,
|
|
516
|
-
// so the bubble renders the blurred placeholder instead.
|
|
517
|
-
text: '',
|
|
518
|
-
user: storyUsers.creator,
|
|
519
|
-
metadata: {
|
|
520
|
-
custom_type: 'MESSAGE_ATTACHMENT',
|
|
521
|
-
amount_text: 'AU$4.99',
|
|
522
|
-
attachment_content_type: 'text',
|
|
523
|
-
},
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
id: 'msg-3',
|
|
527
|
-
text: '',
|
|
528
|
-
user: storyUsers.creator,
|
|
529
|
-
metadata: {
|
|
530
|
-
custom_type: 'MESSAGE_ATTACHMENT',
|
|
531
|
-
amount_text: 'AU$4.99',
|
|
532
|
-
payment_status: 'paid',
|
|
533
|
-
attachment_content_type: 'text',
|
|
534
|
-
},
|
|
535
|
-
},
|
|
536
|
-
{
|
|
537
|
-
id: 'msg-4',
|
|
538
|
-
// Buyer reveal: backend writes the real text back onto the message.
|
|
539
|
-
text: "Here's the address — see you Thursday at 7!",
|
|
540
|
-
user: storyUsers.creator,
|
|
541
|
-
metadata: {
|
|
542
|
-
custom_type: 'MESSAGE_ATTACHMENT',
|
|
543
|
-
amount_text: 'AU$4.99',
|
|
544
|
-
payment_status: 'paid',
|
|
545
|
-
attachment_content_type: 'text',
|
|
546
|
-
},
|
|
547
|
-
},
|
|
548
|
-
],
|
|
549
|
-
}
|
|
550
|
-
LockedTextAttachment.parameters = {
|
|
551
|
-
docs: {
|
|
552
|
-
description: {
|
|
553
|
-
story:
|
|
554
|
-
'A paid text-only message renders as a regular bubble with blurred placeholder copy (msg-2: pending, msg-3: purchased-but-not-yet-revealed) until the real text is revealed (msg-4).',
|
|
555
|
-
},
|
|
556
|
-
},
|
|
557
|
-
}
|
|
558
|
-
|
|
559
251
|
export const ChatbotVariants: StoryFn<TemplateProps> = Template.bind({})
|
|
560
252
|
ChatbotVariants.args = {
|
|
561
253
|
messages: [
|
|
@@ -5,6 +5,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
5
5
|
import { renderWithProviders, screen } from '../../test/utils'
|
|
6
6
|
|
|
7
7
|
import { OutboundContext } from './OutboundContext'
|
|
8
|
+
import { isTipMessage } from './TipMessage'
|
|
8
9
|
|
|
9
10
|
import { CustomMessage } from './index'
|
|
10
11
|
|
|
@@ -58,12 +59,16 @@ vi.mock('./MessageTag', () => ({
|
|
|
58
59
|
isAttachmentMessage: () => false,
|
|
59
60
|
isChatbotMessage: () => false,
|
|
60
61
|
isMediaAttachmentMessage: () => false,
|
|
62
|
+
isPaidMessage: () => false,
|
|
61
63
|
isTextAttachmentMessage: () => false,
|
|
62
|
-
isTipOnlyMessage: () => false,
|
|
63
|
-
isTipMessage: () => false,
|
|
64
64
|
hasPaidDeliveryTag: () => false,
|
|
65
65
|
}))
|
|
66
66
|
|
|
67
|
+
vi.mock('./TipMessage', () => ({
|
|
68
|
+
TipMessage: () => null,
|
|
69
|
+
isTipMessage: vi.fn(() => false),
|
|
70
|
+
}))
|
|
71
|
+
|
|
67
72
|
vi.mock('./LockedAttachment', () => ({
|
|
68
73
|
default: () => null,
|
|
69
74
|
}))
|
|
@@ -79,9 +84,11 @@ vi.mock('../MediaMessage', () => ({
|
|
|
79
84
|
|
|
80
85
|
const mockedUseChannelStateContext = vi.mocked(useChannelStateContext)
|
|
81
86
|
const mockedUseMessageContext = vi.mocked(useMessageContext)
|
|
87
|
+
const mockedIsTipMessage = vi.mocked(isTipMessage)
|
|
82
88
|
|
|
83
89
|
describe('CustomMessage', () => {
|
|
84
90
|
beforeEach(() => {
|
|
91
|
+
mockedIsTipMessage.mockReturnValue(false)
|
|
85
92
|
mockedUseChannelStateContext.mockReturnValue({
|
|
86
93
|
messages: [{ id: 'message-1', user: { id: 'me' } }],
|
|
87
94
|
} as never)
|
|
@@ -207,4 +214,51 @@ describe('CustomMessage', () => {
|
|
|
207
214
|
screen.getByTestId('sent-message-outbound-status')
|
|
208
215
|
).toHaveTextContent('July 9th Class absentee')
|
|
209
216
|
})
|
|
217
|
+
|
|
218
|
+
it('does not render an accompanying text bubble for a whitespace-only tip message', () => {
|
|
219
|
+
mockedIsTipMessage.mockReturnValue(true)
|
|
220
|
+
mockedUseMessageContext.mockReturnValue({
|
|
221
|
+
message: {
|
|
222
|
+
id: 'message-1',
|
|
223
|
+
text: ' \n ',
|
|
224
|
+
type: 'regular',
|
|
225
|
+
status: 'received',
|
|
226
|
+
user: { id: 'me', name: 'Me' },
|
|
227
|
+
metadata: {},
|
|
228
|
+
},
|
|
229
|
+
isMyMessage: () => true,
|
|
230
|
+
threadList: false,
|
|
231
|
+
editing: false,
|
|
232
|
+
endOfGroup: true,
|
|
233
|
+
firstOfGroup: true,
|
|
234
|
+
groupedByUser: true,
|
|
235
|
+
handleAction: vi.fn(),
|
|
236
|
+
handleOpenThread: vi.fn(),
|
|
237
|
+
handleRetry: vi.fn(),
|
|
238
|
+
highlighted: false,
|
|
239
|
+
renderText: undefined,
|
|
240
|
+
isMessageAIGenerated: undefined,
|
|
241
|
+
} as never)
|
|
242
|
+
|
|
243
|
+
renderWithProviders(
|
|
244
|
+
<OutboundContext.Provider value={{}}>
|
|
245
|
+
<CustomMessage />
|
|
246
|
+
</OutboundContext.Provider>
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
expect(screen.queryByText('Message')).not.toBeInTheDocument()
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('renders an accompanying text bubble for a tip message with real text', () => {
|
|
253
|
+
mockedIsTipMessage.mockReturnValue(true)
|
|
254
|
+
// Default mocked message.text ('Hello') from beforeEach.
|
|
255
|
+
|
|
256
|
+
renderWithProviders(
|
|
257
|
+
<OutboundContext.Provider value={{}}>
|
|
258
|
+
<CustomMessage />
|
|
259
|
+
</OutboundContext.Provider>
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
expect(screen.getByText('Message')).toBeInTheDocument()
|
|
263
|
+
})
|
|
210
264
|
})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Meta } from '@storybook/react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Template,
|
|
5
|
+
type TemplateProps,
|
|
6
|
+
} from '../../stories/customMessageStoryTemplate'
|
|
7
|
+
import {
|
|
8
|
+
currentUserArgType,
|
|
9
|
+
storyUsers,
|
|
10
|
+
} from '../../stories/decorators/storyUser'
|
|
11
|
+
|
|
12
|
+
const meta: Meta = {
|
|
13
|
+
title: 'CustomMessage/Locked Attachment',
|
|
14
|
+
argTypes: currentUserArgType,
|
|
15
|
+
args: { currentUser: storyUsers.creator },
|
|
16
|
+
parameters: {
|
|
17
|
+
layout: 'fullscreen',
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
export default meta
|
|
21
|
+
|
|
22
|
+
export const LockedAttachment = Template.bind({})
|
|
23
|
+
LockedAttachment.args = {
|
|
24
|
+
messages: [
|
|
25
|
+
{
|
|
26
|
+
id: 'msg-1',
|
|
27
|
+
text: 'Can I have your workout plan?',
|
|
28
|
+
user: storyUsers.visitor,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'msg-2',
|
|
32
|
+
text: 'Yes, of course!',
|
|
33
|
+
user: storyUsers.creator,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'msg-3',
|
|
37
|
+
text: 'let me know if you have any questions!',
|
|
38
|
+
user: storyUsers.creator,
|
|
39
|
+
metadata: {
|
|
40
|
+
custom_type: 'MESSAGE_ATTACHMENT',
|
|
41
|
+
amount_text: 'AU$9.99',
|
|
42
|
+
attachment_title: "Alicia's Workout Plan",
|
|
43
|
+
attachment_mime_type: 'video/mp4',
|
|
44
|
+
attachment_thumbnail: '/video-thumbnail-blurred.jpg',
|
|
45
|
+
attachment_detail: '1:20',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'msg-4',
|
|
50
|
+
text: 'Looks amazing, unlocking now!',
|
|
51
|
+
user: storyUsers.visitor,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
} satisfies TemplateProps
|
|
55
|
+
|
|
56
|
+
export const LockedTextAttachment = Template.bind({})
|
|
57
|
+
LockedTextAttachment.args = {
|
|
58
|
+
messages: [
|
|
59
|
+
{
|
|
60
|
+
id: 'msg-1',
|
|
61
|
+
text: 'Can you send me the details privately?',
|
|
62
|
+
user: storyUsers.visitor,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'msg-2',
|
|
66
|
+
// Empty text: the real content is redacted server-side until purchase,
|
|
67
|
+
// so the bubble renders the blurred placeholder instead.
|
|
68
|
+
text: '',
|
|
69
|
+
user: storyUsers.creator,
|
|
70
|
+
metadata: {
|
|
71
|
+
custom_type: 'MESSAGE_ATTACHMENT',
|
|
72
|
+
amount_text: 'AU$4.99',
|
|
73
|
+
attachment_content_type: 'text',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'msg-3',
|
|
78
|
+
text: '',
|
|
79
|
+
user: storyUsers.creator,
|
|
80
|
+
metadata: {
|
|
81
|
+
custom_type: 'MESSAGE_ATTACHMENT',
|
|
82
|
+
amount_text: 'AU$4.99',
|
|
83
|
+
payment_status: 'paid',
|
|
84
|
+
attachment_content_type: 'text',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'msg-4',
|
|
89
|
+
// Buyer reveal: backend writes the real text back onto the message.
|
|
90
|
+
text: "Here's the address — see you Thursday at 7!",
|
|
91
|
+
user: storyUsers.creator,
|
|
92
|
+
metadata: {
|
|
93
|
+
custom_type: 'MESSAGE_ATTACHMENT',
|
|
94
|
+
amount_text: 'AU$4.99',
|
|
95
|
+
payment_status: 'paid',
|
|
96
|
+
attachment_content_type: 'text',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
} satisfies TemplateProps
|
|
101
|
+
LockedTextAttachment.parameters = {
|
|
102
|
+
docs: {
|
|
103
|
+
description: {
|
|
104
|
+
story:
|
|
105
|
+
'A paid text-only message renders as a regular bubble with blurred placeholder copy (msg-2: pending, msg-3: purchased-but-not-yet-revealed) until the real text is revealed (msg-4).',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
}
|
|
@@ -19,7 +19,7 @@ export default meta
|
|
|
19
19
|
|
|
20
20
|
interface MockMessageOptions {
|
|
21
21
|
metadata?: {
|
|
22
|
-
custom_type?: '
|
|
22
|
+
custom_type?: 'MESSAGE_CHATBOT' | 'MESSAGE_PAID'
|
|
23
23
|
amount_text?: string
|
|
24
24
|
}
|
|
25
25
|
text?: string
|
|
@@ -48,32 +48,6 @@ const Template: StoryFn<ComponentProps> = (args) => {
|
|
|
48
48
|
// the variant matrix as the single snapshot source of truth.
|
|
49
49
|
const skipInChromatic = { chromatic: { disableSnapshot: true } }
|
|
50
50
|
|
|
51
|
-
export const Tip: StoryFn<ComponentProps> = Template.bind({})
|
|
52
|
-
Tip.args = {
|
|
53
|
-
message: createMockMessage({
|
|
54
|
-
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$5.50' },
|
|
55
|
-
}),
|
|
56
|
-
}
|
|
57
|
-
Tip.parameters = skipInChromatic
|
|
58
|
-
|
|
59
|
-
export const TipStandalone: StoryFn<ComponentProps> = Template.bind({})
|
|
60
|
-
TipStandalone.args = {
|
|
61
|
-
message: createMockMessage({
|
|
62
|
-
text: '',
|
|
63
|
-
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$50.00' },
|
|
64
|
-
}),
|
|
65
|
-
standalone: true,
|
|
66
|
-
}
|
|
67
|
-
TipStandalone.parameters = skipInChromatic
|
|
68
|
-
|
|
69
|
-
export const Paid: StoryFn<ComponentProps> = Template.bind({})
|
|
70
|
-
Paid.args = {
|
|
71
|
-
message: createMockMessage({
|
|
72
|
-
metadata: { custom_type: 'MESSAGE_PAID', amount_text: '$25.00' },
|
|
73
|
-
}),
|
|
74
|
-
}
|
|
75
|
-
Paid.parameters = skipInChromatic
|
|
76
|
-
|
|
77
51
|
export const ChatbotReceiverText: StoryFn<ComponentProps> = Template.bind({})
|
|
78
52
|
ChatbotReceiverText.args = {
|
|
79
53
|
message: createMockMessage({ metadata: { custom_type: 'MESSAGE_CHATBOT' } }),
|
|
@@ -105,6 +79,14 @@ ChatbotSenderAttachment.args = {
|
|
|
105
79
|
}
|
|
106
80
|
ChatbotSenderAttachment.parameters = skipInChromatic
|
|
107
81
|
|
|
82
|
+
export const Paid: StoryFn<ComponentProps> = Template.bind({})
|
|
83
|
+
Paid.args = {
|
|
84
|
+
message: createMockMessage({
|
|
85
|
+
metadata: { custom_type: 'MESSAGE_PAID', amount_text: '$10.00' },
|
|
86
|
+
}),
|
|
87
|
+
}
|
|
88
|
+
Paid.parameters = skipInChromatic
|
|
89
|
+
|
|
108
90
|
export const NoTag: StoryFn<ComponentProps> = Template.bind({})
|
|
109
91
|
NoTag.args = {
|
|
110
92
|
message: createMockMessage(),
|
|
@@ -114,32 +96,6 @@ NoTag.parameters = skipInChromatic
|
|
|
114
96
|
export const AllVariants: StoryFn = () => {
|
|
115
97
|
return (
|
|
116
98
|
<div className="p-12 flex flex-col gap-4">
|
|
117
|
-
<div className="flex items-center gap-4">
|
|
118
|
-
<span className="text-sm w-32">Tip:</span>
|
|
119
|
-
<MessageTag
|
|
120
|
-
message={createMockMessage({
|
|
121
|
-
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$10.50' },
|
|
122
|
-
})}
|
|
123
|
-
/>
|
|
124
|
-
</div>
|
|
125
|
-
<div className="flex items-center gap-4">
|
|
126
|
-
<span className="text-sm w-32">Paid:</span>
|
|
127
|
-
<MessageTag
|
|
128
|
-
message={createMockMessage({
|
|
129
|
-
metadata: { custom_type: 'MESSAGE_PAID', amount_text: '$25.00' },
|
|
130
|
-
})}
|
|
131
|
-
/>
|
|
132
|
-
</div>
|
|
133
|
-
<div className="flex items-center gap-4">
|
|
134
|
-
<span className="text-sm w-32">Tip (standalone):</span>
|
|
135
|
-
<MessageTag
|
|
136
|
-
message={createMockMessage({
|
|
137
|
-
text: '',
|
|
138
|
-
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$50.00' },
|
|
139
|
-
})}
|
|
140
|
-
standalone
|
|
141
|
-
/>
|
|
142
|
-
</div>
|
|
143
99
|
<div className="flex items-center gap-4">
|
|
144
100
|
<span className="text-sm w-32">Chatbot (receiver):</span>
|
|
145
101
|
<MessageTag
|
|
@@ -171,6 +127,14 @@ export const AllVariants: StoryFn = () => {
|
|
|
171
127
|
/>
|
|
172
128
|
</div>
|
|
173
129
|
</div>
|
|
130
|
+
<div className="flex items-center gap-4">
|
|
131
|
+
<span className="text-sm w-32">Paid:</span>
|
|
132
|
+
<MessageTag
|
|
133
|
+
message={createMockMessage({
|
|
134
|
+
metadata: { custom_type: 'MESSAGE_PAID', amount_text: '$10.00' },
|
|
135
|
+
})}
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
174
138
|
<div className="flex items-center gap-4">
|
|
175
139
|
<span className="text-sm w-32">No tag:</span>
|
|
176
140
|
<MessageTag message={createMockMessage()} />
|