@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.
Files changed (71) hide show
  1. package/dist/{Card-B4lMv-0o.js → Card-BOudQBd6.js} +2 -2
  2. package/dist/{Card-B4lMv-0o.js.map → Card-BOudQBd6.js.map} +1 -1
  3. package/dist/{Card-DWL5I62S.cjs → Card-k2959zNX.cjs} +2 -2
  4. package/dist/{Card-DWL5I62S.cjs.map → Card-k2959zNX.cjs.map} +1 -1
  5. package/dist/assets/index.css +1 -1
  6. package/dist/{index-C93LTGLc.js → index-6RdyZh6F.js} +1809 -1912
  7. package/dist/index-6RdyZh6F.js.map +1 -0
  8. package/dist/index-BcU18EjV.cjs +3 -0
  9. package/dist/index-BcU18EjV.cjs.map +1 -0
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.ts +78 -59
  12. package/dist/index.js +30 -26
  13. package/dist/testing.cjs +1 -1
  14. package/dist/testing.cjs.map +1 -1
  15. package/dist/testing.js +2 -2
  16. package/dist/testing.js.map +1 -1
  17. package/package.json +2 -2
  18. package/src/components/AttachmentCard/MediaPlayer.test.tsx +346 -0
  19. package/src/components/AttachmentCard/Thumbnail.test.tsx +215 -0
  20. package/src/components/AttachmentCard/index.test.tsx +160 -0
  21. package/src/components/Avatar/Avatar.test.tsx +126 -0
  22. package/src/components/Avatar/getAvatarEmoji.test.ts +39 -0
  23. package/src/components/ChannelList/ChannelListContext.test.tsx +87 -0
  24. package/src/components/ChannelList/CustomChannelPreview.test.tsx +410 -3
  25. package/src/components/ChannelList/index.test.tsx +132 -1
  26. package/src/components/CustomMessage/CustomMessage.stories.tsx +6 -314
  27. package/src/components/CustomMessage/CustomMessage.test.tsx +56 -2
  28. package/src/components/CustomMessage/Locked.stories.tsx +108 -0
  29. package/src/components/CustomMessage/MessageTag.stories.tsx +17 -53
  30. package/src/components/CustomMessage/MessageTag.test.tsx +7 -40
  31. package/src/components/CustomMessage/MessageTag.tsx +7 -39
  32. package/src/components/CustomMessage/PaidMessage.stories.tsx +42 -0
  33. package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +8 -7
  34. package/src/components/CustomMessage/SentMessageDeliveryStatus.tsx +2 -1
  35. package/src/components/CustomMessage/StreamAttachmentMessage.test.tsx +0 -33
  36. package/src/components/CustomMessage/StreamAttachmentMessage.tsx +0 -6
  37. package/src/components/CustomMessage/Tip.stories.tsx +48 -0
  38. package/src/components/CustomMessage/TipMessage/TipMessage.stories.tsx +51 -0
  39. package/src/components/CustomMessage/TipMessage/TipMessage.test.tsx +65 -0
  40. package/src/components/CustomMessage/TipMessage/index.tsx +27 -0
  41. package/src/components/CustomMessage/index.tsx +22 -19
  42. package/src/components/FaqList/FaqList.test.tsx +204 -0
  43. package/src/components/LinkAttachment/LinkAttachment.test.tsx +327 -0
  44. package/src/components/LinkAttachment/components/_shared/BubbleTail.test.tsx +76 -0
  45. package/src/components/LinkAttachment/components/_shared/CardBody.test.tsx +248 -0
  46. package/src/components/LinkAttachment/components/_shared/CardCta.test.tsx +123 -0
  47. package/src/components/LinkAttachment/components/_shared/CardShell.test.tsx +193 -0
  48. package/src/components/LinkAttachment/components/_shared/CardThumbnail.test.tsx +242 -0
  49. package/src/components/LinkAttachment/components/_shared/hex.test.ts +37 -0
  50. package/src/components/LinkAttachment/components/_shared/normalizeExternalHref.test.ts +72 -0
  51. package/src/components/LinkAttachment/components/_shared/useChinPalette.test.ts +162 -0
  52. package/src/components/MessageAttachment/Audio/AudioAttachment.stories.tsx +58 -165
  53. package/src/components/MessageAttachment/Audio/index.tsx +77 -71
  54. package/src/components/MessageAttachment/MessageAttachment.test.tsx +74 -176
  55. package/src/components/MessageAttachment/index.tsx +9 -25
  56. package/src/components/MessageAttachment/types.ts +9 -19
  57. package/src/components/MessagingShell/MessagingShell.test.tsx +20 -0
  58. package/src/components/MessagingShell/index.tsx +2 -0
  59. package/src/index.ts +4 -0
  60. package/src/logging/index.test.tsx +136 -0
  61. package/src/logging/index.tsx +48 -0
  62. package/src/providers/MessagingProvider.test.tsx +113 -1
  63. package/src/providers/MessagingProvider.tsx +25 -15
  64. package/src/stories/customMessageStoryTemplate.tsx +199 -0
  65. package/src/styles.css +10 -121
  66. package/src/testing/createMockMessagingClient.ts +2 -2
  67. package/src/types.ts +9 -0
  68. package/dist/index-B-9LkWep.cjs +0 -3
  69. package/dist/index-B-9LkWep.cjs.map +0 -1
  70. package/dist/index-C93LTGLc.js.map +0 -1
  71. 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 { minutesAgo } from '../../stories/decorators/storyTime'
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?: 'MESSAGE_TIP' | 'MESSAGE_PAID' | 'MESSAGE_CHATBOT'
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()} />