-
Notifications
You must be signed in to change notification settings - Fork 278
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement message bounce flow (#2254)
### π― Goal π GetStream/stream-chat-js#1213 π GetStream/stream-chat-css#264 In chats with moderation rules set up, message can bounce if its content is deemed potentially harmful. The author of a bounced message should then be presented with four alternatives: 1. Edit the message and try sending it again 2. Try sending it again as-is (this is helpful for "bounce then flag" flow) 3. Remove the message 4. Do nothing. Bounced messages are ephemeral, so it will soon disappear on its own ### π Implementation details This PR introduces a couple of new components, including the `MessageBounceModal` which is rendered by `MessageSimple` when a bounced message is clicked. The contents of the modal (`MessageBounceOption`) is an overridable component that should ideally render three buttons for the first three alternative options listed above. The callbacks for said buttons are provided via `MessageBounceContext`. ### π¨ UI Changes The chat in the screenshot has a semantic filter set up which is triggered by the word "midnight". Here's what a bounced message with the word "midnight" looks like: ![image](https://github.com/GetStream/stream-chat-react/assets/975978/9476cde6-f310-41a8-bb6d-3e7ed0f58421) Clicking on the bounced messages opens `MessageBounceModal`: ![image](https://github.com/GetStream/stream-chat-react/assets/975978/9bbb7201-3342-4ce7-88f2-9a3131c96878) Clicking "Edit Message" opens the standard editing UI: ![image](https://github.com/GetStream/stream-chat-react/assets/975978/ef627801-9b3b-46e5-8d12-eb8b3fd08afd) ### To-Do and Next Steps [The design doc](https://www.figma.com/file/ekifwChR9tR7zRJg1QEzSM/Chat-UI-Kit-1.0-All-platforms?type=design&node-id=23638-313355&mode=design) for this feature also features a notification banner with a button, which is displayed when a message bounces. Clicking the button should bring the user to the bounced message. We don't have a way to have interactive elements within channel notifications at the moment, but this is going to be implemented in further PRs in two steps: 1. Allow passing arbitrary JSX to the notification, not just text 2. Implement a bounced message notification with a button to bring the user to the message - [x] Release `stream-chat-css` with udpated styles - [x] Release `stream-chat-js` with updates types - [x] Cover `MessageBounceModal` and `MessageBounceOptions` with tests - [x] Document new components and customization options - [x] Document the moderation flow
- Loading branch information
1 parent
6a928f6
commit 3878e2f
Showing
31 changed files
with
605 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
152 changes: 152 additions & 0 deletions
152
docusaurus/docs/React/components/contexts/message-bounce-context.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
--- | ||
id: message_bounce_context | ||
sidebar_position: 11 | ||
title: MessageBounceContext | ||
--- | ||
|
||
The `MessageBounceContext` is available inside the modal rendered by the default message component for messages that got bounced by the moderation rules. This context provides callbacks that can be used to deal with the bounced message. | ||
|
||
## Basic Usage | ||
|
||
In most cases when using the default Message UI component implementation you are not going to deal with the `MessageBounceContext` directly. However if you are customizing the Message UI component, or providing a custom `MessageBouncePrompt`, the callbacks provided by this context come in handy. | ||
|
||
Get values from context with our custom hook: | ||
|
||
```jsx | ||
const { message, handleEdit, handleSend, handleDelete } = useMessageBounceContext(); | ||
``` | ||
|
||
Use these callbacks to implement your custom `MessageBouncePrompt`. Normally this component displays three options: edit the message before sending it again, send the message again without changes (this can be useful if you are using the "Bounce then flag" moderation flow), and delete the message. | ||
|
||
```jsx | ||
import { useMessageBounceContext } from 'stream-chat-react'; | ||
|
||
function MyCustomMessageBouncePrompt({ onClose }) { | ||
const { message, handleEdit, handleSend, handleDelete } = useMessageBounceContext(); | ||
return ( | ||
<> | ||
<p>Your message is in violation of our community rules.</p> | ||
<p>Message id: "{message.id}"</p> | ||
<button | ||
type='button' | ||
onClick={() => { | ||
handleEdit(); | ||
onClose(); | ||
}} | ||
> | ||
Edit message | ||
</button> | ||
{/* ... */} | ||
</> | ||
); | ||
} | ||
``` | ||
|
||
Then override the default `MessageBouncePrompt` component with your custom one: | ||
|
||
```jsx | ||
<Channel MessageBouncePrompt={MyCustomMessageBouncePrompt}> | ||
<Window> | ||
<ChannelHeader /> | ||
<MessageList /> | ||
<MessageInput /> | ||
</Window> | ||
<Thread /> | ||
</Channel> | ||
``` | ||
|
||
## Usage in a Custom Message UI component | ||
|
||
When implementing your own Message component from scratch, you should consider implementing UI for bounced messages, especially if you are using one of the moderation flows with message bouncing ("Bounce", "Bounce then flag", or "Bounce then block"). | ||
|
||
To do that, first check if the message is bounced: | ||
|
||
```jsx | ||
import { useMessageContext, isMessageBounced } from 'stream-chat-react'; | ||
|
||
function CustomMessage() { | ||
const { message } = useMessageContext(); | ||
const isBounced = isMessageBounced(message); | ||
// ... | ||
} | ||
``` | ||
|
||
Then, display custom UI in case the message is bounced. Don't forget to wrap the UI with the `MessageBounceProvider`, so that it has access to the callbacks used to deal with the bounced message: | ||
|
||
```jsx | ||
import { useMessageContext, isMessageBounced, MessageBounceProvider } from 'stream-chat-react'; | ||
|
||
function MyCustomMessage() { | ||
const { message } = useMessageContext(); | ||
const isBounced = isMessageBounced(message); | ||
|
||
return ( | ||
<div className='message-wrapper'> | ||
{/* ... */} | ||
<MessageText /> | ||
<MessageStatus /> | ||
{isBounced && ( | ||
<MessageBounceProvider> | ||
<MyCustomMessageBouncePrompt /> | ||
</MessageBounceProvider> | ||
)} | ||
</div> | ||
); | ||
} | ||
|
||
function MyCustomMessageBouncePrompt({ onClose }) { | ||
const { message, handleEdit, handleSend, handleDelete } = useMessageBounceContext(); | ||
return ( | ||
<> | ||
<button | ||
type='button' | ||
onClick={(e) => { | ||
handleEdit(e); | ||
onClose(e); | ||
}} | ||
> | ||
Edit message | ||
</button> | ||
{/* ... */} | ||
</> | ||
); | ||
} | ||
``` | ||
|
||
It only makes sense to render `MessageBounceProvider` in the context of a bounced message, so you'll see a warning in the browser console if you try to render it for any other type of message. | ||
|
||
Implementing a custom Message UI component from scratch is a larger topic, covered by the [Message UI Customization](../../guides/theming/message-ui.mdx) guide. | ||
|
||
## Values | ||
|
||
### message | ||
|
||
The object representing the message that got bounced. | ||
|
||
| Type | | ||
| ------------- | | ||
| StreamMessage | | ||
|
||
### handleEdit | ||
|
||
Call this function to switch the bounced message into editing mode. | ||
|
||
| Type | | ||
| ----------------- | | ||
| ReactEventHandler | | ||
|
||
### handleSend | ||
|
||
Call this function to try sending the bounced message again without changes. | ||
|
||
| Type | | ||
| ----------------- | | ||
| ReactEventHandler | | ||
|
||
### handleDelete | ||
|
||
Call this function to remove the bounced message from the message list. | ||
|
||
| Type | | ||
| ----------------- | | ||
| ReactEventHandler | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import React from 'react'; | ||
|
||
import { StreamMessage, useTranslationContext } from '../../context'; | ||
import { DefaultStreamChatGenerics } from '../../types/types'; | ||
import { isMessageBounced } from './utils'; | ||
|
||
export interface MessageErrorTextProps< | ||
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics | ||
> { | ||
message: StreamMessage<StreamChatGenerics>; | ||
theme: string; | ||
} | ||
|
||
export function MessageErrorText< | ||
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics | ||
>({ message, theme }: MessageErrorTextProps<StreamChatGenerics>) { | ||
const { t } = useTranslationContext('MessageText'); | ||
|
||
if (message.type === 'error' && !isMessageBounced(message)) { | ||
return ( | ||
<div className={`str-chat__${theme}-message--error-message str-chat__message--error-message`}> | ||
{t<string>('Error Β· Unsent')} | ||
</div> | ||
); | ||
} | ||
|
||
if (message.status === 'failed') { | ||
return ( | ||
<div className={`str-chat__${theme}-message--error-message str-chat__message--error-message`}> | ||
{message.errorStatusCode !== 403 | ||
? t<string>('Message Failed Β· Click to try again') | ||
: t<string>('Message Failed Β· Unauthorized')} | ||
</div> | ||
); | ||
} | ||
|
||
return null; | ||
} |
Oops, something went wrong.