├── .eslintcache ├── .eslintrc.js ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── components ├── ChatFeed.jsx ├── LoginForm.jsx ├── MessageForm.jsx ├── MyMessage.jsx └── TheirMessage.jsx └── index.js /.eslintcache: -------------------------------------------------------------------------------- 1 | [{"C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\index.js":"1","C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\App.js":"2","C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\components\\ChatFeed.jsx":"3","C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\components\\MessageForm.jsx":"4","C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\components\\MyMessage.jsx":"5","C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\components\\TheirMessage.jsx":"6","C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\components\\LoginForm.jsx":"7"},{"size":147,"mtime":1610875361799,"results":"8","hashOfConfig":"9"},{"size":746,"mtime":1611421704910,"results":"10","hashOfConfig":"9"},{"size":2049,"mtime":1611404917028,"results":"11","hashOfConfig":"9"},{"size":1506,"mtime":1611421605786,"results":"12","hashOfConfig":"9"},{"size":509,"mtime":1611404919192,"results":"13","hashOfConfig":"9"},{"size":988,"mtime":1611416727413,"results":"14","hashOfConfig":"9"},{"size":1540,"mtime":1611421710887,"results":"15","hashOfConfig":"9"},{"filePath":"16","messages":"17","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"18"},"7np4es",{"filePath":"19","messages":"20","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"18"},{"filePath":"21","messages":"22","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"18"},{"filePath":"23","messages":"24","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"18"},{"filePath":"25","messages":"26","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"18"},{"filePath":"27","messages":"28","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"29","messages":"30","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"18"},"C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\index.js",[],["31","32","33","34","35","36"],"C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\App.js",[],"C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\components\\ChatFeed.jsx",[],"C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\components\\MessageForm.jsx",[],"C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\components\\MyMessage.jsx",[],"C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\components\\TheirMessage.jsx",[],"C:\\Users\\Adrian\\Desktop\\chat_app_project\\src\\components\\LoginForm.jsx",[],{"ruleId":"37","replacedBy":"38"},{"ruleId":"39","replacedBy":"40"},{"ruleId":"41","replacedBy":"42"},{"ruleId":"43","replacedBy":"44"},{"ruleId":"45","replacedBy":"46"},{"ruleId":"47","replacedBy":"48"},"lines-around-directive",["49"],"no-spaced-func",["50"],"global-require",[],"no-buffer-constructor",[],"no-new-require",[],"no-path-concat",[],"padding-line-between-statements","func-call-spacing"] -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | }, 6 | extends: [ 7 | 'plugin:react/recommended', 8 | 'airbnb', 9 | ], 10 | globals: { 11 | Atomics: 'readonly', 12 | SharedArrayBuffer: 'readonly', 13 | }, 14 | parserOptions: { 15 | ecmaFeatures: { 16 | jsx: true, 17 | }, 18 | ecmaVersion: 2018, 19 | sourceType: 'module', 20 | }, 21 | parser: 'babel-eslint', 22 | plugins: [ 23 | 'react', 24 | ], 25 | rules: { 26 | 'react/react-in-jsx-scope': 'off', 27 | 'import/extensions': 0, 28 | 'react/prop-types': 0, 29 | 'linebreak-style': 0, 30 | 'react/state-in-constructor': 0, 31 | 'jsx-a11y/label-has-associated-control': 'off', 32 | 'import/prefer-default-export': 0, 33 | 'max-len': [ 34 | 2, 35 | 250, 36 | ], 37 | 'no-multiple-empty-lines': [ 38 | 'error', 39 | { 40 | max: 1, 41 | maxEOF: 1, 42 | }, 43 | ], 44 | 'no-underscore-dangle': [ 45 | 'error', 46 | { 47 | allow: [ 48 | '_d', 49 | '_dh', 50 | '_h', 51 | '_id', 52 | '_m', 53 | '_n', 54 | '_t', 55 | '_text', 56 | ], 57 | }, 58 | ], 59 | 'react/jsx-props-no-spreading': 'off', 60 | 'object-curly-newline': 0, 61 | 'react/jsx-filename-extension': 0, 62 | 'react/jsx-one-expression-per-line': 0, 63 | 'jsx-a11y/click-events-have-key-events': 0, 64 | 'jsx-a11y/alt-text': 0, 65 | 'jsx-a11y/no-autofocus': 0, 66 | 'jsx-a11y/no-static-element-interactions': 0, 67 | 'react/no-array-index-key': 0, 68 | 'jsx-a11y/anchor-is-valid': [ 69 | 'error', 70 | { 71 | components: [ 72 | 'Link', 73 | ], 74 | specialLink: [ 75 | 'to', 76 | 'hrefLeft', 77 | 'hrefRight', 78 | ], 79 | aspects: [ 80 | 'noHref', 81 | 'invalidHref', 82 | 'preferButton', 83 | ], 84 | }, 85 | ], 86 | }, 87 | }; 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Realtime Chat Application 2 | 3 | ![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https://github.com/Saiprasad16/ReactChatApplication-counter&count_bg=%23BA1BD4&title_bg=%23555555&icon=&icon_color=blueviolet&title=Hits&edge_flat=false&style=plastic) 4 | 5 | ![Chat Application](https://i.ibb.co/vDhx8Md/Whats-App-Image-2021-01-26-at-02-01-43.jpg) 6 | 7 | 8 | 9 | ## Introduction 10 | We will going to create a full Realtime Chat Application. 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat_app_project", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@ant-design/icons": "^4.3.0", 7 | "@testing-library/jest-dom": "^5.11.9", 8 | "@testing-library/react": "^11.2.3", 9 | "@testing-library/user-event": "^12.6.0", 10 | "axios": "^0.21.1", 11 | "react": "^17.0.1", 12 | "react-chat-engine": "^1.5.7", 13 | "react-dom": "^17.0.1", 14 | "react-scripts": "4.0.1", 15 | "web-vitals": "^0.2.4" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "postinstall": "patch-package", 22 | "eject": "react-scripts eject" 23 | }, 24 | "eslintConfig": { 25 | "extends": [ 26 | "react-app", 27 | "react-app/jest" 28 | ] 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | }, 42 | "devDependencies": { 43 | "eslint": "^7.17.0", 44 | "eslint-config-airbnb": "^18.2.1", 45 | "eslint-plugin-import": "^2.22.1", 46 | "eslint-plugin-jsx-a11y": "^6.4.1", 47 | "eslint-plugin-react": "^7.22.0", 48 | "eslint-plugin-react-hooks": "^4.2.0", 49 | "patch-package": "^6.2.2", 50 | "postinstall-postinstall": "^2.1.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saiprasad16/ReactChatApplication/4b60e5a06ad439ed2ab1eeb783071edf29782856/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Chat Application 12 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saiprasad16/ReactChatApplication/4b60e5a06ad439ed2ab1eeb783071edf29782856/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saiprasad16/ReactChatApplication/4b60e5a06ad439ed2ab1eeb783071edf29782856/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/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | /* CHAT STYLES */ 2 | * { 3 | font-family: Avenir, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, 4 | Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, 5 | Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; 6 | letter-spacing: 0.5px; 7 | } 8 | 9 | .ce-chat-list { 10 | background-color: rgb(240, 240, 240) !important; 11 | } 12 | 13 | .ce-chats-container { 14 | background-color: rgb(240, 240, 240) !important; 15 | } 16 | 17 | .ce-active-chat-card { 18 | background-color: #cabcdc !important; 19 | border: 4px solid #cabcdc !important; 20 | border-radius: 0px !important; 21 | } 22 | 23 | .ce-chat-subtitle-text { 24 | color: #595959 !important; 25 | } 26 | 27 | .ce-chat-form-container { 28 | padding-bottom: 20px !important; 29 | } 30 | 31 | .ce-text-input { 32 | border-radius: 6px !important; 33 | border: 1px solid #3b2a50 !important; 34 | } 35 | 36 | .ce-primary-button { 37 | border-radius: 6px !important; 38 | background-color: #7554a0 !important; 39 | position: relative; 40 | bottom: 1px; 41 | } 42 | 43 | .ce-danger-button { 44 | background-color: white !important; 45 | border-radius: 22px !important; 46 | } 47 | 48 | .ce-settings { 49 | background-color: rgb(240, 240, 240) !important; 50 | } 51 | 52 | .ce-autocomplete-input { 53 | border-radius: 6px !important; 54 | border: 1px solid #3b2a50 !important; 55 | } 56 | 57 | .ce-autocomplete-options { 58 | border-radius: 6px !important; 59 | border: 1px solid #3b2a50 !important; 60 | background-color: white !important; 61 | } 62 | 63 | .ce-chat-settings-container { 64 | padding-top: 12px !important; 65 | } 66 | 67 | .ce-chat-avatars-row { 68 | display: none !important; 69 | } 70 | 71 | /* CUSTOM FEED */ 72 | 73 | .chat-feed { 74 | height: 100%; 75 | width: 100%; 76 | overflow: scroll; 77 | background-color: rgb(240, 240, 240); 78 | } 79 | 80 | .chat-title-container { 81 | width: calc(100% - 36px); 82 | padding: 18px; 83 | text-align: center; 84 | } 85 | 86 | .chat-title { 87 | color: #7554a0; 88 | font-weight: 800; 89 | font-size: 24px; 90 | } 91 | 92 | .chat-subtitle { 93 | color: #7554a0; 94 | font-weight: 600; 95 | font-size: 12px; 96 | padding-top: 4px; 97 | } 98 | 99 | .message-row { 100 | float: left; 101 | width: 100%; 102 | display: flex; 103 | margin-left: 18px; 104 | } 105 | 106 | .message-block { 107 | width: 100%; 108 | display: inline-block; 109 | } 110 | 111 | .message-avatar { 112 | width: 44px; 113 | height: 44px; 114 | border-radius: 22px; 115 | color: white; 116 | text-align: center; 117 | background-repeat: no-repeat; 118 | background-position: center; 119 | background-size: 48px; 120 | } 121 | 122 | .message { 123 | padding: 12px; 124 | font-size: 16px; 125 | border-radius: 6px; 126 | max-width: 60%; 127 | } 128 | 129 | .message-image { 130 | margin-right: 18px; 131 | object-fit: cover; 132 | border-radius: 6px; 133 | height: 30vw; 134 | /* width: 30vw; */ 135 | max-height: 200px; 136 | max-width: 200px; 137 | min-height: 100px; 138 | min-width: 100px; 139 | } 140 | 141 | .read-receipts { 142 | position: relative; 143 | bottom: 6px; 144 | } 145 | 146 | .read-receipt { 147 | width: 13px; 148 | height: 13px; 149 | border-radius: 13px; 150 | margin: 1.5px; 151 | background-repeat: no-repeat; 152 | background-position: center; 153 | background-size: 14px; 154 | } 155 | 156 | .message-form-container { 157 | position: absolute; 158 | bottom: 0px; 159 | width: calc(100% - 36px); 160 | padding: 18px; 161 | background-color: rgb(240, 240, 240); 162 | } 163 | 164 | .message-form { 165 | overflow: hidden; 166 | border-radius: 6px; 167 | border: 1px solid #3b2a50; 168 | background-color: white; 169 | } 170 | 171 | .message-input { 172 | height: 40px; 173 | width: calc(100% - 132px); 174 | background-color: white; 175 | border: 1px solid white; 176 | padding: 0px 18px; 177 | outline: none; 178 | font-size: 15px; 179 | } 180 | 181 | .image-button { 182 | cursor: pointer; 183 | padding: 0px 12px; 184 | height: 100%; 185 | } 186 | 187 | .send-button { 188 | height: 42px; 189 | background-color: white; 190 | border: 1px solid white; 191 | padding: 0px 18px; 192 | cursor: pointer; 193 | } 194 | 195 | .send-icon { 196 | top: 1px; 197 | position: relative; 198 | transform: rotate(-90deg); 199 | } 200 | 201 | .picture-icon { 202 | top: 1px; 203 | position: relative; 204 | font-size: 14px; 205 | } 206 | 207 | /* FORM STYLES */ 208 | *, 209 | *::after, 210 | *::before { 211 | margin: 0; 212 | padding: 0; 213 | box-sizing: border-box; 214 | font-size: 62,5%; 215 | } 216 | 217 | .wrapper { 218 | height: 100vh; 219 | width: 100%; 220 | background: rgb(117,84,160); 221 | background: linear-gradient(90deg, rgba(117,84,160,1) 7%, rgba(117,84,160,1) 17%, rgba(106,95,168,1) 29%, rgba(99,103,174,1) 44%, rgba(87,116,184,1) 66%, rgba(70,135,198,1) 83%, rgba(44,163,219,1) 96%, rgba(22,188,237,1) 100%, rgba(0,212,255,1) 100%); 222 | display: flex; 223 | justify-content: center; 224 | align-items: center; 225 | } 226 | 227 | .input { 228 | color: #333; 229 | font-size: 1.2rem; 230 | margin: 0 auto; 231 | padding: 1.5rem 2rem; 232 | border-radius: 0.2rem; 233 | background-color: rgb(255, 255, 255); 234 | border: none; 235 | width: 90%; 236 | display: block; 237 | border-bottom: 0.3rem solid transparent; 238 | transition: all 0.3s; 239 | outline: none; 240 | margin-bottom: 25px; 241 | } 242 | 243 | .form { 244 | width: 400px; 245 | } 246 | 247 | .title { 248 | text-align: center; 249 | color: white; 250 | margin-bottom: 30px; 251 | width: 100%; 252 | font-size: 2.3em;; 253 | } 254 | 255 | .button { 256 | border-radius: 4px; 257 | border: none; 258 | background-color: white; 259 | color: black; 260 | text-align: center; 261 | text-transform: uppercase; 262 | font-size: 22px; 263 | padding: 20px; 264 | width: 200px; 265 | transition: all 0.4s; 266 | cursor: pointer; 267 | margin: 5px; 268 | width: 90%; 269 | } 270 | .button span { 271 | cursor: pointer; 272 | display: inline-block; 273 | position: relative; 274 | transition: 0.4s; 275 | } 276 | .button span:after { 277 | content: '\00bb'; 278 | position: absolute; 279 | opacity: 0; 280 | top: 0; 281 | right: -20px; 282 | transition: 0.5s; 283 | } 284 | .button:hover span { 285 | padding-right: 25px; 286 | } 287 | .button:hover span:after { 288 | opacity: 1; 289 | right: 0; 290 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { ChatEngine } from 'react-chat-engine'; 2 | 3 | import ChatFeed from './components/ChatFeed'; 4 | import LoginForm from './components/LoginForm'; 5 | import './App.css'; 6 | 7 | const projectID = '1b7801d6-8a66-4be4-a442-89219d833dfc'; 8 | 9 | const App = () => { 10 | if (!localStorage.getItem('username')) return ; 11 | 12 | return ( 13 | } 19 | onNewMessage={() => new Audio('https://chat-engine-assets.s3.amazonaws.com/click.mp3').play()} 20 | /> 21 | ); 22 | }; 23 | 24 | // infinite scroll, logout, more customizations... 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /src/components/ChatFeed.jsx: -------------------------------------------------------------------------------- 1 | import MyMessage from './MyMessage'; 2 | import TheirMessage from './TheirMessage'; 3 | import MessageForm from './MessageForm'; 4 | 5 | const ChatFeed = (props) => { 6 | const { chats, activeChat, userName, messages } = props; 7 | 8 | const chat = chats && chats[activeChat]; 9 | 10 | const renderReadReceipts = (message, isMyMessage) => chat.people.map((person, index) => person.last_read === message.id && ( 11 |
19 | )); 20 | 21 | const renderMessages = () => { 22 | const keys = Object.keys(messages); 23 | 24 | return keys.map((key, index) => { 25 | const message = messages[key]; 26 | const lastMessageKey = index === 0 ? null : keys[index - 1]; 27 | const isMyMessage = userName === message.sender.username; 28 | 29 | return ( 30 |
31 |
32 | {isMyMessage 33 | ? 34 | : } 35 |
36 |
37 | {renderReadReceipts(message, isMyMessage)} 38 |
39 |
40 | ); 41 | }); 42 | }; 43 | 44 | if (!chat) return
; 45 | 46 | return ( 47 |
48 |
49 |
{chat?.title}
50 |
51 | {chat.people.map((person) => ` ${person.person.username}`)} 52 |
53 |
54 | {renderMessages()} 55 |
56 |
57 | 58 |
59 |
60 | ); 61 | }; 62 | 63 | export default ChatFeed; 64 | 65 | -------------------------------------------------------------------------------- /src/components/LoginForm.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import axios from 'axios'; 3 | 4 | const projectID = '1b7801d6-8a66-4be4-a442-89219d833dfc'; 5 | 6 | const Modal = () => { 7 | const [username, setUsername] = useState(''); 8 | const [password, setPassword] = useState(''); 9 | const [error, setError] = useState(''); 10 | 11 | const handleSubmit = async (e) => { 12 | e.preventDefault(); 13 | 14 | const authObject = { 'Project-ID': projectID, 'User-Name': username, 'User-Secret': password }; 15 | 16 | try { 17 | await axios.get('https://api.chatengine.io/chats', { headers: authObject }); 18 | 19 | localStorage.setItem('username', username); 20 | localStorage.setItem('password', password); 21 | 22 | window.location.reload(); 23 | setError(''); 24 | } catch (err) { 25 | setError('Oops, incorrect credentials.'); 26 | } 27 | }; 28 | 29 | return ( 30 |
31 |
32 |

Chat Application

33 |
34 | setUsername(e.target.value)} className="input" placeholder="Username" required /> 35 | setPassword(e.target.value)} className="input" placeholder="Password" required /> 36 |
37 | 40 |
41 |
42 |

{error}

43 |
44 |
45 | 46 | ); 47 | }; 48 | 49 | export default Modal; 50 | -------------------------------------------------------------------------------- /src/components/MessageForm.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { SendOutlined, PictureOutlined } from '@ant-design/icons'; 3 | import { sendMessage, isTyping } from 'react-chat-engine'; 4 | 5 | const MessageForm = (props) => { 6 | const [value, setValue] = useState(''); 7 | const { chatId, creds } = props; 8 | 9 | const handleChange = (event) => { 10 | setValue(event.target.value); 11 | 12 | isTyping(props, chatId); 13 | }; 14 | 15 | const handleSubmit = (event) => { 16 | event.preventDefault(); 17 | 18 | const text = value.trim(); 19 | 20 | if (text.length > 0) { 21 | sendMessage(creds, chatId, { text }); 22 | } 23 | 24 | setValue(''); 25 | }; 26 | 27 | const handleUpload = (event) => { 28 | sendMessage(creds, chatId, { files: event.target.files, text: '' }); 29 | }; 30 | 31 | return ( 32 |
33 | 40 | 45 | 52 | 55 |
56 | ); 57 | }; 58 | 59 | export default MessageForm; 60 | -------------------------------------------------------------------------------- /src/components/MyMessage.jsx: -------------------------------------------------------------------------------- 1 | const MyMessage = ({ message }) => { 2 | if (message.attachments && message.attachments.length > 0) { 3 | return ( 4 | message-attachment 10 | ); 11 | } 12 | 13 | return ( 14 |
15 | {message.text} 16 |
17 | ); 18 | }; 19 | 20 | export default MyMessage; 21 | -------------------------------------------------------------------------------- /src/components/TheirMessage.jsx: -------------------------------------------------------------------------------- 1 | const TheirMessage = ({ lastMessage, message }) => { 2 | const isFirstMessageByUser = !lastMessage || lastMessage.sender.username !== message.sender.username; 3 | 4 | return ( 5 |
6 | {isFirstMessageByUser && ( 7 |
11 | )} 12 | {message.attachments && message.attachments.length > 0 13 | ? ( 14 | message-attachment 20 | ) 21 | : ( 22 |
23 | {message.text} 24 |
25 | )} 26 |
27 | ); 28 | }; 29 | 30 | export default TheirMessage; 31 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | --------------------------------------------------------------------------------