@linktr.ee/messaging-react 1.13.1 → 1.14.0
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/assets/index.css +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.js +931 -722
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelView.tsx +34 -23
- package/src/components/CustomMessage/CustomMessage.stories.tsx +208 -0
- package/src/components/CustomMessage/MessageTag.stories.tsx +119 -0
- package/src/components/CustomMessage/MessageTag.tsx +84 -0
- package/src/components/CustomMessage/index.tsx +271 -0
- package/src/components/CustomSystemMessage/index.tsx +1 -0
- package/src/stream-custom-data.ts +11 -0
- package/src/styles.css +45 -0
package/package.json
CHANGED
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
Window,
|
|
15
15
|
MessageList,
|
|
16
16
|
useChannelStateContext,
|
|
17
|
+
WithComponents,
|
|
18
|
+
MessageUIComponentProps,
|
|
17
19
|
} from 'stream-chat-react'
|
|
18
20
|
|
|
19
21
|
import { useMessagingContext } from '../providers/MessagingProvider'
|
|
@@ -23,6 +25,7 @@ import ActionButton from './ActionButton'
|
|
|
23
25
|
import { Avatar } from './Avatar'
|
|
24
26
|
import { CloseButton } from './CloseButton'
|
|
25
27
|
import { CustomDateSeparator } from './CustomDateSeparator'
|
|
28
|
+
import { CustomMessage } from './CustomMessage'
|
|
26
29
|
import { CustomMessageInput } from './CustomMessageInput'
|
|
27
30
|
import { CustomSystemMessage } from './CustomSystemMessage'
|
|
28
31
|
import { ChannelEmptyState } from './MessagingShell/ChannelEmptyState'
|
|
@@ -488,31 +491,39 @@ const ChannelViewInner: React.FC<{
|
|
|
488
491
|
|
|
489
492
|
return (
|
|
490
493
|
<>
|
|
491
|
-
<
|
|
492
|
-
{
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
494
|
+
<WithComponents
|
|
495
|
+
overrides={{
|
|
496
|
+
Message: (props: MessageUIComponentProps) => (
|
|
497
|
+
<CustomMessage {...props} />
|
|
498
|
+
),
|
|
499
|
+
}}
|
|
500
|
+
>
|
|
501
|
+
<Window>
|
|
502
|
+
{/* Custom Channel Header */}
|
|
503
|
+
<div className="p-4">
|
|
504
|
+
<CustomChannelHeader
|
|
505
|
+
onBack={onBack}
|
|
506
|
+
showBackButton={showBackButton}
|
|
507
|
+
onShowInfo={handleShowInfo}
|
|
508
|
+
canShowInfo={Boolean(participant)}
|
|
509
|
+
/>
|
|
510
|
+
</div>
|
|
501
511
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
512
|
+
{/* Message List */}
|
|
513
|
+
<div className="flex-1 overflow-hidden relative">
|
|
514
|
+
<MessageList
|
|
515
|
+
hideDeletedMessages
|
|
516
|
+
hideNewMessageSeparator={false}
|
|
517
|
+
messageActions={undefined}
|
|
518
|
+
/>
|
|
519
|
+
</div>
|
|
510
520
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
521
|
+
{/* Message Input */}
|
|
522
|
+
<CustomMessageInput
|
|
523
|
+
renderActions={() => renderMessageInputActions?.(channel)}
|
|
524
|
+
/>
|
|
525
|
+
</Window>
|
|
526
|
+
</WithComponents>
|
|
516
527
|
|
|
517
528
|
{/* Channel Info Dialog */}
|
|
518
529
|
<ChannelInfoDialog
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import type { Meta, StoryFn } from '@storybook/react'
|
|
2
|
+
import React, { useEffect } from 'react'
|
|
3
|
+
import {
|
|
4
|
+
Channel as ChannelType,
|
|
5
|
+
QueryChannelAPIResponse,
|
|
6
|
+
StreamChat,
|
|
7
|
+
} from 'stream-chat'
|
|
8
|
+
import { Channel, Chat, MessageList, Window } from 'stream-chat-react'
|
|
9
|
+
|
|
10
|
+
import { mockParticipants } from '../../stories/mocks'
|
|
11
|
+
|
|
12
|
+
import { CustomMessage } from './index'
|
|
13
|
+
|
|
14
|
+
const meta: Meta = {
|
|
15
|
+
title: 'Components/CustomMessage',
|
|
16
|
+
component: CustomMessage,
|
|
17
|
+
parameters: {
|
|
18
|
+
layout: 'fullscreen',
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
export default meta
|
|
22
|
+
|
|
23
|
+
const mockUser = {
|
|
24
|
+
id: 'storybook-user',
|
|
25
|
+
name: 'Storybook User',
|
|
26
|
+
image: 'https://i.pravatar.cc/150?img=1',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const createMockChannel = async (
|
|
30
|
+
client: StreamChat,
|
|
31
|
+
messages: TemplateProps['messages']
|
|
32
|
+
) => {
|
|
33
|
+
const participant = mockParticipants[0]
|
|
34
|
+
|
|
35
|
+
const mockMessages = messages.map((msg, index) => ({
|
|
36
|
+
...msg,
|
|
37
|
+
type: msg.type ?? ('regular' as const),
|
|
38
|
+
created_at: new Date(Date.now() - 1000 * 60 * (messages.length - index)),
|
|
39
|
+
updated_at: new Date(Date.now() - 1000 * 60 * (messages.length - index)),
|
|
40
|
+
html: `<p>${msg.text}</p>`,
|
|
41
|
+
attachments: [],
|
|
42
|
+
latest_reactions: [],
|
|
43
|
+
own_reactions: [],
|
|
44
|
+
reaction_counts: {},
|
|
45
|
+
reaction_scores: {},
|
|
46
|
+
reply_count: 0,
|
|
47
|
+
status: 'received',
|
|
48
|
+
cid: 'messaging:storybook-channel-1',
|
|
49
|
+
mentioned_users: [],
|
|
50
|
+
}))
|
|
51
|
+
|
|
52
|
+
const channelData = {
|
|
53
|
+
members: [mockUser.id, participant.id],
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const channel = client.channel(
|
|
57
|
+
'messaging',
|
|
58
|
+
'storybook-channel-1',
|
|
59
|
+
channelData
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
channel.watch = async () => {
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
64
|
+
channel.state.messages = mockMessages as unknown as any[]
|
|
65
|
+
channel.state.members = {
|
|
66
|
+
[mockUser.id]: { user: mockUser, user_id: mockUser.id },
|
|
67
|
+
[participant.id]: { user: participant, user_id: participant.id },
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
channel: channelData,
|
|
71
|
+
members: [],
|
|
72
|
+
messages: mockMessages,
|
|
73
|
+
watchers: [],
|
|
74
|
+
pinned_messages: [],
|
|
75
|
+
duration: '0ms',
|
|
76
|
+
} as unknown as QueryChannelAPIResponse
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
await channel.watch()
|
|
81
|
+
} catch {
|
|
82
|
+
// Ignore errors in mock mode
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return channel
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface TemplateProps {
|
|
89
|
+
messages: Array<{
|
|
90
|
+
id: string
|
|
91
|
+
text: string
|
|
92
|
+
user: typeof mockUser | { id: string; name: string }
|
|
93
|
+
type?: 'regular' | 'system'
|
|
94
|
+
metadata?: {
|
|
95
|
+
custom_type?: 'MESSAGE_TIP' | 'MESSAGE_PAID' | 'MESSAGE_CHATBOT'
|
|
96
|
+
amount_text?: string
|
|
97
|
+
}
|
|
98
|
+
}>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const Template: StoryFn<TemplateProps> = ({ messages }) => {
|
|
102
|
+
const [client] = React.useState(() => {
|
|
103
|
+
const c = new StreamChat('mock-api-key', { allowServerSideConnect: true })
|
|
104
|
+
c.userID = mockUser.id
|
|
105
|
+
c.user = mockUser
|
|
106
|
+
return c
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const [channel, setChannel] = React.useState<ChannelType | null>(null)
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
createMockChannel(client, messages).then(setChannel)
|
|
113
|
+
}, [client, messages])
|
|
114
|
+
|
|
115
|
+
if (!channel) {
|
|
116
|
+
return <div className="p-4">Loading...</div>
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<Chat client={client}>
|
|
121
|
+
<div className="h-screen w-full bg-white">
|
|
122
|
+
<Channel channel={channel} Message={CustomMessage}>
|
|
123
|
+
<Window>
|
|
124
|
+
<MessageList />
|
|
125
|
+
</Window>
|
|
126
|
+
</Channel>
|
|
127
|
+
</div>
|
|
128
|
+
</Chat>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const participant = mockParticipants[0]
|
|
133
|
+
|
|
134
|
+
export const Default: StoryFn<TemplateProps> = Template.bind({})
|
|
135
|
+
Default.args = {
|
|
136
|
+
messages: [
|
|
137
|
+
{ id: 'msg-1', text: 'Hey, how are you?', user: participant },
|
|
138
|
+
{ id: 'msg-2', text: "I'm doing great, thanks!", user: mockUser },
|
|
139
|
+
{ id: 'msg-3', text: 'Awesome! Have a good day.', user: participant },
|
|
140
|
+
],
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export const WithTipTag: StoryFn<TemplateProps> = Template.bind({})
|
|
144
|
+
WithTipTag.args = {
|
|
145
|
+
messages: [
|
|
146
|
+
{ id: 'msg-1', text: 'Love your content!', user: participant },
|
|
147
|
+
{
|
|
148
|
+
id: 'msg-2',
|
|
149
|
+
text: "Here's a tip for you! Keep up the great work.",
|
|
150
|
+
user: participant,
|
|
151
|
+
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$5.50' },
|
|
152
|
+
},
|
|
153
|
+
{ id: 'msg-3', text: 'Thank you so much! 🙏', user: mockUser },
|
|
154
|
+
],
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const TipOnly: StoryFn<TemplateProps> = Template.bind({})
|
|
158
|
+
TipOnly.args = {
|
|
159
|
+
messages: [
|
|
160
|
+
{ id: 'msg-1', text: 'Hello!', user: participant },
|
|
161
|
+
{
|
|
162
|
+
id: 'msg-2',
|
|
163
|
+
text: '',
|
|
164
|
+
user: participant,
|
|
165
|
+
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$50.00' },
|
|
166
|
+
},
|
|
167
|
+
{ id: 'msg-3', text: 'Wow, thank you for the tip!', user: mockUser },
|
|
168
|
+
],
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const MixedTags: StoryFn<TemplateProps> = Template.bind({})
|
|
172
|
+
MixedTags.args = {
|
|
173
|
+
messages: [
|
|
174
|
+
{ id: 'msg-1', text: 'Regular message from a fan', user: participant },
|
|
175
|
+
{
|
|
176
|
+
id: 'msg-2',
|
|
177
|
+
text: 'I wanted to tip you for your amazing work!',
|
|
178
|
+
user: participant,
|
|
179
|
+
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$5.50' },
|
|
180
|
+
},
|
|
181
|
+
{ id: 'msg-3', text: 'Thank you!', user: mockUser },
|
|
182
|
+
{
|
|
183
|
+
id: 'msg-4',
|
|
184
|
+
text: '',
|
|
185
|
+
user: participant,
|
|
186
|
+
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$25.00' },
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: 'msg-5',
|
|
190
|
+
text: 'Wow, a tip with no message! Thanks!',
|
|
191
|
+
user: mockUser,
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: 'msg-6',
|
|
195
|
+
text: 'This is a paid message!',
|
|
196
|
+
user: participant,
|
|
197
|
+
metadata: { custom_type: 'MESSAGE_PAID', amount_text: '$10.00' },
|
|
198
|
+
},
|
|
199
|
+
{ id: 'msg-7', text: 'Got it, thanks!', user: mockUser },
|
|
200
|
+
{
|
|
201
|
+
id: 'msg-8',
|
|
202
|
+
text: 'This is from a chatbot!',
|
|
203
|
+
user: participant,
|
|
204
|
+
metadata: { custom_type: 'MESSAGE_CHATBOT' },
|
|
205
|
+
},
|
|
206
|
+
{ id: 'msg-9', text: 'Thanks for letting me know.', user: mockUser },
|
|
207
|
+
],
|
|
208
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Meta, StoryFn } from '@storybook/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { LocalMessage } from 'stream-chat'
|
|
4
|
+
|
|
5
|
+
import { MessageTag } from './MessageTag'
|
|
6
|
+
|
|
7
|
+
type ComponentProps = React.ComponentProps<typeof MessageTag>
|
|
8
|
+
|
|
9
|
+
const meta: Meta<ComponentProps> = {
|
|
10
|
+
title: 'Components/MessageTag',
|
|
11
|
+
component: MessageTag,
|
|
12
|
+
parameters: {
|
|
13
|
+
layout: 'centered',
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
export default meta
|
|
17
|
+
|
|
18
|
+
interface MockMessageOptions {
|
|
19
|
+
metadata?: {
|
|
20
|
+
custom_type?: 'MESSAGE_TIP' | 'MESSAGE_PAID' | 'MESSAGE_CHATBOT'
|
|
21
|
+
amount_text?: string
|
|
22
|
+
}
|
|
23
|
+
text?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const createMockMessage = (options?: MockMessageOptions): LocalMessage =>
|
|
27
|
+
({
|
|
28
|
+
id: 'msg-1',
|
|
29
|
+
text: options?.text ?? 'Hello world',
|
|
30
|
+
type: 'regular',
|
|
31
|
+
created_at: new Date(),
|
|
32
|
+
updated_at: new Date(),
|
|
33
|
+
metadata: options?.metadata,
|
|
34
|
+
}) as LocalMessage
|
|
35
|
+
|
|
36
|
+
const Template: StoryFn<ComponentProps> = (args) => {
|
|
37
|
+
return (
|
|
38
|
+
<div className="p-12">
|
|
39
|
+
<MessageTag {...args} />
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const Tip: StoryFn<ComponentProps> = Template.bind({})
|
|
45
|
+
Tip.args = {
|
|
46
|
+
message: createMockMessage({
|
|
47
|
+
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$5.50' },
|
|
48
|
+
}),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const TipStandalone: StoryFn<ComponentProps> = Template.bind({})
|
|
52
|
+
TipStandalone.args = {
|
|
53
|
+
message: createMockMessage({
|
|
54
|
+
text: '',
|
|
55
|
+
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$50.00' },
|
|
56
|
+
}),
|
|
57
|
+
standalone: true,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const Paid: StoryFn<ComponentProps> = Template.bind({})
|
|
61
|
+
Paid.args = {
|
|
62
|
+
message: createMockMessage({
|
|
63
|
+
metadata: { custom_type: 'MESSAGE_PAID', amount_text: '$25.00' },
|
|
64
|
+
}),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const Chatbot: StoryFn<ComponentProps> = Template.bind({})
|
|
68
|
+
Chatbot.args = {
|
|
69
|
+
message: createMockMessage({ metadata: { custom_type: 'MESSAGE_CHATBOT' } }),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const NoTag: StoryFn<ComponentProps> = Template.bind({})
|
|
73
|
+
NoTag.args = {
|
|
74
|
+
message: createMockMessage(),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const AllVariants: StoryFn = () => {
|
|
78
|
+
return (
|
|
79
|
+
<div className="p-12 flex flex-col gap-4">
|
|
80
|
+
<div className="flex items-center gap-4">
|
|
81
|
+
<span className="text-sm w-32">Tip:</span>
|
|
82
|
+
<MessageTag
|
|
83
|
+
message={createMockMessage({
|
|
84
|
+
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$10.50' },
|
|
85
|
+
})}
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
<div className="flex items-center gap-4">
|
|
89
|
+
<span className="text-sm w-32">Paid:</span>
|
|
90
|
+
<MessageTag
|
|
91
|
+
message={createMockMessage({
|
|
92
|
+
metadata: { custom_type: 'MESSAGE_PAID', amount_text: '$25.00' },
|
|
93
|
+
})}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
<div className="flex items-center gap-4">
|
|
97
|
+
<span className="text-sm w-32">Tip (standalone):</span>
|
|
98
|
+
<MessageTag
|
|
99
|
+
message={createMockMessage({
|
|
100
|
+
text: '',
|
|
101
|
+
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$50.00' },
|
|
102
|
+
})}
|
|
103
|
+
standalone
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
<div className="flex items-center gap-4">
|
|
107
|
+
<span className="text-sm w-32">Chatbot:</span>
|
|
108
|
+
<MessageTag
|
|
109
|
+
message={createMockMessage({ metadata: { custom_type: 'MESSAGE_CHATBOT' } })}
|
|
110
|
+
/>
|
|
111
|
+
</div>
|
|
112
|
+
<div className="flex items-center gap-4">
|
|
113
|
+
<span className="text-sm w-32">No tag:</span>
|
|
114
|
+
<MessageTag message={createMockMessage()} />
|
|
115
|
+
<span className="text-xs text-stone">(renders nothing)</span>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { GiftIcon } from '@phosphor-icons/react'
|
|
2
|
+
import { LocalMessage } from 'stream-chat'
|
|
3
|
+
|
|
4
|
+
interface MessageTagProps {
|
|
5
|
+
message: LocalMessage
|
|
6
|
+
/** When true, renders as a standalone bubble instead of a small tag */
|
|
7
|
+
standalone?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const SparkleIcon = () => (
|
|
11
|
+
<svg width="12" height="12" viewBox="0 0 10 10" fill="none">
|
|
12
|
+
<path
|
|
13
|
+
d="M10.003 5a.705.705 0 0 1-.469.67L6.7 6.7 5.67 9.535a.715.715 0 0 1-1.34 0L3.3 6.7.466 5.67a.715.715 0 0 1 0-1.34L3.3 3.3 4.33.466a.715.715 0 0 1 1.34 0L6.7 3.3l2.834 1.03a.705.705 0 0 1 .469.67"
|
|
14
|
+
fill="currentColor"
|
|
15
|
+
/>
|
|
16
|
+
</svg>
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
/** Check if a message is a tip based on metadata */
|
|
20
|
+
export const isTipMessage = (message: LocalMessage): boolean => {
|
|
21
|
+
return message.metadata?.custom_type === 'MESSAGE_TIP'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Check if a message is a paid message based on metadata */
|
|
25
|
+
export const isPaidMessage = (message: LocalMessage): boolean => {
|
|
26
|
+
return message.metadata?.custom_type === 'MESSAGE_PAID'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Check if a message is a chatbot message based on metadata */
|
|
30
|
+
export const isChatbotMessage = (message: LocalMessage): boolean => {
|
|
31
|
+
return message.metadata?.custom_type === 'MESSAGE_CHATBOT'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Check if a message has a tip/paid tag (both render the same) */
|
|
35
|
+
export const isTipOrPaidMessage = (message: LocalMessage): boolean => {
|
|
36
|
+
return isTipMessage(message) || isPaidMessage(message)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Check if a message is a tip/paid-only message (no text) */
|
|
40
|
+
export const isTipOnlyMessage = (message: LocalMessage): boolean => {
|
|
41
|
+
return isTipOrPaidMessage(message) && !message.text?.trim()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const MessageTag = ({
|
|
45
|
+
message,
|
|
46
|
+
standalone = false,
|
|
47
|
+
}: MessageTagProps) => {
|
|
48
|
+
const isTipOrPaid = isTipOrPaidMessage(message)
|
|
49
|
+
const isChatbot = isChatbotMessage(message)
|
|
50
|
+
|
|
51
|
+
if (!isTipOrPaid && !isChatbot) {
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (isTipOrPaid) {
|
|
56
|
+
const amountText = message.metadata?.amount_text
|
|
57
|
+
if (!amountText) return null
|
|
58
|
+
|
|
59
|
+
const className = standalone
|
|
60
|
+
? 'message-tip-standalone'
|
|
61
|
+
: 'message-tag message-tag--tip'
|
|
62
|
+
|
|
63
|
+
const label = standalone
|
|
64
|
+
? `${amountText} tip`
|
|
65
|
+
: `Delivered with ${amountText} tip`
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className={className}>
|
|
69
|
+
<GiftIcon size={standalone ? 14 : 12} />
|
|
70
|
+
<span>{label}</span>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Chatbot tag
|
|
76
|
+
return (
|
|
77
|
+
<div className="message-tag message-tag--chatbot">
|
|
78
|
+
<span className="message-tag__icon" style={{ marginTop: -1 }}>
|
|
79
|
+
<SparkleIcon />
|
|
80
|
+
</span>
|
|
81
|
+
<span className="message-tag__label">Chatbot</span>
|
|
82
|
+
</div>
|
|
83
|
+
)
|
|
84
|
+
}
|