├── .eslintcache ├── .gitignore ├── README.md ├── chat.png ├── createRoom.png ├── debug.log ├── joinRoom.png ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── register.png ├── src ├── assets │ ├── audio │ │ └── message.mp3 │ └── images │ │ ├── chat_bg.jpg │ │ └── chat_bg_v2.jpg ├── components │ ├── App │ │ ├── index.js │ │ ├── style.css │ │ └── test.js │ ├── Chat │ │ ├── index.tsx │ │ └── style.css │ ├── ChatFooter │ │ ├── index.tsx │ │ └── style.css │ ├── ChatHeader │ │ ├── index.tsx │ │ └── style.css │ ├── ConfirmationDialog │ │ ├── index.tsx │ │ └── style.css │ ├── GeneralSnackbar │ │ ├── index.tsx │ │ └── style.css │ ├── Login │ │ ├── index.tsx │ │ └── style.css │ ├── NewRoom │ │ ├── index.tsx │ │ └── style.css │ ├── NotFound │ │ ├── index.tsx │ │ └── style.css │ ├── Room │ │ ├── index.tsx │ │ └── style.css │ ├── RoomDetails │ │ ├── index.tsx │ │ └── style.css │ ├── Sidebar │ │ ├── index.tsx │ │ └── style.css │ ├── SidebarHeader │ │ ├── index.tsx │ │ └── style.css │ ├── SidebarRooms │ │ ├── index.tsx │ │ └── style.css │ └── Signup │ │ ├── index.tsx │ │ └── style.css ├── constants.ts ├── context │ ├── ChatContext.ts │ └── UserContext.tsx ├── index.css ├── index.js ├── react-app-env.d.ts ├── reportWebVitals.js ├── services │ ├── Axios.ts │ ├── Http.ts │ └── SocketService.ts ├── setupTests.js └── types.ts └── tsconfig.json /.eslintcache: -------------------------------------------------------------------------------- 1 | [{"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\index.js":"1","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\reportWebVitals.js":"2","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\constants.ts":"3","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\App\\index.js":"4","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\Chat\\index.tsx":"5","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\Sidebar\\index.tsx":"6","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\Room\\index.tsx":"7","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\services\\SocketService.ts":"8","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\services\\Axios.ts":"9","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\Signup\\index.tsx":"10","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\Login\\index.tsx":"11","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\NewRoom\\index.tsx":"12","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\SidebarRooms\\index.tsx":"13","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\RoomDetails\\index.tsx":"14","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\NotFound\\index.tsx":"15","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\services\\Http.ts":"16","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\ConfirmationDialog\\index.tsx":"17","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\GeneralSnackbar\\index.tsx":"18","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\SidebarHeader\\index.tsx":"19","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\ChatHeader\\index.tsx":"20","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\ChatFooter\\index.tsx":"21","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\context\\UserContext.tsx":"22","C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\context\\ChatContext.ts":"23"},{"size":460,"mtime":1612342568435,"results":"24","hashOfConfig":"25"},{"size":362,"mtime":499162500000,"results":"26","hashOfConfig":"25"},{"size":429,"mtime":1612774205891,"results":"27","hashOfConfig":"25"},{"size":1448,"mtime":1612522299442,"results":"28","hashOfConfig":"25"},{"size":3180,"mtime":1612775220262,"results":"29","hashOfConfig":"25"},{"size":1300,"mtime":1612857227792,"results":"30","hashOfConfig":"25"},{"size":6471,"mtime":1612857227312,"results":"31","hashOfConfig":"25"},{"size":1522,"mtime":1612855515246,"results":"32","hashOfConfig":"25"},{"size":425,"mtime":1612855517314,"results":"33","hashOfConfig":"25"},{"size":3630,"mtime":1612455228339,"results":"34","hashOfConfig":"25"},{"size":2661,"mtime":1612518288575,"results":"35","hashOfConfig":"25"},{"size":2656,"mtime":1612514519778,"results":"36","hashOfConfig":"25"},{"size":1205,"mtime":1612857226673,"results":"37","hashOfConfig":"25"},{"size":3345,"mtime":1612780898853,"results":"38","hashOfConfig":"25"},{"size":192,"mtime":1612357024513,"results":"39","hashOfConfig":"25"},{"size":3130,"mtime":1612358494431,"results":"40","hashOfConfig":"25"},{"size":1057,"mtime":1612356813881,"results":"41","hashOfConfig":"25"},{"size":978,"mtime":1612519403824,"results":"42","hashOfConfig":"25"},{"size":2172,"mtime":1612514609310,"results":"43","hashOfConfig":"25"},{"size":934,"mtime":1612505661063,"results":"44","hashOfConfig":"25"},{"size":1287,"mtime":1612514653602,"results":"45","hashOfConfig":"25"},{"size":430,"mtime":1612518354628,"results":"46","hashOfConfig":"25"},{"size":268,"mtime":1612520927894,"results":"47","hashOfConfig":"25"},{"filePath":"48","messages":"49","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"50"},"ijghcb",{"filePath":"51","messages":"52","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"50"},{"filePath":"53","messages":"54","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"56","messages":"57","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"50"},{"filePath":"58","messages":"59","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"60","messages":"61","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"62","messages":"63","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"64","messages":"65","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"66","messages":"67","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"68","messages":"69","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"70","messages":"71","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"72","messages":"73","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"74","messages":"75","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"76","messages":"77","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"78","messages":"79","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"80","messages":"81","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"82","messages":"83","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"84","messages":"85","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"86","messages":"87","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"88","messages":"89","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"90","messages":"91","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"92","messages":"93","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},{"filePath":"94","messages":"95","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"55"},"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\index.js",[],["96","97"],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\reportWebVitals.js",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\constants.ts",[],["98","99"],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\App\\index.js",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\Chat\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\Sidebar\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\Room\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\services\\SocketService.ts",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\services\\Axios.ts",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\Signup\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\Login\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\NewRoom\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\SidebarRooms\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\RoomDetails\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\NotFound\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\services\\Http.ts",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\ConfirmationDialog\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\GeneralSnackbar\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\SidebarHeader\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\ChatHeader\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\components\\ChatFooter\\index.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\context\\UserContext.tsx",[],"C:\\Users\\roseb\\Documents\\Personal\\chat-frontend\\src\\context\\ChatContext.ts",[],{"ruleId":"100","replacedBy":"101"},{"ruleId":"102","replacedBy":"103"},{"ruleId":"100","replacedBy":"101"},{"ruleId":"102","replacedBy":"103"},"no-native-reassign",["104"],"no-negated-in-lhs",["105"],"no-global-assign","no-unsafe-negation"] -------------------------------------------------------------------------------- /.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.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .env 21 | .vscode 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Netlify Status][netlify-shield]][netlify-url] 2 | 3 |
4 |

5 |

Realtime Chat App

6 | Product Screenshot 7 |

8 | A web-based messaging application that delivers messages instantaneously. 9 |

10 | View Live Demo » 11 |

12 | View Video Demo » 13 |

14 | Report Bug 15 | · 16 | Request Feature 17 |

18 |

19 | 20 |

Table of Contents

21 |
    22 |
  1. 23 | About The Project 24 | 27 |
  2. 28 |
  3. 29 | Getting Started 30 | 34 |
  4. 35 |
  5. Usage
  6. 36 |
  7. Implementation Pipeline
  8. 37 |
  9. Contact
  10. 38 |
39 | 40 | ## About The Project 41 | 42 | ### Built With 43 | 44 | - **[React](https://reactjs.org/)** 45 | - **[Socket.io](https://socket.io/)** 46 | - **[Typescript](https://www.typescriptlang.org/)** 47 | - [Node.js](https://nodejs.org/en/) 48 | - [MongoDB](https://www.mongodb.com/) 49 | 50 | ## Getting Started 51 | 52 | To get a local copy up and running follow these simple steps. 53 | 54 | ### Prerequisites 55 | 56 | Install latest version of npm 57 | 58 | - npm 59 | ```sh 60 | npm install npm@latest -g 61 | ``` 62 | 63 | ### Installation 64 | 65 | 1. Clone the project 66 | ```sh 67 | git clone https://github.com/crookedfingerworks/chat-frontend.git 68 | ``` 69 | 2. Go to project directory and Install NPM packages 70 | ```sh 71 | npm install 72 | ``` 73 | 3. Create .env file with the ff. content 74 | ```sh 75 | REACT_APP_SERVER_URL=https://rose-chat-backend.herokuapp.com 76 | ``` 77 | 4. Start the application 78 | ```sh 79 | npm start 80 | ``` 81 | 82 | ## Usage 83 | 84 | **Creating an Account** 85 | 86 | ![](register.png) 87 | 88 | 1. In the login page, click 'Register here'. 89 | 2. Input the necessary fields. Don't worry. It won't take long. 90 | 3. You'll be redirected to the login page. Enter your newly created credentials. 91 | 92 | **Creating a Room** 93 | 94 | ![](createRoom.png) 95 | 96 | 1. Click the message icon on the sidebar header. 97 | 2. Input the necessary fields and proceed. 98 | 3. Share the randomly-generated room code with people you want to invite in the room. 99 | 100 | **Joining a Room** 101 | 102 | ![](joinRoom.png) 103 | 104 | 1. Obtain the room code from the room creator. 105 | 2. Click the message icon on the sidebar header. 106 | 3. Click 'Join Room' tab option. 107 | 4. Input room code and proceed. 108 | 109 | ## Implementation Pipeline 110 | 111 |
    112 |
  1. Upload Group Image
  2. 113 |
  3. "User is typing" indicator
  4. 114 |
  5. Emoticons
  6. 115 |
116 | 117 | ## Contact 118 | 119 | crooked.finger.works@gmail.com 120 | 121 | Project Link: [https://github.com/crookedfingerworks/chat-frontend](https://github.com/crookedfingerworks/chat-frontend) 122 | 123 | [netlify-shield]: https://img.shields.io/netlify/24e36167-88a7-4e1e-93f5-0986aa1c1b7d?style=for-the-badge 124 | [netlify-url]: https://app.netlify.com/sites/rose-chat-client/deploys 125 | -------------------------------------------------------------------------------- /chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbilag/chat-frontend/06496016f292e3bab4e31b2f1cc1f09dd9fb9c2e/chat.png -------------------------------------------------------------------------------- /createRoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbilag/chat-frontend/06496016f292e3bab4e31b2f1cc1f09dd9fb9c2e/createRoom.png -------------------------------------------------------------------------------- /debug.log: -------------------------------------------------------------------------------- 1 | [0103/152954.377:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) 2 | [0105/160917.925:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) 3 | -------------------------------------------------------------------------------- /joinRoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbilag/chat-frontend/06496016f292e3bab4e31b2f1cc1f09dd9fb9c2e/joinRoom.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.3", 7 | "@material-ui/icons": "^4.11.2", 8 | "@testing-library/jest-dom": "^5.11.9", 9 | "@testing-library/react": "^11.2.5", 10 | "@testing-library/user-event": "^12.6.3", 11 | "@types/react-router-dom": "^5.1.7", 12 | "@types/socket.io-client": "^1.4.35", 13 | "@types/styled-components": "^5.1.7", 14 | "axios": "^0.21.1", 15 | "date-fns": "^2.17.0", 16 | "react": "^17.0.1", 17 | "react-debounce-input": "^3.2.3", 18 | "react-dom": "^17.0.1", 19 | "react-router-dom": "^5.2.0", 20 | "react-scripts": "4.0.1", 21 | "react-scrollbars-custom": "^4.0.25", 22 | "rxjs": "^6.6.3", 23 | "socket.io-client": "3.1.1", 24 | "styled-components": "^5.2.1", 25 | "typescript": "^4.1.3", 26 | "web-vitals": "^0.2.4" 27 | }, 28 | "scripts": { 29 | "start": "react-scripts start", 30 | "build": "react-scripts build", 31 | "test": "react-scripts test", 32 | "eject": "react-scripts eject" 33 | }, 34 | "eslintConfig": { 35 | "extends": [ 36 | "react-app", 37 | "react-app/jest" 38 | ] 39 | }, 40 | "browserslist": { 41 | "production": [ 42 | ">0.2%", 43 | "not dead", 44 | "not op_mini all" 45 | ], 46 | "development": [ 47 | "last 1 chrome version", 48 | "last 1 firefox version", 49 | "last 1 safari version" 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbilag/chat-frontend/06496016f292e3bab4e31b2f1cc1f09dd9fb9c2e/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | 28 | 29 | 30 | React App 31 | 32 | 33 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbilag/chat-frontend/06496016f292e3bab4e31b2f1cc1f09dd9fb9c2e/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbilag/chat-frontend/06496016f292e3bab4e31b2f1cc1f09dd9fb9c2e/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 | -------------------------------------------------------------------------------- /register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbilag/chat-frontend/06496016f292e3bab4e31b2f1cc1f09dd9fb9c2e/register.png -------------------------------------------------------------------------------- /src/assets/audio/message.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbilag/chat-frontend/06496016f292e3bab4e31b2f1cc1f09dd9fb9c2e/src/assets/audio/message.mp3 -------------------------------------------------------------------------------- /src/assets/images/chat_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbilag/chat-frontend/06496016f292e3bab4e31b2f1cc1f09dd9fb9c2e/src/assets/images/chat_bg.jpg -------------------------------------------------------------------------------- /src/assets/images/chat_bg_v2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbilag/chat-frontend/06496016f292e3bab4e31b2f1cc1f09dd9fb9c2e/src/assets/images/chat_bg_v2.jpg -------------------------------------------------------------------------------- /src/components/App/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; 3 | import './style.css'; 4 | import Room from '../Room'; 5 | import SignUp from '../Signup'; 6 | import Login from '../Login'; 7 | import NotFound from '../NotFound'; 8 | import { SocketService } from '../../services/SocketService'; 9 | import { USER_INITIAL_VALUE } from '../../constants'; 10 | import { UserContext } from '../../context/UserContext'; 11 | import { ChatContext } from '../../context/ChatContext'; 12 | import { StylesProvider } from '@material-ui/core/styles'; 13 | 14 | const routes = [ 15 | { path: '/signup', component: SignUp }, 16 | { path: '/login', component: Login }, 17 | { path: '/room', component: Room }, 18 | { path: '/', component: Login } 19 | ]; 20 | 21 | const chat = new SocketService(); 22 | 23 | function App() { 24 | const userJSON = localStorage.getItem('chat-app-user'); 25 | const [ userDetails, setUserDetails ] = useState(userJSON !== null ? JSON.parse(userJSON) : USER_INITIAL_VALUE); 26 | 27 | return ( 28 | 29 | 30 | 31 |
32 | 33 | 34 | {routes.map(({ path, component }) => )} 35 | 36 | 37 | 38 |
39 |
40 |
41 |
42 | ); 43 | } 44 | 45 | export default App; 46 | -------------------------------------------------------------------------------- /src/components/App/style.css: -------------------------------------------------------------------------------- 1 | .app { 2 | height: 100vh; 3 | } 4 | .MuiSvgIcon-root { 5 | font-size: 1.25rem; 6 | } 7 | .MuiAvatar-root.avatar--large { 8 | width: 6.5rem; 9 | height: 6.5rem; 10 | } 11 | .MuiAvatar-colorDefault { 12 | color: #fff; 13 | background-color: #b3b3b3; 14 | } 15 | .MuiButton-contained { 16 | background-color: #023047; 17 | border-radius: 0.75rem; 18 | font-size: 0.8rem; 19 | letter-spacing: 0.15rem; 20 | margin: 1.75rem auto 0; 21 | padding: 0.75rem 5rem; 22 | } 23 | .MuiButton-contained:hover { 24 | background-color: #002538; 25 | } 26 | .MuiButton-contained.secondary { 27 | background-color: #219ebc; 28 | } 29 | .MuiButton-contained.secondary:hover { 30 | background-color: #1e8fac; 31 | } 32 | input, 33 | textarea { 34 | border-radius: 0.75rem; 35 | padding: 0.8rem; 36 | border: 1px solid #dadada; 37 | outline: none; 38 | } 39 | .auth__wrapper { 40 | height: 100vh; 41 | display: flex; 42 | place-items: center; 43 | justify-content: center; 44 | background-color: #023047; 45 | } 46 | .auth__wrapper .area__wrapper { 47 | background-color: #f1faee; 48 | text-align: center; 49 | padding: 5rem 3rem; 50 | width: 25rem; 51 | min-height: 15rem; 52 | border-radius: 0.75rem; 53 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); 54 | } 55 | .auth__wrapper .area__wrapper > form { 56 | display: flex; 57 | flex-flow: row wrap; 58 | } 59 | .error__msg { 60 | margin: 0 1rem; 61 | font-size: 0.75rem; 62 | color: #a90b0b; 63 | } 64 | 65 | .MuiPaper-root { 66 | border-radius: 0.75rem; 67 | } 68 | .MuiDialogTitle-root, 69 | .MuiDialogContent-root { 70 | background-color: #f1faee; 71 | } 72 | .MuiButton-textPrimary { 73 | color: #002538; 74 | } 75 | .MuiSnackbarContent-root { 76 | background-color: #219ebc; 77 | } 78 | -------------------------------------------------------------------------------- /src/components/App/test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/Chat/index.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar } from '@material-ui/core'; 2 | import React, { useState, useEffect, useCallback } from 'react'; 3 | import Scrollbar from 'react-scrollbars-custom'; 4 | import './style.css'; 5 | import { MessagePopulated } from '../../types'; 6 | import chatHttp from '../../services/Http'; 7 | import { parseISO, differenceInCalendarDays, format, formatDistanceToNow } from 'date-fns'; 8 | import PersonIcon from '@material-ui/icons/Person'; 9 | import { useChat } from '../../context/ChatContext'; 10 | import { useUser } from '../../context/UserContext'; 11 | import ChatHeader from '../ChatHeader'; 12 | import ChatFooter from '../ChatFooter'; 13 | 14 | export interface ChatProps { 15 | roomCode: string; 16 | } 17 | 18 | const Chat = ({ roomCode }: ChatProps) => { 19 | const [ messages, setMessages ] = useState([] as MessagePopulated[]); 20 | const chatSocket = useChat(); 21 | const { userDetails } = useUser(); 22 | const setRef = useCallback((node) => { 23 | if (node) { 24 | node.scrollIntoView({ smooth: true }); 25 | } 26 | }, []); 27 | 28 | useEffect( 29 | () => { 30 | if (chatSocket === null) return; 31 | const subscription = chatSocket.onMessage().subscribe(({ newMsg, updatedRoom }) => { 32 | if (newMsg.roomCode === roomCode) { 33 | setMessages((prevMsgs) => [ ...prevMsgs, newMsg ]); 34 | } 35 | }); 36 | return () => { 37 | subscription.unsubscribe(); 38 | }; 39 | }, 40 | [ chatSocket, roomCode ] 41 | ); 42 | 43 | useEffect( 44 | () => { 45 | chatHttp 46 | .getMessages({ roomCode }) 47 | .then(({ data }) => { 48 | setMessages((prevMsgs) => data.messages); 49 | }) 50 | .catch(({ response }) => { 51 | console.log(response.data); 52 | }); 53 | }, 54 | [ roomCode ] 55 | ); 56 | 57 | const formatDate = (date: Date) => { 58 | return differenceInCalendarDays(new Date(), date) > 2 59 | ? format(date, 'EEE MMM d h:m b') 60 | : formatDistanceToNow(date, { addSuffix: true }); 61 | }; 62 | 63 | return ( 64 |
65 | 66 |
67 | 68 |
69 | {messages.map(({ content, user, createdAt }, i) => { 70 | const lastMessage = messages.length - 1 === i; 71 | return ( 72 |
77 |
78 | 79 | {user.firstName && user.lastName ? ( 80 | user.firstName.charAt(0) + user.lastName.charAt(0) 81 | ) : ( 82 | 83 | )} 84 | 85 |

86 | 87 | {userDetails.username === user.username ? 'You' : user.username} 88 | 89 | {content} 90 |

91 |
92 | {formatDate(parseISO(createdAt))} 93 |
94 | ); 95 | })} 96 |
97 |
98 |
99 | 100 |
101 | ); 102 | }; 103 | 104 | export default Chat; 105 | -------------------------------------------------------------------------------- /src/components/Chat/style.css: -------------------------------------------------------------------------------- 1 | .chat { 2 | flex-grow: 10; 3 | } 4 | .chat__body { 5 | background-color: #f1faee; 6 | height: calc(100vh - 7.3rem); 7 | } 8 | .chat__main { 9 | padding: 0 1rem 1.75rem; 10 | } 11 | .chat__block { 12 | margin-top: 1rem; 13 | } 14 | .message__block { 15 | display: flex; 16 | } 17 | .message__block .MuiAvatar-root { 18 | margin-right: 0.5rem; 19 | margin-top: 0.2rem; 20 | } 21 | .message__block .chat__person { 22 | font-size: small; 23 | display: block; 24 | margin-bottom: 0.25rem; 25 | font-weight: 600; 26 | letter-spacing: 0.03rem; 27 | } 28 | .chat__message { 29 | position: relative; 30 | width: fit-content; 31 | background-color: #219ebc; 32 | color: #fff; 33 | padding: 0.75rem 1rem; 34 | border-radius: 0.75rem; 35 | margin: 0; 36 | box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 2px 1px -1px rgba(0, 0, 0, 0.12), 0 1px 3px 0 rgba(0, 0, 0, 0.2); 37 | } 38 | .chat__timestamp { 39 | font-size: x-small; 40 | margin-left: 3.75rem; 41 | color: gray; 42 | } 43 | .chat__block--sender .MuiAvatar-root { 44 | order: 1; 45 | margin-right: 0; 46 | margin-left: 0.5rem; 47 | } 48 | .chat__block--sender .chat__message { 49 | margin-left: auto; 50 | background-color: #8ecae6; 51 | } 52 | .chat__block--sender .chat__timestamp { 53 | display: inline-block; 54 | text-align: right; 55 | margin-left: 0; 56 | margin-right: 3.75rem; 57 | width: calc(100% - 3.75rem); 58 | } 59 | -------------------------------------------------------------------------------- /src/components/ChatFooter/index.tsx: -------------------------------------------------------------------------------- 1 | import { IconButton } from '@material-ui/core'; 2 | import React, { useState } from 'react'; 3 | import { useChat } from '../../context/ChatContext'; 4 | import SendIcon from '@material-ui/icons/Send'; 5 | import { ChatMessage, User } from '../../types'; 6 | import './style.css'; 7 | 8 | export interface ChatFooterProps { 9 | roomCode: string; 10 | loggedInUser: User; 11 | } 12 | function ChatFooter({ roomCode, loggedInUser }: ChatFooterProps) { 13 | const [ input, setInput ] = useState(''); 14 | const chatSocket = useChat(); 15 | const sendMessage = async (e: React.MouseEvent) => { 16 | e.preventDefault(); 17 | if (input) { 18 | const messageDetails: ChatMessage = { 19 | userRoom: { 20 | name: loggedInUser.username, 21 | room: roomCode 22 | }, 23 | content: input 24 | }; 25 | setInput(''); 26 | 27 | console.log('sending message: ' + JSON.stringify(messageDetails)); 28 | chatSocket.send(messageDetails); 29 | } 30 | }; 31 | return ( 32 |
33 |
34 | setInput(e.target.value)} type="text" placeholder="Start typing.." /> 35 | 38 |
39 | 40 | 41 | 42 |
43 | ); 44 | } 45 | 46 | export default ChatFooter; 47 | -------------------------------------------------------------------------------- /src/components/ChatFooter/style.css: -------------------------------------------------------------------------------- 1 | .chat__footer { 2 | display: flex; 3 | flex-flow: row nowrap; 4 | justify-content: space-between; 5 | align-items: center; 6 | height: 3.5rem; 7 | border-top: 1px solid #cecece; 8 | background-color: #fff; 9 | } 10 | .chat__footer > form { 11 | flex: 1; 12 | display: flex; 13 | margin-left: 0.5rem; 14 | } 15 | .chat__footer > form > input { 16 | flex: 1; 17 | } 18 | .chat__footer > form > button { 19 | display: none; 20 | } 21 | .chat__footer > .MuiButtonBase-root { 22 | margin: -0.25rem 0.25rem 0; 23 | } 24 | .chat__footer .MuiButtonBase-root .MuiSvgIcon-root { 25 | transform: rotate(-45deg); 26 | } 27 | -------------------------------------------------------------------------------- /src/components/ChatHeader/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { parseISO } from 'date-fns'; 3 | import { MessagePopulated } from '../../types'; 4 | import './style.css'; 5 | 6 | export interface ChatHeaderProps { 7 | roomCode: string; 8 | messages: MessagePopulated[]; 9 | formatDate: (date: Date) => string; 10 | } 11 | 12 | function ChatHeader({ roomCode, messages, formatDate }: ChatHeaderProps) { 13 | return ( 14 |
15 |
16 |

Room {roomCode}

17 |

18 | {messages.length > 0 ? ( 19 | 'Last activity ' + formatDate(parseISO(messages[messages.length - 1].createdAt)) 20 | ) : ( 21 | 'No recent activities...' 22 | )} 23 |

24 |
25 |
26 | {/* TODO future implementation */} 27 | {/* 28 | 29 | 30 | 31 | 32 | */} 33 |
34 |
35 | ); 36 | } 37 | 38 | export default ChatHeader; 39 | -------------------------------------------------------------------------------- /src/components/ChatHeader/style.css: -------------------------------------------------------------------------------- 1 | .chat__header { 2 | display: flex; 3 | flex-flow: row nowrap; 4 | align-items: center; 5 | justify-content: space-between; 6 | padding: 0.35rem; 7 | border-bottom: 1px solid #cecece; 8 | background-color: #fff; 9 | } 10 | .chat__headerInfo { 11 | flex: 1; 12 | padding-left: 1rem; 13 | } 14 | .chat__headerInfo > h3 { 15 | font-weight: 700; 16 | margin: 0; 17 | } 18 | .chat__headerInfo > p { 19 | color: gray; 20 | margin: 0 0 0.35rem; 21 | font-size: 0.8rem; 22 | } 23 | .chat__headerIcons > .MuiIconButton-root { 24 | margin-right: 0.5rem; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/ConfirmationDialog/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from '@material-ui/core'; 2 | import React from 'react'; 3 | import './style.css'; 4 | 5 | export interface ConfirmationDialogProps { 6 | content: string; 7 | open: boolean; 8 | onClose: (willProceed: boolean) => void; 9 | } 10 | 11 | function ConfirmationDialog({ content, open, onClose }: ConfirmationDialogProps) { 12 | return ( 13 | onClose(false)} 16 | aria-labelledby="confirmation-dialog-title" 17 | aria-describedby="confirmation-dialog-description" 18 | > 19 | Are you sure? 20 | 21 | {content} 22 | 23 | 24 | 27 | 30 | 31 | 32 | ); 33 | } 34 | 35 | export default ConfirmationDialog; 36 | -------------------------------------------------------------------------------- /src/components/ConfirmationDialog/style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbilag/chat-frontend/06496016f292e3bab4e31b2f1cc1f09dd9fb9c2e/src/components/ConfirmationDialog/style.css -------------------------------------------------------------------------------- /src/components/GeneralSnackbar/index.tsx: -------------------------------------------------------------------------------- 1 | import { IconButton, Snackbar } from '@material-ui/core'; 2 | import CloseIcon from '@material-ui/icons/Close'; 3 | import React from 'react'; 4 | import './style.css'; 5 | 6 | export interface GeneralSnackbarProps { 7 | message: string; 8 | open: boolean; 9 | onClose: () => void; 10 | } 11 | 12 | function GeneralSnackbar({ message, open, onClose }: GeneralSnackbarProps) { 13 | const handleClose = (event: React.SyntheticEvent | React.MouseEvent, reason?: string) => { 14 | if (reason === 'clickaway') { 15 | return; 16 | } 17 | onClose(); 18 | }; 19 | 20 | return ( 21 |
22 | 33 | 34 | 35 | } 36 | /> 37 |
38 | ); 39 | } 40 | 41 | export default GeneralSnackbar; 42 | -------------------------------------------------------------------------------- /src/components/GeneralSnackbar/style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbilag/chat-frontend/06496016f292e3bab4e31b2f1cc1f09dd9fb9c2e/src/components/GeneralSnackbar/style.css -------------------------------------------------------------------------------- /src/components/Login/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import { Button } from '@material-ui/core'; 3 | import chatHttp from '../../services/Http'; 4 | import './style.css'; 5 | import { useUser } from '../../context/UserContext'; 6 | import { useHistory } from 'react-router-dom'; 7 | 8 | export interface LoginProps { 9 | history: ReturnType; 10 | } 11 | function Login({ history }: LoginProps) { 12 | const usernameRef = useRef(null); 13 | const passwordRef = useRef(null); 14 | const [ errorMsg, setErrorMsg ] = useState(''); 15 | const { userDetails, setUserDetails } = useUser(); 16 | 17 | const proceed = async (e: React.MouseEvent) => { 18 | e.preventDefault(); 19 | if (usernameRef.current && usernameRef.current.value && passwordRef.current && passwordRef.current.value) { 20 | chatHttp 21 | .login({ username: usernameRef.current.value, password: passwordRef.current.value }) 22 | .then(({ authorization, data }) => { 23 | setErrorMsg(''); 24 | localStorage.setItem('chat-app-auth', authorization); 25 | localStorage.setItem('chat-app-user', JSON.stringify(data.userDetails)); 26 | setUserDetails(data.userDetails); 27 | history.push('/room'); 28 | }) 29 | .catch(({ response }) => { 30 | console.log(response.data); 31 | setErrorMsg(response.data.message); 32 | }); 33 | } else { 34 | setErrorMsg('Fill-in both username and password'); 35 | } 36 | }; 37 | 38 | const goToSignup = async () => { 39 | history.push('/signup'); 40 | }; 41 | 42 | useEffect(() => { 43 | if (usernameRef.current) usernameRef.current.focus(); 44 | }, []); 45 | 46 | useEffect( 47 | () => { 48 | const token = localStorage.getItem('chat-app-auth'); 49 | if (token && userDetails.username) { 50 | chatHttp.changeLoginStatus({ newValue: true }); 51 | history.push('/room'); 52 | } 53 | }, 54 | [ history, userDetails ] 55 | ); 56 | 57 | return ( 58 |
59 |
60 |

REALTIME CHAT

61 |

by Rose Bilag

62 |
63 | 64 | 65 | 75 |

76 | Don't have an account yet? 77 | Register here 78 | 79 |

80 |
81 | {errorMsg && {errorMsg}} 82 |
83 |
84 | ); 85 | } 86 | 87 | export default Login; 88 | -------------------------------------------------------------------------------- /src/components/Login/style.css: -------------------------------------------------------------------------------- 1 | .login__area h1 { 2 | margin: 0; 3 | } 4 | .login__area > p { 5 | margin: 0; 6 | color: gray; 7 | font-weight: 500; 8 | } 9 | .login__area > form { 10 | margin-top: 2rem; 11 | } 12 | .login__area > form input { 13 | margin: 0.35rem 0; 14 | width: calc(100% - 2rem); 15 | } 16 | .login__area form p { 17 | margin: 1rem auto; 18 | font-size: 0.9rem; 19 | } 20 | .login__area .signup__link { 21 | cursor: pointer; 22 | margin: 0.25rem; 23 | font-weight: 600; 24 | -webkit-transition: .25s ease-out; 25 | -moz-transition: .25s ease-out; 26 | -ms-transition: .25s ease-out; 27 | -o-transition: .25s ease-out; 28 | transition: .25s ease-out; 29 | } 30 | .login__area .signup__link:hover { 31 | color: #219ebc; 32 | } 33 | -------------------------------------------------------------------------------- /src/components/NewRoom/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, ButtonGroup, Dialog, DialogTitle, DialogContent } from '@material-ui/core'; 3 | import chatHttp from '../../services/Http'; 4 | import './style.css'; 5 | import { useChat } from '../../context/ChatContext'; 6 | import { useUser } from '../../context/UserContext'; 7 | import { RoomPopulated } from '../../types'; 8 | 9 | export interface NewRoomProps { 10 | open: boolean; 11 | onClose: (value: null | RoomPopulated) => void; 12 | } 13 | 14 | function NewRoom({ open, onClose }: NewRoomProps) { 15 | const [ isNew, setisNew ] = useState(true); 16 | const [ description, setDescription ] = useState(''); 17 | const [ roomCode, setRoomCode ] = useState(''); 18 | const chatSocket = useChat(); 19 | const { userDetails } = useUser(); 20 | 21 | const handleClose = (val: null | RoomPopulated) => { 22 | onClose(val); 23 | }; 24 | 25 | const proceed = async (e: React.MouseEvent) => { 26 | e.preventDefault(); 27 | if (isNew || (!isNew && roomCode)) { 28 | try { 29 | let { data } = isNew ? await chatHttp.createRoom({ description }) : await chatHttp.joinRoom({ roomCode }); 30 | if (data) { 31 | chatSocket.join({ name: userDetails.username, room: data.room.code }, true); 32 | setisNew(true); 33 | setDescription(''); 34 | setRoomCode(''); 35 | handleClose(data.room); 36 | } 37 | } catch (e) { 38 | console.log(e.response.data); 39 | } 40 | } 41 | }; 42 | 43 | return ( 44 | handleClose(null)} aria-labelledby="new-room-dialog" open={open} className="newRoom"> 45 | New Room 46 | 47 |
48 | 49 | 55 | 61 | 62 | 63 | {isNew ? ( 64 |