├── .env
├── .github
└── workflows
│ └── deploy-github-pages.yml
├── .gitignore
├── LICENCE
├── README.md
├── catalog-info.yaml
├── package.json
├── public
├── archive_icon.png
├── document_icon.png
├── double_tick.png
├── double_tick_as_read.png
├── favicon.ico
├── icon_copy.png
├── icon_delete.png
├── icon_edit.png
├── icon_freeze.png
├── icon_not_delivered.png
├── icon_pin.png
├── icon_reschedule.png
├── icon_send-message.png
├── icon_thread.png
├── icon_unpin.png
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── operator_icon.png
├── profile_img.png
├── report_icon.png
├── robots.txt
└── unread_icon.png
├── react-sample.gif
└── src
├── App.js
├── Home.js
├── constants
└── constants.js
├── index.css
├── index.js
├── samples
├── BasicGroupChannelSample.js
├── BasicOpenChannelSample.js
├── GroupChannelArchive.js
├── GroupChannelBanUnbanUsers.js
├── GroupChannelCategorizeByCustomType.js
├── GroupChannelCategorizeMessagesByCustomType.js
├── GroupChannelDisplayOGTags.js
├── GroupChannelFreezeUnfreeze.js
├── GroupChannelMarkMessagesAsRead.js
├── GroupChannelMembersListOrder.js
├── GroupChannelMessageThreading.js
├── GroupChannelMuteUnmuteUsers.js
├── GroupChannelOperatorsList.js
├── GroupChannelPinnedMessages.js
├── GroupChannelPolls.js
├── GroupChannelReactToAMessage.js
├── GroupChannelRegisterUnregisterOperator.js
├── GroupChannelReportAMessageUserChannel.js
├── GroupChannelRetrieveAListOfBannedOrMutedUsers.js
├── GroupChannelRetrieveNumberOfMembersHaventReadMessage.js
├── GroupChannelRetrieveNumberOfMembersHaventReceivedMessage.js
├── GroupChannelRetrieveOnlineStatus.js
├── GroupChannelScheduledMessages.js
├── GroupChannelSendAnAdminMessage.js
├── GroupChannelStructuredData.js
├── GroupChannelTypes.js
├── GroupChannelTypingIndicatorSample.js
├── GroupChannelUpdateDeleteMessageByOperator.js
├── GroupChannelUserDoNotDisturbOrSnooze.js
├── GroupChannelUserProfileUpdate.js
├── GroupChannelUsersOnlineStatus.js
├── OpenChannelAddExtraDataToMessage.js
├── OpenChannelBanUnbanUsers.js
├── OpenChannelCategorizeByCustomType.js
├── OpenChannelCategorizeMessagesByCustomType.js
├── OpenChannelCopyMessage.js
├── OpenChannelDisplayOGTags.js
├── OpenChannelFreeze.js
├── OpenChannelMembersListOrder.js
├── OpenChannelMessageThreading.js
├── OpenChannelMetadataAndMetacounter.js
├── OpenChannelMuteUnmuteUsers.js
├── OpenChannelRegisterUnregisterOperator.js
├── OpenChannelReportAMessageUserChannel.js
├── OpenChannelSendAnAdminMessage.js
├── OpenChannelSendAndReceiveVariousTypesOfFiles.js
├── OpenChannelStructuredData.js
├── OpenChannelThumbnails.js
├── OpenChannelUpdateDeleteMessageByOperator.js
├── OpenChannelUserDoNotDisturbOrSnooze.js
├── OpenChannelUserProfileUpdate.js
└── OpenChannelUsersOnlineStatus.js
└── utils
└── messageUtils.js
/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_SEND_BIRD_TOKEN=7c3cec032dc7d99179470ac86bfc1b27b89fdc01
2 | REACT_APP_SEND_BIRD_ID=6B7BD614-BE69-41C1-8948-8AE067E3B366
--------------------------------------------------------------------------------
/.github/workflows/deploy-github-pages.yml:
--------------------------------------------------------------------------------
1 | name: Deploy React App to GitHub Pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - main # 또는 원하는 브랜치 이름
7 |
8 | jobs:
9 | build-and-deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v2
14 |
15 | - name: Set up Node.js
16 | uses: actions/setup-node@v2
17 | with:
18 | node-version: '18.x' # 또는 프로젝트에 맞는 Node.js 버전
19 |
20 | - name: Install dependencies
21 | run: yarn install
22 |
23 | - name: Build
24 | run: yarn build
25 | env:
26 | CI: false # ESLint 에러 무시
27 | PUBLIC_URL: https://sendbird.github.io/sendbird-chat-sample-react
28 |
29 | - name: Deploy to GitHub Pages
30 | uses: peaceiris/actions-gh-pages@v3
31 | with:
32 | github_token: ${{ secrets.GITHUB_TOKEN }}
33 | publish_dir: ./build
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 | .idea/
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # build files
28 | yarn.lock
29 | package-lock.json
30 |
31 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Sendbird
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sendbird Chat SDK React Sample
2 | > Note: This sample is a Chat SDK sample, not the UIKit React sample.
3 | > [Visit here if you are looking for the **UIKit React sample**.](https://github.com/sendbird/sendbird-uikit-react/tree/main/samples)
4 |
5 | 
6 |
7 | ## Overview
8 | A simple react app that demonstrates how to use the Sendbird [Chat SDK](https://sendbird.com/docs/chat).
9 |
10 | ## Requirement
11 | A Sendbird [account](https://dashboard.sendbird.com/auth/signup).
12 | Node version >16 is required. Download [here](https://nodejs.org/en/).
13 |
14 | ## 🔒 Security tip
15 | When a new Sendbird application is created in the dashboard the default security settings are set permissive to simplify running samples and implementing your first code.
16 |
17 | Before launching make sure to review the security tab under ⚙️ Settings -> Security, and set Access token permission to Read Only or Disabled so that unauthenticated users can not login as someone else. And review the Access Control lists. Most apps will want to disable "Allow retrieving user list" as that could expose usage numbers and other information.
18 |
19 |
20 | ## Setup
21 | Replace {YOUR_SENDBIRD_APP_ID} in constants.js with yout Sendbird app ID.
22 | To get the ID of your Sendbird application, sign in to your dashboard, select the application, go to the Settings > Application > General, and then check the Application ID.
23 |
24 | ## Install
25 | run `npm install`
26 |
27 | ## Running the app
28 |
29 | run `npm start`
30 |
31 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
32 |
33 | ## Considerations in real world app
34 | - In this sample repo users are connecting to sendbird using a user ID (Sendbird Dashboard --> Security --> Read & Write). Read & Write is not secure and will create a new user automatically from the SDK if none exists. In production be sure to change the Sendbird Dashboard security settings to Deny login, and [authenticate users](https://sendbird.com/docs/chat/v3/javascript/guides/authentication#2-connect-to-sendbird-server-with-a-user-id-and-a-token) with a Sendbird generated Session Token.
35 | - Typescript types are available from the Sendbird Chat SDK if you are building with TS.
36 | - Chat is based around user generated input so consider mitigations against XSS attacks.
37 | - Pagination of channel and message lists will be a must have in any real world application.
38 |
39 |
40 |
41 | # Gotchas
42 | - Hot reload can cause issues with the Sendbird Websocket connection while developing. Work around here https://github.com/facebook/create-react-app/issues/2519#issuecomment-318867289
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/catalog-info.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: backstage.io/v1alpha1
2 | kind: Component
3 | metadata:
4 | name: sendbird-chat-sample-react
5 | description: SendBird Chat SDK sample for React
6 | annotations:
7 | github.com/project-slug: sendbird/sendbird-chat-sample-react
8 | spec:
9 | type: library
10 | lifecycle: production
11 | owner: dep-client-platform
12 | system: sendbird-chat
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sendbird-chat-sample-react",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@sendbird/chat": "^4.2.3",
7 | "moment": "^2.29.1",
8 | "react": "^17.0.2",
9 | "react-dom": "^17.0.2",
10 | "react-router-dom": "^6.2.1",
11 | "react-scripts": "5.0.0",
12 | "uuid": "^8.3.2"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test",
18 | "eject": "react-scripts eject"
19 | },
20 | "eslintConfig": {
21 | "extends": [
22 | "react-app",
23 | "react-app/jest"
24 | ]
25 | },
26 | "browserslist": {
27 | "production": [
28 | ">0.2%",
29 | "not dead",
30 | "not op_mini all"
31 | ],
32 | "development": [
33 | "last 1 chrome version",
34 | "last 1 firefox version",
35 | "last 1 safari version"
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/public/archive_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/archive_icon.png
--------------------------------------------------------------------------------
/public/document_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/document_icon.png
--------------------------------------------------------------------------------
/public/double_tick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/double_tick.png
--------------------------------------------------------------------------------
/public/double_tick_as_read.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/double_tick_as_read.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/favicon.ico
--------------------------------------------------------------------------------
/public/icon_copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/icon_copy.png
--------------------------------------------------------------------------------
/public/icon_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/icon_delete.png
--------------------------------------------------------------------------------
/public/icon_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/icon_edit.png
--------------------------------------------------------------------------------
/public/icon_freeze.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/icon_freeze.png
--------------------------------------------------------------------------------
/public/icon_not_delivered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/icon_not_delivered.png
--------------------------------------------------------------------------------
/public/icon_pin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/icon_pin.png
--------------------------------------------------------------------------------
/public/icon_reschedule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/icon_reschedule.png
--------------------------------------------------------------------------------
/public/icon_send-message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/icon_send-message.png
--------------------------------------------------------------------------------
/public/icon_thread.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/icon_thread.png
--------------------------------------------------------------------------------
/public/icon_unpin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/icon_unpin.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
25 | React App
26 |
27 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/operator_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/operator_icon.png
--------------------------------------------------------------------------------
/public/profile_img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/profile_img.png
--------------------------------------------------------------------------------
/public/report_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/report_icon.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/unread_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/public/unread_icon.png
--------------------------------------------------------------------------------
/react-sample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendbird/sendbird-chat-sample-react/6628b653e4cfd194e0c53df481a1449a7ffdc240/react-sample.gif
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Routes, Route } from 'react-router-dom';
3 | import Home from './Home';
4 | // Basic samples
5 | import BasicOpenChannelSample from './samples/BasicOpenChannelSample';
6 | import BasicGroupChannelSample from './samples/BasicGroupChannelSample';
7 |
8 | // Open channel samples
9 | import OpenChannelCategorizeByCustomType from './samples/OpenChannelCategorizeByCustomType';
10 | import OpenChannelThumbnails from './samples/OpenChannelThumbnails';
11 | import OpenChannelMessageThreading from './samples/OpenChannelMessageThreading';
12 | import OpenChannelSendAnAdminMessage from './samples/OpenChannelSendAnAdminMessage'
13 | import OpenChannelFreeze from './samples/OpenChannelFreeze';
14 | import OpenChannelSendAndReceiveVariousTypesOfFiles from './samples/OpenChannelSendAndReceiveVariousTypesOfFiles'
15 | import OpenChannelCopyMessage from './samples/OpenChannelCopyMessage';
16 | import OpenChannelDisplayOGTags from './samples/OpenChannelDisplayOGTags';
17 | import OpenChannelMetadataAndMetacounter from './samples/OpenChannelMetadataAndMetacounter';
18 | import OpenChannelAddExtraDataToMessage from './samples/OpenChannelAddExtraDataToMessage';
19 | import OpenChannelReportAMessageUserChannel from './samples/OpenChannelReportAMessageUserChannel'
20 | import OpenChannelUsersOnlineStatus from './samples/OpenChannelUsersOnlineStatus';
21 | import OpenChannelCategorizeMessagesByCustomType from './samples/OpenChannelCategorizeMessagesByCustomType';
22 | import OpenChannelUserProfileUpdate from './samples/OpenChannelUserProfileUpdate';
23 | import OpenChannelStructuredData from './samples/OpenChannelStructuredData';
24 | import OpenChannelUserDoNotDisturbOrSnooze from './samples/OpenChannelUserDoNotDisturbOrSnooze';
25 | import OpenChannelRegisterUnregisterOperator from './samples/OpenChannelRegisterUnregisterOperator';
26 | import OpenChannelMuteUnmuteUsers from './samples/OpenChannelMuteUnmuteUsers';
27 | import OpenChannelBanUnbanUsers from './samples/OpenChannelBanUnbanUsers';
28 | import OpenChannelUpdateDeleteMessageByOperator from './samples/OpenChannelUpdateDeleteMessageByOperator';
29 | import OpenChannelMembersListOrder from './samples/OpenChannelMembersListOrder';
30 |
31 | // Group channel samples
32 | import GroupChannelTypingIndicatorSample from './samples/GroupChannelTypingIndicatorSample';
33 | import GroupChannelMessageThreading from './samples/GroupChannelMessageThreading'
34 | import GroupChannelSendAnAdminMessage from './samples/GroupChannelSendAnAdminMessage';
35 | import GroupChannelFreezeUnfreeze from './samples/GroupChannelFreezeUnfreeze';
36 | import GroupChannelDisplayOGTags from './samples/GroupChannelDisplayOGTags';
37 | import GroupChannelMarkMessagesAsRead from './samples/GroupChannelMarkMessagesAsRead';
38 | import GroupChannelReactToAMessage from './samples/GroupChannelReactToAMessage';
39 | import GroupChannelCategorizeByCustomType from './samples/GroupChannelCategorizeByCustomType';
40 | import GroupChannelReportAMessageUserChannel from './samples/GroupChannelReportAMessageUserChannel'
41 | import GroupChannelCategorizeMessagesByCustomType from './samples/GroupChannelCategorizeMessagesByCustomType';
42 | import GroupChannelRetrieveOnlineStatus from './samples/GroupChannelRetrieveOnlineStatus';
43 | import GroupChannelRegisterUnregisterOperator from './samples/GroupChannelRegisterUnregisterOperator';
44 | import GroupChannelReactChannelTypes from './samples/GroupChannelTypes';
45 | import GroupChannelUpdateDeleteMessageByOperator from './samples/GroupChannelUpdateDeleteMessageByOperator';
46 | import GroupChannelArchive from './samples/GroupChannelArchive'
47 | import GroupChannelMuteUnmuteUsers from './samples/GroupChannelMuteUnmuteUsers';
48 | import GroupChannelBanUnbanUsers from './samples/GroupChannelBanUnbanUsers';
49 | import GroupChannelRetrieveNumberOfMembersHaventReceivedMessage from './samples/GroupChannelRetrieveNumberOfMembersHaventReceivedMessage';
50 | import GroupChannelOperatorsList from './samples/GroupChannelOperatorsList';
51 | import GroupChannelMembersListOrder from './samples/GroupChannelMembersListOrder';
52 | import GroupChannelRetrieveAListOfBannedOrMutedUsers from './samples/GroupChannelRetrieveAListOfBannedOrMutedUsers';
53 | import GroupChannelUserProfileUpdate from './samples/GroupChannelUserProfileUpdate';
54 | import GroupChannelRetrieveNumberOfMembersHaventReadMessage from './samples/GroupChannelRetrieveNumberOfMembersHaventReadMessage';
55 | import GroupChannelUserDoNotDisturbOrSnooze from './samples/GroupChannelUserDoNotDisturbOrSnooze';
56 | import GroupChannelStructuredData from './samples/GroupChannelStructuredData';
57 | import GroupChannelPolls from './samples/GroupChannelPolls.js';
58 | import GroupChannelUsersOnlineStatus from './samples/GroupChannelUsersOnlineStatus';
59 | import GroupChannelScheduledMessages from './samples/GroupChannelScheduledMessages';
60 | import GroupChannelPinnedMessages from './samples/GroupChannelPinnedMessages';
61 |
62 | const App = () => {
63 | return (
64 |
65 |
66 | } />
67 | } />
68 | } />
69 | } />
70 | } />
71 | } />
72 | } />
73 | } />
74 | } />
75 | } />
76 | } />
77 | } />
78 | } />
79 | } />
80 | } />
81 | } />
82 | } />
83 | } />
84 | } />
85 | } />
86 | } />
87 | } />
88 | } />
89 | } />
90 | } />
91 | } />
92 | } />
93 | } />
94 | } />
95 | } />
96 | } />
97 | } />
98 | } />
99 | } />
100 | } />
101 | } />
102 | } />
103 | } />
104 | } />
105 | } />
106 | } />
107 | } />
108 | } />
109 | } />
110 | } />
111 | } />
112 | } />
113 | } />
114 | } />
115 | } />
116 | } />
117 | } />
118 | } />
119 | } />
120 | } />
121 |
122 |
123 | );
124 | }
125 |
126 | export default App;
127 |
--------------------------------------------------------------------------------
/src/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const Home = () => {
5 | return (
6 |
7 |
8 |
Basic Code Samples
9 |
10 | - Basic Open Channel
11 | - Basic Group Channel
12 |
13 |
14 |
15 |
Open Channel Code Samples
16 |
17 | - Open Channel Send an Admin message
18 | - Open Channel Message Threading
19 | - Open Channel Copy Message
20 | - Open Channel Send and Receive Various Types of Files
21 | - Open Channel Display OG-tags
22 | - Open Channel with Categorize by custom type feature
23 | - Open Channel with auto generated Thumbnails feature
24 | - Open Channel with Freeze feature
25 | - Open Channel Report a message,user or channel
26 | - Open Channel with Categorize messages by custom type feature
27 | - Open Channel Metadata and Metacounter
28 | - Open Channel Add extra data to message
29 | - Open Channel Users online status
30 | - Open Channel User Profile update
31 | - Open Channel Structured Data
32 | - Open Channel User do not disturb or snooze
33 | - Open Channel Register and Unregister operator
34 | - Open Channel Mute and Unmute users
35 | - Open Channel Ban and Unban users
36 | - Open Channel Update and delete message by operator
37 | - Open Channel Members list order
38 |
39 |
40 |
41 |
Group Channel Code Samples
42 |
43 | - Group Channel with Typing Indicator
44 | - Group Channel Message Threading
45 | - Group Channel Send An Admin Message
46 | - Group Channel with Freeze feature
47 | - Group Channel Display OG-tags
48 | - Group Channel React to a message
49 | - Group Channel with Categorize by custom type feature
50 | - Group Channel Report a message,user, or channel
51 | - Group Channel mark messages as read
52 | - Group Channel retrieve online status
53 | - Group Channel local caching
54 | - Group Channel with Categorize messages by custom type feature
55 | - Group Channel Register and Unregister operator
56 | - Group Channel Types
57 | - Group Channel Update and delete message by operator
58 | - Group Channel Archive
59 | - Group Channel Mute Unmute users
60 | - Group Channel Ban and Unban users
61 | - Group Channel Retrieve number of members haven't received message
62 | - Group Channel Operators List
63 | - Group Channel Members list order
64 | - Group Channel Retrieve banned or muted users
65 | - Group Channel Users online status
66 | - Group Channel User Profile update
67 | - Group Channel Retrieve number of members haven't read message
68 | - Group Channel User do not disturb or snooze
69 | - Group Channel Structured Data
70 | - Group Channel Polls
71 | - Group Channel Scheduled Messages
72 | - Group Channel Pinned Messages
73 |
74 |
75 |
76 | );
77 | }
78 |
79 | export default Home;
80 |
--------------------------------------------------------------------------------
/src/constants/constants.js:
--------------------------------------------------------------------------------
1 | export const SENDBIRD_INFO = { appId: 'FEA2129A-EA73-4EB9-9E0B-EC738E7EB768' };
2 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | html, body, #root, #root>div {
2 | height: 100%
3 | }
4 |
5 | body {
6 | margin: 0;
7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
9 | sans-serif;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 |
14 | img{
15 | width:300px;
16 | }
17 |
18 | button{
19 | border: none;
20 | color: white;
21 | padding: 14px 32px;
22 | text-align: center;
23 | text-decoration: none;
24 | display: inline-block;
25 | font-size: 16px;
26 | border-radius:2px;
27 | cursor: pointer;
28 | background-color: #424242;
29 | }
30 |
31 | input{
32 | width: 30%;
33 | padding: 6px;
34 | border: 1px solid #cbcbcb;
35 | outline:none;
36 | }
37 |
38 |
39 | p {
40 | margin: 0;
41 | }
42 |
43 | code {
44 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
45 | monospace;
46 | }
47 | .container{
48 | height:100%;
49 | background:white;
50 | display: flex;
51 | }
52 |
53 | .channel-type{
54 | padding: 12px;
55 | color: #6210CC;
56 | }
57 |
58 | .group-card {
59 | width: fit-content;
60 | padding: 15px;
61 | margin-top: 20px;
62 | border: 1px solid white;
63 | border-radius: 10px;
64 | }
65 |
66 | .card {
67 | width: fit-content;
68 | padding: 15px;
69 | margin-left: 60px;
70 | border: 1px solid black;
71 | border-radius: 10px;
72 | }
73 |
74 | .card-name {
75 | font-weight: bold;
76 | }
77 |
78 | .channel-list {
79 | background: white;
80 | flex:1;
81 | border-right: 1px solid #cbcbcb;
82 | overflow-y: scroll;
83 | }
84 |
85 | .channel-list-item{
86 | background: #fcfcfc;
87 | padding:12px;
88 | border-top: 1px solid #ececec;
89 | display:flex;
90 | justify-content: space-between;
91 | align-items: center;
92 | min-height: 42px;
93 | }
94 |
95 | .channel-list-item:hover{
96 | background:#f0f0f0;
97 | }
98 |
99 | .channel-list-item:hover .channel-icon{
100 | visibility: visible;
101 | }
102 |
103 | .sort-channels {
104 | width: 100%;
105 | font-size: 16px;
106 | padding: 7px;
107 | cursor: pointer;
108 | }
109 |
110 | .unarchive {
111 | transform: rotate(180deg);
112 | }
113 |
114 | .archived-channel {
115 | padding: 10px;
116 | }
117 |
118 | .last-message{
119 | font-weight:300;
120 | }
121 |
122 | .channel-list-item-name{
123 | font-weight: 600;
124 | cursor: pointer;
125 | overflow: hidden;
126 | max-width: 75ch;
127 | text-overflow: ellipsis;
128 | white-space: nowrap;
129 | flex:1;
130 | }
131 |
132 | .channel-list-item-sup {
133 | margin: 0 0 0 5px;
134 | font-size: 10px;
135 | }
136 |
137 | .channel-icon{
138 | visibility:hidden;
139 | width: 20px;
140 | }
141 |
142 | .channel{
143 | background: white;
144 | flex: 3;
145 | display: flex;
146 | flex-direction: column;
147 | overflow-y: scroll;
148 | margin-bottom: 102px;
149 | }
150 |
151 | .channel-fixed-header {
152 | margin-top: 160px;
153 | }
154 |
155 | .thread {
156 | max-width: 35vw;
157 | }
158 |
159 | .thread, .thread-input {
160 | border-left: 1px solid lightgray;
161 | }
162 |
163 | .underline {
164 | width: 100%;
165 | border-bottom: 1px solid lightgray;
166 | }
167 |
168 | .threads {
169 | max-width: 35vw;
170 | }
171 |
172 | .threads, .threads-input {
173 | border-left: 1px solid lightgray;
174 | }
175 |
176 | .underline {
177 | width: 100%;
178 | border-bottom: 1px solid lightgray;
179 | }
180 |
181 | .channel-controls {
182 | display: flex;
183 | align-items: center;
184 | justify-content: space-between;
185 | }
186 |
187 | .leave-channel{
188 | margin-left: 12px;
189 | padding: 8px;
190 | }
191 |
192 | .freeze-channel {
193 | margin-right: 12px;
194 | }
195 |
196 | .freeze-channel input {
197 | width: auto;
198 | }
199 |
200 | .message-list{
201 | height: 100%;
202 | display: flex;
203 | flex-direction: column;
204 |
205 | }
206 |
207 | .message-item{
208 | margin: 12px;
209 | display:flex;
210 | flex-direction: row-reverse;
211 | justify-content: flex-end;
212 | }
213 |
214 | .message.message-from-you{
215 | margin-left: auto;
216 | background: #39a9ff;
217 | flex-direction: row;
218 | margin-right: 12px;
219 | }
220 |
221 | .message-item.message-from-you{
222 | flex-direction: row;
223 | }
224 |
225 | .oc-message, .oc-message-og-tags{
226 | padding:12px;
227 | display:flex;
228 | }
229 |
230 | .oc-message-og-tags {
231 | flex-direction: column;
232 | max-width: 50%;
233 | padding: 5px;
234 | margin-left: 7vw;
235 | margin-top: -10px;
236 | box-sizing: border-box;
237 | border: 1px solid black;
238 | }
239 |
240 | .oc-message, .message-og-tags {
241 | padding:12px 0px;
242 | display:flex;
243 | }
244 |
245 | .message-og-tags {
246 | flex-direction: column;
247 | padding: 5px;
248 | box-sizing: border-box;
249 | }
250 |
251 | .og-tags-title {
252 | margin: 0 0 10px;
253 | }
254 |
255 | .og-tags-description, .og-tags-url, .metadata-modal-list {
256 | margin: 0 0 5px;
257 | }
258 |
259 | .og-tags-img, .show-users-status-btn, .members-invite-btn {
260 | width: 100%;
261 | }
262 |
263 | .oc-admin-message {
264 | background: rgba(255, 0, 0, 0.05);
265 | }
266 |
267 | .report-notification {
268 | background: rgba(0, 255, 0, 0.5);
269 | color: white;
270 | width: 100%;
271 | text-align: center;
272 | cursor: pointer;
273 | margin: 5px 0;
274 | padding: 5px;
275 | }
276 |
277 | .message{
278 | border-radius: 8px;
279 | width:70%;
280 | background: #4c4c4c;
281 | color:white;
282 | padding: 24px;
283 | margin-left: 12px;
284 | }
285 |
286 | .admin-message {
287 | background: rgba(255, 0, 0, 0.05);
288 | width: 100%;
289 | color: black;
290 | margin-left: 0;
291 | }
292 |
293 | .message-info{
294 | margin-bottom:4px;
295 | display: flex;
296 | justify-content: space-between;
297 | line-height: 24px;
298 | }
299 |
300 | .message-icon{
301 | visibility:hidden;
302 | width: 14px;
303 | filter: brightness(0) invert(1);
304 | }
305 |
306 | .oc-message-icon, .oc-channel-list-icon{
307 | visibility:hidden;
308 | width: 14px;
309 | }
310 |
311 |
312 | .message:hover .message-icon, .channel-list-item:hover .oc-channel-list-icon, .member-item:hover .message-icon, .double_tick-icon, .member-item-wrapper .message-icon {
313 | visibility: visible;
314 | }
315 |
316 | .double_tick-icon-read {
317 | filter: none;
318 | }
319 |
320 | .mute-button {
321 | margin-top: 3px;
322 | }
323 |
324 | .member-item .message-icon {
325 | filter: none;
326 | }
327 |
328 | .oc-document-message, .message-user-info, .react-button-wrapper, .reactions-list, .metadata-modal-list, .create-channel-wrapper {
329 | display: flex;
330 | }
331 |
332 | .reactions-list {
333 | list-style: none;
334 | margin: 0;
335 | padding-left: 0;
336 | background: white;
337 | border-radius: 8px;
338 | }
339 |
340 | .reactions-list li {
341 | margin-right: 2px;
342 | padding: 4px;
343 | }
344 |
345 | .react-button-wrapper {
346 | margin: 5px 5px 0 0;
347 | width: 100%;
348 | justify-content: flex-end;
349 | }
350 |
351 | .react-button-img {
352 | width: 20px;
353 | filter: none;
354 | }
355 |
356 | .reactions {
357 | margin: 5px 0 0;
358 | }
359 |
360 | .reactions-item {
361 | cursor: pointer;
362 | margin-right: 5px;
363 | }
364 |
365 | .reactions-item-inner {
366 | font-size: 10px;
367 | }
368 |
369 | .oc-document-message-icon {
370 | width: 24px;
371 | }
372 |
373 | .oc-message:hover .oc-message-icon, .message-input .oc-message-icon, .metadata-modal-list .oc-message-icon {
374 | visibility: visible;
375 | }
376 |
377 | .oc-message-sender-name{
378 | color:#6210CC;
379 | margin-left:4px;
380 | font-weight: 600;
381 | margin-right:4px;
382 | }
383 |
384 | .message-sender-name{
385 | font-weight: 600;
386 | margin-right:4px;
387 | }
388 |
389 | .channel-header{
390 | padding: 12px;
391 | font-size: 24px;
392 | font-weight: 800;
393 | }
394 |
395 | .channel-header-wrapper {
396 | position: fixed;
397 | width: 100%;
398 | top: 0;
399 | }
400 |
401 | .members-list{
402 | border-left: 1px solid #cbcbcb;
403 | padding: 12px;
404 | }
405 |
406 | .member-item{
407 | padding: 8px 0px 0px 0px;
408 | cursor: pointer;
409 | font-weight: 600;
410 | border-bottom: 1px solid #cfcfcf;
411 | padding-bottom: 4px;
412 | display:flex;
413 | align-items: center;
414 | justify-content: space-between;
415 | }
416 |
417 | .member-item-wrapper {
418 | display: flex;
419 | flex-direction: column;
420 | margin: 5px 0;
421 | }
422 |
423 | .member-item:hover{
424 | background:#f0f0f0;
425 | }
426 |
427 | .member-item-name{
428 | margin-left:12px;
429 | }
430 |
431 | .member-list-btn {
432 | margin: 0 0 5px;
433 | }
434 |
435 | .member-selected{
436 | color: #62EEAB;
437 | }
438 |
439 | .operators-list {
440 | padding: 20px 0;
441 | font-weight: bold;
442 | }
443 |
444 | .operator-item {
445 | padding: 8px 0px 0px 0px;
446 | cursor: pointer;
447 | font-weight: 600;
448 | border-bottom: 1px solid #cfcfcf;
449 | padding-bottom: 4px;
450 | display:flex;
451 | align-items: center;
452 | justify-content: space-between;
453 | }
454 |
455 | .operator-item:hover {
456 | background:#f0f0f0;
457 | }
458 |
459 | .message-input{
460 | position: fixed;
461 | bottom: 0px;
462 | display: flex;
463 | border-top: 1px solid #cbcbcb;
464 | width: 100%;
465 | padding: 12px;
466 | background: white;
467 | }
468 |
469 | .overlay{
470 | position: fixed;
471 | display: flex;
472 | justify-content: center;
473 | align-items: center;
474 | width: 100%;
475 | height: 100%;
476 | background: #ecececb0;
477 | z-index: 1;
478 | }
479 |
480 | .overlay-content{
481 | overflow-y: scroll;
482 | max-height: 400px;
483 | padding:80px;
484 | opacity: 1;
485 | background: white;
486 | border: 1px solid #cbcbcb;
487 | width: 40%;
488 | }
489 |
490 | .custom-type-messages {
491 | position: fixed;
492 | display: flex;
493 | justify-content: center;
494 | align-items: center;
495 | width: 100%;
496 | height: 100%;
497 | z-index: 1;
498 | }
499 |
500 | .custom-type-messages-content {
501 | overflow-y: scroll;
502 | max-height: 400px;
503 | padding:20px;
504 | opacity: 1;
505 | background: white;
506 | border: 1px solid #cbcbcb;
507 | width: 40%;
508 | }
509 |
510 | .custom-type-button {
511 | margin: 12px 0 0 12px;
512 | }
513 |
514 | .form-input{
515 | margin-top: 12px;
516 | margin-bottom: 12px;
517 | padding:18px;
518 | width:100%;
519 | border-radius: 2px;
520 | }
521 |
522 | .form-button{
523 | margin-right:4px;
524 | }
525 |
526 | .user-submit-button{
527 | background-color: #6210CC;
528 | }
529 |
530 | .create-channel-wrapper {
531 | align-items: baseline;
532 | }
533 |
534 | .channel-create-button{
535 | background-color: #62EEAB;
536 | width: 100%;
537 | }
538 |
539 | .create-channel-checkbox {
540 | width: auto;
541 | margin: 0 5px 0 10px;
542 | }
543 |
544 | .select-custom-type {
545 | padding: 12px;
546 | }
547 |
548 | .select-custom-type select {
549 | width: 100%;
550 | font-size: 16px;
551 | padding-top: 12px;
552 | outline: none;
553 | border: none;
554 | cursor: pointer;
555 | }
556 |
557 | .send-message-button{
558 | border-radius: 0px 4px 4px 0px;
559 | margin-right:8px;
560 | }
561 |
562 | .control-button {
563 | background: inherit;
564 | padding:4px;
565 | margin-right:1px;
566 | }
567 |
568 | .error{
569 | background:rgb(26, 22, 22);
570 | display:flex;
571 | padding:24px;
572 | width:100%;
573 | }
574 |
575 | .file-upload-label, .user-profile-image-upload-label, .number-of-undelivered-members-btn {
576 | cursor: pointer;
577 | }
578 |
579 | .file-upload-label img{
580 | width: 46px;
581 | vertical-align: middle;
582 | }
583 |
584 | .message-type-add {
585 | cursor: pointer;
586 | padding: 12px;
587 | }
588 |
589 | .message-type-label {
590 | margin: 0 10px;
591 | }
592 |
593 | .message-type-select {
594 | font-size: 16px;
595 | padding: 7px;
596 | outline: none;
597 | cursor: pointer;
598 | }
599 |
600 | .profile-image-fallback{
601 | width: 50px;
602 | background: #e9e9e9;
603 | height: 50px;
604 | border-radius: 25px;
605 | display: flex;
606 | align-items: center;
607 | justify-content: center;
608 | }
609 |
610 | .profile-image{
611 | width: 50px;
612 | height: 50px;
613 | border-radius: 25px;
614 | object-fit: cover;
615 | }
616 |
617 | .typing-indicator {
618 | position: fixed;
619 | bottom:80px;
620 | background-color: #fff;
621 | opacity: 0.8;
622 | margin-left: 20px;
623 | margin-top:20px;
624 | font-weight: 500;
625 | }
626 |
627 | .display-none {
628 | display: none;
629 | }
630 |
631 | .message-input-column {
632 | flex-direction: column;
633 | }
634 |
635 | .freeze-notification {
636 | position: sticky;
637 | top:0;
638 | width: 100%;
639 | line-height: 30px;
640 | opacity: 70%;
641 | background-color: rgb(173,201,255);
642 | text-align: center;
643 | }
644 |
645 | .frozen-icon {
646 | width: 20px;
647 | margin-right: 10px;
648 | }
649 |
650 | .underline {
651 | width: 100%;
652 | border-bottom: 1px solid lightgray;
653 | }
654 |
655 | .user-copied-message {
656 | position: fixed;
657 | right: 20px;
658 | }
659 |
660 | .image-size-error {
661 | width: 100%;
662 | background: rgba(255, 0, 0, 0.2);
663 | padding: 5px;
664 | margin: 5px 0;
665 | text-align: center;
666 | }
667 |
668 | .channel-header-wrapper {
669 | display: flex;
670 | justify-content: space-between;
671 | }
672 |
673 | .create-metadata-conteiner {
674 | display: flex;
675 | justify-content: space-between;
676 | align-items: baseline;
677 | padding: 12px;
678 | }
679 |
680 | .create-metadata-title {
681 | margin: 0;
682 | padding: 5px;
683 | font-size: 16px;
684 | }
685 |
686 | .create-metadata-btn {
687 | padding: 5px 10px;
688 | }
689 |
690 | .metadata-modal-input {
691 | display: flex;
692 | margin: 0 0 10px;
693 | }
694 |
695 | .choise-report-obj {
696 | cursor: pointer;
697 | color: #6210CC;
698 | text-decoration: underline;
699 | }
700 |
701 | .number-of-undelivered-members {
702 | padding: 5px 0;
703 | display: flex;
704 | justify-content: space-between;
705 | }
706 |
707 | .number-of-undelivered-message-btn:hover::after {
708 | content: attr(data-title);
709 | position: absolute;
710 | border: 1px solid gray;
711 | background-color: darkgray;
712 | border-radius: 5px;
713 | padding: 2px 4px;
714 | font-size: 10px;
715 | }
716 |
717 | .banned-muted-users-list {
718 | margin-top: 20px;
719 | }
720 |
721 | .show-users-status-btn {
722 | display: block;
723 | margin-top: 5px;
724 | }
725 |
726 | .user-online-status {
727 | margin: 0 0 0 5px;
728 | }
729 | .user-profile-title {
730 | margin-top: 0;
731 | }
732 |
733 | .user-profile-image-wrapper, .user-profile-nickname-wrapper {
734 | display: flex;
735 | align-items: center;
736 | justify-content: space-between;
737 | padding: 5px;
738 | }
739 |
740 | .user-profile-nickname-wrapper {
741 | margin: 0 0 10px;
742 | }
743 |
744 | .user-profile-image {
745 | border-radius: 50%;
746 | padding: 5px;
747 | width: 50px;
748 | height: 50px;
749 | object-fit: cover;
750 | }
751 |
752 | .user-profile-nickname-input {
753 | width: auto;
754 | }
755 |
756 | .user-profile-nickname-button {
757 | border: none;
758 | background: transparent;
759 | color: black;
760 | }
761 |
762 | .user-profile-image-upload-label, .user-profile-nickname-button {
763 | font-family: Arial, Helvetica, sans-serif;
764 | font-weight: 700;
765 | padding: 5px;
766 | }
767 |
768 | .number-of-undelivered-members {
769 | padding: 5px 0;
770 | display: flex;
771 | justify-content: space-between;
772 | }
773 |
774 | .number-of-undelivered-message-btn:hover::after {
775 | content: attr(data-title);
776 | position: absolute;
777 | border: 1px solid gray;
778 | background-color: darkgray;
779 | border-radius: 5px;
780 | padding: 2px 4px;
781 | font-size: 10px;
782 | }
783 |
784 | .do-not-disturb-wrapper, .do-not-disturb-start-wrapper, .do-not-disturb-end-wrapper, .notifications-snooze-wrapper, .notifications-snooze-input-wrapper {
785 | display: flex;
786 | }
787 |
788 | .do-not-disturb-wrapper, .notifications-snooze-wrapper {
789 | flex-direction: column;
790 | }
791 |
792 | .do-not-disturb-wrapper, .do-not-disturb-start-wrapper, .do-not-disturb-end-wrapper, .notifications-snooze-wrapper, .notifications-snooze-input-wrapper {
793 | margin-bottom: 10px;
794 | }
795 |
796 | .notifications-overlay-content {
797 | padding: 40px;
798 | width: 50%;
799 | max-height: 500px;
800 | }
801 |
802 | .notification-modal-label, .notification-modal-input {
803 | margin-right: 10px;
804 | }
805 |
806 | .notification-modal-input {
807 | width: 40%;
808 | }
809 |
810 | .open-poll-btn {
811 | padding: 5px;
812 | background: transparent;
813 | color: black;
814 | margin-left: 2px;
815 | }
816 |
817 | .create-poll-modal, .create-poll-modal_inputs {
818 | display: flex;
819 | flex-direction: column;
820 | }
821 |
822 | .create-poll-modal {
823 | position: relative;
824 | }
825 |
826 | .create-poll-modal input {
827 | padding: 14px 32px 14px 6px;
828 | }
829 |
830 | .create-poll-modal_inputs {
831 | justify-content: space-between;
832 | margin-bottom: 10px;
833 | }
834 |
835 | .create-poll-modal .input_wrapper {
836 | margin-bottom: 5px;
837 | }
838 |
839 | .create-poll-modal .input_wrapper input[type="text"] {
840 | width: 60%;
841 | }
842 |
843 | .create-poll-modal h3 {
844 | text-align: center;
845 | }
846 |
847 | .option-add_btn {
848 | border: 1px solid #cbcbcb;
849 | border-radius: 0 4px 4px 0;
850 | font-size: 14px;
851 | }
852 |
853 | .poll-close_btn {
854 | position: absolute;
855 | top: 40px;
856 | right: 40px;
857 | cursor: pointer;
858 | }
859 |
860 | .poll-create_btn {
861 | margin: 0 auto;
862 | }
863 |
864 | .poll-options {
865 | width: 100%;
866 | }
867 |
868 | .options_item {
869 | padding: 0 4px;
870 | margin-right: 4px;
871 | background: #cbcbcb;
872 | color: white;
873 | border-radius: 4px;
874 | cursor: pointer;
875 | }
876 |
877 | .oc-members-list {
878 | margin-bottom: 80px;
879 | overflow-x: hidden;
880 | overflow-y: scroll;
881 | }
882 |
883 | .scheduled-messages-list-modal {
884 | position: relative;
885 | }
886 |
887 | .close-button {
888 | background: none;
889 | padding: 0;
890 | position: absolute;
891 | top: 24px;
892 | right: 24px;
893 | }
894 |
895 | .update-input {
896 | display: flex;
897 | }
898 |
899 | .update-input input {
900 | width: 50%;
901 | }
902 |
903 | .scheduled-message-header {
904 | font-weight: 800;
905 | margin-bottom: 30px;
906 | }
907 |
908 | .schedule-message {
909 | margin-top: 20px;
910 | }
911 |
912 | .cancel-button {
913 | margin-right: 10px;
914 | }
915 |
916 | .scheduled-messages-settings-modal {
917 | z-index: 2;
918 | }
919 |
920 | .settings-header {
921 | margin-bottom: 30px;
922 | font-weight: 800;
923 | }
924 |
925 | .schedule-message-error {
926 | color: red;
927 | margin-bottom: 20px;
928 | }
929 |
930 | [data-tooltip] {
931 | position: relative;
932 | }
933 |
934 | [data-tooltip]::after {
935 | content: attr(data-tooltip);
936 | position: absolute;
937 | left: 0;
938 | top: 0;
939 | opacity: 0;
940 | transition: 1s;
941 | pointer-events: none;
942 | padding: 2px;
943 | background-color: #424242;
944 | border-radius: 4px;
945 | font-size: 10px;
946 | width: max-content;
947 | z-index: 100;
948 | }
949 |
950 | [data-tooltip]:hover::after {
951 | opacity: 1;
952 | top: -1.5em;
953 | }
954 |
955 | .option-icon {
956 | visibility:hidden;
957 | width: 14px;
958 | filter: brightness(0) invert(1);
959 | }
960 |
961 | .option_wrapper:hover .option-icon {
962 | visibility: visible;
963 | }
964 |
965 | .create-poll-modal .option input[type="text"] {
966 | width: 55%;
967 | }
968 |
969 | .add-new-option {
970 | background: white;
971 | border-radius: 4px;
972 | color: black;
973 | padding: 5px;
974 | font-size: 12px;
975 | margin-top: 10px;
976 | }
977 |
978 | .poll-message {
979 | position: relative;
980 | }
981 |
982 | .poll-status {
983 | position: absolute;
984 | bottom: 24px;
985 | right: 24px;
986 | font-size: 10px;
987 | padding: 5px;
988 | }
989 |
990 | .option_wrapper label, .add-new-option {
991 | margin-right: 10px;
992 | }
993 |
994 | .poll-status_wrapper {
995 | width: 100%;
996 | min-height: 25px;
997 | }
998 |
999 | .vote {
1000 | margin-right: 5px;
1001 | padding: 5px;
1002 | font-size: 10px;
1003 | background: white;
1004 | color: black;
1005 | border-radius: 50%;
1006 | }
1007 |
1008 | .pin-checkbox {
1009 | display: inline-block;
1010 | }
1011 |
1012 | .message-img {
1013 | max-width: 100%;
1014 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import { HashRouter as Router } from "react-router-dom";
6 |
7 |
8 | ReactDOM.render(
9 |
10 |
11 | ,
12 | document.getElementById('root')
13 | );
14 |
--------------------------------------------------------------------------------
/src/samples/BasicOpenChannelSample.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react';
2 | import { ConnectionHandler } from '@sendbird/chat';
3 |
4 | import { v4 as uuid } from 'uuid';
5 |
6 | import SendbirdChat from '@sendbird/chat';
7 | import {
8 | OpenChannelModule,
9 | OpenChannelHandler,
10 | } from '@sendbird/chat/openChannel';
11 |
12 | import { SENDBIRD_INFO } from '../constants/constants';
13 | import { timestampToTime, handleEnterPress } from '../utils/messageUtils';
14 |
15 | let sb;
16 |
17 | const BasicOpenChannelSample = (props) => {
18 |
19 | const [state, updateState] = useState({
20 | currentlyJoinedChannel: null,
21 | currentlyUpdatingChannel: null,
22 | messages: [],
23 | channels: [],
24 | showChannelCreate: false,
25 | messageInputValue: "",
26 | userNameInputValue: "",
27 | userIdInputValue: "",
28 | channelNameInputValue: "",
29 | settingUpUser: true,
30 | file: null,
31 | messageToUpdate: null,
32 | loading: false,
33 | error: false
34 | });
35 |
36 | //need to access state in message reeived callback
37 | const stateRef = useRef();
38 | stateRef.current = state;
39 |
40 | const channelRef = useRef();
41 |
42 | const scrollToBottom = (item, smooth) => {
43 | item?.scrollTo({
44 | top: item.scrollHeight,
45 | behavior: smooth
46 | })
47 | }
48 |
49 | useEffect(() => {
50 | scrollToBottom(channelRef.current)
51 | }, [state.currentlyJoinedChannel])
52 |
53 | useEffect(() => {
54 | scrollToBottom(channelRef.current, 'smooth')
55 | }, [state.messages])
56 |
57 | const onError = (error) => {
58 | updateState({ ...state, error: error.message });
59 | console.log(error);
60 | }
61 |
62 | const handleJoinChannel = async (channelUrl) => {
63 | if (state.currentlyJoinedChannel?.url === channelUrl) {
64 | return null;
65 | }
66 | const { channels } = state;
67 | updateState({ ...state, loading: true });
68 | const channelToJoin = channels.find((channel) => channel.url === channelUrl);
69 | await channelToJoin.enter();
70 | const [messages, error] = await loadMessages(channelToJoin);
71 |
72 | if (error) {
73 | return onError(error);
74 | }
75 |
76 | // setup connection event handlers
77 | const connectionHandler = new ConnectionHandler();
78 |
79 | connectionHandler.onReconnectSucceeded = async () => {
80 | const [messages, error] = await loadMessages(channelToJoin);
81 |
82 | updateState({ ...stateRef.current, messages: messages });
83 | }
84 |
85 | sb.addConnectionHandler(uuid(), connectionHandler);
86 |
87 |
88 | //listen for incoming messages
89 | const channelHandler = new OpenChannelHandler();
90 | channelHandler.onMessageUpdated = (channel, message) => {
91 | const messageIndex = stateRef.current.messages.findIndex((item => item.messageId == message.messageId));
92 | const updatedMessages = [...stateRef.current.messages];
93 | updatedMessages[messageIndex] = message;
94 | updateState({ ...stateRef.current, messages: updatedMessages });
95 | }
96 |
97 | channelHandler.onMessageReceived = (channel, message) => {
98 | const updatedMessages = [...stateRef.current.messages, message];
99 | updateState({ ...stateRef.current, messages: updatedMessages });
100 | };
101 |
102 | channelHandler.onMessageDeleted = (channel, message) => {
103 | const updatedMessages = stateRef.current.messages.filter((messageObject) => {
104 | return messageObject.messageId !== message;
105 | });
106 | updateState({ ...stateRef.current, messages: updatedMessages });
107 | }
108 | sb.openChannel.addOpenChannelHandler(uuid(), channelHandler);
109 | updateState({ ...state, currentlyJoinedChannel: channelToJoin, messages: messages, loading: false })
110 | }
111 |
112 | const handleLeaveChannel = async () => {
113 | const { currentlyJoinedChannel } = state;
114 | await currentlyJoinedChannel.exit();
115 |
116 | updateState({ ...state, currentlyJoinedChannel: null })
117 | }
118 |
119 | const handleCreateChannel = async () => {
120 | const { channelNameInputValue } = state;
121 | const [openChannel, error] = await createChannel(channelNameInputValue);
122 | if (error) {
123 | return onError(error);
124 | }
125 | const updatedChannels = [openChannel, ...state.channels];
126 | updateState({ ...state, channels: updatedChannels, showChannelCreate: false });
127 | }
128 |
129 | const handleDeleteChannel = async (channelUrl) => {
130 | const [channel, error] = await deleteChannel(channelUrl);
131 | if (error) {
132 | return onError(error);
133 | }
134 | const updatedChannels = state.channels.filter((channel) => {
135 | return channel.url !== channelUrl;
136 | });
137 | updateState({ ...state, channels: updatedChannels });
138 | }
139 |
140 | const handleUpdateChannel = async () => {
141 | const { currentlyUpdatingChannel, channelNameInputValue, channels } = state;
142 | const [updatedChannel, error] = await updateChannel(currentlyUpdatingChannel, channelNameInputValue);
143 | if (error) {
144 | return onError(error);
145 | }
146 | const indexToReplace = channels.findIndex((channel) => channel.url === currentlyUpdatingChannel.channelUrl);
147 | const updatedChannels = [...channels];
148 | updatedChannels[indexToReplace] = updatedChannel;
149 | updateState({ ...state, channels: updatedChannels, currentlyUpdatingChannel: null });
150 | }
151 |
152 | const toggleChannelDetails = (channel) => {
153 | if (channel) {
154 | updateState({ ...state, currentlyUpdatingChannel: channel });
155 | } else {
156 | updateState({ ...state, currentlyUpdatingChannel: null });
157 | }
158 | }
159 |
160 | const toggleShowCreateChannel = () => {
161 | updateState({ ...state, showChannelCreate: !state.showChannelCreate });
162 | }
163 |
164 | const onChannelNamenIputChange = (e) => {
165 | const channelNameInputValue = e.currentTarget.value;
166 | updateState({ ...state, channelNameInputValue });
167 | }
168 |
169 | const onUserNameInputChange = (e) => {
170 | const userNameInputValue = e.currentTarget.value;
171 | updateState({ ...state, userNameInputValue });
172 | }
173 |
174 | const onUserIdInputChange = (e) => {
175 | const userIdInputValue = e.currentTarget.value;
176 | updateState({ ...state, userIdInputValue });
177 | }
178 |
179 | const onMessageInputChange = (e) => {
180 | const messageInputValue = e.currentTarget.value;
181 | updateState({ ...state, messageInputValue });
182 | }
183 |
184 | const sendMessage = async () => {
185 | const { messageToUpdate, currentlyJoinedChannel, messages } = state;
186 |
187 | if (messageToUpdate) {
188 | const userMessageUpdateParams = {};
189 | userMessageUpdateParams.message = state.messageInputValue;
190 | const updatedMessage = await currentlyJoinedChannel.updateUserMessage(messageToUpdate.messageId, userMessageUpdateParams)
191 | const messageIndex = messages.findIndex((item => item.messageId == messageToUpdate.messageId));
192 | messages[messageIndex] = updatedMessage;
193 | updateState({ ...state, messages: messages, messageInputValue: "", messageToUpdate: null });
194 | } else {
195 | const userMessageParams = {};
196 | userMessageParams.message = state.messageInputValue;
197 | currentlyJoinedChannel.sendUserMessage(userMessageParams).onSucceeded((message) => {
198 | const updatedMessages = [...messages, message];
199 | updateState({ ...state, messages: updatedMessages, messageInputValue: "" });
200 | }).onFailed((error) => {
201 | console.log(error)
202 | console.log("failed")
203 | });
204 | }
205 | }
206 |
207 | const onFileInputChange = async (e) => {
208 | if (e.currentTarget.files && e.currentTarget.files.length > 0) {
209 | const { currentlyJoinedChannel, messages } = state;
210 | const fileMessageParams = {};
211 | fileMessageParams.file = e.currentTarget.files[0];
212 | currentlyJoinedChannel.sendFileMessage(fileMessageParams).onSucceeded((message) => {
213 | const updatedMessages = [...messages, message];
214 | updateState({ ...state, messages: updatedMessages, messageInputValue: "", file: null });
215 | }).onFailed((error) => {
216 | console.log(error)
217 | console.log("failed")
218 | });
219 | }
220 | }
221 |
222 | const handleDeleteMessage = async (messageToDelete) => {
223 | const { currentlyJoinedChannel } = state;
224 | await deleteMessage(currentlyJoinedChannel, messageToDelete); // Delete
225 | }
226 |
227 | const updateMessage = async (message) => {
228 | updateState({ ...state, messageToUpdate: message, messageInputValue: message.message });
229 | }
230 |
231 | const setupUser = async () => {
232 | const { userNameInputValue, userIdInputValue } = state;
233 | const sendbirdChat = await SendbirdChat.init({
234 | appId: SENDBIRD_INFO.appId,
235 | localCacheEnabled: true,
236 | modules: [new OpenChannelModule()]
237 | });
238 |
239 | try {
240 | await sendbirdChat.connect(userIdInputValue);
241 | } catch (e) {
242 | console.log("error", e)
243 | }
244 |
245 | await sendbirdChat.setChannelInvitationPreference(true);
246 |
247 | const userUpdateParams = {};
248 | userUpdateParams.nickname = userNameInputValue;
249 | userUpdateParams.userId = userIdInputValue;
250 | await sendbirdChat.updateCurrentUserInfo(userUpdateParams).then(data => console.log(data));
251 |
252 | sb = sendbirdChat;
253 |
254 |
255 |
256 | updateState({ ...state, loading: true });
257 | const [channels, error] = await loadChannels();
258 | if (error) {
259 | return onError(error);
260 | }
261 | updateState({ ...state, channels: channels, loading: false, settingUpUser: false });
262 | }
263 |
264 | if (state.loading) {
265 | return Loading...
266 | }
267 |
268 | if (state.error) {
269 | return {state.error} check console for more information.
270 | }
271 |
272 | console.log('- - - - State object very useful for debugging - - - -');
273 | console.log(state);
274 |
275 | return (
276 | <>
277 |
284 |
290 |
295 |
300 |
305 |
310 |
316 |
317 | >
318 | );
319 | };
320 |
321 | // Chat UI Components
322 | const ChannelList = ({ channels, handleJoinChannel, toggleShowCreateChannel, handleDeleteChannel, toggleChannelDetails }) => {
323 | return (
324 |
325 |
326 |
Open Channels
327 |
328 |
329 | {channels.map(channel => {
330 | const userIsOperator = channel.operators.some((operator) => operator.userId === sb.currentUser.userId)
331 | return (
332 |
333 |
{ handleJoinChannel(channel.url) }}>
335 | {channel.name}
336 |
337 | {userIsOperator &&
338 |
339 |
342 |
345 |
346 | }
347 |
348 | );
349 | })}
350 |
351 | );
352 | }
353 |
354 | const Channel = ({ currentlyJoinedChannel, handleLeaveChannel, children, channelRef }) => {
355 | if (currentlyJoinedChannel) {
356 | return
357 |
{currentlyJoinedChannel.name}
358 |
359 |
360 |
361 |
{children}
362 |
;
363 | }
364 | return ;
365 | }
366 |
367 | const ChannelHeader = ({ children }) => {
368 | return {children}
;
369 | }
370 |
371 | const MessagesList = ({ messages, handleDeleteMessage, updateMessage }) => {
372 | return messages.map(message => {
373 | return (
374 |
375 |
380 |
);
381 | })
382 | }
383 |
384 | const Message = ({ message, updateMessage, handleDeleteMessage }) => {
385 | if (!message.sender) return null; if (message.url) {
386 | return (
387 |
388 |
{timestampToTime(message.createdAt)}
389 |
{message.sender.nickname}{' '}
390 |

391 |
392 | );
393 | }
394 |
395 | const messageSentByCurrentUser = message.sender.userId === sb.currentUser.userId;
396 | return (
397 |
398 |
{timestampToTime(message.createdAt)}
399 |
400 |
{message.sender.nickname}{':'}
401 |
{message.message}
402 |
403 | {messageSentByCurrentUser && <>
404 |
407 |
410 | >}
411 |
412 | );
413 | }
414 |
415 | const MessageInput = ({ value, onChange, sendMessage, onFileInputChange }) => {
416 | return (
417 |
437 | );
438 | }
439 |
440 | const ChannelDetails = ({
441 | currentlyUpdatingChannel,
442 | toggleChannelDetails,
443 | handleUpdateChannel,
444 | onChannelNamenIputChange
445 | }) => {
446 | if (currentlyUpdatingChannel) {
447 | return
448 |
449 |
Update Channel Details
450 |
Channel name
451 |
452 |
453 |
454 |
455 |
;
456 | }
457 | return null;
458 | }
459 |
460 | const ChannelCreate = ({
461 | showChannelCreate,
462 | toggleShowCreateChannel,
463 | handleCreateChannel,
464 | onChannelNamenIputChange
465 | }) => {
466 | if (showChannelCreate) {
467 | return
468 |
469 |
470 |
Create Channel
471 |
472 |
Name
473 |
handleEnterPress(event, handleCreateChannel)} />
474 |
475 |
476 |
477 |
478 |
479 |
;
480 | }
481 | return null;
482 | }
483 |
484 | const CreateUserForm = ({
485 | setupUser,
486 | settingUpUser,
487 | userNameInputValue,
488 | userIdInputValue,
489 | onUserNameInputChange,
490 | onUserIdInputChange
491 | }) => {
492 | if (settingUpUser) {
493 | return
494 |
handleEnterPress(event, setupUser)}>
495 |
User ID
496 |
497 |
501 |
502 |
User Nickname
503 |
507 |
508 |
509 |
515 |
516 |
517 |
518 | } else {
519 | return null;
520 | }
521 | }
522 |
523 |
524 | // Helpful functions that call Sendbird
525 | const loadChannels = async () => {
526 | try {
527 | const openChannelQuery = sb.openChannel.createOpenChannelListQuery({ limit: 30 });
528 | const channels = await openChannelQuery.next();
529 | return [channels, null];
530 |
531 | } catch (error) {
532 | return [null, error];
533 | }
534 | }
535 |
536 | const loadMessages = async (channel) => {
537 | try {
538 |
539 | //list all messages
540 | const messageListParams = {};
541 | messageListParams.nextResultSize = 20;
542 | const messages = await channel.getMessagesByTimestamp(0, messageListParams);
543 | return [messages, null];
544 | } catch (error) {
545 | return [null, error]
546 | }
547 | }
548 |
549 | const createChannel = async (channelName) => {
550 | try {
551 | const openChannelParams = {};
552 | openChannelParams.name = channelName;
553 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
554 | const openChannel = await sb.openChannel.createChannel(openChannelParams);
555 | return [openChannel, null];
556 | } catch (error) {
557 | return [null, error];
558 | }
559 | }
560 |
561 | const deleteChannel = async (channelUrl) => {
562 | try {
563 | const channel = await sb.openChannel.getChannel(channelUrl);
564 | await channel.delete();
565 | return [channel, null];
566 | } catch (error) {
567 | return [null, error];
568 | }
569 | }
570 |
571 | const updateChannel = async (currentlyUpdatingChannel, channelNameInputValue) => {
572 | try {
573 | const channel = await sb.openChannel.getChannel(currentlyUpdatingChannel.url);
574 | const openChannelParams = {};
575 | openChannelParams.name = channelNameInputValue;
576 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
577 | const updatedChannel = await channel.updateChannel(openChannelParams);
578 | return [updatedChannel, null];
579 | } catch (error) {
580 | return [null, error];
581 | }
582 | }
583 |
584 | const deleteMessage = async (currentlyJoinedChannel, messageToDelete) => {
585 | await currentlyJoinedChannel.deleteMessage(messageToDelete);
586 | }
587 |
588 | export default BasicOpenChannelSample;
589 |
--------------------------------------------------------------------------------
/src/samples/OpenChannelCategorizeByCustomType.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react';
2 | import { ConnectionHandler } from '@sendbird/chat';
3 |
4 | import { v4 as uuid } from 'uuid';
5 |
6 | import SendbirdChat from '@sendbird/chat';
7 | import {
8 | OpenChannelModule,
9 | OpenChannelHandler,
10 | } from '@sendbird/chat/openChannel';
11 |
12 | import { SENDBIRD_INFO } from '../constants/constants';
13 | import { timestampToTime, handleEnterPress } from '../utils/messageUtils';
14 |
15 | let sb;
16 |
17 | const OpenChannelCategorizeByCustomType = (props) => {
18 |
19 | const [state, updateState] = useState({
20 | currentlyJoinedChannel: null,
21 | currentlyUpdatingChannel: null,
22 | messages: [],
23 | channels: [],
24 | currentCustomType: 'all',
25 | sortedChannelsByType: [],
26 | showChannelCreate: false,
27 | messageInputValue: "",
28 | userNameInputValue: "",
29 | userIdInputValue: "",
30 | channelNameInputValue: "",
31 | settingUpUser: true,
32 | file: null,
33 | messageToUpdate: null,
34 | loading: false,
35 | error: false
36 | });
37 |
38 | //need to access state in message reeived callback
39 | const stateRef = useRef();
40 | stateRef.current = state;
41 |
42 | const channelRef = useRef();
43 |
44 | const scrollToBottom = (item, smooth) => {
45 | item?.scrollTo({
46 | top: item.scrollHeight,
47 | behavior: smooth
48 | })
49 | }
50 |
51 | useEffect(() => {
52 | scrollToBottom(channelRef.current)
53 | }, [state.currentlyJoinedChannel])
54 |
55 | useEffect(() => {
56 | scrollToBottom(channelRef.current, 'smooth')
57 | }, [state.messages])
58 |
59 | const onError = (error) => {
60 | updateState({ ...state, error: error.message });
61 | console.log(error);
62 | }
63 |
64 | const handleJoinChannel = async (channelUrl) => {
65 | if (state.currentlyJoinedChannel?.url === channelUrl) {
66 | return null;
67 | }
68 | const { channels } = state;
69 | updateState({ ...state, loading: true });
70 | const channelToJoin = channels.find((channel) => channel.url === channelUrl);
71 | await channelToJoin.enter();
72 | const [messages, error] = await loadMessages(channelToJoin);
73 | if (error) {
74 | return onError(error);
75 |
76 | }
77 |
78 | // setup connection event handlers
79 | const connectionHandler = new ConnectionHandler();
80 |
81 | connectionHandler.onReconnectSucceeded = async () => {
82 | const [messages, error] = await loadMessages(channelToJoin);
83 |
84 | updateState({ ...stateRef.current, messages: messages });
85 | }
86 |
87 | sb.addConnectionHandler(uuid(), connectionHandler);
88 |
89 | //listen for incoming messages
90 | const channelHandler = new OpenChannelHandler();
91 | channelHandler.onMessageUpdated = (channel, message) => {
92 | const messageIndex = stateRef.current.messages.findIndex((item => item.messageId == message.messageId));
93 | const updatedMessages = [...stateRef.current.messages];
94 | updatedMessages[messageIndex] = message;
95 | updateState({ ...stateRef.current, messages: updatedMessages });
96 | }
97 |
98 | channelHandler.onMessageReceived = (channel, message) => {
99 | const updatedMessages = [...stateRef.current.messages, message];
100 | updateState({ ...stateRef.current, messages: updatedMessages });
101 | };
102 |
103 | channelHandler.onMessageDeleted = (channel, message) => {
104 | const updatedMessages = stateRef.current.messages.filter((messageObject) => {
105 | return messageObject.messageId !== message;
106 | });
107 | updateState({ ...stateRef.current, messages: updatedMessages });
108 | }
109 | sb.openChannel.addOpenChannelHandler(uuid(), channelHandler);
110 | updateState({ ...state, currentlyJoinedChannel: channelToJoin, messages: messages, loading: false })
111 | }
112 |
113 | const handleLeaveChannel = async () => {
114 | const { currentlyJoinedChannel } = state;
115 | await currentlyJoinedChannel.exit();
116 |
117 | updateState({ ...state, currentlyJoinedChannel: null })
118 |
119 | }
120 |
121 | const handleCreateChannel = async () => {
122 | const { channelNameInputValue } = state;
123 | const [openChannel, error] = await createChannel(channelNameInputValue);
124 | if (error) {
125 | return onError(error);
126 | }
127 | const updatedChannels = [openChannel, ...state.channels];
128 | updateState({ ...state, channels: updatedChannels, showChannelCreate: false });
129 | }
130 |
131 | const handleDeleteChannel = async (channelUrl) => {
132 | const [channel, error] = await deleteChannel(channelUrl);
133 | if (error) {
134 | return onError(error);
135 | }
136 | const updatedChannels = state.channels.filter((channel) => {
137 | return channel.url !== channelUrl;
138 | });
139 | updateState({ ...state, channels: updatedChannels });
140 | }
141 |
142 | const handleChangeCustomType = (e) => {
143 | updateState({ ...state, currentCustomType: e.target.value })
144 | }
145 |
146 | const handleUpdateChannel = async () => {
147 | const { currentlyUpdatingChannel, channelNameInputValue, channels } = state;
148 | const [updatedChannel, error] = await updateChannel(currentlyUpdatingChannel, channelNameInputValue);
149 | if (error) {
150 | return onError(error);
151 | }
152 | const indexToReplace = channels.findIndex((channel) => channel.url === currentlyUpdatingChannel.channelUrl);
153 | const updatedChannels = [...channels];
154 | updatedChannels[indexToReplace] = updatedChannel;
155 | updateState({ ...state, channels: updatedChannels, currentlyUpdatingChannel: null });
156 | }
157 |
158 | const toggleChannelDetails = (channel) => {
159 | if (channel) {
160 | updateState({ ...state, currentlyUpdatingChannel: channel });
161 | } else {
162 | updateState({ ...state, currentlyUpdatingChannel: null });
163 | }
164 | }
165 |
166 | const toggleShowCreateChannel = () => {
167 | updateState({ ...state, showChannelCreate: !state.showChannelCreate });
168 | }
169 |
170 | const onChannelNamenIputChange = (e) => {
171 | const channelNameInputValue = e.currentTarget.value;
172 | updateState({ ...state, channelNameInputValue });
173 | }
174 |
175 | const onUserNameInputChange = (e) => {
176 | const userNameInputValue = e.currentTarget.value;
177 | updateState({ ...state, userNameInputValue });
178 | }
179 |
180 | const onUserIdInputChange = (e) => {
181 | const userIdInputValue = e.currentTarget.value;
182 | updateState({ ...state, userIdInputValue });
183 | }
184 |
185 | const onMessageInputChange = (e) => {
186 | const messageInputValue = e.currentTarget.value;
187 | updateState({ ...state, messageInputValue });
188 | }
189 |
190 | const sendMessage = async () => {
191 | const { messageToUpdate, currentlyJoinedChannel, messages } = state;
192 |
193 | if (messageToUpdate) {
194 | const userMessageUpdateParams = {};
195 | userMessageUpdateParams.message = state.messageInputValue;
196 | const updatedMessage = await currentlyJoinedChannel.updateUserMessage(messageToUpdate.messageId, userMessageUpdateParams)
197 | const messageIndex = messages.findIndex((item => item.messageId == messageToUpdate.messageId));
198 | messages[messageIndex] = updatedMessage;
199 | updateState({ ...state, messages: messages, messageInputValue: "", messageToUpdate: null });
200 | } else {
201 | const userMessageParams = {};
202 | userMessageParams.message = state.messageInputValue;
203 | currentlyJoinedChannel.sendUserMessage(userMessageParams).onSucceeded((message) => {
204 | const updatedMessages = [...messages, message];
205 | updateState({ ...state, messages: updatedMessages, messageInputValue: "" });
206 | }).onFailed((error) => {
207 | console.log(error)
208 | console.log("failed")
209 | });
210 | }
211 | }
212 |
213 | const onFileInputChange = async (e) => {
214 | if (e.currentTarget.files && e.currentTarget.files.length > 0) {
215 | const { currentlyJoinedChannel, messages } = state;
216 | const fileMessageParams = {};
217 | fileMessageParams.file = e.currentTarget.files[0];
218 | currentlyJoinedChannel.sendFileMessage(fileMessageParams).onSucceeded((message) => {
219 | const updatedMessages = [...messages, message];
220 | updateState({ ...state, messages: updatedMessages, messageInputValue: "", file: null });
221 |
222 | }).onFailed((error) => {
223 | console.log(error)
224 | console.log("failed")
225 | });
226 | }
227 | }
228 |
229 | const handleDeleteMessage = async (messageToDelete) => {
230 | const { currentlyJoinedChannel } = state;
231 | await deleteMessage(currentlyJoinedChannel, messageToDelete); // Delete
232 | }
233 |
234 | const updateMessage = async (message) => {
235 | updateState({ ...state, messageToUpdate: message, messageInputValue: message.message });
236 | }
237 |
238 | const setupUser = async () => {
239 | const { userNameInputValue, userIdInputValue } = state;
240 | const sendbirdChat = await SendbirdChat.init({
241 | appId: SENDBIRD_INFO.appId,
242 | localCacheEnabled: true,
243 | modules: [new OpenChannelModule()]
244 | });
245 |
246 | try {
247 | await sendbirdChat.connect(userIdInputValue);
248 | } catch (e) {
249 | console.log("error", e)
250 | }
251 | await sendbirdChat.setChannelInvitationPreference(true);
252 |
253 | const userUpdateParams = {};
254 | userUpdateParams.nickname = userNameInputValue;
255 | userUpdateParams.userId = userIdInputValue;
256 | await sendbirdChat.updateCurrentUserInfo(userUpdateParams);
257 |
258 | sb = sendbirdChat;
259 | updateState({ ...state, loading: true });
260 | const [channels, error] = await loadChannels();
261 |
262 | if (error) {
263 | return onError(error);
264 | }
265 | updateState({ ...state, channels: channels, loading: false, settingUpUser: false });
266 | }
267 |
268 | if (state.loading) {
269 | return Loading...
270 | }
271 |
272 | if (state.error) {
273 | return {state.error} check console for more information.
274 | }
275 |
276 | console.log('- - - - State object very useful for debugging - - - -');
277 | console.log(state);
278 |
279 | return (
280 | <>
281 |
288 |
296 |
301 |
306 |
311 |
316 |
323 |
324 | >
325 | );
326 | };
327 |
328 | // Chat UI Components
329 | const ChannelList = ({ channels, currentCustomType, handleChangeCustomType, handleJoinChannel, toggleShowCreateChannel, handleDeleteChannel, toggleChannelDetails }) => {
330 | return (
331 |
332 |
333 |
Open Channels
334 |
335 |
336 |
337 |
Custom type:
338 |
348 |
349 | {channels
350 | .filter(channel => currentCustomType === 'all' ? channel : channel.customType === currentCustomType)
351 | .map(channel => {
352 | const userIsOperator = channel.operators.some((operator) => operator.userId === sb.currentUser.userId)
353 | return (
354 |
355 |
{ handleJoinChannel(channel.url) }}>
357 | {channel.name}
358 |
359 | {userIsOperator &&
360 |
361 |
364 |
367 |
}
368 |
369 | );
370 | })
371 | }
372 |
373 | );
374 | }
375 |
376 | const Channel = ({ currentlyJoinedChannel, handleLeaveChannel, children, channelRef }) => {
377 | if (currentlyJoinedChannel) {
378 | return
379 |
{currentlyJoinedChannel.name}
380 |
381 |
382 |
383 |
{children}
384 |
;
385 | }
386 | return ;
387 | }
388 |
389 | const ChannelHeader = ({ children }) => {
390 | return {children}
;
391 | }
392 |
393 | const MessagesList = ({ messages, handleDeleteMessage, updateMessage }) => {
394 | return messages.map(message => {
395 | return (
396 |
397 |
402 |
403 | );
404 | })
405 | }
406 |
407 | const Message = ({ message, updateMessage, handleDeleteMessage }) => {
408 | if (!message.sender) return null; if (message.url) {
409 | return (
410 |
411 |
{timestampToTime(message.createdAt)}
412 |
{message.sender.nickname}{' '}
413 |

414 |
415 | );
416 | }
417 |
418 | const messageSentByCurrentUser = message.sender.userId === sb.currentUser.userId;
419 | return (
420 |
421 |
{timestampToTime(message.createdAt)}
422 |
423 |
{message.sender.nickname}{':'}
424 |
{message.message}
425 |
426 | {messageSentByCurrentUser && <>
427 |
430 |
433 | >}
434 |
435 | );
436 | }
437 |
438 | const MessageInput = ({ value, onChange, sendMessage, onFileInputChange }) => {
439 | return (
440 |
460 | );
461 | }
462 |
463 | const ChannelDetails = ({
464 | currentlyUpdatingChannel,
465 | toggleChannelDetails,
466 | handleUpdateChannel,
467 | onChannelNamenIputChange
468 | }) => {
469 | if (currentlyUpdatingChannel) {
470 | return
471 |
472 |
Update Channel Details
473 |
Channel name
474 |
475 |
476 |
477 |
478 |
;
479 | }
480 | return null;
481 | }
482 |
483 | const ChannelCreate = ({
484 | showChannelCreate,
485 | toggleShowCreateChannel,
486 | handleCreateChannel,
487 | onChannelNamenIputChange
488 | }) => {
489 | if (showChannelCreate) {
490 | return
491 |
492 |
493 |
Create Channel
494 |
495 |
Name
496 |
handleEnterPress(event, handleCreateChannel)} />
497 |
498 |
499 |
500 |
501 |
502 |
;
503 | }
504 | return null;
505 | }
506 |
507 | const CreateUserForm = ({
508 | setupUser,
509 | settingUpUser,
510 | userNameInputValue,
511 | userIdInputValue,
512 | onUserNameInputChange,
513 | onUserIdInputChange
514 | }) => {
515 | if (settingUpUser) {
516 | return
517 |
handleEnterPress(event, setupUser)}>
518 |
User ID
519 |
524 |
User Nickname
525 |
530 |
531 |
535 |
536 |
537 |
538 | } else {
539 | return null;
540 | }
541 | }
542 |
543 | // Helpful functions that call Sendbird
544 | const loadChannels = async () => {
545 | try {
546 | const openChannelQuery = sb.openChannel.createOpenChannelListQuery({ limit: 30 });
547 | const channels = await openChannelQuery.next();
548 | return [channels, null];
549 | } catch (error) {
550 | return [null, error];
551 | }
552 | }
553 |
554 | const loadMessages = async (channel) => {
555 | try {
556 |
557 | //list all messages
558 | const messageListParams = {};
559 | messageListParams.nextResultSize = 20;
560 | const messages = await channel.getMessagesByTimestamp(0, messageListParams);
561 | return [messages, null];
562 | } catch (error) {
563 | return [null, error]
564 | }
565 | }
566 |
567 |
568 | const createChannel = async (channelName) => {
569 | try {
570 | const openChannelParams = {};
571 | openChannelParams.name = channelName;
572 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
573 | const openChannel = await sb.openChannel.createChannel(openChannelParams);
574 | return [openChannel, null];
575 | } catch (error) {
576 | return [null, error];
577 | }
578 | }
579 |
580 | const deleteChannel = async (channelUrl) => {
581 | try {
582 | const channel = await sb.openChannel.getChannel(channelUrl);
583 | await channel.delete();
584 | return [channel, null];
585 | } catch (error) {
586 | return [null, error];
587 | }
588 | }
589 |
590 | const updateChannel = async (currentlyUpdatingChannel, channelNameInputValue) => {
591 | try {
592 | const channel = await sb.openChannel.getChannel(currentlyUpdatingChannel.url);
593 | const openChannelParams = {};
594 | openChannelParams.name = channelNameInputValue;
595 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
596 | const updatedChannel = await channel.updateChannel(openChannelParams);
597 | return [updatedChannel, null];
598 | } catch (error) {
599 | return [null, error];
600 | }
601 | }
602 |
603 | const deleteMessage = async (currentlyJoinedChannel, messageToDelete) => {
604 | await currentlyJoinedChannel.deleteMessage(messageToDelete);
605 | }
606 |
607 | export default OpenChannelCategorizeByCustomType;
608 |
--------------------------------------------------------------------------------
/src/samples/OpenChannelDisplayOGTags.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react';
2 | import { ConnectionHandler } from '@sendbird/chat';
3 |
4 | import { v4 as uuid } from 'uuid';
5 |
6 | import SendbirdChat from '@sendbird/chat';
7 | import {
8 | OpenChannelModule,
9 | OpenChannelHandler,
10 | } from '@sendbird/chat/openChannel';
11 |
12 | import { SENDBIRD_INFO } from '../constants/constants';
13 | import { timestampToTime, handleEnterPress } from '../utils/messageUtils';
14 |
15 | let sb;
16 |
17 | const OpenChannelDisplayOGTags = (props) => {
18 |
19 | const [state, updateState] = useState({
20 | currentlyJoinedChannel: null,
21 | currentlyUpdatingChannel: null,
22 | messages: [],
23 | channels: [],
24 | showChannelCreate: false,
25 | messageInputValue: "",
26 | userNameInputValue: "",
27 | userIdInputValue: "",
28 | channelNameInputValue: "",
29 | settingUpUser: true,
30 | file: null,
31 | messageToUpdate: null,
32 | loading: false,
33 | error: false
34 | });
35 |
36 | //need to access state in message reeived callback
37 | const stateRef = useRef();
38 | stateRef.current = state;
39 |
40 | const channelRef = useRef();
41 |
42 | const scrollToBottom = (item, smooth) => {
43 | item?.scrollTo({
44 | top: item.scrollHeight,
45 | behavior: smooth
46 | })
47 | }
48 |
49 | useEffect(() => {
50 | scrollToBottom(channelRef.current)
51 | }, [state.currentlyJoinedChannel])
52 |
53 | useEffect(() => {
54 | scrollToBottom(channelRef.current, 'smooth')
55 | }, [state.messages])
56 |
57 | const onError = (error) => {
58 | updateState({ ...state, error: error.message });
59 | console.log(error);
60 | }
61 |
62 | const handleJoinChannel = async (channelUrl) => {
63 | if (state.currentlyJoinedChannel?.url === channelUrl) {
64 | return null;
65 | }
66 | const { channels } = state;
67 | updateState({ ...state, loading: true });
68 | const channelToJoin = channels.find((channel) => channel.url === channelUrl);
69 | await channelToJoin.enter();
70 | const [messages, error] = await loadMessages(channelToJoin);
71 | if (error) {
72 | return onError(error);
73 | }
74 |
75 | // setup connection event handlers
76 | const connectionHandler = new ConnectionHandler();
77 |
78 | connectionHandler.onReconnectSucceeded = async () => {
79 | const [messages, error] = await loadMessages(channelToJoin);
80 |
81 | updateState({ ...stateRef.current, messages: messages });
82 | }
83 |
84 | sb.addConnectionHandler(uuid(), connectionHandler);
85 |
86 |
87 | //listen for incoming messages
88 | const channelHandler = new OpenChannelHandler();
89 | channelHandler.onMessageUpdated = (channel, message) => {
90 | const messageIndex = stateRef.current.messages.findIndex((item => item.messageId == message.messageId));
91 | const updatedMessages = [...stateRef.current.messages];
92 | updatedMessages[messageIndex] = message;
93 | updateState({ ...stateRef.current, messages: updatedMessages });
94 | }
95 |
96 | channelHandler.onMessageReceived = (channel, message) => {
97 | const updatedMessages = [...stateRef.current.messages, message];
98 | updateState({ ...stateRef.current, messages: updatedMessages });
99 | };
100 |
101 | channelHandler.onMessageDeleted = (channel, message) => {
102 | const updatedMessages = stateRef.current.messages.filter((messageObject) => {
103 | return messageObject.messageId !== message;
104 | });
105 | updateState({ ...stateRef.current, messages: updatedMessages });
106 | }
107 | sb.openChannel.addOpenChannelHandler(uuid(), channelHandler);
108 | updateState({ ...state, currentlyJoinedChannel: channelToJoin, messages: messages, loading: false })
109 | }
110 |
111 | const handleLeaveChannel = async () => {
112 | const { currentlyJoinedChannel } = state;
113 | await currentlyJoinedChannel.exit();
114 |
115 | updateState({ ...state, currentlyJoinedChannel: null })
116 | }
117 |
118 | const handleCreateChannel = async () => {
119 | const { channelNameInputValue } = state;
120 | const [openChannel, error] = await createChannel(channelNameInputValue);
121 | if (error) {
122 | return onError(error);
123 | }
124 | const updatedChannels = [openChannel, ...state.channels];
125 | updateState({ ...state, channels: updatedChannels, showChannelCreate: false });
126 | }
127 |
128 | const handleDeleteChannel = async (channelUrl) => {
129 | const [channel, error] = await deleteChannel(channelUrl);
130 | if (error) {
131 | return onError(error);
132 | }
133 | const updatedChannels = state.channels.filter((channel) => {
134 | return channel.url !== channelUrl;
135 | });
136 | updateState({ ...state, channels: updatedChannels });
137 | }
138 |
139 | const handleUpdateChannel = async () => {
140 | const { currentlyUpdatingChannel, channelNameInputValue, channels } = state;
141 | const [updatedChannel, error] = await updateChannel(currentlyUpdatingChannel, channelNameInputValue);
142 | if (error) {
143 | return onError(error);
144 | }
145 | const indexToReplace = channels.findIndex((channel) => channel.url === currentlyUpdatingChannel.channelUrl);
146 | const updatedChannels = [...channels];
147 | updatedChannels[indexToReplace] = updatedChannel;
148 | updateState({ ...state, channels: updatedChannels, currentlyUpdatingChannel: null });
149 | }
150 |
151 | const toggleChannelDetails = (channel) => {
152 | if (channel) {
153 | updateState({ ...state, currentlyUpdatingChannel: channel });
154 | } else {
155 | updateState({ ...state, currentlyUpdatingChannel: null });
156 | }
157 | }
158 |
159 | const toggleShowCreateChannel = () => {
160 | updateState({ ...state, showChannelCreate: !state.showChannelCreate });
161 | }
162 |
163 | const onChannelNamenIputChange = (e) => {
164 | const channelNameInputValue = e.currentTarget.value;
165 | updateState({ ...state, channelNameInputValue });
166 | }
167 |
168 | const onUserNameInputChange = (e) => {
169 | const userNameInputValue = e.currentTarget.value;
170 | updateState({ ...state, userNameInputValue });
171 | }
172 |
173 | const onUserIdInputChange = (e) => {
174 | const userIdInputValue = e.currentTarget.value;
175 | updateState({ ...state, userIdInputValue });
176 | }
177 |
178 | const onMessageInputChange = (e) => {
179 | const messageInputValue = e.currentTarget.value;
180 | updateState({ ...state, messageInputValue });
181 | }
182 |
183 | const sendMessage = async () => {
184 | const { messageToUpdate, currentlyJoinedChannel, messages } = state;
185 |
186 | if (messageToUpdate) {
187 | const userMessageUpdateParams = {};
188 | userMessageUpdateParams.message = state.messageInputValue;
189 | const updatedMessage = await currentlyJoinedChannel.updateUserMessage(messageToUpdate.messageId, userMessageUpdateParams)
190 | const messageIndex = messages.findIndex((item => item.messageId == messageToUpdate.messageId));
191 | messages[messageIndex] = updatedMessage;
192 | updateState({ ...state, messages: messages, messageInputValue: "", messageToUpdate: null });
193 | } else {
194 | const userMessageParams = {};
195 | userMessageParams.message = state.messageInputValue;
196 | currentlyJoinedChannel.sendUserMessage(userMessageParams).onSucceeded((message) => {
197 | const updatedMessages = [...messages, message];
198 | updateState({ ...state, messages: updatedMessages, messageInputValue: "" });
199 | }).onFailed((error) => {
200 | console.log(error)
201 | console.log("failed")
202 | });
203 | }
204 | }
205 |
206 | const onFileInputChange = async (e) => {
207 | if (e.currentTarget.files && e.currentTarget.files.length > 0) {
208 | const { currentlyJoinedChannel, messages } = state;
209 | const fileMessageParams = {};
210 | fileMessageParams.file = e.currentTarget.files[0];
211 | currentlyJoinedChannel.sendFileMessage(fileMessageParams).onSucceeded((message) => {
212 | const updatedMessages = [...messages, message];
213 | updateState({ ...state, messages: updatedMessages, messageInputValue: "", file: null });
214 | }).onFailed((error) => {
215 | console.log(error)
216 | console.log("failed")
217 | });
218 | }
219 | }
220 |
221 | const handleDeleteMessage = async (messageToDelete) => {
222 | const { currentlyJoinedChannel } = state;
223 | await deleteMessage(currentlyJoinedChannel, messageToDelete); // Delete
224 | }
225 |
226 | const updateMessage = async (message) => {
227 | updateState({ ...state, messageToUpdate: message, messageInputValue: message.message });
228 | }
229 |
230 | const setupUser = async () => {
231 | const { userNameInputValue, userIdInputValue } = state;
232 | const sendbirdChat = await SendbirdChat.init({
233 | appId: SENDBIRD_INFO.appId,
234 | localCacheEnabled: true,
235 | modules: [new OpenChannelModule()]
236 | });
237 |
238 | try {
239 | await sendbirdChat.connect(userIdInputValue);
240 | } catch (e) {
241 | console.log("error", e)
242 | }
243 | await sendbirdChat.setChannelInvitationPreference(true);
244 |
245 | const userUpdateParams = {};
246 | userUpdateParams.nickname = userNameInputValue;
247 | userUpdateParams.userId = userIdInputValue;
248 | await sendbirdChat.updateCurrentUserInfo(userUpdateParams);
249 |
250 | sb = sendbirdChat;
251 | updateState({ ...state, loading: true });
252 | const [channels, error] = await loadChannels();
253 | if (error) {
254 | return onError(error);
255 | }
256 | updateState({ ...state, channels: channels, loading: false, settingUpUser: false });
257 | }
258 |
259 | if (state.loading) {
260 | return Loading...
261 | }
262 |
263 | if (state.error) {
264 | return {state.error} check console for more information.
265 | }
266 |
267 | console.log('- - - - State object very useful for debugging - - - -');
268 | console.log(state);
269 |
270 | return (
271 | <>
272 |
279 |
285 |
290 |
295 |
300 |
305 |
312 |
313 | >
314 | );
315 | };
316 |
317 | // Chat UI Components
318 | const ChannelList = ({ channels, handleJoinChannel, toggleShowCreateChannel, handleDeleteChannel, toggleChannelDetails }) => {
319 | return (
320 |
321 |
322 |
Open Channels
323 |
324 |
325 | {
326 | channels.map(channel => {
327 | const userIsOperator = channel.operators.some((operator) => operator.userId === sb.currentUser.userId)
328 | return (
329 |
330 |
{ handleJoinChannel(channel.url) }}>
332 | {channel.name}
333 |
334 | {userIsOperator &&
335 |
336 |
339 |
342 |
343 | }
344 |
345 | );
346 | })
347 | }
348 |
349 | );
350 | }
351 |
352 | const Channel = ({ currentlyJoinedChannel, handleLeaveChannel, children, channelRef }) => {
353 | if (currentlyJoinedChannel) {
354 | return
355 |
{currentlyJoinedChannel.name}
356 |
357 |
358 |
359 |
{children}
360 |
;
361 | }
362 | return ;
363 | }
364 |
365 | const ChannelHeader = ({ children }) => {
366 | return {children}
;
367 | }
368 |
369 | const MessagesList = ({ messages, handleDeleteMessage, updateMessage }) => {
370 | return messages.map(message => {
371 | return (
372 |
373 |
378 |
379 | );
380 | })
381 | }
382 |
383 | const Message = ({ message, updateMessage, handleDeleteMessage }) => {
384 | if (!message.sender) return null; if (message.url) {
385 | return (
386 |
387 |
{timestampToTime(message.createdAt)}
388 |
{message.sender.nickname}{' '}
389 |

390 |
391 | );
392 | }
393 |
394 | const messageSentByCurrentUser = message.sender.userId === sb.currentUser.userId;
395 | return (
396 | <>
397 |
398 |
{timestampToTime(message.createdAt)}
399 |
{message.sender.nickname}{':'}
400 |
{message.message}
401 |
402 | {messageSentByCurrentUser && <>
403 |
406 |
409 | >}
410 |
411 | {message.ogMetaData && }
417 | >
418 | );
419 | }
420 |
421 | const MessageInput = ({ value, onChange, sendMessage, onFileInputChange }) => {
422 | return (
423 |
443 | );
444 | }
445 |
446 | const ChannelDetails = ({
447 | currentlyUpdatingChannel,
448 | toggleChannelDetails,
449 | handleUpdateChannel,
450 | onChannelNamenIputChange
451 | }) => {
452 | if (currentlyUpdatingChannel) {
453 | return
454 |
455 |
Update Channel Details
456 |
Channel name
457 |
458 |
459 |
460 |
461 |
;
462 | }
463 | return null;
464 | }
465 |
466 | const ChannelCreate = ({
467 | showChannelCreate,
468 | toggleShowCreateChannel,
469 | handleCreateChannel,
470 | onChannelNamenIputChange
471 | }) => {
472 | if (showChannelCreate) {
473 | return
474 |
475 |
476 |
Create Channel
477 |
478 |
Name
479 |
handleEnterPress(event, handleCreateChannel)} />
480 |
481 |
482 |
483 |
484 |
485 |
;
486 | }
487 | return null;
488 | }
489 |
490 | const CreateUserForm = ({
491 | setupUser,
492 | settingUpUser,
493 | userNameInputValue,
494 | userIdInputValue,
495 | onUserNameInputChange,
496 | onUserIdInputChange
497 | }) => {
498 | if (settingUpUser) {
499 | return
500 |
handleEnterPress(event, setupUser)}>
501 |
User ID
502 |
507 |
User Nickname
508 |
513 |
514 |
518 |
519 |
520 |
521 | } else {
522 | return null;
523 | }
524 | }
525 |
526 | // Helpful functions that call Sendbird
527 | const loadChannels = async () => {
528 | try {
529 | const openChannelQuery = sb.openChannel.createOpenChannelListQuery({ limit: 30 });
530 | const channels = await openChannelQuery.next();
531 | return [channels, null];
532 | } catch (error) {
533 | return [null, error];
534 | }
535 | }
536 |
537 | const loadMessages = async (channel) => {
538 | try {
539 |
540 | //list all messages
541 | const messageListParams = {};
542 | messageListParams.nextResultSize = 20;
543 | const messages = await channel.getMessagesByTimestamp(0, messageListParams);
544 | return [messages, null];
545 | } catch (error) {
546 | return [null, error]
547 | }
548 | }
549 |
550 | const createChannel = async (channelName) => {
551 | try {
552 | const openChannelParams = {};
553 | openChannelParams.name = channelName;
554 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
555 | const openChannel = await sb.openChannel.createChannel(openChannelParams);
556 | return [openChannel, null];
557 | } catch (error) {
558 | return [null, error];
559 | }
560 | }
561 |
562 | const deleteChannel = async (channelUrl) => {
563 | try {
564 | const channel = await sb.openChannel.getChannel(channelUrl);
565 | await channel.delete();
566 | return [channel, null];
567 | } catch (error) {
568 | return [null, error];
569 | }
570 | }
571 |
572 | const updateChannel = async (currentlyUpdatingChannel, channelNameInputValue) => {
573 | try {
574 | const channel = await sb.openChannel.getChannel(currentlyUpdatingChannel.url);
575 | const openChannelParams = {};
576 | openChannelParams.name = channelNameInputValue;
577 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
578 | const updatedChannel = await channel.updateChannel(openChannelParams);
579 | return [updatedChannel, null];
580 | } catch (error) {
581 | return [null, error];
582 | }
583 | }
584 |
585 | const deleteMessage = async (currentlyJoinedChannel, messageToDelete) => {
586 | await currentlyJoinedChannel.deleteMessage(messageToDelete);
587 | }
588 |
589 | export default OpenChannelDisplayOGTags;
590 |
--------------------------------------------------------------------------------
/src/samples/OpenChannelFreeze.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react';
2 | import { ConnectionHandler } from '@sendbird/chat';
3 |
4 | import { v4 as uuid } from 'uuid';
5 |
6 | import SendbirdChat from '@sendbird/chat';
7 | import {
8 | OpenChannelModule,
9 | OpenChannelHandler,
10 | } from '@sendbird/chat/openChannel';
11 |
12 | import { SENDBIRD_INFO } from '../constants/constants';
13 | import { timestampToTime, handleEnterPress } from '../utils/messageUtils';
14 |
15 | let sb;
16 |
17 | const OpenChannelFreeze = (props) => {
18 |
19 | const [state, updateState] = useState({
20 | currentlyJoinedChannel: null,
21 | currentlyUpdatingChannel: null,
22 | messages: [],
23 | channels: [],
24 | showChannelCreate: false,
25 | messageInputValue: "",
26 | userNameInputValue: "",
27 | userIdInputValue: "",
28 | channelNameInputValue: "",
29 | settingUpUser: true,
30 | file: null,
31 | messageToUpdate: null,
32 | loading: false,
33 | error: false
34 | });
35 |
36 | //need to access state in message reeived callback
37 | const stateRef = useRef();
38 | stateRef.current = state;
39 |
40 | const channelRef = useRef();
41 |
42 | const scrollToBottom = (item, smooth) => {
43 | item?.scrollTo({
44 | top: item.scrollHeight,
45 | behavior: smooth
46 | })
47 | }
48 |
49 | useEffect(() => {
50 | scrollToBottom(channelRef.current)
51 | }, [state.currentlyJoinedChannel])
52 |
53 | useEffect(() => {
54 | scrollToBottom(channelRef.current, 'smooth')
55 | }, [state.messages])
56 |
57 | const onError = (error) => {
58 | updateState({ ...state, error: error.message });
59 | console.log(error);
60 | }
61 |
62 | const handleJoinChannel = async (channelUrl) => {
63 | if (state.currentlyJoinedChannel?.url === channelUrl) {
64 | return null;
65 | }
66 | const { channels } = state;
67 | updateState({ ...state, loading: true });
68 | const channelToJoin = channels.find((channel) => channel.url === channelUrl);
69 | await channelToJoin.enter();
70 | const [messages, error] = await loadMessages(channelToJoin);
71 | if (error) {
72 | return onError(error);
73 | }
74 |
75 | // setup connection event handlers
76 | const connectionHandler = new ConnectionHandler();
77 |
78 | connectionHandler.onReconnectSucceeded = async () => {
79 | const [messages, error] = await loadMessages(channelToJoin);
80 |
81 | updateState({ ...stateRef.current, messages: messages });
82 | }
83 |
84 | sb.addConnectionHandler(uuid(), connectionHandler);
85 |
86 |
87 | //listen for incoming messages
88 | const channelHandler = new OpenChannelHandler();
89 | channelHandler.onMessageUpdated = (channel, message) => {
90 | const messageIndex = stateRef.current.messages.findIndex((item => item.messageId == message.messageId));
91 | const updatedMessages = [...stateRef.current.messages];
92 | updatedMessages[messageIndex] = message;
93 | updateState({ ...stateRef.current, messages: updatedMessages });
94 | }
95 |
96 | channelHandler.onMessageReceived = (channel, message) => {
97 | const updatedMessages = [...stateRef.current.messages, message];
98 | updateState({ ...stateRef.current, messages: updatedMessages });
99 | };
100 |
101 | sb.openChannel.addOpenChannelHandler(uuid(), channelHandler);
102 | updateState({ ...state, currentlyJoinedChannel: channelToJoin, messages: messages, loading: false })
103 | }
104 |
105 | const handleLeaveChannel = async () => {
106 | const { currentlyJoinedChannel } = state;
107 | await currentlyJoinedChannel.exit();
108 |
109 | updateState({ ...state, currentlyJoinedChannel: null })
110 | }
111 |
112 | const handleCreateChannel = async () => {
113 | const { channelNameInputValue } = state;
114 | const [openChannel, error] = await createChannel(channelNameInputValue);
115 | if (error) {
116 | return onError(error);
117 | }
118 | const updatedChannels = [openChannel, ...state.channels];
119 | updateState({ ...state, channels: updatedChannels, showChannelCreate: false });
120 | }
121 |
122 | const handleDeleteChannel = async (channelUrl) => {
123 | const [channel, error] = await deleteChannel(channelUrl);
124 | if (error) {
125 | return onError(error);
126 | }
127 | const updatedChannels = state.channels.filter((channel) => {
128 | return channel.url !== channelUrl;
129 | });
130 | updateState({ ...state, channels: updatedChannels });
131 | }
132 |
133 | const handleUpdateChannel = async () => {
134 | const { currentlyUpdatingChannel, channelNameInputValue, channels } = state;
135 | const [updatedChannel, error] = await updateChannel(currentlyUpdatingChannel, channelNameInputValue);
136 | if (error) {
137 | return onError(error);
138 | }
139 | const indexToReplace = channels.findIndex((channel) => channel.url === currentlyUpdatingChannel.channelUrl);
140 | const updatedChannels = [...channels];
141 | updatedChannels[indexToReplace] = updatedChannel;
142 | updateState({ ...state, channels: updatedChannels, currentlyUpdatingChannel: null });
143 | }
144 |
145 | const toggleChannelDetails = (channel) => {
146 | if (channel) {
147 | updateState({ ...state, currentlyUpdatingChannel: channel });
148 | } else {
149 | updateState({ ...state, currentlyUpdatingChannel: null });
150 | }
151 | }
152 |
153 | const toggleShowCreateChannel = () => {
154 | updateState({ ...state, showChannelCreate: !state.showChannelCreate });
155 | }
156 |
157 | const onChannelNamenIputChange = (e) => {
158 | const channelNameInputValue = e.currentTarget.value;
159 | updateState({ ...state, channelNameInputValue });
160 | }
161 |
162 | const onUserNameInputChange = (e) => {
163 | const userNameInputValue = e.currentTarget.value;
164 | updateState({ ...state, userNameInputValue });
165 | }
166 |
167 | const onUserIdInputChange = (e) => {
168 | const userIdInputValue = e.currentTarget.value;
169 | updateState({ ...state, userIdInputValue });
170 | }
171 |
172 | const onMessageInputChange = (e) => {
173 | const messageInputValue = e.currentTarget.value;
174 | updateState({ ...state, messageInputValue });
175 | }
176 |
177 | const sendMessage = async () => {
178 | const { messageToUpdate, currentlyJoinedChannel, messages } = state;
179 |
180 | if (messageToUpdate) {
181 | const userMessageUpdateParams = {};
182 | userMessageUpdateParams.message = state.messageInputValue;
183 | const updatedMessage = await currentlyJoinedChannel.updateUserMessage(messageToUpdate.messageId, userMessageUpdateParams)
184 | const messageIndex = messages.findIndex((item => item.messageId == messageToUpdate.messageId));
185 | messages[messageIndex] = updatedMessage;
186 | updateState({ ...state, messages: messages, messageInputValue: "", messageToUpdate: null });
187 | } else {
188 | const userMessageParams = {};
189 | userMessageParams.message = state.messageInputValue;
190 | currentlyJoinedChannel.sendUserMessage(userMessageParams).onSucceeded((message) => {
191 | const updatedMessages = [...messages, message];
192 | updateState({ ...state, messages: updatedMessages, messageInputValue: "" });
193 |
194 | }).onFailed((error) => {
195 | console.log(error)
196 | console.log("failed")
197 | });
198 | }
199 | }
200 |
201 | const onFileInputChange = async (e) => {
202 | if (e.currentTarget.files && e.currentTarget.files.length > 0) {
203 | const { currentlyJoinedChannel, messages } = state;
204 | const fileMessageParams = {};
205 | fileMessageParams.file = e.currentTarget.files[0];
206 | currentlyJoinedChannel.sendFileMessage(fileMessageParams).onSucceeded((message) => {
207 | const updatedMessages = [...messages, message];
208 | updateState({ ...state, messages: updatedMessages, messageInputValue: "", file: null });
209 | }).onFailed((error) => {
210 | console.log(error)
211 | console.log("failed")
212 | });
213 | }
214 | }
215 |
216 | const handleDeleteMessage = async (messageToDelete) => {
217 | const { currentlyJoinedChannel, messages } = state;
218 | await deleteMessage(currentlyJoinedChannel, messageToDelete);
219 | const updatedMessages = messages.filter((message) => {
220 | return message.messageId !== messageToDelete.messageId;
221 | });
222 | updateState({ ...state, messages: updatedMessages });
223 | }
224 |
225 | const updateMessage = async (message) => {
226 | updateState({ ...state, messageToUpdate: message, messageInputValue: message.message });
227 | }
228 |
229 | const setupUser = async () => {
230 | const { userNameInputValue, userIdInputValue } = state;
231 | const sendbirdChat = await SendbirdChat.init({
232 | appId: SENDBIRD_INFO.appId,
233 | localCacheEnabled: true,
234 | modules: [new OpenChannelModule()]
235 | });
236 |
237 | try {
238 | await sendbirdChat.connect(userIdInputValue);
239 | } catch (e) {
240 | console.log("error", e)
241 | }
242 | await sendbirdChat.setChannelInvitationPreference(true);
243 |
244 | const userUpdateParams = {};
245 | userUpdateParams.nickname = userNameInputValue;
246 | userUpdateParams.userId = userIdInputValue;
247 | await sendbirdChat.updateCurrentUserInfo(userUpdateParams);
248 |
249 | sb = sendbirdChat;
250 | updateState({ ...state, loading: true });
251 | const [channels, error] = await loadChannels();
252 | if (error) {
253 | return onError(error);
254 | }
255 | updateState({ ...state, channels: channels, loading: false, settingUpUser: false });
256 | }
257 |
258 | if (state.loading) {
259 | return Loading...
260 | }
261 |
262 | if (state.error) {
263 | return {state.error} check console for more information.
264 | }
265 |
266 | console.log('- - - - State object very useful for debugging - - - -');
267 | console.log(state);
268 |
269 | return (
270 | <>
271 |
278 |
284 |
289 |
294 |
299 | {state.currentlyJoinedChannel?.isFrozen && DisplayFreezeMessage()}
300 |
305 |
312 |
313 | >
314 | );
315 | };
316 |
317 | // Chat UI Components
318 | const ChannelList = ({ channels, handleJoinChannel, toggleShowCreateChannel, handleDeleteChannel, toggleChannelDetails }) => {
319 | return (
320 |
321 |
322 |
Open Channels
323 |
324 |
325 | {
326 | channels.map(channel => {
327 | const userIsOperator = channel.operators.some((operator) => operator.userId === sb.currentUser.userId)
328 | return (
329 |
330 |
{ handleJoinChannel(channel.url) }}>
332 | {channel.name}
333 | {channel.isFrozen &&

}
334 |
335 | {userIsOperator &&
336 |
337 |
340 |
343 |
344 | }
345 |
346 | );
347 | })
348 | }
349 |
350 | );
351 | }
352 |
353 | const Channel = ({ currentlyJoinedChannel, handleLeaveChannel, children, channelRef }) => {
354 | if (currentlyJoinedChannel) {
355 | return
356 |
{currentlyJoinedChannel.name}
357 |
358 |
359 |
360 |
{children}
361 |
;
362 | }
363 | return ;
364 | }
365 |
366 | const ChannelHeader = ({ children }) => {
367 | return {children}
;
368 | }
369 |
370 | const MessagesList = ({ messages, handleDeleteMessage, updateMessage }) => {
371 | return messages.map(message => {
372 | return (
373 |
374 |
379 |
380 | );
381 | })
382 | }
383 |
384 | const Message = ({ message, updateMessage, handleDeleteMessage }) => {
385 | if (!message.sender) return null; if (message.url) {
386 | return (
387 |
388 |
{timestampToTime(message.createdAt)}
389 |
{message.sender.nickname}{' '}
390 |

391 |
);
392 | }
393 |
394 | const messageSentByCurrentUser = message.sender.userId === sb.currentUser.userId;
395 | return (
396 |
397 |
{timestampToTime(message.createdAt)}
398 |
{message.sender.nickname}{':'}
399 |
{message.message}
400 |
401 | {messageSentByCurrentUser && <>
402 |
405 |
408 | >}
409 |
410 | );
411 | }
412 |
413 | const MessageInput = ({ value, onChange, sendMessage, onFileInputChange }) => {
414 | return (
415 |
435 | );
436 | }
437 |
438 | const ChannelDetails = ({
439 | currentlyUpdatingChannel,
440 | toggleChannelDetails,
441 | handleUpdateChannel,
442 | onChannelNamenIputChange
443 | }) => {
444 | if (currentlyUpdatingChannel) {
445 | return
446 |
447 |
Update Channel Details
448 |
Channel name
449 |
450 |
451 |
452 |
453 |
;
454 | }
455 | return null;
456 | }
457 |
458 | const ChannelCreate = ({
459 | showChannelCreate,
460 | toggleShowCreateChannel,
461 | handleCreateChannel,
462 | onChannelNamenIputChange
463 | }) => {
464 | if (showChannelCreate) {
465 | return
466 |
467 |
468 |
Create Channel
469 |
470 |
Name
471 |
handleEnterPress(event, handleCreateChannel)} />
472 |
473 |
474 |
475 |
476 |
477 |
;
478 | }
479 | return null;
480 | }
481 |
482 | const CreateUserForm = ({
483 | setupUser,
484 | settingUpUser,
485 | userNameInputValue,
486 | userIdInputValue,
487 | onUserNameInputChange,
488 | onUserIdInputChange
489 | }) => {
490 | if (settingUpUser) {
491 | return
492 |
handleEnterPress(event, setupUser)}>
493 |
User ID
494 |
499 |
User Nickname
500 |
505 |
506 |
510 |
511 |
512 |
513 | } else {
514 | return null;
515 | }
516 | }
517 |
518 | const DisplayFreezeMessage = () => (
519 | Channel Frozen
520 | )
521 |
522 | // Helpful functions that call Sendbird
523 | const loadChannels = async () => {
524 | try {
525 | const openChannelQuery = sb.openChannel.createOpenChannelListQuery({ limit: 30 });
526 | const channels = await openChannelQuery.next();
527 | return [channels, null];
528 | } catch (error) {
529 | return [null, error];
530 | }
531 | }
532 |
533 | const loadMessages = async (channel) => {
534 | try {
535 |
536 | //list all messages
537 | const messageListParams = {};
538 | messageListParams.nextResultSize = 20;
539 | const messages = await channel.getMessagesByTimestamp(0, messageListParams);
540 | return [messages, null];
541 | } catch (error) {
542 | return [null, error]
543 | }
544 | }
545 |
546 | const createChannel = async (channelName) => {
547 | try {
548 | const openChannelParams = {};
549 | openChannelParams.name = channelName;
550 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
551 | const openChannel = await sb.openChannel.createChannel(openChannelParams);
552 | return [openChannel, null];
553 | } catch (error) {
554 | return [null, error];
555 | }
556 | }
557 |
558 | const deleteChannel = async (channelUrl) => {
559 | try {
560 | const channel = await sb.openChannel.getChannel(channelUrl);
561 | await channel.delete();
562 | return [channel, null];
563 | } catch (error) {
564 | return [null, error];
565 | }
566 | }
567 |
568 | const updateChannel = async (currentlyUpdatingChannel, channelNameInputValue) => {
569 | try {
570 | const channel = await sb.openChannel.getChannel(currentlyUpdatingChannel.url);
571 | const openChannelParams = {};
572 | openChannelParams.name = channelNameInputValue;
573 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
574 | const updatedChannel = await channel.updateChannel(openChannelParams);
575 | return [updatedChannel, null];
576 | } catch (error) {
577 | return [null, error];
578 | }
579 | }
580 |
581 | const deleteMessage = async (currentlyJoinedChannel, messageToDelete) => {
582 | await currentlyJoinedChannel.deleteMessage(messageToDelete);
583 | }
584 |
585 | export default OpenChannelFreeze;
586 |
--------------------------------------------------------------------------------
/src/samples/OpenChannelSendAnAdminMessage.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react';
2 | import { ConnectionHandler } from '@sendbird/chat';
3 |
4 | import { v4 as uuid } from 'uuid';
5 |
6 | import SendbirdChat from '@sendbird/chat';
7 | import {
8 | OpenChannelModule,
9 | OpenChannelHandler,
10 | } from '@sendbird/chat/openChannel';
11 |
12 | import { SENDBIRD_INFO } from '../constants/constants';
13 | import { timestampToTime, handleEnterPress } from '../utils/messageUtils';
14 |
15 | let sb;
16 |
17 | const OpenChannelSendAnAdminMessage = (props) => {
18 |
19 | const [state, updateState] = useState({
20 | currentlyJoinedChannel: null,
21 | currentlyUpdatingChannel: null,
22 | messages: [],
23 | channels: [],
24 | showChannelCreate: false,
25 | messageInputValue: "",
26 | userNameInputValue: "",
27 | userIdInputValue: "",
28 | channelNameInputValue: "",
29 | settingUpUser: true,
30 | file: null,
31 | messageToUpdate: null,
32 | loading: false,
33 | error: false
34 | });
35 |
36 | //need to access state in message reeived callback
37 | const stateRef = useRef();
38 | stateRef.current = state;
39 |
40 | const channelRef = useRef();
41 |
42 | const scrollToBottom = (item, smooth) => {
43 | item?.scrollTo({
44 | top: item.scrollHeight,
45 | behavior: smooth
46 | })
47 | }
48 |
49 | useEffect(() => {
50 | scrollToBottom(channelRef.current)
51 | }, [state.currentlyJoinedChannel])
52 |
53 | useEffect(() => {
54 | scrollToBottom(channelRef.current, 'smooth')
55 | }, [state.messages])
56 |
57 | const onError = (error) => {
58 | updateState({ ...state, error: error.message });
59 | console.log(error);
60 | }
61 |
62 | const handleJoinChannel = async (channelUrl) => {
63 | if (state.currentlyJoinedChannel?.url === channelUrl) {
64 | return null;
65 | }
66 | const { channels } = state;
67 | updateState({ ...state, loading: true });
68 | const channelToJoin = channels.find((channel) => channel.url === channelUrl);
69 | await channelToJoin.enter();
70 | const [messages, error] = await loadMessages(channelToJoin);
71 | if (error) {
72 | return onError(error);
73 | }
74 |
75 | // setup connection event handlers
76 | const connectionHandler = new ConnectionHandler();
77 |
78 | connectionHandler.onReconnectSucceeded = async () => {
79 | const [messages, error] = await loadMessages(channelToJoin);
80 |
81 | updateState({ ...stateRef.current, messages: messages });
82 | }
83 |
84 | sb.addConnectionHandler(uuid(), connectionHandler);
85 |
86 | //listen for incoming messages
87 | const channelHandler = new OpenChannelHandler();
88 | channelHandler.onMessageUpdated = (channel, message) => {
89 | const messageIndex = stateRef.current.messages.findIndex((item => item.messageId == message.messageId));
90 | const updatedMessages = [...stateRef.current.messages];
91 | updatedMessages[messageIndex] = message;
92 | updateState({ ...stateRef.current, messages: updatedMessages });
93 | }
94 |
95 | channelHandler.onMessageReceived = (channel, message) => {
96 | const updatedMessages = [...stateRef.current.messages, message];
97 | updateState({ ...stateRef.current, messages: updatedMessages });
98 | };
99 |
100 | channelHandler.onMessageDeleted = (channel, message) => {
101 | const updatedMessages = stateRef.current.messages.filter((messageObject) => {
102 | return messageObject.messageId !== message;
103 | });
104 | updateState({ ...stateRef.current, messages: updatedMessages });
105 | }
106 | sb.openChannel.addOpenChannelHandler(uuid(), channelHandler);
107 | updateState({ ...state, currentlyJoinedChannel: channelToJoin, messages: messages, loading: false })
108 | }
109 |
110 | const handleLeaveChannel = async () => {
111 | const { currentlyJoinedChannel } = state;
112 | await currentlyJoinedChannel.exit();
113 |
114 | updateState({ ...state, currentlyJoinedChannel: null })
115 | }
116 |
117 | const handleCreateChannel = async () => {
118 | const { channelNameInputValue } = state;
119 | const [openChannel, error] = await createChannel(channelNameInputValue);
120 | if (error) {
121 | return onError(error);
122 | }
123 | const updatedChannels = [openChannel, ...state.channels];
124 | updateState({ ...state, channels: updatedChannels, showChannelCreate: false });
125 | }
126 |
127 | const handleDeleteChannel = async (channelUrl) => {
128 | const [channel, error] = await deleteChannel(channelUrl);
129 | if (error) {
130 | return onError(error);
131 | }
132 | const updatedChannels = state.channels.filter((channel) => {
133 | return channel.url !== channelUrl;
134 | });
135 | updateState({ ...state, channels: updatedChannels });
136 | }
137 |
138 | const handleUpdateChannel = async () => {
139 | const { currentlyUpdatingChannel, channelNameInputValue, channels } = state;
140 | const [updatedChannel, error] = await updateChannel(currentlyUpdatingChannel, channelNameInputValue);
141 | if (error) {
142 | return onError(error);
143 | }
144 | const indexToReplace = channels.findIndex((channel) => channel.url === currentlyUpdatingChannel.channelUrl);
145 | const updatedChannels = [...channels];
146 | updatedChannels[indexToReplace] = updatedChannel;
147 | updateState({ ...state, channels: updatedChannels, currentlyUpdatingChannel: null });
148 | }
149 |
150 | const toggleChannelDetails = (channel) => {
151 | if (channel) {
152 | updateState({ ...state, currentlyUpdatingChannel: channel });
153 | } else {
154 | updateState({ ...state, currentlyUpdatingChannel: null });
155 | }
156 | }
157 |
158 | const toggleShowCreateChannel = () => {
159 | updateState({ ...state, showChannelCreate: !state.showChannelCreate });
160 | }
161 |
162 | const onChannelNamenIputChange = (e) => {
163 | const channelNameInputValue = e.currentTarget.value;
164 | updateState({ ...state, channelNameInputValue });
165 | }
166 |
167 | const onUserNameInputChange = (e) => {
168 | const userNameInputValue = e.currentTarget.value;
169 | updateState({ ...state, userNameInputValue });
170 | }
171 |
172 | const onUserIdInputChange = (e) => {
173 | const userIdInputValue = e.currentTarget.value;
174 | updateState({ ...state, userIdInputValue });
175 | }
176 |
177 | const onMessageInputChange = (e) => {
178 | const messageInputValue = e.currentTarget.value;
179 | updateState({ ...state, messageInputValue });
180 | }
181 |
182 | const sendMessage = async () => {
183 | const { messageToUpdate, currentlyJoinedChannel, messages } = state;
184 |
185 | if (messageToUpdate) {
186 | const userMessageUpdateParams = {};
187 | userMessageUpdateParams.message = state.messageInputValue;
188 | const updatedMessage = await currentlyJoinedChannel.updateUserMessage(messageToUpdate.messageId, userMessageUpdateParams)
189 | const messageIndex = messages.findIndex((item => item.messageId == messageToUpdate.messageId));
190 | messages[messageIndex] = updatedMessage;
191 | updateState({ ...state, messages: messages, messageInputValue: "", messageToUpdate: null });
192 | } else {
193 | const userMessageParams = {};
194 | userMessageParams.message = state.messageInputValue;
195 | currentlyJoinedChannel.sendUserMessage(userMessageParams).onSucceeded((message) => {
196 | const updatedMessages = [...messages, message];
197 | updateState({ ...state, messages: updatedMessages, messageInputValue: "" });
198 | }).onFailed((error) => {
199 | console.log(error)
200 | console.log("failed")
201 | });
202 | }
203 | }
204 |
205 | const onFileInputChange = async (e) => {
206 | if (e.currentTarget.files && e.currentTarget.files.length > 0) {
207 | const { currentlyJoinedChannel, messages } = state;
208 | const fileMessageParams = {};
209 | fileMessageParams.file = e.currentTarget.files[0];
210 | currentlyJoinedChannel.sendFileMessage(fileMessageParams).onSucceeded((message) => {
211 | const updatedMessages = [...messages, message];
212 | updateState({ ...state, messages: updatedMessages, messageInputValue: "", file: null });
213 | }).onFailed((error) => {
214 | console.log(error)
215 | console.log("failed")
216 | });
217 | }
218 | }
219 |
220 | const handleDeleteMessage = async (messageToDelete) => {
221 | const { currentlyJoinedChannel } = state;
222 | await deleteMessage(currentlyJoinedChannel, messageToDelete); // Delete
223 | }
224 |
225 | const updateMessage = async (message) => {
226 | updateState({ ...state, messageToUpdate: message, messageInputValue: message.message });
227 | }
228 |
229 | const setupUser = async () => {
230 | const { userNameInputValue, userIdInputValue } = state;
231 | const sendbirdChat = await SendbirdChat.init({
232 | appId: SENDBIRD_INFO.appId,
233 | localCacheEnabled: true,
234 | modules: [new OpenChannelModule()]
235 | });
236 |
237 | try {
238 | await sendbirdChat.connect(userIdInputValue);
239 | } catch (e) {
240 | console.log("error", e)
241 | }
242 | await sendbirdChat.setChannelInvitationPreference(true);
243 |
244 | const userUpdateParams = {};
245 | userUpdateParams.nickname = userNameInputValue;
246 | userUpdateParams.userId = userIdInputValue;
247 | await sendbirdChat.updateCurrentUserInfo(userUpdateParams);
248 |
249 | sb = sendbirdChat;
250 | updateState({ ...state, loading: true });
251 | const [channels, error] = await loadChannels();
252 | if (error) {
253 | return onError(error);
254 | }
255 | updateState({ ...state, channels: channels, loading: false, settingUpUser: false });
256 | }
257 |
258 | if (state.loading) {
259 | return Loading...
260 | }
261 |
262 | if (state.error) {
263 | return {state.error} check console for more information.
264 | }
265 |
266 | console.log('- - - - State object very useful for debugging - - - -');
267 | console.log(state);
268 |
269 | return (
270 | <>
271 |
278 |
284 |
289 |
294 |
299 |
303 |
310 |
311 | >
312 | );
313 | };
314 |
315 | // Chat UI Components
316 | const ChannelList = ({ channels, handleJoinChannel, toggleShowCreateChannel, handleDeleteChannel, toggleChannelDetails }) => {
317 | return (
318 |
319 |
320 |
Open Channels
321 |
322 |
323 | {
324 | channels.map(channel => {
325 | const userIsOperator = channel.operators.some((operator) => operator.userId === sb.currentUser.userId)
326 | return (
327 |
328 |
{ handleJoinChannel(channel.url) }}
331 | >
332 | {channel.name}
333 |
334 | {
335 | userIsOperator &&
336 |
337 |
341 |
344 |
345 | }
346 |
347 | );
348 | })
349 | }
350 |
351 | );
352 | }
353 |
354 | const Channel = ({ currentlyJoinedChannel, handleLeaveChannel, children, channelRef }) => {
355 | if (currentlyJoinedChannel) {
356 | return
357 |
{currentlyJoinedChannel.name}
358 |
359 |
360 |
361 |
{children}
362 |
;
363 | }
364 | return ;
365 | }
366 |
367 | const ChannelHeader = ({ children }) => {
368 | return {children}
;
369 | }
370 |
371 | const MessagesList = ({ messages, handleDeleteMessage, updateMessage }) => {
372 | return messages.map(message => {
373 | return (
374 |
375 |
380 |
381 | );
382 | })
383 | }
384 |
385 | const Message = ({ message, updateMessage, handleDeleteMessage }) => {
386 | if (message.url) {
387 | return (
388 |
389 |
{timestampToTime(message.createdAt)}
390 |
{message.sender.nickname}{' '}
391 |

392 |
393 | );
394 | }
395 |
396 | if (message.messageType === "admin") {
397 | return
398 | }
399 |
400 | const messageSentByCurrentUser = message.sender.userId === sb.currentUser.userId;
401 | return (
402 |
403 |
{timestampToTime(message.createdAt)}
404 |
{message.sender.nickname}{':'}
405 |
{message.message}
406 | {
407 | messageSentByCurrentUser &&
408 | <>
409 |
412 |
415 | >
416 | }
417 |
418 | );
419 | }
420 |
421 | const MessageInput = ({ value, onChange, sendMessage, onFileInputChange }) => {
422 | return (
423 |
443 | );
444 | }
445 |
446 | const ChannelDetails = ({
447 | currentlyUpdatingChannel,
448 | toggleChannelDetails,
449 | handleUpdateChannel,
450 | onChannelNamenIputChange
451 | }) => {
452 | if (currentlyUpdatingChannel) {
453 | return
454 |
455 |
Update Channel Details
456 |
Channel name
457 |
458 |
459 |
460 |
461 |
;
462 | }
463 | return null;
464 | }
465 |
466 | const ChannelCreate = ({
467 | showChannelCreate,
468 | toggleShowCreateChannel,
469 | handleCreateChannel,
470 | onChannelNamenIputChange
471 | }) => {
472 | if (showChannelCreate) {
473 | return
474 |
475 |
476 |
Create Channel
477 |
478 |
Name
479 |
handleEnterPress(event, handleCreateChannel)} />
480 |
481 |
482 |
483 |
484 |
485 |
;
486 | }
487 | return null;
488 | }
489 |
490 | const CreateUserForm = ({
491 | setupUser,
492 | settingUpUser,
493 | userNameInputValue,
494 | userIdInputValue,
495 | onUserNameInputChange,
496 | onUserIdInputChange
497 | }) => {
498 | if (settingUpUser) {
499 | return
500 |
handleEnterPress(event, setupUser)}>
501 |
User ID
502 |
507 |
User Nickname
508 |
513 |
514 |
518 |
519 |
520 |
521 | } else {
522 | return null;
523 | }
524 | }
525 |
526 | const AdminMessage = ({ message }) => (
527 |
528 |
{timestampToTime(message.createdAt)}
529 |
{message.messageType}{':'}
530 |
{message.message}
531 |
532 | )
533 |
534 | // Helpful functions that call Sendbird
535 | const loadChannels = async () => {
536 | try {
537 | const openChannelQuery = sb.openChannel.createOpenChannelListQuery({ limit: 30 });
538 | const channels = await openChannelQuery.next();
539 | return [channels, null];
540 | } catch (error) {
541 | return [null, error];
542 | }
543 | }
544 |
545 | const loadMessages = async (channel) => {
546 | try {
547 |
548 | //list all messages
549 | const messageListParams = {};
550 | messageListParams.nextResultSize = 20;
551 | const messages = await channel.getMessagesByTimestamp(0, messageListParams);
552 | return [messages, null];
553 | } catch (error) {
554 | return [null, error]
555 | }
556 | }
557 |
558 | const createChannel = async (channelName) => {
559 | try {
560 | const openChannelParams = {};
561 | openChannelParams.name = channelName;
562 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
563 | const openChannel = await sb.openChannel.createChannel(openChannelParams);
564 | return [openChannel, null];
565 | } catch (error) {
566 | return [null, error];
567 | }
568 | }
569 |
570 | const deleteChannel = async (channelUrl) => {
571 | try {
572 | const channel = await sb.openChannel.getChannel(channelUrl);
573 | await channel.delete();
574 | return [channel, null];
575 | } catch (error) {
576 | return [null, error];
577 | }
578 | }
579 |
580 | const updateChannel = async (currentlyUpdatingChannel, channelNameInputValue) => {
581 | try {
582 | const channel = await sb.openChannel.getChannel(currentlyUpdatingChannel.url);
583 | const openChannelParams = {};
584 | openChannelParams.name = channelNameInputValue;
585 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
586 | const updatedChannel = await channel.updateChannel(openChannelParams);
587 | return [updatedChannel, null];
588 | } catch (error) {
589 | return [null, error];
590 | }
591 | }
592 |
593 | const deleteMessage = async (currentlyJoinedChannel, messageToDelete) => {
594 | await currentlyJoinedChannel.deleteMessage(messageToDelete);
595 | }
596 |
597 | export default OpenChannelSendAnAdminMessage;
598 |
--------------------------------------------------------------------------------
/src/samples/OpenChannelSendAndReceiveVariousTypesOfFiles.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react';
2 | import { ConnectionHandler } from '@sendbird/chat';
3 |
4 | import { v4 as uuid } from 'uuid';
5 |
6 | import SendbirdChat from '@sendbird/chat';
7 | import {
8 | OpenChannelModule,
9 | OpenChannelHandler,
10 | } from '@sendbird/chat/openChannel';
11 |
12 | import { SENDBIRD_INFO } from '../constants/constants';
13 | import { timestampToTime, handleEnterPress } from '../utils/messageUtils';
14 |
15 | let sb;
16 |
17 | const OpenChannelSendAndReceiveVariousTypesOfFiles = (props) => {
18 |
19 | const [state, updateState] = useState({
20 | currentlyJoinedChannel: null,
21 | currentlyUpdatingChannel: null,
22 | messages: [],
23 | channels: [],
24 | showChannelCreate: false,
25 | messageInputValue: "",
26 | userNameInputValue: "",
27 | userIdInputValue: "",
28 | channelNameInputValue: "",
29 | settingUpUser: true,
30 | file: null,
31 | messageToUpdate: null,
32 | loading: false,
33 | error: false,
34 | isImageSizeError: false
35 | });
36 |
37 | //need to access state in message reeived callback
38 | const stateRef = useRef();
39 | stateRef.current = state;
40 |
41 | const channelRef = useRef();
42 |
43 | const scrollToBottom = (item, smooth) => {
44 | item?.scrollTo({
45 | top: item.scrollHeight,
46 | behavior: smooth
47 | })
48 | }
49 |
50 | useEffect(() => {
51 | scrollToBottom(channelRef.current)
52 | }, [state.currentlyJoinedChannel])
53 |
54 | useEffect(() => {
55 | scrollToBottom(channelRef.current, 'smooth')
56 | }, [state.messages])
57 |
58 | const onError = (error) => {
59 | updateState({ ...state, error: error.message });
60 | console.log(error);
61 | }
62 |
63 | const handleJoinChannel = async (channelUrl) => {
64 | if (state.currentlyJoinedChannel?.url === channelUrl) {
65 | return null;
66 | }
67 | const { channels } = state;
68 | updateState({ ...state, loading: true });
69 | const channelToJoin = channels.find((channel) => channel.url === channelUrl);
70 | await channelToJoin.enter();
71 | const [messages, error] = await loadMessages(channelToJoin);
72 | if (error) {
73 | return onError(error);
74 | }
75 |
76 | // setup connection event handlers
77 | const connectionHandler = new ConnectionHandler();
78 |
79 | connectionHandler.onReconnectSucceeded = async () => {
80 | const [messages, error] = await loadMessages(channelToJoin);
81 |
82 | updateState({ ...stateRef.current, messages: messages });
83 | }
84 |
85 | sb.addConnectionHandler(uuid(), connectionHandler);
86 |
87 |
88 | //listen for incoming messages
89 | const channelHandler = new OpenChannelHandler();
90 | channelHandler.onMessageUpdated = (channel, message) => {
91 | const messageIndex = stateRef.current.messages.findIndex((item => item.messageId == message.messageId));
92 | const updatedMessages = [...stateRef.current.messages];
93 | updatedMessages[messageIndex] = message;
94 | updateState({ ...stateRef.current, messages: updatedMessages });
95 | }
96 |
97 | channelHandler.onMessageReceived = (channel, message) => {
98 | const updatedMessages = [...stateRef.current.messages, message];
99 | updateState({ ...stateRef.current, messages: updatedMessages });
100 | };
101 |
102 | channelHandler.onMessageDeleted = (channel, message) => {
103 | const updatedMessages = stateRef.current.messages.filter((messageObject) => {
104 | return messageObject.messageId !== message;
105 | });
106 | updateState({ ...stateRef.current, messages: updatedMessages });
107 | }
108 | sb.openChannel.addOpenChannelHandler(uuid(), channelHandler);
109 | updateState({ ...state, currentlyJoinedChannel: channelToJoin, messages: messages, loading: false })
110 | }
111 |
112 | const handleLeaveChannel = async () => {
113 | const { currentlyJoinedChannel } = state;
114 | await currentlyJoinedChannel.exit();
115 | updateState({ ...state, currentlyJoinedChannel: null })
116 | }
117 |
118 | const handleCreateChannel = async () => {
119 | const { channelNameInputValue } = state;
120 | const [openChannel, error] = await createChannel(channelNameInputValue);
121 | if (error) {
122 | return onError(error);
123 | }
124 | const updatedChannels = [openChannel, ...state.channels];
125 | updateState({ ...state, channels: updatedChannels, showChannelCreate: false });
126 | }
127 |
128 | const handleDeleteChannel = async (channelUrl) => {
129 | const [channel, error] = await deleteChannel(channelUrl);
130 | if (error) {
131 | return onError(error);
132 | }
133 | const updatedChannels = state.channels.filter((channel) => {
134 | return channel.url !== channelUrl;
135 | });
136 | updateState({ ...state, channels: updatedChannels });
137 | }
138 |
139 | const handleUpdateChannel = async () => {
140 | const { currentlyUpdatingChannel, channelNameInputValue, channels } = state;
141 | const [updatedChannel, error] = await updateChannel(currentlyUpdatingChannel, channelNameInputValue);
142 | if (error) {
143 | return onError(error);
144 | }
145 | const indexToReplace = channels.findIndex((channel) => channel.url === currentlyUpdatingChannel.channelUrl);
146 | const updatedChannels = [...channels];
147 | updatedChannels[indexToReplace] = updatedChannel;
148 | updateState({ ...state, channels: updatedChannels, currentlyUpdatingChannel: null });
149 | }
150 |
151 | const toggleChannelDetails = (channel) => {
152 | if (channel) {
153 | updateState({ ...state, currentlyUpdatingChannel: channel });
154 | } else {
155 | updateState({ ...state, currentlyUpdatingChannel: null });
156 | }
157 | }
158 |
159 | const toggleShowCreateChannel = () => {
160 | updateState({ ...state, showChannelCreate: !state.showChannelCreate });
161 | }
162 |
163 | const onChannelNamenIputChange = (e) => {
164 | const channelNameInputValue = e.currentTarget.value;
165 | updateState({ ...state, channelNameInputValue });
166 | }
167 |
168 | const onUserNameInputChange = (e) => {
169 | const userNameInputValue = e.currentTarget.value;
170 | updateState({ ...state, userNameInputValue });
171 | }
172 |
173 | const onUserIdInputChange = (e) => {
174 | const userIdInputValue = e.currentTarget.value;
175 | updateState({ ...state, userIdInputValue });
176 | }
177 |
178 | const onMessageInputChange = (e) => {
179 | const messageInputValue = e.currentTarget.value;
180 | updateState({ ...state, messageInputValue });
181 | }
182 |
183 | const sendMessage = async () => {
184 | const { messageToUpdate, currentlyJoinedChannel, messages } = state;
185 |
186 | if (messageToUpdate) {
187 | const userMessageUpdateParams = {};
188 | userMessageUpdateParams.message = state.messageInputValue;
189 | const updatedMessage = await currentlyJoinedChannel.updateUserMessage(messageToUpdate.messageId, userMessageUpdateParams)
190 | const messageIndex = messages.findIndex((item => item.messageId == messageToUpdate.messageId));
191 | messages[messageIndex] = updatedMessage;
192 | updateState({ ...state, messages: messages, messageInputValue: "", messageToUpdate: null });
193 | } else {
194 | const userMessageParams = {};
195 | userMessageParams.message = state.messageInputValue;
196 | currentlyJoinedChannel.sendUserMessage(userMessageParams).onSucceeded((message) => {
197 | const updatedMessages = [...messages, message];
198 | updateState({ ...state, messages: updatedMessages, messageInputValue: "" });
199 | }).onFailed((error) => {
200 | console.log(error)
201 | console.log("failed")
202 | });
203 | }
204 | }
205 |
206 | const onFileInputChange = async (e) => {
207 | if (e.currentTarget.files && e.currentTarget.files.length > 0) {
208 | const { currentlyJoinedChannel, messages } = state;
209 | const fileMessageParams = {};
210 | fileMessageParams.file = e.currentTarget.files[0];
211 | currentlyJoinedChannel.sendFileMessage(fileMessageParams).onSucceeded((message) => {
212 | message.type = fileMessageParams.mimeType;
213 | const updatedMessages = [...messages, message];
214 | updateState({ ...state, messages: updatedMessages, messageInputValue: "", file: null });
215 | }).onFailed((error) => {
216 | console.log(error)
217 | console.log("failed")
218 | updateState({ ...state, isImageSizeError: true })
219 | });
220 | }
221 | }
222 |
223 | const handleDeleteMessage = async (messageToDelete) => {
224 | const { currentlyJoinedChannel } = state;
225 | await deleteMessage(currentlyJoinedChannel, messageToDelete); // Delete
226 | }
227 |
228 | const updateMessage = async (message) => {
229 | updateState({ ...state, messageToUpdate: message, messageInputValue: message.message });
230 | }
231 |
232 | const setupUser = async () => {
233 | const { userNameInputValue, userIdInputValue } = state;
234 | const sendbirdChat = await SendbirdChat.init({
235 | appId: SENDBIRD_INFO.appId,
236 | localCacheEnabled: true,
237 | modules: [new OpenChannelModule()]
238 | });
239 |
240 | try {
241 | await sendbirdChat.connect(userIdInputValue);
242 | } catch (e) {
243 | console.log("error", e)
244 | }
245 | await sendbirdChat.setChannelInvitationPreference(true);
246 |
247 | const userUpdateParams = {};
248 | userUpdateParams.nickname = userNameInputValue;
249 | userUpdateParams.userId = userIdInputValue;
250 | await sendbirdChat.updateCurrentUserInfo(userUpdateParams);
251 |
252 | sb = sendbirdChat;
253 | updateState({ ...state, loading: true });
254 | const [channels, error] = await loadChannels();
255 | if (error) {
256 | return onError(error);
257 | }
258 | updateState({ ...state, channels: channels, loading: false, settingUpUser: false });
259 | }
260 |
261 | if (state.loading) {
262 | return Loading...
263 | }
264 |
265 | if (state.error) {
266 | return {state.error} check console for more information.
267 | }
268 |
269 | console.log('- - - - State object very useful for debugging - - - -');
270 | console.log(state);
271 |
272 | return (
273 | <>
274 |
281 |
287 |
292 |
297 |
303 |
307 |
314 |
315 | >
316 | );
317 | };
318 |
319 | // Chat UI Components
320 | const ChannelList = ({ channels, handleJoinChannel, toggleShowCreateChannel, handleDeleteChannel, toggleChannelDetails }) => {
321 | return (
322 |
323 |
324 |
Open Channels
325 |
326 |
327 | {
328 | channels.map(channel => {
329 | const userIsOperator = channel.operators.some((operator) => operator.userId === sb.currentUser.userId)
330 | return (
331 |
332 |
{ handleJoinChannel(channel.url) }}>
334 | {channel.name}
335 |
336 | {userIsOperator &&
337 |
338 |
341 |
344 |
345 | }
346 |
347 | );
348 | })
349 | }
350 |
351 | );
352 | }
353 |
354 | const Channel = ({ currentlyJoinedChannel, handleLeaveChannel, children, isImageSizeError, channelRef }) => {
355 | if (currentlyJoinedChannel) {
356 | return
357 |
{currentlyJoinedChannel.name}
358 | {isImageSizeError &&
There was an error sending the file.
}
359 |
360 |
361 |
362 |
{children}
363 |
;
364 | }
365 | return ;
366 | }
367 |
368 | const ChannelHeader = ({ children }) => {
369 | return {children}
;
370 | }
371 |
372 | const MessagesList = ({ messages, handleDeleteMessage, updateMessage }) => {
373 | return messages.map(message => {
374 | return (
375 |
376 |
381 |
382 | );
383 | })
384 | }
385 |
386 | const Message = ({ message, updateMessage, handleDeleteMessage }) => {
387 | if (!message.sender) return null; if (message.url) {
388 | const isImageMessage = message.type.includes("image");
389 | return (
390 |
398 | );
399 | }
400 |
401 | const messageSentByCurrentUser = message.sender.userId === sb.currentUser.userId;
402 | return (
403 |
404 |
{timestampToTime(message.createdAt)}
405 |
{message.sender.nickname}{':'}
406 |
{message.message}
407 |
408 | {messageSentByCurrentUser && <>
409 |
412 |
415 | >}
416 |
417 | );
418 | }
419 |
420 | const MessageInput = ({ value, onChange, sendMessage, onFileInputChange }) => {
421 | return (
422 |
442 | );
443 | }
444 |
445 | const ChannelDetails = ({
446 | currentlyUpdatingChannel,
447 | toggleChannelDetails,
448 | handleUpdateChannel,
449 | onChannelNamenIputChange
450 | }) => {
451 | if (currentlyUpdatingChannel) {
452 | return
453 |
454 |
Update Channel Details
455 |
Channel name
456 |
457 |
458 |
459 |
460 |
;
461 | }
462 | return null;
463 | }
464 |
465 | const ChannelCreate = ({
466 | showChannelCreate,
467 | toggleShowCreateChannel,
468 | handleCreateChannel,
469 | onChannelNamenIputChange
470 | }) => {
471 | if (showChannelCreate) {
472 | return
473 |
474 |
475 |
Create Channel
476 |
477 |
Name
478 |
handleEnterPress(event, handleCreateChannel)} />
479 |
480 |
481 |
482 |
483 |
484 |
;
485 | }
486 | return null;
487 | }
488 |
489 | const CreateUserForm = ({
490 | setupUser,
491 | settingUpUser,
492 | userNameInputValue,
493 | userIdInputValue,
494 | onUserNameInputChange,
495 | onUserIdInputChange
496 | }) => {
497 | if (settingUpUser) {
498 | return
499 |
handleEnterPress(event, setupUser)}>
500 |
User ID
501 |
506 |
User Nickname
507 |
511 |
512 |
516 |
517 |
518 |
519 | } else {
520 | return null;
521 | }
522 | }
523 |
524 | // Helpful functions that call Sendbird
525 | const loadChannels = async () => {
526 | try {
527 | const openChannelQuery = sb.openChannel.createOpenChannelListQuery({ limit: 30 });
528 | const channels = await openChannelQuery.next();
529 | return [channels, null];
530 | } catch (error) {
531 | return [null, error];
532 | }
533 | }
534 |
535 | const loadMessages = async (channel) => {
536 | try {
537 |
538 | //list all messages
539 | const messageListParams = {};
540 | messageListParams.nextResultSize = 20;
541 | const messages = await channel.getMessagesByTimestamp(0, messageListParams);
542 | return [messages, null];
543 | } catch (error) {
544 | return [null, error]
545 | }
546 | }
547 |
548 | const createChannel = async (channelName) => {
549 | try {
550 | const openChannelParams = {};
551 | openChannelParams.name = channelName;
552 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
553 | const openChannel = await sb.openChannel.createChannel(openChannelParams);
554 | return [openChannel, null];
555 | } catch (error) {
556 | return [null, error];
557 | }
558 | }
559 |
560 | const deleteChannel = async (channelUrl) => {
561 | try {
562 | const channel = await sb.openChannel.getChannel(channelUrl);
563 | await channel.delete();
564 | return [channel, null];
565 | } catch (error) {
566 | return [null, error];
567 | }
568 | }
569 |
570 | const updateChannel = async (currentlyUpdatingChannel, channelNameInputValue) => {
571 | try {
572 | const channel = await sb.openChannel.getChannel(currentlyUpdatingChannel.url);
573 | const openChannelParams = {};
574 | openChannelParams.name = channelNameInputValue;
575 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
576 | const updatedChannel = await channel.updateChannel(openChannelParams);
577 | return [updatedChannel, null];
578 | } catch (error) {
579 | return [null, error];
580 | }
581 | }
582 |
583 | const deleteMessage = async (currentlyJoinedChannel, messageToDelete) => {
584 | await currentlyJoinedChannel.deleteMessage(messageToDelete);
585 | }
586 |
587 | export default OpenChannelSendAndReceiveVariousTypesOfFiles;
588 |
--------------------------------------------------------------------------------
/src/samples/OpenChannelThumbnails.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react';
2 | import { ConnectionHandler } from '@sendbird/chat';
3 |
4 | import { v4 as uuid } from 'uuid';
5 |
6 | import SendbirdChat from '@sendbird/chat';
7 | import {
8 | OpenChannelModule,
9 | OpenChannelHandler,
10 | } from '@sendbird/chat/openChannel';
11 |
12 | import { SENDBIRD_INFO } from '../constants/constants';
13 | import { timestampToTime, handleEnterPress } from '../utils/messageUtils';
14 |
15 | let sb;
16 |
17 | const OpenChannelThumbnails = (props) => {
18 |
19 | const [state, updateState] = useState({
20 | currentlyJoinedChannel: null,
21 | currentlyUpdatingChannel: null,
22 | messages: [],
23 | channels: [],
24 | showChannelCreate: false,
25 | messageInputValue: "",
26 | userNameInputValue: "",
27 | userIdInputValue: "",
28 | channelNameInputValue: "",
29 | settingUpUser: true,
30 | file: null,
31 | messageToUpdate: null,
32 | loading: false,
33 | error: false
34 | });
35 |
36 | //need to access state in message reeived callback
37 | const stateRef = useRef();
38 | stateRef.current = state;
39 |
40 | const channelRef = useRef();
41 |
42 | const scrollToBottom = (item, smooth) => {
43 | item?.scrollTo({
44 | top: item.scrollHeight,
45 | behavior: smooth
46 | })
47 | }
48 |
49 | useEffect(() => {
50 | scrollToBottom(channelRef.current)
51 | }, [state.currentlyJoinedChannel])
52 |
53 | useEffect(() => {
54 | scrollToBottom(channelRef.current, 'smooth')
55 | }, [state.messages])
56 |
57 | const onError = (error) => {
58 | updateState({ ...state, error: error.message });
59 | console.log(error);
60 | }
61 |
62 | const handleJoinChannel = async (channelUrl) => {
63 | if (state.currentlyJoinedChannel?.url === channelUrl) {
64 | return null;
65 | }
66 | const { channels } = state;
67 | updateState({ ...state, loading: true });
68 | const channelToJoin = channels.find((channel) => channel.url === channelUrl);
69 | await channelToJoin.enter();
70 | const [messages, error] = await loadMessages(channelToJoin);
71 | if (error) {
72 | return onError(error);
73 | }
74 |
75 | // setup connection event handlers
76 | const connectionHandler = new ConnectionHandler();
77 |
78 | connectionHandler.onReconnectSucceeded = async () => {
79 | const [messages, error] = await loadMessages(channelToJoin);
80 |
81 | updateState({ ...stateRef.current, messages: messages });
82 | }
83 |
84 | sb.addConnectionHandler(uuid(), connectionHandler);
85 |
86 | //listen for incoming messages
87 | const channelHandler = new OpenChannelHandler();
88 | channelHandler.onMessageUpdated = (channel, message) => {
89 | const messageIndex = stateRef.current.messages.findIndex((item => item.messageId == message.messageId));
90 | const updatedMessages = [...stateRef.current.messages];
91 | updatedMessages[messageIndex] = message;
92 | updateState({ ...stateRef.current, messages: updatedMessages });
93 | }
94 |
95 | channelHandler.onMessageReceived = (channel, message) => {
96 | const updatedMessages = [...stateRef.current.messages, message];
97 | updateState({ ...stateRef.current, messages: updatedMessages });
98 | };
99 |
100 | channelHandler.onMessageDeleted = (channel, message) => {
101 | const updatedMessages = stateRef.current.messages.filter((messageObject) => {
102 | return messageObject.messageId !== message;
103 | });
104 | updateState({ ...stateRef.current, messages: updatedMessages });
105 | }
106 | sb.openChannel.addOpenChannelHandler(uuid(), channelHandler);
107 | updateState({ ...state, currentlyJoinedChannel: channelToJoin, messages: messages, loading: false })
108 | }
109 |
110 | const handleLeaveChannel = async () => {
111 | const { currentlyJoinedChannel } = state;
112 | await currentlyJoinedChannel.exit();
113 | updateState({ ...state, currentlyJoinedChannel: null })
114 | }
115 |
116 | const handleCreateChannel = async () => {
117 | const { channelNameInputValue } = state;
118 | const [openChannel, error] = await createChannel(channelNameInputValue);
119 | if (error) {
120 | return onError(error);
121 | }
122 | const updatedChannels = [openChannel, ...state.channels];
123 | updateState({ ...state, channels: updatedChannels, showChannelCreate: false });
124 | }
125 |
126 | const handleDeleteChannel = async (channelUrl) => {
127 | const [channel, error] = await deleteChannel(channelUrl);
128 | if (error) {
129 | return onError(error);
130 | }
131 | const updatedChannels = state.channels.filter((channel) => {
132 | return channel.url !== channelUrl;
133 | });
134 | updateState({ ...state, channels: updatedChannels });
135 | }
136 |
137 | const handleUpdateChannel = async () => {
138 | const { currentlyUpdatingChannel, channelNameInputValue, channels } = state;
139 | const [updatedChannel, error] = await updateChannel(currentlyUpdatingChannel, channelNameInputValue);
140 | if (error) {
141 | return onError(error);
142 | }
143 | const indexToReplace = channels.findIndex((channel) => channel.url === currentlyUpdatingChannel.channelUrl);
144 | const updatedChannels = [...channels];
145 | updatedChannels[indexToReplace] = updatedChannel;
146 | updateState({ ...state, channels: updatedChannels, currentlyUpdatingChannel: null });
147 | }
148 |
149 | const toggleChannelDetails = (channel) => {
150 | if (channel) {
151 | updateState({ ...state, currentlyUpdatingChannel: channel });
152 | } else {
153 | updateState({ ...state, currentlyUpdatingChannel: null });
154 | }
155 | }
156 |
157 | const toggleShowCreateChannel = () => {
158 | updateState({ ...state, showChannelCreate: !state.showChannelCreate });
159 | }
160 |
161 | const onChannelNamenIputChange = (e) => {
162 | const channelNameInputValue = e.currentTarget.value;
163 | updateState({ ...state, channelNameInputValue });
164 | }
165 |
166 | const onUserNameInputChange = (e) => {
167 | const userNameInputValue = e.currentTarget.value;
168 | updateState({ ...state, userNameInputValue });
169 | }
170 |
171 | const onUserIdInputChange = (e) => {
172 | const userIdInputValue = e.currentTarget.value;
173 | updateState({ ...state, userIdInputValue });
174 | }
175 |
176 | const onMessageInputChange = (e) => {
177 | const messageInputValue = e.currentTarget.value;
178 | updateState({ ...state, messageInputValue });
179 | }
180 |
181 | const sendMessage = async () => {
182 | const { messageToUpdate, currentlyJoinedChannel, messages } = state;
183 |
184 | if (messageToUpdate) {
185 | const userMessageUpdateParams = {};
186 | userMessageUpdateParams.message = state.messageInputValue;
187 | const updatedMessage = await currentlyJoinedChannel.updateUserMessage(messageToUpdate.messageId, userMessageUpdateParams)
188 | const messageIndex = messages.findIndex((item => item.messageId == messageToUpdate.messageId));
189 | messages[messageIndex] = updatedMessage;
190 | updateState({ ...state, messages: messages, messageInputValue: "", messageToUpdate: null });
191 | } else {
192 | const userMessageParams = {};
193 | userMessageParams.message = state.messageInputValue;
194 | currentlyJoinedChannel.sendUserMessage(userMessageParams).onSucceeded((message) => {
195 | const updatedMessages = [...messages, message];
196 | updateState({ ...state, messages: updatedMessages, messageInputValue: "" });
197 | }).onFailed((error) => {
198 | console.log(error)
199 | console.log("failed")
200 | });
201 | }
202 | }
203 |
204 | const onFileInputChange = async (e) => {
205 | if (e.currentTarget.files && e.currentTarget.files.length > 0) {
206 | const { currentlyJoinedChannel, messages } = state;
207 | const fileMessageParams = {};
208 | fileMessageParams.file = e.currentTarget.files[0];
209 | fileMessageParams.thumbnailSizes = [{ maxWidth: 100, maxHeight: 100 }, { maxWidth: 200, maxHeight: 200 }];
210 | currentlyJoinedChannel.sendFileMessage(fileMessageParams).onSucceeded((message) => {
211 | const updatedMessages = [...messages, message];
212 | updateState({ ...state, messages: updatedMessages, messageInputValue: "", file: null });
213 | }).onFailed((error) => {
214 | console.log(error)
215 | console.log("failed")
216 | });
217 | }
218 | }
219 |
220 | const handleDeleteMessage = async (messageToDelete) => {
221 | const { currentlyJoinedChannel } = state;
222 | await deleteMessage(currentlyJoinedChannel, messageToDelete); // Delete
223 | }
224 |
225 | const updateMessage = async (message) => {
226 | updateState({ ...state, messageToUpdate: message, messageInputValue: message.message });
227 | }
228 |
229 | const setupUser = async () => {
230 | const { userNameInputValue, userIdInputValue } = state;
231 | const sendbirdChat = await SendbirdChat.init({
232 | appId: SENDBIRD_INFO.appId,
233 | localCacheEnabled: true,
234 | modules: [new OpenChannelModule()]
235 | });
236 |
237 | try {
238 | await sendbirdChat.connect(userIdInputValue);
239 | } catch (e) {
240 | console.log("error", e)
241 | }
242 | await sendbirdChat.setChannelInvitationPreference(true);
243 |
244 | const userUpdateParams = {};
245 | userUpdateParams.nickname = userNameInputValue;
246 | userUpdateParams.userId = userIdInputValue;
247 | await sendbirdChat.updateCurrentUserInfo(userUpdateParams);
248 |
249 | sb = sendbirdChat;
250 | updateState({ ...state, loading: true });
251 | const [channels, error] = await loadChannels();
252 | if (error) {
253 | return onError(error);
254 | }
255 | updateState({ ...state, channels: channels, loading: false, settingUpUser: false });
256 | }
257 |
258 | if (state.loading) {
259 | return Loading...
260 | }
261 |
262 | if (state.error) {
263 | return {state.error} check console for more information.
264 | }
265 |
266 | console.log('- - - - State object very useful for debugging - - - -');
267 | console.log(state);
268 |
269 | return (
270 | <>
271 |
278 |
284 |
289 |
294 |
299 |
303 |
310 |
311 | >
312 | );
313 | };
314 |
315 | // Chat UI Components
316 | const ChannelList = ({ channels, handleJoinChannel, toggleShowCreateChannel, handleDeleteChannel, toggleChannelDetails }) => {
317 | return (
318 |
319 |
320 |
Open Channels
321 |
322 |
323 | {channels.map(channel => {
324 | const userIsOperator = channel.operators.some((operator) => operator.userId === sb.currentUser.userId)
325 | return (
326 |
327 |
{ handleJoinChannel(channel.url) }}>
329 | {channel.name}
330 |
331 | {userIsOperator &&
332 |
333 |
336 |
339 |
340 | }
341 |
342 | );
343 | })}
344 |
345 | );
346 | }
347 |
348 | const Channel = ({ currentlyJoinedChannel, handleLeaveChannel, children, channelRef }) => {
349 | if (currentlyJoinedChannel) {
350 | return
351 |
{currentlyJoinedChannel.name}
352 |
353 |
354 |
355 |
{children}
356 |
;
357 | }
358 | return ;
359 | }
360 |
361 | const ChannelHeader = ({ children }) => {
362 | return {children}
;
363 | }
364 |
365 | const MessagesList = ({ messages, handleDeleteMessage, updateMessage }) => {
366 | return messages.map(message => {
367 | return (
368 |
369 |
374 |
375 | );
376 | })
377 | }
378 |
379 | const Message = ({ message, updateMessage, handleDeleteMessage }) => {
380 | if (!message.sender) return null; if (message.url) {
381 | return (
382 | );
383 | }
384 |
385 | const messageSentByCurrentUser = message.sender.userId === sb.currentUser.userId;
386 | return (
387 |
388 |
{timestampToTime(message.createdAt)}
389 |
{message.sender.nickname}{':'}
390 |
{message.message}
391 |
392 | {messageSentByCurrentUser && <>
393 |
396 |
399 | >}
400 |
401 | );
402 | }
403 |
404 | const MessageInput = ({ value, onChange, sendMessage, onFileInputChange }) => {
405 | return (
406 |
426 | );
427 | }
428 |
429 | const ChannelDetails = ({
430 | currentlyUpdatingChannel,
431 | toggleChannelDetails,
432 | handleUpdateChannel,
433 | onChannelNamenIputChange
434 | }) => {
435 | if (currentlyUpdatingChannel) {
436 | return
437 |
438 |
Update Channel Details
439 |
Channel name
440 |
441 |
442 |
443 |
444 |
;
445 | }
446 | return null;
447 | }
448 |
449 | const ChannelCreate = ({
450 | showChannelCreate,
451 | toggleShowCreateChannel,
452 | handleCreateChannel,
453 | onChannelNamenIputChange
454 | }) => {
455 | if (showChannelCreate) {
456 | return
457 |
458 |
459 |
Create Channel
460 |
461 |
Name
462 |
handleEnterPress(event, handleCreateChannel)} />
463 |
464 |
465 |
466 |
467 |
468 |
;
469 | }
470 | return null;
471 | }
472 |
473 | const CreateUserForm = ({
474 | setupUser,
475 | settingUpUser,
476 | userNameInputValue,
477 | userIdInputValue,
478 | onUserNameInputChange,
479 | onUserIdInputChange
480 | }) => {
481 | if (settingUpUser) {
482 | return
483 |
handleEnterPress(event, setupUser)}>
484 |
User ID
485 |
489 |
User Nickname
490 |
494 |
495 |
499 |
500 |
501 |
502 | } else {
503 | return null;
504 | }
505 | }
506 |
507 | const Thumbnails = ({ createdAt, sender, thumbnails, message }) => {
508 | const thumbnail = thumbnails[0];
509 |
510 | return (
511 |
512 |
{timestampToTime(createdAt)}
513 |
{sender.nickname}{' '}
514 | {thumbnail ?

:

}
515 |
516 | );
517 | };
518 |
519 |
520 | // Helpful functions that call Sendbird
521 | const loadChannels = async () => {
522 | try {
523 | const openChannelQuery = sb.openChannel.createOpenChannelListQuery({ limit: 30 });
524 | const channels = await openChannelQuery.next();
525 | return [channels, null];
526 | } catch (error) {
527 | return [null, error];
528 | }
529 | }
530 |
531 | const loadMessages = async (channel) => {
532 | try {
533 |
534 | //list all messages
535 | const messageListParams = {};
536 | messageListParams.nextResultSize = 20;
537 | const messages = await channel.getMessagesByTimestamp(0, messageListParams);
538 | return [messages, null];
539 | } catch (error) {
540 | return [null, error]
541 | }
542 | }
543 |
544 | const createChannel = async (channelName) => {
545 | try {
546 | const openChannelParams = {};
547 | openChannelParams.name = channelName;
548 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
549 | const openChannel = await sb.openChannel.createChannel(openChannelParams);
550 | return [openChannel, null];
551 | } catch (error) {
552 | return [null, error];
553 | }
554 | }
555 |
556 | const deleteChannel = async (channelUrl) => {
557 | try {
558 | const channel = await sb.openChannel.getChannel(channelUrl);
559 | await channel.delete();
560 | return [channel, null];
561 | } catch (error) {
562 | return [null, error];
563 | }
564 | }
565 |
566 | const updateChannel = async (currentlyUpdatingChannel, channelNameInputValue) => {
567 | try {
568 | const channel = await sb.openChannel.getChannel(currentlyUpdatingChannel.url);
569 | const openChannelParams = {};
570 | openChannelParams.name = channelNameInputValue;
571 | openChannelParams.operatorUserIds = [sb.currentUser.userId];
572 | const updatedChannel = await channel.updateChannel(openChannelParams);
573 | return [updatedChannel, null];
574 | } catch (error) {
575 | return [null, error];
576 | }
577 | }
578 |
579 | const deleteMessage = async (currentlyJoinedChannel, messageToDelete) => {
580 | await currentlyJoinedChannel.deleteMessage(messageToDelete);
581 | }
582 |
583 | export default OpenChannelThumbnails;
584 |
--------------------------------------------------------------------------------
/src/utils/messageUtils.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 |
3 | export const protectFromXSS = (text) => {
4 | return text
5 | .replace(/\&/g, '&')
6 | .replace(/\/g, '>')
8 | .replace(/\"/g, '"')
9 | .replace(/\'/g, ''');
10 | };
11 |
12 | export const timestampToTime = (timestamp) => {
13 | const now = new Date().getTime();
14 | const nowDate = moment.unix(now.toString().length === 13 ? now / 1000 : now).format('MM/DD');
15 | let date = moment.unix(timestamp.toString().length === 13 ? timestamp / 1000 : timestamp).format('MM/DD');
16 | if (date === 'Invalid date') {
17 | date = '';
18 | }
19 | return nowDate === date
20 | ? moment.unix(timestamp.toString().length === 13 ? timestamp / 1000 : timestamp).format('HH:mm')
21 | : date;
22 | };
23 |
24 | export const handleEnterPress = (event, callback) => {
25 | if (event.key === 'Enter') {
26 | callback()
27 | }
28 | }
--------------------------------------------------------------------------------