@linktr.ee/messaging-react 1.25.0 → 1.25.1
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 +3 -1
- package/dist/index.js +535 -472
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelList/CustomChannelPreview.test.tsx +31 -0
- package/src/components/CustomSystemMessage/CustomSystemMessage.stories.tsx +8 -0
- package/src/components/CustomSystemMessage/CustomSystemMessage.test.tsx +48 -2
- package/src/components/CustomSystemMessage/index.tsx +119 -13
- package/src/stream-custom-data.ts +3 -0
- package/src/styles.css +0 -30
package/package.json
CHANGED
|
@@ -129,6 +129,37 @@ describe('CustomChannelPreview', () => {
|
|
|
129
129
|
).not.toBeInTheDocument()
|
|
130
130
|
})
|
|
131
131
|
|
|
132
|
+
it('ignores age safety system messages when resolving the channel preview text', () => {
|
|
133
|
+
const channel = createMockChannel([
|
|
134
|
+
{
|
|
135
|
+
id: 'msg-1',
|
|
136
|
+
text: 'Still available to chat',
|
|
137
|
+
type: 'regular',
|
|
138
|
+
created_at: new Date('2026-01-01T00:00:00.000Z'),
|
|
139
|
+
user: { id: 'participant-1', name: 'Alice' },
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 'msg-2',
|
|
143
|
+
type: 'system',
|
|
144
|
+
created_at: new Date('2026-01-01T00:01:00.000Z'),
|
|
145
|
+
metadata: {
|
|
146
|
+
custom_type: 'SYSTEM_AGE_SAFETY_BLOCKED',
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
])
|
|
150
|
+
|
|
151
|
+
renderWithProviders(
|
|
152
|
+
<CustomChannelPreview {...defaultProps} channel={channel} />
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
expect(screen.getByText('Still available to chat')).toBeInTheDocument()
|
|
156
|
+
expect(
|
|
157
|
+
screen.queryByText(
|
|
158
|
+
'This user isn’t able to reply because they don’t meet our age safety guidelines.'
|
|
159
|
+
)
|
|
160
|
+
).not.toBeInTheDocument()
|
|
161
|
+
})
|
|
162
|
+
|
|
132
163
|
it('shows fallback text when all messages are system messages', () => {
|
|
133
164
|
const channel = createMockChannel([
|
|
134
165
|
{
|
|
@@ -47,6 +47,14 @@ DmAgentResumed.args = createStoryProps({
|
|
|
47
47
|
},
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
+
export const AgeSafetyBlocked: StoryFn<EventComponentProps> = Template.bind({})
|
|
51
|
+
AgeSafetyBlocked.args = createStoryProps({
|
|
52
|
+
text: ' ',
|
|
53
|
+
metadata: {
|
|
54
|
+
custom_type: 'SYSTEM_AGE_SAFETY_BLOCKED',
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
50
58
|
export const GenericFallback: StoryFn<EventComponentProps> = Template.bind({})
|
|
51
59
|
GenericFallback.args = createStoryProps({
|
|
52
60
|
text: 'Message activity event',
|
|
@@ -60,6 +60,51 @@ describe('CustomSystemMessage', () => {
|
|
|
60
60
|
expect(dmAgentSystemMessage).toHaveTextContent('DM Agent resumed')
|
|
61
61
|
})
|
|
62
62
|
|
|
63
|
+
it('renders the age safety blocked subtype with fallback copy and warning treatment', () => {
|
|
64
|
+
const { container } = renderWithProviders(
|
|
65
|
+
<CustomSystemMessage
|
|
66
|
+
{...createProps({
|
|
67
|
+
text: ' ',
|
|
68
|
+
metadata: {
|
|
69
|
+
custom_type: 'SYSTEM_AGE_SAFETY_BLOCKED',
|
|
70
|
+
},
|
|
71
|
+
})}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const ageSafetySystemMessage = screen.getByTestId(
|
|
76
|
+
'age-safety-system-message'
|
|
77
|
+
)
|
|
78
|
+
expect(ageSafetySystemMessage).toHaveAttribute(
|
|
79
|
+
'data-age-safety-system-type',
|
|
80
|
+
'SYSTEM_AGE_SAFETY_BLOCKED'
|
|
81
|
+
)
|
|
82
|
+
expect(ageSafetySystemMessage).toHaveTextContent(
|
|
83
|
+
'This user isn’t able to reply because they don’t meet our age safety guidelines.'
|
|
84
|
+
)
|
|
85
|
+
const emphasisLink = screen.getByRole('link', {
|
|
86
|
+
name: 'age safety guidelines.',
|
|
87
|
+
})
|
|
88
|
+
expect(emphasisLink).toHaveClass('mes-age-safety-system-message__emphasis')
|
|
89
|
+
expect(emphasisLink).toHaveAttribute(
|
|
90
|
+
'href',
|
|
91
|
+
'https://linktr.ee/s/about/contact'
|
|
92
|
+
)
|
|
93
|
+
expect(emphasisLink).toHaveAttribute('target', '_blank')
|
|
94
|
+
expect(emphasisLink).toHaveAttribute('rel', 'noopener noreferrer')
|
|
95
|
+
expect(
|
|
96
|
+
screen.getByTestId('age-safety-system-message-icon')
|
|
97
|
+
).toBeInTheDocument()
|
|
98
|
+
expect(
|
|
99
|
+
container.querySelector('.mes-age-safety-system-message__icon')
|
|
100
|
+
).toBeInTheDocument()
|
|
101
|
+
expect(
|
|
102
|
+
container.querySelector(
|
|
103
|
+
'[data-testid="age-safety-system-message-icon"] path[opacity="0.2"]'
|
|
104
|
+
)
|
|
105
|
+
).toBeInTheDocument()
|
|
106
|
+
})
|
|
107
|
+
|
|
63
108
|
it('falls back to legacy generic rendering when subtype is absent or unknown', () => {
|
|
64
109
|
const { container } = renderWithProviders(
|
|
65
110
|
<CustomSystemMessage
|
|
@@ -71,11 +116,12 @@ describe('CustomSystemMessage', () => {
|
|
|
71
116
|
/>
|
|
72
117
|
)
|
|
73
118
|
|
|
74
|
-
expect(
|
|
119
|
+
expect(
|
|
120
|
+
screen.queryByTestId('dm-agent-system-message')
|
|
121
|
+
).not.toBeInTheDocument()
|
|
75
122
|
expect(screen.getByText('Legacy system message')).toBeInTheDocument()
|
|
76
123
|
expect(
|
|
77
124
|
container.querySelector('.str-chat__message--system__line')
|
|
78
125
|
).toBeInTheDocument()
|
|
79
126
|
})
|
|
80
|
-
|
|
81
127
|
})
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { SparkleIcon } from '@phosphor-icons/react'
|
|
1
|
+
import { ProhibitIcon, SparkleIcon } from '@phosphor-icons/react'
|
|
2
2
|
import { MessageTimestamp, type EventComponentProps } from 'stream-chat-react'
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
AgeSafetySystemType,
|
|
6
|
+
DmAgentSystemType,
|
|
7
|
+
} from '../../stream-custom-data'
|
|
5
8
|
|
|
6
9
|
const DM_AGENT_SYSTEM_TYPES: readonly DmAgentSystemType[] = [
|
|
7
10
|
'SYSTEM_DM_AGENT_PAUSED',
|
|
@@ -14,51 +17,154 @@ const DM_AGENT_SYSTEM_MESSAGE_FALLBACK_TEXT: Record<DmAgentSystemType, string> =
|
|
|
14
17
|
SYSTEM_DM_AGENT_RESUMED: 'DM Agent has rejoined the conversation',
|
|
15
18
|
}
|
|
16
19
|
|
|
20
|
+
const AGE_SAFETY_SYSTEM_TYPES: readonly AgeSafetySystemType[] = [
|
|
21
|
+
'SYSTEM_AGE_SAFETY_BLOCKED',
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
const AGE_SAFETY_SYSTEM_MESSAGE_FALLBACK_TEXT: Record<
|
|
25
|
+
AgeSafetySystemType,
|
|
26
|
+
string
|
|
27
|
+
> = {
|
|
28
|
+
SYSTEM_AGE_SAFETY_BLOCKED:
|
|
29
|
+
'This user isn’t able to reply because they don’t meet our age safety guidelines.',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const AGE_SAFETY_SYSTEM_MESSAGE_EMPHASIS = 'age safety guidelines.'
|
|
33
|
+
const AGE_SAFETY_SYSTEM_MESSAGE_URL = 'https://linktr.ee/s/about/contact'
|
|
34
|
+
|
|
17
35
|
const isDmAgentSystemType = (
|
|
18
36
|
value: string | undefined
|
|
19
37
|
): value is DmAgentSystemType => {
|
|
20
38
|
return DM_AGENT_SYSTEM_TYPES.includes(value as DmAgentSystemType)
|
|
21
39
|
}
|
|
22
40
|
|
|
23
|
-
const
|
|
41
|
+
const isAgeSafetySystemType = (
|
|
42
|
+
value: string | undefined
|
|
43
|
+
): value is AgeSafetySystemType => {
|
|
44
|
+
return AGE_SAFETY_SYSTEM_TYPES.includes(value as AgeSafetySystemType)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type CustomSystemMessageVariant =
|
|
48
|
+
| {
|
|
49
|
+
kind: 'dm-agent'
|
|
50
|
+
type: DmAgentSystemType
|
|
51
|
+
}
|
|
52
|
+
| {
|
|
53
|
+
kind: 'age-safety'
|
|
54
|
+
type: AgeSafetySystemType
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const getCustomSystemMessageVariant = (
|
|
24
58
|
message: EventComponentProps['message']
|
|
25
|
-
):
|
|
59
|
+
): CustomSystemMessageVariant | undefined => {
|
|
26
60
|
const metadataCustomType = message.metadata?.custom_type
|
|
27
61
|
if (isDmAgentSystemType(metadataCustomType)) {
|
|
28
|
-
return
|
|
62
|
+
return {
|
|
63
|
+
kind: 'dm-agent',
|
|
64
|
+
type: metadataCustomType,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (isAgeSafetySystemType(metadataCustomType)) {
|
|
69
|
+
return {
|
|
70
|
+
kind: 'age-safety',
|
|
71
|
+
type: metadataCustomType,
|
|
72
|
+
}
|
|
29
73
|
}
|
|
30
74
|
|
|
31
75
|
const fallbackType = message.dm_agent_system_type
|
|
32
76
|
if (isDmAgentSystemType(fallbackType)) {
|
|
33
|
-
return
|
|
77
|
+
return {
|
|
78
|
+
kind: 'dm-agent',
|
|
79
|
+
type: fallbackType,
|
|
80
|
+
}
|
|
34
81
|
}
|
|
35
82
|
|
|
36
83
|
return undefined
|
|
37
84
|
}
|
|
38
85
|
|
|
86
|
+
const renderAgeSafetyMessageText = (messageText: string): React.ReactNode => {
|
|
87
|
+
const emphasisIndex = messageText.indexOf(AGE_SAFETY_SYSTEM_MESSAGE_EMPHASIS)
|
|
88
|
+
if (emphasisIndex === -1) {
|
|
89
|
+
return messageText
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const emphasisEndIndex =
|
|
93
|
+
emphasisIndex + AGE_SAFETY_SYSTEM_MESSAGE_EMPHASIS.length
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<>
|
|
97
|
+
{messageText.slice(0, emphasisIndex)}
|
|
98
|
+
<a
|
|
99
|
+
href={AGE_SAFETY_SYSTEM_MESSAGE_URL}
|
|
100
|
+
target="_blank"
|
|
101
|
+
rel="noopener noreferrer"
|
|
102
|
+
className="mes-age-safety-system-message__emphasis font-medium text-inherit underline"
|
|
103
|
+
>
|
|
104
|
+
{AGE_SAFETY_SYSTEM_MESSAGE_EMPHASIS}
|
|
105
|
+
</a>
|
|
106
|
+
{messageText.slice(emphasisEndIndex)}
|
|
107
|
+
</>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
39
111
|
export const CustomSystemMessage: React.FC<EventComponentProps> = (props) => {
|
|
40
112
|
const isDateHidden = props.message.hide_date === true
|
|
41
|
-
const
|
|
113
|
+
const customSystemMessageVariant = getCustomSystemMessageVariant(
|
|
114
|
+
props.message
|
|
115
|
+
)
|
|
42
116
|
|
|
43
|
-
if (
|
|
117
|
+
if (customSystemMessageVariant?.kind === 'dm-agent') {
|
|
44
118
|
const messageText =
|
|
45
119
|
props.message.text?.trim() ||
|
|
46
|
-
DM_AGENT_SYSTEM_MESSAGE_FALLBACK_TEXT[
|
|
120
|
+
DM_AGENT_SYSTEM_MESSAGE_FALLBACK_TEXT[customSystemMessageVariant.type]
|
|
47
121
|
|
|
48
122
|
return (
|
|
49
123
|
<div className="str-chat__message--system" data-testid="message-system">
|
|
50
124
|
<div
|
|
51
|
-
className="mes-dm-agent-system-message"
|
|
125
|
+
className="mes-dm-agent-system-message mx-auto mb-2 inline-flex w-fit max-w-[min(100%,480px)] items-center justify-center gap-[10px] rounded-[12px] border border-[rgba(0,0,0,0.08)] p-3 text-[rgba(0,0,0,0.55)]"
|
|
52
126
|
data-testid="dm-agent-system-message"
|
|
53
|
-
data-dm-agent-system-type={
|
|
127
|
+
data-dm-agent-system-type={customSystemMessageVariant.type}
|
|
54
128
|
>
|
|
55
129
|
<SparkleIcon
|
|
56
130
|
size={16}
|
|
57
131
|
weight="regular"
|
|
58
132
|
aria-hidden
|
|
59
|
-
className="mes-dm-agent-system-message__sparkle"
|
|
133
|
+
className="mes-dm-agent-system-message__sparkle shrink-0"
|
|
134
|
+
/>
|
|
135
|
+
<p className="mes-dm-agent-system-message__text m-0 text-center text-[14px] font-normal leading-5 tracking-[0.21px]">
|
|
136
|
+
{messageText}
|
|
137
|
+
</p>
|
|
138
|
+
</div>
|
|
139
|
+
{!isDateHidden && <MessageTimestamp message={props.message} />}
|
|
140
|
+
</div>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (customSystemMessageVariant?.kind === 'age-safety') {
|
|
145
|
+
const messageText =
|
|
146
|
+
props.message.text?.trim() ||
|
|
147
|
+
AGE_SAFETY_SYSTEM_MESSAGE_FALLBACK_TEXT[customSystemMessageVariant.type]
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div className="str-chat__message--system" data-testid="message-system">
|
|
151
|
+
<div
|
|
152
|
+
className="mes-age-safety-system-message box-border mx-auto mb-2 flex w-full max-w-[329px] items-start justify-center gap-3 rounded-[12px] border border-[var(--border-secondary,rgba(0,0,0,0.08))] bg-[var(--bg-warning-subtle,#fef3c6)] px-2 py-4 pl-5 text-[color:var(--text-warning-on-warning,#894b00)]"
|
|
153
|
+
data-testid="age-safety-system-message"
|
|
154
|
+
data-age-safety-system-type={customSystemMessageVariant.type}
|
|
155
|
+
>
|
|
156
|
+
<ProhibitIcon
|
|
157
|
+
size={24}
|
|
158
|
+
weight="duotone"
|
|
159
|
+
aria-hidden
|
|
160
|
+
className="mes-age-safety-system-message__icon shrink-0 text-[color:var(--text-warning-on-warning,#894b00)]"
|
|
161
|
+
data-testid="age-safety-system-message-icon"
|
|
60
162
|
/>
|
|
61
|
-
<
|
|
163
|
+
<div className="mes-age-safety-system-message__content min-w-0 flex-[1_0_0]">
|
|
164
|
+
<p className="m-0 text-balance text-left text-[12px] font-normal leading-4 tracking-[0.21px] text-[color:var(--text-warning-on-warning,#894b00)]">
|
|
165
|
+
{renderAgeSafetyMessageText(messageText)}
|
|
166
|
+
</p>
|
|
167
|
+
</div>
|
|
62
168
|
</div>
|
|
63
169
|
{!isDateHidden && <MessageTimestamp message={props.message} />}
|
|
64
170
|
</div>
|
|
@@ -18,10 +18,13 @@ export type DmAgentSystemType =
|
|
|
18
18
|
| 'SYSTEM_DM_AGENT_PAUSED'
|
|
19
19
|
| 'SYSTEM_DM_AGENT_RESUMED'
|
|
20
20
|
|
|
21
|
+
export type AgeSafetySystemType = 'SYSTEM_AGE_SAFETY_BLOCKED'
|
|
22
|
+
|
|
21
23
|
export type MessageCustomType =
|
|
22
24
|
| 'MESSAGE_TIP'
|
|
23
25
|
| 'MESSAGE_PAID'
|
|
24
26
|
| 'MESSAGE_CHATBOT'
|
|
27
|
+
| AgeSafetySystemType
|
|
25
28
|
| DmAgentSystemType
|
|
26
29
|
|
|
27
30
|
/**
|
package/src/styles.css
CHANGED
|
@@ -231,33 +231,3 @@
|
|
|
231
231
|
color: #016e1a;
|
|
232
232
|
background-color: #dbf0e0;
|
|
233
233
|
}
|
|
234
|
-
|
|
235
|
-
/* DM Agent pause/resume system message variants */
|
|
236
|
-
.mes-dm-agent-system-message {
|
|
237
|
-
display: inline-flex;
|
|
238
|
-
align-items: center;
|
|
239
|
-
justify-content: center;
|
|
240
|
-
gap: 10px;
|
|
241
|
-
margin-inline: auto;
|
|
242
|
-
margin-bottom: 8px;
|
|
243
|
-
width: fit-content;
|
|
244
|
-
max-width: min(100%, 480px);
|
|
245
|
-
padding: 12px;
|
|
246
|
-
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
247
|
-
border-radius: 12px;
|
|
248
|
-
color: rgba(0, 0, 0, 0.55);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.mes-dm-agent-system-message__sparkle {
|
|
252
|
-
flex-shrink: 0;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.mes-dm-agent-system-message__text {
|
|
256
|
-
margin: 0;
|
|
257
|
-
font-size: 14px;
|
|
258
|
-
font-weight: 400;
|
|
259
|
-
line-height: 20px;
|
|
260
|
-
letter-spacing: 0.21px;
|
|
261
|
-
color: rgba(0, 0, 0, 0.55);
|
|
262
|
-
text-align: center;
|
|
263
|
-
}
|