├── .github └── workflows │ └── main.yml ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public └── index.html ├── serverless.yml └── src ├── App.js ├── CRUD ├── axios.config.js ├── config.json └── queries.js ├── Components ├── DesktopApp │ ├── AuthScreens │ │ ├── ForgetPassword.js │ │ ├── Login.js │ │ ├── NewPassword.js │ │ └── Signup.js │ ├── ChatArea │ │ ├── ChatArea.css │ │ ├── ChatArea.js │ │ └── ChatBubble.js │ ├── CreateGroup.js │ ├── DesktopApp.js │ ├── JoinGroup.js │ └── Sidebar │ │ ├── Sidebar.css │ │ ├── Sidebar.js │ │ └── SidebarChat.js └── MobileApp │ ├── AuthScreens │ ├── ForgetPassword.js │ ├── Login.js │ ├── NewPassword.js │ └── Signup.js │ ├── ChatArea │ ├── ChatArea.css │ ├── ChatArea.js │ └── ChatBubble.js │ ├── CreateGroup.js │ ├── MobileApp.js │ └── Sidebar │ ├── JoinGroup.js │ ├── Sidebar.css │ ├── Sidebar.js │ └── SidebarChat.js ├── Images ├── Module.svg ├── chat2.svg └── chat3.svg ├── aws-exports.js ├── dummychat.js ├── index.css └── index.js /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Continuous deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | serverless-deploy: 10 | runs-on: ubuntu-latest 11 | 12 | env: 13 | AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} 14 | AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | - uses: actions/setup-node@v1 19 | with: 20 | node-version: 12 21 | 22 | - name: Install serverless 23 | run: npm install -g serverless 24 | 25 | - name: Install npm 26 | run: npm install 27 | 28 | - name: Build Project 29 | run: npm run build 30 | 31 | - name: Deploy dev 32 | if: github.ref == 'refs/heads/master' 33 | run: serverless --verbose 34 | env: 35 | BUCKET_NAME: chat-loop 36 | REGION: ap-southeast-1 -------------------------------------------------------------------------------- /.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 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chat Loop 2 | [![Open Source Love](https://badges.frapsoft.com/os/v2/open-source.svg?v=103)](https://github.com/smilegupta) 3 | [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/smilegupta) [![Made With Love](https://img.shields.io/badge/Made%20With-Love-orange.svg)](https://github.com/smilegupta) 4 | 5 | Chat Loop is a highly scalable, low cost and high performant chat application built on AWS and React leveraging GraphQL subscriptions for real time communication. 6 | 7 | ## Project Overview 8 | 9 | 10 | ## Application Links 11 | 12 | Frontend Code -> [https://github.com/smilegupta/chatloop-frontend](https://github.com/smilegupta/chatloop-frontend) 13 |
14 | Deployed URL -> [https://chat-loop.smilegupta.tech/](https://chat-loop.smilegupta.tech/) 15 |
16 | Backend Code -> [https://github.com/smilegupta/chatloop-backend](https://github.com/smilegupta/chatloop-backend) 17 |
18 | API Docs -> [https://kb4r9.csb.app/](https://kb4r9.csb.app/) 19 | 20 | ## Tech Stack 21 | 22 | Frontned: Reactjs 23 |
24 | Styling: CSS and Material UI 25 |
26 | Database: DynamoDB 27 |
28 | Authentication and Authorisation: Cogito and Amplify 29 |
30 | Integrations: GraphQL ( AWS AppSync ) 31 |
32 | Compute: AWS Lambda 33 |
34 | Deployment: CI/CD setup using GitHub Actions via Serverless Framework 35 | 36 |

37 | 38 |

39 |

40 | Architectural Diargram 41 |

42 | 43 | ## Overall Functionlity 44 | - Create and join chatrooms 45 | - Mobile Responsive 46 | 47 | ## Upcoming Features 48 | - Emoji Keypad 49 | - 1:1 Chats 50 | - Support to leave group 51 | - Reply to messages 52 | - Starring messges 53 | - Delete a message 54 | - Support for sharing images and files 55 | - Edit personal profile details like name and profile image 56 | 57 | 58 | ***Glad to see you here! Show some love by [starring](https://github.com/smilegupta/chatloop-frontend/) this repo.*** 59 | 60 | [![Facebook](https://img.shields.io/static/v1.svg?label=follow&message=@smileguptaaa&color=grey&logo=facebook&style=flat&logoColor=white&colorA=blue)](https://www.facebook.com/smileguptaaa) [![Instagram](https://img.shields.io/static/v1.svg?label=follow&message=@smileguptaaa&color=grey&logo=instagram&style=flat&logoColor=white&colorA=blue)](https://www.instagram.com/smileguptaaa/) [![LinkedIn](https://img.shields.io/static/v1.svg?label=connect&message=@smilegupta&color=grey&logo=linkedin&style=flat&logoColor=white&colorA=blue)](https://www.linkedin.com/in/smilegupta/) [![Twitter](https://img.shields.io/static/v1.svg?label=connect&message=@smileguptaaa&color=grey&logo=twitter&style=flat&logoColor=white&colorA=blue)](https://twitter.com/smileguptaaa) 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatloop", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.4", 7 | "@material-ui/icons": "^4.11.2", 8 | "@testing-library/jest-dom": "^5.12.0", 9 | "@testing-library/react": "^11.2.6", 10 | "@testing-library/user-event": "^12.8.3", 11 | "aws-amplify": "^3.3.27", 12 | "axios": "^0.21.1", 13 | "moment": "^2.29.1", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-router-dom": "^5.2.0", 17 | "react-scripts": "4.0.3", 18 | "react-toastify": "^7.0.4", 19 | "toast": "^0.3.43", 20 | "uuid": "^8.3.2", 21 | "web-vitals": "^1.1.1" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build", 26 | "test": "react-scripts test", 27 | "eject": "react-scripts eject" 28 | }, 29 | "eslintConfig": { 30 | "extends": [ 31 | "react-app", 32 | "react-app/jest" 33 | ] 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.2%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | Chat Loop 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | name: chat-loop 2 | 3 | image-dock: 4 | component: "@serverless/website" 5 | region: ${env.REGION} 6 | inputs: 7 | code: 8 | src: ./build 9 | bucketName: ${env.BUCKET_NAME} 10 | domain: chat-loop.smilegupta.tech -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import "./index.css"; 3 | import { useState, useEffect, Fragment } from "react"; 4 | import DesktopApp from "./Components/DesktopApp/DesktopApp"; 5 | import MobileApp from "./Components/MobileApp/MobileApp"; 6 | import Amplify, { Auth } from "aws-amplify"; 7 | import { createMuiTheme, ThemeProvider } from "@material-ui/core"; 8 | import { axiosFun } from "./CRUD/axios.config"; 9 | import { listUserDetails } from "./CRUD/queries"; 10 | import awsconfig from "./aws-exports"; 11 | Amplify.configure(awsconfig); 12 | 13 | function App() { 14 | // State Varaible 15 | const [isMobile, setIsMobile] = useState(false); 16 | const [isAuthenticated, setAuthenticated] = useState(false); 17 | const [isAuthenticating, setAuthenticating] = useState(true); 18 | const [user, setUser] = useState(null); 19 | const [conversations, setConversations] = useState(null); 20 | const [subscriptionArray, setSubscriptionArray] = useState(null); 21 | let [currentConversationMessages, setCurrentConversationMessages] = useState(null); 22 | 23 | // Props for Session Management 24 | const authProps = { 25 | isAuthenticated, 26 | user, 27 | setUser, 28 | setAuthenticated, 29 | conversations, 30 | setConversations, 31 | subscriptionArray, 32 | setSubscriptionArray, 33 | currentConversationMessages, 34 | setCurrentConversationMessages 35 | }; 36 | 37 | const theme = createMuiTheme({ 38 | palette: { 39 | primary: { 40 | main: "#DE2B64", 41 | }, 42 | secondary: { 43 | main: "#5E5470", 44 | }, 45 | }, 46 | }); 47 | 48 | // Checking the device type used by app 49 | useEffect(() => { 50 | async function sessionChecker() { 51 | try { 52 | await Auth.currentSession(); 53 | setAuthenticated(true); 54 | const currentUser = await Auth.currentAuthenticatedUser(); 55 | const conversation = await axiosFun( 56 | listUserDetails(currentUser.username) 57 | ); 58 | setConversations(conversation.data.listUserss.items[0]); 59 | setSubscriptionArray(conversation.data.listUserss.items[0].conversations.items) 60 | setUser(currentUser); 61 | } catch (err) { 62 | console.error(err); 63 | } 64 | setAuthenticating(false); 65 | } 66 | sessionChecker(); 67 | if ( 68 | /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( 69 | navigator.userAgent 70 | ) 71 | ) { 72 | setIsMobile(true); 73 | } 74 | }, []); 75 | 76 | return ( 77 | 78 | 79 | {isAuthenticating === false && ( 80 | <> 81 | {" "} 82 | {!isMobile ? ( 83 | 84 | ) : ( 85 | 86 | )}{" "} 87 | 88 | )} 89 | 90 | 91 | ); 92 | } 93 | 94 | export default App; 95 | -------------------------------------------------------------------------------- /src/CRUD/axios.config.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import AWSCONSTANTS from "../aws-exports"; 3 | const apiUrl = AWSCONSTANTS.aws_appsync_graphqlEndpoint; 4 | const headers = { 5 | "x-api-key": AWSCONSTANTS.aws_appsync_apiKey, 6 | }; 7 | 8 | export async function axiosFun(query) { 9 | const res = await axios.post(apiUrl, query, { 10 | headers: headers, 11 | }); 12 | return res.data; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/CRUD/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "cognito": { 3 | "REGION": "ap-south-1", 4 | "USER_POOL_ID": "ap-south-1_zQTcU5coK", 5 | "APP_CLIENT_ID": "71b5287m8kfm3o5c393tu6a3u0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/CRUD/queries.js: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from "uuid"; 2 | import moment from "moment"; 3 | 4 | export const GetUserDetails = { 5 | query: `query GetUserDetails { 6 | listUserss(userId: "86de105c-2c17-41ef-8b90-a4c1871999cb") { 7 | items { 8 | userId 9 | name 10 | profileImage 11 | theme 12 | createdAt 13 | conversations { 14 | items { 15 | conversationId 16 | conversationName 17 | conversationImage 18 | conversationType 19 | lastMessage 20 | lastMessageAt 21 | } 22 | nextToken 23 | } 24 | } 25 | } 26 | }`, 27 | }; 28 | 29 | export const listAllChatRooms = { 30 | query: `query ListChatRooms { 31 | listChatRoomss { 32 | items { 33 | chatRoomId 34 | chatRoomImage 35 | description 36 | name 37 | } 38 | } 39 | } 40 | `, 41 | }; 42 | 43 | export const createUser = (userId, name, profileImage) => { 44 | const query = { 45 | query: `mutation create { 46 | createUsers(input : { 47 | name: "${name}", 48 | userId: "${userId}", 49 | profileImage: "${profileImage}" 50 | }) 51 | { 52 | userId 53 | } 54 | } 55 | `, 56 | }; 57 | return query; 58 | }; 59 | 60 | export const listUserDetails = (userId) => { 61 | const query = { 62 | query: `query listUserDetails { 63 | listUserss(userId: "${userId}") { 64 | items { 65 | name 66 | profileImage 67 | userId 68 | conversations { 69 | items { 70 | conversationId 71 | conversationImage 72 | conversationName 73 | conversationType 74 | lastMessage 75 | lastMessageAt 76 | description 77 | } 78 | } 79 | } 80 | } 81 | }`, 82 | }; 83 | return query; 84 | }; 85 | 86 | export const createChatRoom = (userId, chatName, description) => { 87 | const chatId = uuidv4(); 88 | const query = { 89 | query: `mutation MyMutation { 90 | createChatRooms( 91 | input: {chatRoomId: "${chatId}", createdBy: "${userId}", description: "${description}", name: "${chatName}", chatRoomImage: "https://avatars.dicebear.com/api/jdenticon/${Math.floor( 92 | Math.random() * 5000 93 | )}.svg"} 94 | ) { 95 | chatRoomId 96 | chatRoomImage 97 | } 98 | }`, 99 | }; 100 | return query; 101 | }; 102 | 103 | export const sendMessage = ( 104 | name, 105 | chatId, 106 | chatName, 107 | userId, 108 | chatRoomImage, 109 | message, 110 | description, 111 | conversationType 112 | ) => { 113 | const query = { 114 | query: `mutation sendMessage { 115 | sendMessage( 116 | authorName: "${name}" 117 | conversationId: "${chatId}" 118 | conversationImage: "${chatRoomImage}" 119 | conversationType: ${conversationType} 120 | authorId: "${userId}" 121 | message: "${message}" 122 | description: "${description}" 123 | sentAt: "${moment.utc(new Date()).format()}" 124 | conversationName: "${chatName}" 125 | ) { 126 | authorId 127 | authorName 128 | conversationId 129 | conversationName 130 | message 131 | messageId 132 | sentAt 133 | } 134 | }`, 135 | }; 136 | return query; 137 | }; 138 | 139 | export const getMessages = (chatId) => { 140 | const query = { 141 | query: `query getmessages { 142 | listMessagess(conversationId: "${chatId}", limit: 100000) { 143 | items { 144 | authorId 145 | authorName 146 | message 147 | messageId 148 | sentAt 149 | } 150 | } 151 | } 152 | `, 153 | }; 154 | return query; 155 | }; 156 | 157 | export const getLastMessage = (chatRoomId) => { 158 | const query = { 159 | query: `query MyQuery { 160 | listMessagess(conversationId: "${chatRoomId}", sortDirection: DESC, limit: 1) { 161 | items { 162 | message 163 | sentAt 164 | } 165 | } 166 | }`, 167 | }; 168 | return query; 169 | }; 170 | 171 | export const joinChatRoom = ( 172 | conversationId, 173 | conversationImage, 174 | conversationName, 175 | conversationType, 176 | lastMessage, 177 | lastMessageAt, 178 | userId, 179 | description 180 | ) => { 181 | const query = { 182 | query: `mutation JoinChatRoom { 183 | createUserConversations(input: {conversationId: "${conversationId}", description:"${description}", conversationImage: "${conversationImage}", conversationName: "${conversationName}", conversationType: ${conversationType}, lastMessage: "${lastMessage}", lastMessageAt: "${lastMessageAt}", userId: "${userId}"}) { 184 | conversationType 185 | conversationId 186 | conversationName 187 | lastMessage 188 | lastMessageAt 189 | } 190 | } 191 | `, 192 | }; 193 | return query; 194 | }; 195 | -------------------------------------------------------------------------------- /src/Components/DesktopApp/AuthScreens/ForgetPassword.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { 3 | Grid, 4 | Container, 5 | TextField, 6 | Button, 7 | Typography, 8 | makeStyles, 9 | } from "@material-ui/core"; 10 | import { Link, useParams, useHistory } from "react-router-dom"; 11 | import { Auth } from "aws-amplify"; 12 | import { toast } from "react-toastify"; 13 | toast.configure(); 14 | 15 | const useStyles = makeStyles({ 16 | field: { 17 | marginTop: 20, 18 | marginBottom: 20, 19 | display: "block", 20 | }, 21 | subtitles: { 22 | marginTop: 5, 23 | marginBottom: 5, 24 | display: "block", 25 | }, 26 | }); 27 | 28 | const ForgetPassword = () => { 29 | const params = useParams(); 30 | const history = useHistory(); 31 | const classes = useStyles(); 32 | const [email, setemail] = useState(params.email); 33 | const handleSubmit = async (e) => { 34 | e.preventDefault(); 35 | try { 36 | await Auth.forgotPassword(email); 37 | toast.success("Sent a verification code to your email!", { 38 | position: "top-right", 39 | autoClose: 5000, 40 | hideProgressBar: false, 41 | closeOnClick: true, 42 | pauseOnHover: true, 43 | draggable: true, 44 | }); 45 | history.push(`/new-password/${email}`); 46 | } catch (err) { 47 | let error = err.message || "Something went wrong!"; 48 | toast.error(error, { 49 | position: "top-right", 50 | autoClose: 5000, 51 | hideProgressBar: false, 52 | closeOnClick: true, 53 | pauseOnHover: true, 54 | draggable: true, 55 | }); 56 | } 57 | }; 58 | return ( 59 | 60 | 69 | 87 | 88 | Reset Password 89 | 90 | 91 | Enter the email address associated with your account 92 | 93 |
94 | setemail(e.target.value)} 103 | /> 104 | 112 | 113 | 119 | Try signing in again? 120 | 121 | 122 | {" "} 123 | Sign In 124 | 125 | 126 | 127 |
128 | 140 | 141 |
142 | ); 143 | }; 144 | 145 | export default ForgetPassword; 146 | -------------------------------------------------------------------------------- /src/Components/DesktopApp/AuthScreens/Login.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { 3 | Grid, 4 | Container, 5 | TextField, 6 | Button, 7 | Typography, 8 | makeStyles, 9 | } from "@material-ui/core"; 10 | import { Link, useHistory } from "react-router-dom"; 11 | import { Auth } from "aws-amplify"; 12 | import { toast } from "react-toastify"; 13 | import { axiosFun } from "../../../CRUD/axios.config"; 14 | import { listUserDetails } from "../../../CRUD/queries"; 15 | toast.configure(); 16 | 17 | const useStyles = makeStyles({ 18 | field: { 19 | marginTop: 20, 20 | display: "block", 21 | }, 22 | subtitles: { 23 | marginTop: 5, 24 | marginBottom: 5, 25 | display: "block", 26 | }, 27 | }); 28 | 29 | const Login = ({ auth }) => { 30 | const history = useHistory(); 31 | const classes = useStyles(); 32 | const [email, setemail] = useState(""); 33 | const [password, setpassword] = useState(""); 34 | const [resendEmail, setResendEmail] = useState(false); 35 | 36 | const handleSubmit = async (e) => { 37 | e.preventDefault(); 38 | try { 39 | const res = await Auth.signIn(email, password); 40 | let message = "Signed in successfully! Welcome back!!"; 41 | toast.success(message, { 42 | position: "top-right", 43 | autoClose: 0, 44 | hideProgressBar: false, 45 | closeOnClick: true, 46 | pauseOnHover: true, 47 | draggable: true, 48 | }); 49 | //setLoading(false); 50 | auth.setAuthenticated(true); 51 | auth.setUser(res); 52 | const conversation = await axiosFun(listUserDetails(res.username)) 53 | auth.setConversations(conversation.data.listUserss.items[0]) 54 | auth.setSubscriptionArray(conversation.data.listUserss.items[0].conversations.items) 55 | history.push(`/`); 56 | } catch (err) { 57 | let error = err.message; 58 | if (err.message === "User is not confirmed.") { 59 | error = 60 | "Your account verification not complete. Please complete the verification before logging in."; 61 | } 62 | toast.error(error, { 63 | position: "top-right", 64 | autoClose: 0, 65 | hideProgressBar: false, 66 | closeOnClick: true, 67 | pauseOnHover: true, 68 | draggable: true, 69 | }); 70 | if (err.message === "User is not confirmed.") setResendEmail(true); 71 | } 72 | }; 73 | 74 | // Resend Confirmation Link 75 | const resendConfirmationLink = async (e) => { 76 | e.preventDefault(); 77 | try { 78 | await Auth.resendSignUp(email); 79 | toast.success( 80 | "Verification email resent successfully. Please verify your account by clicking that link before logging in.", 81 | { 82 | position: "top-right", 83 | autoClose: 5000, 84 | hideProgressBar: false, 85 | closeOnClick: true, 86 | pauseOnHover: true, 87 | draggable: true, 88 | } 89 | ); 90 | setResendEmail(false); 91 | } catch (err) { 92 | let error = err.message || "Something went wrong!"; 93 | toast.error(error, { 94 | position: "top-right", 95 | autoClose: 5000, 96 | hideProgressBar: false, 97 | closeOnClick: true, 98 | pauseOnHover: true, 99 | draggable: true, 100 | }); 101 | } 102 | }; 103 | 104 | return ( 105 | 106 | 115 | 127 | 145 | 146 | Sign In 147 | 148 |
149 | setemail(e.target.value)} 158 | /> 159 | {resendEmail && ( 160 | 167 | resendConfirmationLink(e)}> Missed Confirmation Link? 168 | 169 | )} 170 | setpassword(e.target.value)} 173 | label="Password" 174 | variant="outlined" 175 | required 176 | fullWidth 177 | type="password" 178 | value={password} 179 | /> 180 | 186 | Forgot Password?{" "} 187 | 188 | 189 | {" "} 190 | Click here{" "} 191 | 192 | 193 | 194 | 207 |   208 | 217 | 218 | 224 | New User?{" "} 225 | 226 | 227 | {" "} 228 | Register{" "} 229 | 230 | 231 | 232 |
233 |
234 |
235 | ); 236 | }; 237 | 238 | export default Login; 239 | -------------------------------------------------------------------------------- /src/Components/DesktopApp/AuthScreens/NewPassword.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { 3 | Grid, 4 | Container, 5 | TextField, 6 | Button, 7 | Typography, 8 | makeStyles, 9 | } from "@material-ui/core"; 10 | import { Link, useParams, useHistory } from "react-router-dom"; 11 | import { Auth } from "aws-amplify"; 12 | import { toast } from "react-toastify"; 13 | toast.configure(); 14 | 15 | const useStyles = makeStyles({ 16 | field: { 17 | marginTop: 20, 18 | marginBottom: 20, 19 | display: "block", 20 | }, 21 | subtitles: { 22 | marginTop: 5, 23 | marginBottom: 5, 24 | display: "block", 25 | }, 26 | }); 27 | 28 | const NewPassword = () => { 29 | const params = useParams(); 30 | const history = useHistory(); 31 | const classes = useStyles(); 32 | const [verificationCode, setverificationCode] = useState(""); 33 | const [password, setpassword] = useState(""); 34 | 35 | const handleSubmit = async (e) => { 36 | e.preventDefault(); 37 | try { 38 | await Auth.forgotPasswordSubmit( 39 | params.email, 40 | verificationCode.trim(), 41 | password 42 | ); 43 | toast.success("Password created successfully!", { 44 | position: "top-right", 45 | autoClose: 5000, 46 | hideProgressBar: false, 47 | closeOnClick: true, 48 | pauseOnHover: true, 49 | draggable: true, 50 | }); 51 | history.push(`/signin`); 52 | } catch (err) { 53 | let error = err.message || "Something went wrong!"; 54 | toast.error(error, { 55 | position: "top-right", 56 | autoClose: 5000, 57 | hideProgressBar: false, 58 | closeOnClick: true, 59 | pauseOnHover: true, 60 | draggable: true, 61 | }); 62 | } 63 | }; 64 | 65 | return ( 66 | 67 | 76 | 88 | 106 | 107 | Reset Password 108 | 109 |
110 | setverificationCode(e.target.value)} 118 | value={verificationCode} 119 | /> 120 | setpassword(e.target.value)} 123 | label="New Password (min 8 chars: lc+uc+sc+num)" 124 | variant="outlined" 125 | required 126 | fullWidth 127 | type="password" 128 | className={classes.field} 129 | /> 130 | 138 | 139 | 145 | Try signing in again?{" "} 146 | 147 | 148 | {" "} 149 | Sign In{" "} 150 | 151 | 152 | 153 |
154 |
155 |
156 | ); 157 | }; 158 | 159 | export default NewPassword; 160 | -------------------------------------------------------------------------------- /src/Components/DesktopApp/AuthScreens/Signup.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { 3 | Grid, 4 | Container, 5 | TextField, 6 | Button, 7 | Typography, 8 | makeStyles, 9 | } from "@material-ui/core"; 10 | import { Link, useHistory } from "react-router-dom"; 11 | import { Auth } from "aws-amplify"; 12 | import { toast } from "react-toastify"; 13 | import { axiosFun } from "../../../CRUD/axios.config"; 14 | import { createUser } from "../../../CRUD/queries"; 15 | toast.configure(); 16 | 17 | const useStyles = makeStyles({ 18 | field: { 19 | marginTop: 20, 20 | marginBottom: 20, 21 | display: "block", 22 | }, 23 | subtitles: { 24 | marginTop: 5, 25 | marginBottom: 5, 26 | display: "block", 27 | }, 28 | }); 29 | 30 | const Signup = () => { 31 | const history = useHistory(); 32 | const classes = useStyles(); 33 | const [name, setName] = useState(""); 34 | const [username, setusername] = useState(""); 35 | const [password, setpassword] = useState(""); 36 | const profileImage = `https://avatars.dicebear.com/api/bottts/${Math.floor( 37 | Math.random() * 5000 38 | )}.svg`; 39 | const handleSubmit = async (e) => { 40 | e.preventDefault(); 41 | try { 42 | const res = await Auth.signUp({ 43 | username, 44 | password, 45 | attributes: { 46 | name: name, 47 | picture: profileImage, 48 | }, 49 | }); 50 | await axiosFun(createUser(res.userSub, name, profileImage)); 51 | let message = 52 | "Verification email successfully. Please verify your account by clicking that link before logging in."; 53 | toast.success(message, { 54 | position: "top-right", 55 | autoClose: 0, 56 | hideProgressBar: false, 57 | closeOnClick: true, 58 | pauseOnHover: true, 59 | draggable: true, 60 | }); 61 | history.push(`/signin`); 62 | } catch (error) { 63 | toast.error(error.message, { 64 | position: "top-right", 65 | autoClose: 0, 66 | hideProgressBar: false, 67 | closeOnClick: true, 68 | pauseOnHover: true, 69 | draggable: true, 70 | }); 71 | } 72 | }; 73 | return ( 74 | 75 | 84 | 102 | 103 | Sign Up 104 | 105 |
106 | setName(e.target.value)} 115 | /> 116 | 117 | setusername(e.target.value)} 126 | /> 127 | setpassword(e.target.value)} 129 | label="Password (min 8 chars: lc+uc+sc+num)" 130 | variant="outlined" 131 | required 132 | fullWidth 133 | type="password" 134 | value={password} 135 | className={classes.field} 136 | /> 137 | 145 | 146 | 152 | Already Have Account?{" "} 153 | 154 | 155 | Sign In 156 | 157 | 158 | 159 |
160 | 172 | 173 |
174 | ); 175 | }; 176 | 177 | export default Signup; 178 | -------------------------------------------------------------------------------- /src/Components/DesktopApp/ChatArea/ChatArea.css: -------------------------------------------------------------------------------- 1 | /************ General CSS ****************/ 2 | 3 | .chat { 4 | flex: 0.75; 5 | display: flex; 6 | flex-direction: column; 7 | } 8 | 9 | /************ Chat Header ****************/ 10 | 11 | .chat_header { 12 | padding: 20px; 13 | display: flex; 14 | align-items: center; 15 | } 16 | 17 | .chat_header_info { 18 | flex: 1; 19 | padding-left: 20px; 20 | } 21 | 22 | .chat_header_info > h3 { 23 | margin-bottom: 3px; 24 | font-weight: 500; 25 | color: #5E5470; 26 | } 27 | 28 | .chat_header_info > p { 29 | color: #A6A4AE; 30 | font-size: 14px; 31 | } 32 | 33 | .chat_header_right { 34 | display: flex; 35 | justify-content: space-between; 36 | min-width: 20px; 37 | } 38 | 39 | /************ Chat Body ****************/ 40 | 41 | .chat_body { 42 | flex: 1; 43 | background-repeat: repeat; 44 | background-position: center; 45 | padding: 2% 4%; 46 | overflow-y: scroll; 47 | } 48 | 49 | /************ Chat Messages ****************/ 50 | 51 | .chat_message { 52 | position: relative; 53 | font-size: 16px; 54 | background-color: #ffffff; 55 | border-radius: 10px; 56 | min-width: 10vw; 57 | width: fit-content; 58 | max-width: 30vw; 59 | margin-bottom: 30px; 60 | border-radius: 20px; 61 | padding: 20px; 62 | box-shadow: 1px 4px 20px -6px rgba(94, 84, 112, 1); 63 | } 64 | 65 | .chat_name { 66 | color: #5E5470; 67 | font-size: 12px; 68 | margin-bottom: 5px; 69 | display: block; 70 | font-weight: bold; 71 | } 72 | 73 | .chat_reciever { 74 | margin-left: auto; 75 | background-color: #DE2B64 !important; 76 | color: #ffffff; 77 | } 78 | 79 | .chat_timestamp { 80 | font-size: xx-small; 81 | text-align: left; 82 | display: block; 83 | color: #A6A4AE; 84 | margin-top: 10px; 85 | } 86 | 87 | .chat_reciever_timestamp { 88 | font-size: xx-small; 89 | text-align: right; 90 | display: block; 91 | color: #ffffff; 92 | margin-top: 10px; 93 | } 94 | 95 | /************ Chat Footer ****************/ 96 | 97 | .chat_footer { 98 | display: flex; 99 | justify-content: space-between; 100 | align-items: center; 101 | height: 62px; 102 | } 103 | 104 | .chat_footer > form { 105 | flex: 1; 106 | display: flex; 107 | } 108 | 109 | .chat_footer > form > input { 110 | flex: 1; 111 | padding: 10px; 112 | border: none; 113 | margin-right: 20px; 114 | background-color: #ffffff; 115 | border-radius: 20px; 116 | height: 24px; 117 | margin-left: 10px; 118 | color: #5E5470; 119 | margin-bottom: 20px; 120 | box-shadow: 1px 4px 20px -6px #5e5470; 121 | padding-left: 20px; 122 | } 123 | 124 | .chat_footer > form > input:focus-visible { 125 | outline: none; 126 | } 127 | 128 | .chat_footer > form > button { 129 | display: none; 130 | } 131 | 132 | .chat_footer > .MuiSvgIcon-root { 133 | padding: 10px; 134 | color: grey; 135 | } 136 | 137 | .chat_no_room_selected { 138 | color: #2F2E41; 139 | } 140 | -------------------------------------------------------------------------------- /src/Components/DesktopApp/ChatArea/ChatArea.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable react-hooks/exhaustive-deps */ 3 | import { Avatar, Grid } from "@material-ui/core"; 4 | import "./ChatArea.css"; 5 | import { Fragment, useEffect, useState, useRef } from "react"; 6 | import chatImage from "../../../Images/chat2.svg"; 7 | import { axiosFun } from "../../../CRUD/axios.config"; 8 | import { getMessages, sendMessage } from "../../../CRUD/queries"; 9 | import ChatBubble from "./ChatBubble"; 10 | 11 | const ChatArea = ({ match, auth }) => { 12 | const [message, setMessage] = useState(""); 13 | const [error, setError] = useState(""); 14 | const [data, setData] = useState(null); 15 | const scrollref = useRef(); 16 | useEffect(() => { 17 | getChats(); 18 | }, [match.params.roomId]); 19 | 20 | useEffect(() => { 21 | scrollref.current?.scrollIntoView({ behaviour: "smooth" }); 22 | }); 23 | 24 | const getChats = async () => { 25 | const res = await axiosFun(getMessages(match.params.roomId)); 26 | setData(res.data.listMessagess.items); 27 | auth.currentConversationMessages = { 28 | conversationId: match.params.roomId, 29 | ...res.data.listMessagess, 30 | }; 31 | auth.setCurrentConversationMessages({ 32 | conversationId: match.params.roomId, 33 | ...res.data.listMessagess, 34 | }); 35 | }; 36 | 37 | // Validation 38 | const validateFields = () => { 39 | setError(""); 40 | if (message === null || message === "") { 41 | setError("You can't send a blank message"); 42 | return false; 43 | } 44 | return true; 45 | }; 46 | 47 | const sendMessageFun = async (e) => { 48 | e.preventDefault(); 49 | if (!validateFields()) return; 50 | try { 51 | await axiosFun( 52 | sendMessage( 53 | auth.conversations.name, 54 | match.params.roomId, 55 | match.params.name, 56 | auth.conversations.userId, 57 | `https://avatars.dicebear.com/api/jdenticon/${match.params.img}.svg`, 58 | message, 59 | match.params.description, 60 | "chatRoom" 61 | ) 62 | ); 63 | setMessage(""); 64 | } catch (err) { 65 | console.error(err); 66 | } 67 | }; 68 | 69 | return ( 70 |
71 | {data && data.length > 0 ? ( 72 | 73 |
74 | 77 |
78 |

{match.params.name}

79 |

{match.params.description}

80 |
81 |
82 |
83 | {auth.currentConversationMessages !== undefined && 84 | auth.currentConversationMessages && 85 | auth.currentConversationMessages.items.map((chat, idx) => ( 86 | 95 | ))} 96 |
97 |
98 | 99 |
100 |
sendMessageFun(e)}> 101 | setMessage(e.target.value)} 106 | onBlur={validateFields} 107 | /> 108 | 109 |
110 |
111 |
112 | ) : ( 113 | 120 | Portal Logo 126 |

127 | Choose a conversation/chatroom to see your chats 128 |

129 |
130 | )} 131 |
132 | ); 133 | }; 134 | 135 | export default ChatArea; 136 | -------------------------------------------------------------------------------- /src/Components/DesktopApp/ChatArea/ChatBubble.js: -------------------------------------------------------------------------------- 1 | import { Fragment } from "react"; 2 | import moment from "moment"; 3 | 4 | const ChatBubble = ({ authorName, message, sentAt, sender }) => { 5 | return ( 6 | 7 | {!sender ? ( 8 |

9 | {authorName} 10 | {message} 11 | 12 | {" "} 13 | {moment(sentAt).format("lll")}{" "} 14 | 15 |

16 | ) : ( 17 |

18 | {message} 19 |
20 | 21 | {" "} 22 | {moment(sentAt).format("lll")}{" "} 23 | 24 |

25 | )} 26 |
27 | ); 28 | }; 29 | 30 | export default ChatBubble; 31 | -------------------------------------------------------------------------------- /src/Components/DesktopApp/CreateGroup.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { makeStyles, TextField, Button, Typography } from "@material-ui/core"; 3 | import Modal from "@material-ui/core/Modal"; 4 | import { axiosFun } from "../../CRUD/axios.config"; 5 | import { 6 | createChatRoom, 7 | sendMessage, 8 | listUserDetails, 9 | } from "../../CRUD/queries"; 10 | import { toast } from "react-toastify"; 11 | import _ from "lodash"; 12 | toast.configure(); 13 | 14 | function getModalStyle() { 15 | return { 16 | top: "50%", 17 | left: "50%", 18 | transform: "translate(-50%, -50%)", 19 | width: "50vw", 20 | height: "50vh", 21 | }; 22 | } 23 | 24 | const useStyles = makeStyles((theme) => ({ 25 | paper: { 26 | position: "absolute", 27 | backgroundColor: theme.palette.background.paper, 28 | border: "2px solid #3855aa", 29 | boxShadow: "-1px 4px 20px -6px rgba(94, 84, 112, 1)", 30 | padding: "16px 24px", 31 | display: "flex", 32 | flexDirection: "column", 33 | justifyContent: "center", 34 | borderRadius: "20px", 35 | }, 36 | field: { 37 | marginTop: 20, 38 | display: "block", 39 | }, 40 | })); 41 | 42 | export default function CreateGroup({ open, setOpen, auth }) { 43 | const name = auth.conversations.name; 44 | const userId = auth.conversations.userId; 45 | const classes = useStyles(); 46 | const [modalStyle] = useState(getModalStyle); 47 | const [chatName, setChatName] = useState(""); 48 | const [description, setDescription] = useState(""); 49 | let [visited, setVisited] = useState({ 50 | chatName: false, 51 | description: false, 52 | }); 53 | let [errors, setErrors] = useState({}); 54 | const [loading, setLoading] = useState(false); 55 | 56 | // Final Validation 57 | const finalValidate = () => { 58 | errors = {}; 59 | 60 | if (chatName === undefined || chatName === "") 61 | errors.name = "Please enter group name"; 62 | 63 | if (description === undefined || description === "") 64 | errors.description = "Please enter group description"; 65 | 66 | setErrors(errors); 67 | 68 | if (Object.entries(errors).length === 0) { 69 | return true; 70 | } else { 71 | return false; 72 | } 73 | }; 74 | 75 | // Initial Validation 76 | const doValidate = () => { 77 | errors = {}; 78 | 79 | if ((chatName === undefined || chatName === "") && visited.name) 80 | errors.name = "Please enter group name"; 81 | 82 | if ((description === undefined || description === "") && visited.name) 83 | errors.description = "Please enter group description"; 84 | 85 | setErrors(errors); 86 | 87 | if (Object.entries(errors).length === 0) { 88 | return true; 89 | } else { 90 | return false; 91 | } 92 | }; 93 | 94 | // Handle Visited Fields 95 | const handleVisited = (e) => { 96 | let { name } = e.target || e; 97 | _.set(visited, name, true); 98 | setVisited({ ...visited }); 99 | doValidate(); 100 | }; 101 | 102 | const handleSubmit = async (e) => { 103 | e.preventDefault(); 104 | if (!finalValidate()) return; 105 | setLoading(true); 106 | try { 107 | const output = await axiosFun( 108 | createChatRoom(userId, chatName, description, name) 109 | ); 110 | const message = "Chat Room Created Successfully"; 111 | toast.success(message, { 112 | position: "top-right", 113 | autoClose: 0, 114 | hideProgressBar: false, 115 | closeOnClick: true, 116 | pauseOnHover: true, 117 | draggable: true, 118 | }); 119 | 120 | await axiosFun( 121 | sendMessage( 122 | name, 123 | output.data.createChatRooms.chatRoomId, 124 | chatName, 125 | userId, 126 | output.data.createChatRooms.chatRoomImage, 127 | "Welcome to the group", 128 | description, 129 | "chatRoom" 130 | ) 131 | ); 132 | const conversation = await axiosFun(listUserDetails(userId)); 133 | auth.setConversations(conversation.data.listUserss.items[0]); 134 | auth.setSubscriptionArray( 135 | conversation.data.listUserss.items[0].conversations.items 136 | ); 137 | setDescription(""); 138 | setChatName(""); 139 | setOpen(false); 140 | setLoading(false); 141 | } catch (error) { 142 | toast.error(error.message, { 143 | position: "top-right", 144 | autoClose: 0, 145 | hideProgressBar: false, 146 | closeOnClick: true, 147 | pauseOnHover: true, 148 | draggable: true, 149 | }); 150 | setLoading(false); 151 | } 152 | setLoading(false); 153 | }; 154 | 155 | const handleClose = () => { 156 | setDescription(""); 157 | setChatName(""); 158 | setOpen(false); 159 | }; 160 | 161 | return ( 162 |
163 | handleClose()}> 164 |
165 | 166 | Create Chat Room 167 | 168 |
handleSubmit(e)}> 169 | setChatName(e.target.value)} 181 | onBlur={handleVisited} 182 | name="chatName" 183 | /> 184 | {errors.chatName || ""} 185 | 186 | setDescription(e.target.value)} 190 | label="Description" 191 | variant="outlined" 192 | required 193 | fullWidth 194 | type="text" 195 | multiline 196 | rows={3} 197 | inputProps={{ 198 | maxLength: 128, 199 | }} 200 | onBlur={handleVisited} 201 | name="description" 202 | /> 203 | {errors.description || ""} 204 | 214 | 215 |
216 |
217 |
218 | ); 219 | } 220 | -------------------------------------------------------------------------------- /src/Components/DesktopApp/DesktopApp.js: -------------------------------------------------------------------------------- 1 | import Sidebar from "./Sidebar/Sidebar"; 2 | import ChatArea from "./ChatArea/ChatArea"; 3 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; 4 | import Signup from "./AuthScreens/Signup"; 5 | import { Fragment } from "react"; 6 | import Login from "./AuthScreens/Login"; 7 | import ForgetPassword from "./AuthScreens/ForgetPassword"; 8 | import NewPassword from "./AuthScreens/NewPassword"; 9 | 10 | const DesktopApp = ({ auth }) => { 11 | return ( 12 | 13 | {!auth.isAuthenticated ? ( 14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | } 30 | /> 31 | } 34 | /> 35 | } 38 | /> 39 | 40 | 41 |
42 |
43 | ) : ( 44 |
45 |
46 | 47 | 48 | 49 | } 52 | /> 53 | } 57 | /> 58 | 59 | 60 |
61 |
62 | )} 63 |
64 | ); 65 | }; 66 | 67 | export default DesktopApp; 68 | -------------------------------------------------------------------------------- /src/Components/DesktopApp/JoinGroup.js: -------------------------------------------------------------------------------- 1 | import React, { useState, Fragment, useEffect } from "react"; 2 | import { 3 | makeStyles, 4 | Typography, 5 | Modal, 6 | List, 7 | ListItem, 8 | Divider, 9 | ListItemText, 10 | ListItemAvatar, 11 | Avatar, 12 | } from "@material-ui/core"; 13 | import { 14 | listAllChatRooms, 15 | getLastMessage, 16 | joinChatRoom, 17 | listUserDetails, 18 | } from "../../CRUD/queries"; 19 | import { axiosFun } from "../../CRUD/axios.config"; 20 | import { toast } from "react-toastify"; 21 | toast.configure(); 22 | 23 | function getModalStyle() { 24 | return { 25 | top: "50%", 26 | left: "50%", 27 | transform: "translate(-50%, -50%)", 28 | width: "40vw", 29 | height: "70vh", 30 | overflowY: "scroll" 31 | }; 32 | } 33 | 34 | const useStyles = makeStyles((theme) => ({ 35 | paper: { 36 | position: "absolute", 37 | backgroundColor: theme.palette.background.paper, 38 | border: "2px solid #3855aa", 39 | boxShadow: "-1px 4px 20px -6px rgba(94, 84, 112, 1)", 40 | padding: "32px", 41 | borderRadius: "20px", 42 | }, 43 | field: { 44 | marginTop: 20, 45 | marginBottom: 20, 46 | display: "block", 47 | }, 48 | root: { 49 | width: "100%", 50 | backgroundColor: theme.palette.background.paper, 51 | }, 52 | inline: { 53 | display: "inline", 54 | }, 55 | })); 56 | 57 | export default function JoinGroup({ open, setOpen, auth }) { 58 | 59 | const classes = useStyles(); 60 | const [chatRoomList, setChatRoomList] = useState(null); 61 | const [modalStyle] = useState(getModalStyle); 62 | useEffect(() => { 63 | async function fetchData() { 64 | const response = await axiosFun(listAllChatRooms); 65 | setChatRoomList(response.data.listChatRoomss.items); 66 | } 67 | fetchData(); 68 | }, [open]); 69 | 70 | const newListMaker = () => { 71 | const userChatRoomIds = auth.conversations.conversations.items.map(conversation => conversation.conversationId); 72 | return chatRoomList.filter(room => !(userChatRoomIds.includes(room.chatRoomId))) 73 | } 74 | 75 | const joinGroupFun = async ( 76 | chatRoomId, 77 | chatRoomImage, 78 | chatRoomName, 79 | chatDescription 80 | ) => { 81 | try { 82 | const res = await axiosFun(getLastMessage(chatRoomId)); 83 | await axiosFun( 84 | joinChatRoom( 85 | chatRoomId, 86 | chatRoomImage, 87 | chatRoomName, 88 | "chatRoom", 89 | res.data.listMessagess.items[0].message, 90 | res.data.listMessagess.items[0].sentAt, 91 | auth.conversations.userId, 92 | chatDescription 93 | ) 94 | ); 95 | const message = "Chat Room Joined Successfully"; 96 | toast.success(message, { 97 | position: "top-right", 98 | autoClose: 0, 99 | hideProgressBar: false, 100 | closeOnClick: true, 101 | pauseOnHover: true, 102 | draggable: true, 103 | }); 104 | const conversation = await axiosFun( 105 | listUserDetails(auth.conversations.userId) 106 | ); 107 | auth.setConversations(conversation.data.listUserss.items[0]); 108 | auth.setSubscriptionArray( 109 | conversation.data.listUserss.items[0].conversations.items 110 | ); 111 | setOpen(false); 112 | } catch (err) { 113 | console.log(err); 114 | } 115 | }; 116 | 117 | const handleClose = () => { 118 | setOpen(false); 119 | }; 120 | 121 | return ( 122 |
123 | handleClose()}> 124 |
125 | 126 | Explore Rooms 127 | 128 | 129 | {chatRoomList && newListMaker().length ? ( 130 | { 131 | newListMaker().map((list, idx) => ( 132 | 133 | 136 | joinGroupFun( 137 | list.chatRoomId, 138 | list.chatRoomImage, 139 | list.name, 140 | list.description 141 | ) 142 | } 143 | className="cursor-pointer" 144 | > 145 | 146 | 147 | 148 | {list.description}} 151 | /> 152 | 153 | 154 | 155 | )) 156 | } 157 | ) : ( 158 |
159 | You are already a part of all the available groups! Great going. 160 |
161 |
)} 162 |
163 |
164 |
165 |
166 | ); 167 | } 168 | -------------------------------------------------------------------------------- /src/Components/DesktopApp/Sidebar/Sidebar.css: -------------------------------------------------------------------------------- 1 | /************ General CSS ****************/ 2 | 3 | .sidebar { 4 | flex: 0.25; 5 | display: flex; 6 | flex-direction: column; 7 | border-radius: 20px; 8 | margin: 20px; 9 | background-color: #ffffff; 10 | box-shadow: -1px 4px 20px -6px rgba(94, 84, 112, 1); 11 | } 12 | 13 | /************ Sidebar Header ****************/ 14 | 15 | .sidebar_header { 16 | display: flex; 17 | justify-content: space-between; 18 | padding: 20px; 19 | } 20 | 21 | .side_header_right { 22 | display: flex; 23 | align-items: center; 24 | justify-content: space-between; 25 | min-width: 5vw; 26 | } 27 | 28 | .side_header_right > .MuiSvgIcon-root { 29 | margin-right: 2vw; 30 | font-size: 24px !important; 31 | fill: #B6C8EB !important; 32 | } 33 | 34 | /************ Sidebar Search Container ****************/ 35 | 36 | .sidebar_search { 37 | display: flex; 38 | align-items: center; 39 | height: 39px; 40 | padding: 10px; 41 | margin-bottom: 20px; 42 | } 43 | 44 | .sidebar_search_container { 45 | display: flex; 46 | align-items: center; 47 | background-color: #E1E7F7; 48 | width: 100%; 49 | border-radius: 20px; 50 | height: 48px; 51 | margin-left: 15px; 52 | margin-right: 15px; 53 | } 54 | .sidebar_search_container > input { 55 | border: none; 56 | width: 100%; 57 | padding: 10px; 58 | background-color: #E1E7F7; 59 | border-radius: 20px; 60 | height: 24px; 61 | margin-left: 10px; 62 | color: #5E5470; 63 | } 64 | 65 | .sidebar_search_container > input:focus-visible { 66 | outline: none; 67 | } 68 | 69 | .sidebar_search_container > .MuiSvgIcon-root { 70 | color: #A6A4AE; 71 | padding: 10px; 72 | } 73 | 74 | /************ Sidebar Chats ****************/ 75 | 76 | .sidebar_chats { 77 | flex: 1; 78 | background-color: white; 79 | overflow-y: scroll; 80 | border-bottom-left-radius: 20px; 81 | border-bottom-right-radius: 20px; 82 | } 83 | 84 | .sidebar_chat { 85 | display: flex; 86 | padding: 20px; 87 | cursor: pointer; 88 | border-bottom: 1px solid #f6f6f6; 89 | } 90 | 91 | .sidebar_chat_selected { 92 | background-color: #E1E7F7; 93 | -webkit-box-shadow: none; 94 | -moz-box-shadow: none; 95 | box-shadow: none; 96 | border-right: #E1E7F7; 97 | border-left: 3px solid #DE2B64; 98 | } 99 | 100 | .sidebar_chat:hover { 101 | background-color: #B6C8EB; 102 | } 103 | 104 | .sidebar_chat_info > h2 { 105 | font-size: 16px; 106 | margin-bottom: 8px; 107 | color: #5E5470; 108 | } 109 | 110 | .sidebar_chat_info { 111 | margin-left: 15px; 112 | font-size: 12px; 113 | color: #A6A4AE; 114 | } 115 | 116 | .sidebar_chat_elipse { 117 | overflow: hidden; 118 | display: -webkit-box; 119 | -webkit-line-clamp: 1; 120 | -webkit-box-orient: vertical; 121 | } 122 | 123 | .no_sidebar_chat { 124 | display: flex; 125 | padding: 20px; 126 | cursor: pointer; 127 | text-align: center; 128 | } 129 | 130 | .no_sidebar_chat > span{ 131 | width: 100%; 132 | } -------------------------------------------------------------------------------- /src/Components/DesktopApp/Sidebar/Sidebar.js: -------------------------------------------------------------------------------- 1 | import "./Sidebar.css"; 2 | import { Avatar, IconButton } from "@material-ui/core"; 3 | import AddIcon from "@material-ui/icons/Add"; 4 | import SearchOutlinedIcon from "@material-ui/icons/SearchOutlined"; 5 | import ExitToAppIcon from "@material-ui/icons/ExitToApp"; 6 | import SidebarChat from "./SidebarChat"; 7 | import { Auth } from "aws-amplify"; 8 | import { useHistory } from "react-router-dom"; 9 | import { toast } from "react-toastify"; 10 | import { Fragment, useState } from "react"; 11 | import CreateGroup from "../CreateGroup"; 12 | import JoinGroup from "../JoinGroup"; 13 | toast.configure(); 14 | 15 | const Sidebar = ({ auth }) => { 16 | const history = useHistory(); 17 | const [open, setOpen] = useState(false); 18 | const [openJoinGroup, setOpenJoinGroup] = useState(false); 19 | 20 | // Logout Function 21 | const handleLogout = async (e) => { 22 | e.preventDefault(); 23 | try { 24 | Auth.signOut(); 25 | auth.setAuthenticated(false); 26 | auth.setUser(null); 27 | auth.setConversations(null); 28 | let message = "Logged Out Successfully"; 29 | toast.success(message, { 30 | position: "top-right", 31 | autoClose: 0, 32 | hideProgressBar: false, 33 | closeOnClick: true, 34 | pauseOnHover: true, 35 | draggable: true, 36 | }); 37 | history.push("/"); 38 | } catch (err) { 39 | console.error(err.message); 40 | } 41 | }; 42 | 43 | return ( 44 | 45 | {auth.conversations && auth.subscriptionArray ? ( 46 |
47 |
48 | 49 |
50 | setOpenJoinGroup(true)}> 51 | 52 | 53 | setOpen(true)}> 54 | 55 | 56 | handleLogout(e)}> 57 | 58 | 59 |
60 |
61 |
62 | {auth.subscriptionArray.length ? ( 63 | 64 | {" "} 65 | {auth.subscriptionArray 66 | .sort( 67 | (a, b) => 68 | new Date(b.lastMessageAt) - new Date(a.lastMessageAt) 69 | ) 70 | .map((chat, idx) => ( 71 | 83 | ))} 84 | 85 | ) : ( 86 |
87 | {" "} 88 | No chats yet. {" "} 89 |
90 | )} 91 |
92 | 93 | 94 |
95 | ) : ( 96 |
Loading
97 | )} 98 |
99 | ); 100 | }; 101 | 102 | export default Sidebar; 103 | -------------------------------------------------------------------------------- /src/Components/DesktopApp/Sidebar/SidebarChat.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import { Avatar, Chip } from "@material-ui/core"; 3 | import moment from "moment"; 4 | import { Link } from "react-router-dom"; 5 | import { API, graphqlOperation } from "aws-amplify"; 6 | import { useEffect } from "react"; 7 | 8 | const SidebarChat = ({ 9 | lastMessageAt, 10 | conversationImage, 11 | chatRoomId, 12 | name, 13 | lastMessage, 14 | description, 15 | auth, 16 | newMessages, 17 | }) => { 18 | const updateSubscriptionArray = (subscriptionDetails) => { 19 | auth.subscriptionArray.forEach((data) => { 20 | if (data.conversationId === subscriptionDetails.conversationId) { 21 | data.lastMessage = subscriptionDetails.message; 22 | data.lastMessageAt = subscriptionDetails.sentAt; 23 | if ( 24 | subscriptionDetails.conversationId !== 25 | auth.currentConversationMessages.conversationId 26 | ) 27 | data.newMessages = 28 | "newMessages" in data ? (data.newMessages += 1) : 1; 29 | } 30 | }); 31 | }; 32 | 33 | const updateCurrentConversations = (subscriptionDetails) => { 34 | if ( 35 | subscriptionDetails.conversationId === 36 | auth.currentConversationMessages.conversationId 37 | ) { 38 | auth.currentConversationMessages.items.push(subscriptionDetails); 39 | const temp = auth.currentConversationMessages; 40 | // auth.setCurrentConversationMessages([]) 41 | auth.setCurrentConversationMessages(temp); 42 | } 43 | }; 44 | 45 | const subscriptionRequest = ` 46 | subscription MySubscription { 47 | subscribeToNewMessage(conversationId: "${chatRoomId}") { 48 | authorId 49 | authorName 50 | message 51 | sentAt 52 | conversationId 53 | } 54 | } 55 | `; 56 | 57 | let subscriptionOnCreate; 58 | 59 | const subscription = () => { 60 | subscriptionOnCreate = API.graphql( 61 | graphqlOperation(subscriptionRequest) 62 | ).subscribe({ 63 | next: (res) => { 64 | updateCurrentConversations(res.value.data.subscribeToNewMessage); 65 | updateSubscriptionArray(res.value.data.subscribeToNewMessage); 66 | auth.setSubscriptionArray([]); 67 | auth.setSubscriptionArray(auth.subscriptionArray); 68 | }, 69 | }); 70 | }; 71 | 72 | const resetNewMessages = (conversationId) => { 73 | const temp = auth.subscriptionArray.map((data) => { 74 | if (data.conversationId === conversationId) data.newMessages = 0; 75 | return data; 76 | }); 77 | auth.setSubscriptionArray([]); 78 | auth.setSubscriptionArray(temp); 79 | }; 80 | 81 | useEffect(() => { 82 | subscription(); 83 | return () => { 84 | subscriptionOnCreate.unsubscribe(); 85 | }; 86 | }, []); 87 | 88 | useEffect(() => { 89 | auth.setSubscriptionArray(auth.subscriptionArray); 90 | }, [auth.subscriptionArray]); 91 | 92 | return ( 93 | 99 |
resetNewMessages(chatRoomId)} 102 | > 103 | 104 |
105 |

106 | {" "} 107 | {name}  {" "} 108 | {newMessages > 0 && ( 109 | 115 | )}{" "} 116 |

117 |

{lastMessage}

118 |

119 | {" "} 120 | Last Active: {moment(lastMessageAt).format("lll")}{" "} 121 |

122 |
123 |
124 | 125 | ); 126 | }; 127 | 128 | export default SidebarChat; 129 | -------------------------------------------------------------------------------- /src/Components/MobileApp/AuthScreens/ForgetPassword.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { 3 | Grid, 4 | Container, 5 | TextField, 6 | Button, 7 | Typography, 8 | makeStyles, 9 | } from "@material-ui/core"; 10 | import { Link, useParams, useHistory } from "react-router-dom"; 11 | import { Auth } from "aws-amplify"; 12 | import { toast } from "react-toastify"; 13 | toast.configure(); 14 | 15 | const useStyles = makeStyles({ 16 | field: { 17 | marginTop: 20, 18 | marginBottom: 20, 19 | display: "block", 20 | }, 21 | subtitles: { 22 | marginTop: 5, 23 | marginBottom: 5, 24 | display: "block", 25 | }, 26 | }); 27 | 28 | const ForgetPassword = () => { 29 | const params = useParams(); 30 | const history = useHistory(); 31 | const classes = useStyles(); 32 | const [email, setemail] = useState(params.email); 33 | 34 | const handleSubmit = async (e) => { 35 | e.preventDefault(); 36 | try { 37 | await Auth.forgotPassword(email); 38 | toast.success("Sent a verification code to your email!", { 39 | position: "top-right", 40 | autoClose: 5000, 41 | hideProgressBar: false, 42 | closeOnClick: true, 43 | pauseOnHover: true, 44 | draggable: true, 45 | }); 46 | history.push(`/new-password/${email}`); 47 | } catch (err) { 48 | let error = err.message || "Something went wrong!"; 49 | toast.error(error, { 50 | position: "top-right", 51 | autoClose: 5000, 52 | hideProgressBar: false, 53 | closeOnClick: true, 54 | pauseOnHover: true, 55 | draggable: true, 56 | }); 57 | } 58 | }; 59 | 60 | return ( 61 |
62 | 63 | 81 | 82 | Reset Password 83 | 84 | 91 | Enter the email address associated with your account 92 | 93 |
94 | setemail(e.target.value)} 102 | value={email} 103 | /> 104 | 112 | 113 | 119 | Try signing in again?{" "} 120 | 121 | 122 | {" "} 123 | Sign In{" "} 124 | 125 | 126 | 127 |
128 |
129 |
130 | ); 131 | }; 132 | 133 | export default ForgetPassword; 134 | -------------------------------------------------------------------------------- /src/Components/MobileApp/AuthScreens/Login.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { 3 | Grid, 4 | Container, 5 | TextField, 6 | Button, 7 | Typography, 8 | makeStyles, 9 | } from "@material-ui/core"; 10 | import { Link, useHistory } from "react-router-dom"; 11 | import { Auth } from "aws-amplify"; 12 | import { toast } from "react-toastify"; 13 | import { axiosFun } from "../../../CRUD/axios.config"; 14 | import { listUserDetails } from "../../../CRUD/queries"; 15 | toast.configure(); 16 | 17 | const useStyles = makeStyles({ 18 | field: { 19 | marginTop: 20, 20 | display: "block", 21 | }, 22 | subtitles: { 23 | marginTop: 5, 24 | marginBottom: 5, 25 | display: "block", 26 | }, 27 | }); 28 | 29 | const Login = ({ auth }) => { 30 | const history = useHistory(); 31 | const classes = useStyles(); 32 | const [email, setemail] = useState(""); 33 | const [password, setpassword] = useState(""); 34 | const [resendEmail, setResendEmail] = useState(false); 35 | 36 | const handleSubmit = async (e) => { 37 | e.preventDefault(); 38 | try { 39 | const res = await Auth.signIn(email, password); 40 | let message = "Signed in successfully! Welcome back!!"; 41 | toast.success(message, { 42 | position: "top-right", 43 | autoClose: 0, 44 | hideProgressBar: false, 45 | closeOnClick: true, 46 | pauseOnHover: true, 47 | draggable: true, 48 | }); 49 | //setLoading(false); 50 | auth.setAuthenticated(true); 51 | auth.setUser(res); 52 | const conversation = await axiosFun(listUserDetails(res.username)); 53 | auth.setConversations(conversation.data.listUserss.items[0]); 54 | auth.setSubscriptionArray(conversation.data.listUserss.items[0].conversations.items) 55 | history.push(`/`); 56 | } catch (err) { 57 | let error = err.message; 58 | if (err.message === "User is not confirmed.") { 59 | error = 60 | "Your account verification not complete. Please complete the verification before logging in."; 61 | } 62 | toast.error(error, { 63 | position: "top-right", 64 | autoClose: 0, 65 | hideProgressBar: false, 66 | closeOnClick: true, 67 | pauseOnHover: true, 68 | draggable: true, 69 | }); 70 | if (err.message === "User is not confirmed.") setResendEmail(true); 71 | } 72 | }; 73 | 74 | // Resend Confirmation Link 75 | const resendConfirmationLink = async (e) => { 76 | e.preventDefault(); 77 | try { 78 | await Auth.resendSignUp(email); 79 | toast.success( 80 | "Verification email resent successfully. Please verify your account by clicking that link before logging in.", 81 | { 82 | position: "top-right", 83 | autoClose: 5000, 84 | hideProgressBar: false, 85 | closeOnClick: true, 86 | pauseOnHover: true, 87 | draggable: true, 88 | } 89 | ); 90 | setResendEmail(false); 91 | } catch (err) { 92 | let error = err.message || "Something went wrong!"; 93 | toast.error(error, { 94 | position: "top-right", 95 | autoClose: 5000, 96 | hideProgressBar: false, 97 | closeOnClick: true, 98 | pauseOnHover: true, 99 | draggable: true, 100 | }); 101 | } 102 | }; 103 | 104 | return ( 105 |
106 | 107 | 125 | 126 | Sign In 127 | 128 |
129 | setemail(e.target.value)} 138 | /> 139 | {resendEmail && ( 140 | 147 | resendConfirmationLink(e)}> 148 | {" "} 149 | Missed Confirmation Link? 150 | 151 | 152 | )} 153 | setpassword(e.target.value)} 156 | label="Password" 157 | variant="outlined" 158 | required 159 | fullWidth 160 | type="password" 161 | className={classes.field} 162 | /> 163 | 169 | Forgot Password?{" "} 170 | 171 | 172 | {" "} 173 | Click here{" "} 174 | 175 | 176 | 177 | 185 | 186 | 192 | New User?{" "} 193 | 194 | 195 | {" "} 196 | Register{" "} 197 | 198 | 199 | 200 |
201 |
202 |
203 | ); 204 | }; 205 | 206 | export default Login; 207 | -------------------------------------------------------------------------------- /src/Components/MobileApp/AuthScreens/NewPassword.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { 3 | Grid, 4 | Container, 5 | TextField, 6 | Button, 7 | Typography, 8 | makeStyles, 9 | } from "@material-ui/core"; 10 | import { Link, useParams, useHistory } from "react-router-dom"; 11 | import { Auth } from "aws-amplify"; 12 | import { toast } from "react-toastify"; 13 | toast.configure(); 14 | 15 | const useStyles = makeStyles({ 16 | field: { 17 | marginTop: 20, 18 | marginBottom: 20, 19 | display: "block", 20 | }, 21 | subtitles: { 22 | marginTop: 5, 23 | marginBottom: 5, 24 | display: "block", 25 | }, 26 | }); 27 | 28 | const NewPassword = () => { 29 | const params = useParams(); 30 | const history = useHistory(); 31 | const classes = useStyles(); 32 | const [verificationCode, setverificationCode] = useState(""); 33 | const [password, setpassword] = useState(""); 34 | 35 | const handleSubmit = async (e) => { 36 | e.preventDefault(); 37 | try { 38 | await Auth.forgotPasswordSubmit( 39 | params.email, 40 | verificationCode.trim(), 41 | password 42 | ); 43 | toast.success("Password created successfully!", { 44 | position: "top-right", 45 | autoClose: 5000, 46 | hideProgressBar: false, 47 | closeOnClick: true, 48 | pauseOnHover: true, 49 | draggable: true, 50 | }); 51 | history.push(`/signin`); 52 | } catch (err) { 53 | let error = err.message || "Something went wrong!"; 54 | toast.error(error, { 55 | position: "top-right", 56 | autoClose: 5000, 57 | hideProgressBar: false, 58 | closeOnClick: true, 59 | pauseOnHover: true, 60 | draggable: true, 61 | }); 62 | } 63 | }; 64 | 65 | return ( 66 |
67 | 68 | 86 | 87 | Reset Password 88 | 89 |
90 | setverificationCode(e.target.value)} 98 | value={verificationCode} 99 | /> 100 | setpassword(e.target.value)} 102 | label="New Password (min 8 chars: lc+uc+sc+num)" 103 | variant="outlined" 104 | required 105 | fullWidth 106 | type="password" 107 | className={classes.field} 108 | /> 109 | 117 | 118 | 124 | Try signing in again?{" "} 125 | 126 | 127 | {" "} 128 | Sign In{" "} 129 | 130 | 131 | 132 |
133 |
134 |
135 | ); 136 | }; 137 | 138 | export default NewPassword; 139 | -------------------------------------------------------------------------------- /src/Components/MobileApp/AuthScreens/Signup.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { 3 | Grid, 4 | Container, 5 | TextField, 6 | Button, 7 | Typography, 8 | makeStyles, 9 | } from "@material-ui/core"; 10 | import { Link, useHistory } from "react-router-dom"; 11 | import { Auth } from "aws-amplify"; 12 | import { toast } from "react-toastify"; 13 | import { axiosFun } from "../../../CRUD/axios.config"; 14 | import { createUser } from "../../../CRUD/queries"; 15 | toast.configure(); 16 | 17 | const useStyles = makeStyles({ 18 | field: { 19 | marginTop: 20, 20 | marginBottom: 20, 21 | display: "block", 22 | }, 23 | subtitles: { 24 | marginTop: 5, 25 | marginBottom: 5, 26 | display: "block", 27 | }, 28 | }); 29 | 30 | const Signup = () => { 31 | const history = useHistory(); 32 | const classes = useStyles(); 33 | const [name, setName] = useState(""); 34 | const [username, setusername] = useState(""); 35 | const [password, setpassword] = useState(""); 36 | const profileImage = `https://avatars.dicebear.com/api/bottts/${Math.floor( 37 | Math.random() * 5000 38 | )}.svg`; 39 | const handleSubmit = async (e) => { 40 | e.preventDefault(); 41 | try { 42 | const res = await Auth.signUp({ 43 | username, 44 | password, 45 | attributes: { 46 | name: name, 47 | picture: profileImage, 48 | }, 49 | }); 50 | 51 | await axiosFun(createUser(res.userSub, name, profileImage)); 52 | let message = 53 | "Verification email successfully. Please verify your account by clicking that link before logging in."; 54 | toast.success(message, { 55 | position: "top-right", 56 | autoClose: 0, 57 | hideProgressBar: false, 58 | closeOnClick: true, 59 | pauseOnHover: true, 60 | draggable: true, 61 | }); 62 | history.push(`/signin`); 63 | } catch (error) { 64 | toast.error(error.message, { 65 | position: "top-right", 66 | autoClose: 0, 67 | hideProgressBar: false, 68 | closeOnClick: true, 69 | pauseOnHover: true, 70 | draggable: true, 71 | }); 72 | } 73 | }; 74 | 75 | return ( 76 |
77 | 78 | 96 | 97 | Sign Up 98 | 99 |
100 | setName(e.target.value)} 109 | /> 110 | 111 | setusername(e.target.value)} 120 | /> 121 | setpassword(e.target.value)} 123 | label="Password (min 8 chars: lc+uc+sc+num)" 124 | variant="outlined" 125 | required 126 | fullWidth 127 | type="password" 128 | value={password} 129 | className={classes.field} 130 | /> 131 | 139 | 140 | 146 | Already Have Account?{" "} 147 | 148 | 149 | {" "} 150 | Sign In{" "} 151 | 152 | 153 | 154 |
155 |
156 |
157 | ); 158 | }; 159 | 160 | export default Signup; 161 | -------------------------------------------------------------------------------- /src/Components/MobileApp/ChatArea/ChatArea.css: -------------------------------------------------------------------------------- 1 | /************ General CSS ****************/ 2 | 3 | .mobile_chat { 4 | flex: 1; 5 | display: flex; 6 | flex-direction: column; 7 | background-color: #e1e7f7; 8 | } 9 | 10 | /************ mobile_chat Header ****************/ 11 | 12 | .mobile_chat_header { 13 | padding: 20px; 14 | display: flex; 15 | align-items: center; 16 | } 17 | 18 | .mobile_chat_header > .MuiSvgIcon-root { 19 | color: grey; 20 | } 21 | 22 | .mobile_chat_header_info { 23 | flex: 1; 24 | padding-left: 20px; 25 | } 26 | 27 | .mobile_chat_header_info > h3 { 28 | margin-bottom: 3px; 29 | font-weight: 500; 30 | color: #5e5470; 31 | } 32 | 33 | .mobile_chat_header_info > p { 34 | color: #a6a4ae; 35 | font-size: 14px; 36 | } 37 | 38 | .mobile_chat_header_right { 39 | display: flex; 40 | justify-content: space-between; 41 | min-width: 20px; 42 | } 43 | 44 | /************ mobile_chat Body ****************/ 45 | 46 | .mobile_chat_body { 47 | flex: 1; 48 | background-repeat: repeat; 49 | background-position: center; 50 | padding: 2% 4%; 51 | overflow-y: scroll; 52 | } 53 | 54 | /************ mobile_chat Messages ****************/ 55 | 56 | .mobile_chat_message { 57 | position: relative; 58 | font-size: 16px; 59 | background-color: #ffffff; 60 | border-radius: 10px; 61 | min-width: 30vw; 62 | width: fit-content; 63 | max-width: 80vw; 64 | margin-bottom: 30px; 65 | border-radius: 20px; 66 | padding: 20px; 67 | box-shadow: 1px 4px 20px -6px rgba(94, 84, 112, 1); 68 | } 69 | 70 | .mobile_chat_name { 71 | color: #5e5470; 72 | font-size: 12px; 73 | margin-bottom: 5px; 74 | display: block; 75 | font-weight: bold; 76 | } 77 | 78 | .mobile_chat_reciever { 79 | margin-left: auto; 80 | background-color: #de2b64 !important; 81 | color: #ffffff; 82 | } 83 | 84 | .mobile_chat_timestamp { 85 | font-size: xx-small; 86 | text-align: left; 87 | display: block; 88 | color: #a6a4ae; 89 | margin-top: 10px; 90 | } 91 | 92 | .mobile_chat_reciever_timestamp { 93 | font-size: xx-small; 94 | text-align: right; 95 | display: block; 96 | color: #ffffff; 97 | margin-top: 10px; 98 | } 99 | 100 | /************ mobile_chat Footer ****************/ 101 | 102 | .mobile_chat_footer { 103 | display: flex; 104 | justify-content: space-between; 105 | align-items: center; 106 | height: 62px; 107 | } 108 | 109 | .mobile_chat_footer > form { 110 | flex: 1; 111 | display: flex; 112 | } 113 | 114 | .mobile_chat_footer > form > input { 115 | flex: 1; 116 | padding: 10px; 117 | border: none; 118 | margin-right: 10px; 119 | background-color: #ffffff; 120 | border-radius: 20px; 121 | height: 24px; 122 | margin-left: 10px; 123 | color: #5e5470; 124 | } 125 | 126 | .mobile_chat_footer > form > input:focus-visible { 127 | outline: none; 128 | } 129 | 130 | .mobile_chat_footer > form > button { 131 | display: none; 132 | } 133 | 134 | .mobile_chat_footer > .MuiSvgIcon-root { 135 | padding: 10px; 136 | color: grey; 137 | } 138 | -------------------------------------------------------------------------------- /src/Components/MobileApp/ChatArea/ChatArea.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable react-hooks/exhaustive-deps */ 3 | import { Avatar, IconButton } from "@material-ui/core"; 4 | import "./ChatArea.css"; 5 | import { useEffect, useState, Fragment, useRef } from "react"; 6 | import ArrowBackIcon from "@material-ui/icons/ArrowBack"; 7 | import { Link } from "react-router-dom"; 8 | import { axiosFun } from "../../../CRUD/axios.config"; 9 | import { getMessages, sendMessage } from "../../../CRUD/queries"; 10 | import ChatBubble from "./ChatBubble"; 11 | import { API, graphqlOperation } from "aws-amplify"; 12 | 13 | const ChatArea = ({ match, auth }) => { 14 | const [message, setMessage] = useState(""); 15 | const [error, setError] = useState(""); 16 | const [data, setData] = useState(null); 17 | const scrollref = useRef(); 18 | useEffect(() => { 19 | getChats(); 20 | }, [match.params.roomId]); 21 | 22 | useEffect(() => { 23 | scrollref.current?.scrollIntoView({ behaviour: "smooth" }); 24 | }); 25 | 26 | const getChats = async () => { 27 | const res = await axiosFun(getMessages(match.params.roomId)); 28 | setData(res.data.listMessagess.items); 29 | auth.currentConversationMessages = { 30 | conversationId: match.params.roomId, 31 | ...res.data.listMessagess, 32 | }; 33 | auth.setCurrentConversationMessages({ 34 | conversationId: match.params.roomId, 35 | ...res.data.listMessagess, 36 | }); 37 | }; 38 | 39 | // Validation 40 | const validateFields = () => { 41 | setError(""); 42 | if (message === null || message === "") { 43 | setError("You can't send a blank message"); 44 | return false; 45 | } 46 | return true; 47 | }; 48 | 49 | const sendMessageFun = async (e) => { 50 | e.preventDefault(); 51 | if (!validateFields()) return; 52 | try { 53 | await axiosFun( 54 | sendMessage( 55 | auth.conversations.name, 56 | match.params.roomId, 57 | match.params.name, 58 | auth.conversations.userId, 59 | `https://avatars.dicebear.com/api/jdenticon/${match.params.img}.svg`, 60 | message, 61 | match.params.description, 62 | "chatRoom" 63 | ) 64 | ); 65 | setMessage(""); 66 | } catch (err) { 67 | console.error(err); 68 | } 69 | }; 70 | 71 | const updateSubscriptionArray = (subscriptionDetails) => { 72 | auth.subscriptionArray.forEach((data) => { 73 | if (data.conversationId === subscriptionDetails.conversationId) { 74 | data.lastMessage = subscriptionDetails.message; 75 | data.lastMessageAt = subscriptionDetails.sentAt; 76 | if ( 77 | subscriptionDetails.conversationId !== 78 | auth.currentConversationMessages.conversationId 79 | ) 80 | data.newMessages = 81 | "newMessages" in data ? (data.newMessages += 1) : 1; 82 | } 83 | }); 84 | }; 85 | 86 | const updateCurrentConversations = (subscriptionDetails) => { 87 | if ( 88 | subscriptionDetails.conversationId === 89 | auth.currentConversationMessages.conversationId 90 | ) { 91 | auth.currentConversationMessages.items.push(subscriptionDetails); 92 | const temp = auth.currentConversationMessages; 93 | auth.setCurrentConversationMessages(temp); 94 | } 95 | }; 96 | 97 | const subscriptionRequest = ` 98 | subscription MySubscription { 99 | subscribeToNewMessage(conversationId: "${match.params.roomId}") { 100 | authorId 101 | authorName 102 | message 103 | sentAt 104 | conversationId 105 | } 106 | } 107 | `; 108 | 109 | let subscriptionOnCreate; 110 | const subscription = () => { 111 | subscriptionOnCreate = API.graphql( 112 | graphqlOperation(subscriptionRequest) 113 | ).subscribe({ 114 | next: (res) => { 115 | updateCurrentConversations(res.value.data.subscribeToNewMessage); 116 | updateSubscriptionArray(res.value.data.subscribeToNewMessage); 117 | auth.setSubscriptionArray(auth.subscriptionArray); 118 | }, 119 | }); 120 | }; 121 | 122 | useEffect(() => { 123 | subscription(); 124 | return () => { 125 | subscriptionOnCreate.unsubscribe(); 126 | }; 127 | }, []); 128 | 129 | return ( 130 | <> 131 | {data && data.length > 0 && ( 132 |
133 |
134 | 135 | 136 | 137 | 138 | 139 | 142 |
143 |

{match.params.name}

144 |

{match.params.description}

145 |
146 |
147 |
148 | {auth.currentConversationMessages !== undefined && 149 | auth.currentConversationMessages && 150 | auth.currentConversationMessages.items.map((chat, idx) => ( 151 | 160 | ))} 161 |
162 |
163 |
164 |
sendMessageFun(e)}> 165 | setMessage(e.target.value)} 170 | onBlur={validateFields} 171 | /> 172 | 173 |
174 |
175 |
176 | )} 177 | 178 | ); 179 | }; 180 | 181 | export default ChatArea; 182 | -------------------------------------------------------------------------------- /src/Components/MobileApp/ChatArea/ChatBubble.js: -------------------------------------------------------------------------------- 1 | import { Fragment } from "react"; 2 | import moment from "moment"; 3 | 4 | const ChatBubble = ({ authorName, message, sentAt, sender }) => { 5 | return ( 6 | 7 | {!sender ? ( 8 |

9 | {authorName} 10 | {message} 11 | 12 | {" "} 13 | {moment(sentAt).format("lll")}{" "} 14 | 15 |

16 | ) : ( 17 |

18 | {message} 19 |
20 | 21 | {" "} 22 | {moment(sentAt).format("lll")}{" "} 23 | 24 |

25 | )} 26 |
27 | ); 28 | }; 29 | 30 | export default ChatBubble; 31 | -------------------------------------------------------------------------------- /src/Components/MobileApp/CreateGroup.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { makeStyles, TextField, Button, Typography } from "@material-ui/core"; 3 | import Modal from "@material-ui/core/Modal"; 4 | import { axiosFun } from "../../CRUD/axios.config"; 5 | import { 6 | createChatRoom, 7 | sendMessage, 8 | listUserDetails, 9 | } from "../../CRUD/queries"; 10 | import { toast } from "react-toastify"; 11 | toast.configure(); 12 | 13 | function getModalStyle() { 14 | return { 15 | top: "50%", 16 | left: "50%", 17 | transform: "translate(-50%, -50%)", 18 | width: "60vw", 19 | height: "60vh", 20 | }; 21 | } 22 | 23 | const useStyles = makeStyles((theme) => ({ 24 | paper: { 25 | position: "absolute", 26 | backgroundColor: theme.palette.background.paper, 27 | border: "2px solid #3855aa", 28 | boxShadow: "-1px 4px 20px -6px rgba(94, 84, 112, 1)", 29 | padding: "16px 24px", 30 | display: "flex", 31 | flexDirection: "column", 32 | justifyContent: "center", 33 | borderRadius: "20px", 34 | }, 35 | field: { 36 | marginTop: 20, 37 | marginBottom: 20, 38 | display: "block", 39 | }, 40 | })); 41 | 42 | export default function CreateGroup({ open, setOpen, auth }) { 43 | const name = auth.conversations.name; 44 | const userId = auth.conversations.userId; 45 | const classes = useStyles(); 46 | const [modalStyle] = useState(getModalStyle); 47 | const [chatName, setChatName] = useState(""); 48 | const [description, setDescription] = useState(""); 49 | 50 | const handleSubmit = async (e) => { 51 | e.preventDefault(); 52 | try { 53 | const output = await axiosFun( 54 | createChatRoom(userId, chatName, description, name) 55 | ); 56 | const message = "Chat Room Created Successfully"; 57 | toast.success(message, { 58 | position: "top-right", 59 | autoClose: 0, 60 | hideProgressBar: false, 61 | closeOnClick: true, 62 | pauseOnHover: true, 63 | draggable: true, 64 | }); 65 | 66 | await axiosFun( 67 | sendMessage( 68 | name, 69 | output.data.createChatRooms.chatRoomId, 70 | chatName, 71 | userId, 72 | output.data.createChatRooms.chatRoomImage, 73 | "Welcome to the group", 74 | "chatRoom" 75 | ) 76 | ); 77 | const conversation = await axiosFun(listUserDetails(userId)); 78 | auth.setConversations(conversation.data.listUserss.items[0]); 79 | auth.setSubscriptionArray(conversation.data.listUserss.items[0].conversations.items) 80 | setDescription(""); 81 | setChatName(""); 82 | setOpen(false); 83 | } catch (error) { 84 | toast.error(error.message, { 85 | position: "top-right", 86 | autoClose: 0, 87 | hideProgressBar: false, 88 | closeOnClick: true, 89 | pauseOnHover: true, 90 | draggable: true, 91 | }); 92 | } 93 | }; 94 | 95 | const handleClose = () => { 96 | setDescription(""); 97 | setChatName(""); 98 | setOpen(false); 99 | }; 100 | 101 | return ( 102 |
103 | handleClose()}> 104 |
105 | 106 | Create Chat Room 107 | 108 |
handleSubmit(e)}> 109 | setChatName(e.target.value)} 121 | /> 122 | 123 | setDescription(e.target.value)} 127 | label="Description" 128 | variant="outlined" 129 | required 130 | fullWidth 131 | type="text" 132 | multiline 133 | rows={3} 134 | inputProps={{ 135 | maxLength: 128, 136 | }} 137 | /> 138 | 147 | 148 |
149 |
150 |
151 | ); 152 | } 153 | -------------------------------------------------------------------------------- /src/Components/MobileApp/MobileApp.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Sidebar from "./Sidebar/Sidebar"; 3 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; 4 | import ChatArea from "./ChatArea/ChatArea"; 5 | import Login from "./AuthScreens/Login"; 6 | import Signup from "./AuthScreens/Signup"; 7 | import ForgetPassword from "./AuthScreens/ForgetPassword"; 8 | import NewPassword from "./AuthScreens/NewPassword"; 9 | 10 | const MobileApp = ({ auth }) => { 11 | return ( 12 |
13 |
14 | {!auth.isAuthenticated ? ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | } 29 | /> 30 | } 33 | /> 34 | } 37 | /> 38 | 39 | 40 | ) : ( 41 | 42 | 43 | 44 | 45 | 46 | } 49 | /> 50 | 51 | 52 | )} 53 |
54 |
55 | ); 56 | }; 57 | 58 | export default MobileApp; 59 | -------------------------------------------------------------------------------- /src/Components/MobileApp/Sidebar/JoinGroup.js: -------------------------------------------------------------------------------- 1 | import React, { useState, Fragment, useEffect } from "react"; 2 | import { 3 | makeStyles, 4 | Typography, 5 | Modal, 6 | List, 7 | ListItem, 8 | Divider, 9 | ListItemText, 10 | ListItemAvatar, 11 | Avatar, 12 | } from "@material-ui/core"; 13 | import { 14 | listAllChatRooms, 15 | getLastMessage, 16 | joinChatRoom, 17 | listUserDetails, 18 | } from "../../../CRUD/queries"; 19 | import { axiosFun } from "../../../CRUD/axios.config"; 20 | import { toast } from "react-toastify"; 21 | toast.configure(); 22 | 23 | function getModalStyle() { 24 | return { 25 | top: "50%", 26 | left: "50%", 27 | transform: "translate(-50%, -50%)", 28 | width: "60vw", 29 | height: "70vh", 30 | overflowY: "scroll", 31 | }; 32 | } 33 | 34 | const useStyles = makeStyles((theme) => ({ 35 | paper: { 36 | position: "absolute", 37 | backgroundColor: theme.palette.background.paper, 38 | border: "2px solid #3855aa", 39 | boxShadow: "-1px 4px 20px -6px rgba(94, 84, 112, 1)", 40 | padding: "32px", 41 | borderRadius: "20px", 42 | }, 43 | field: { 44 | marginTop: 20, 45 | marginBottom: 20, 46 | display: "block", 47 | }, 48 | root: { 49 | width: "100%", 50 | backgroundColor: theme.palette.background.paper, 51 | }, 52 | inline: { 53 | display: "inline", 54 | }, 55 | })); 56 | 57 | export default function JoinGroup({ open, setOpen, auth }) { 58 | 59 | const classes = useStyles(); 60 | const [chatRoomList, setChatRoomList] = useState(null); 61 | const [modalStyle] = useState(getModalStyle); 62 | useEffect(() => { 63 | async function fetchData() { 64 | const response = await axiosFun(listAllChatRooms); 65 | setChatRoomList(response.data.listChatRoomss.items); 66 | } 67 | fetchData(); 68 | }, [open]); 69 | 70 | const newListMaker = () => { 71 | const userChatRoomIds = auth.conversations.conversations.items.map(conversation => conversation.conversationId); 72 | return chatRoomList.filter(room => !(userChatRoomIds.includes(room.chatRoomId))) 73 | } 74 | 75 | const joinGroupFun = async ( 76 | chatRoomId, 77 | chatRoomImage, 78 | chatRoomName, 79 | chatDescription 80 | ) => { 81 | try { 82 | const res = await axiosFun(getLastMessage(chatRoomId)); 83 | await axiosFun( 84 | joinChatRoom( 85 | chatRoomId, 86 | chatRoomImage, 87 | chatRoomName, 88 | "chatRoom", 89 | res.data.listMessagess.items[0].message, 90 | res.data.listMessagess.items[0].sentAt, 91 | auth.conversations.userId, 92 | chatDescription 93 | ) 94 | ); 95 | const message = "Chat Room Joined Successfully"; 96 | toast.success(message, { 97 | position: "top-right", 98 | autoClose: 0, 99 | hideProgressBar: false, 100 | closeOnClick: true, 101 | pauseOnHover: true, 102 | draggable: true, 103 | }); 104 | const conversation = await axiosFun( 105 | listUserDetails(auth.conversations.userId) 106 | ); 107 | auth.setConversations(conversation.data.listUserss.items[0]); 108 | auth.setSubscriptionArray( 109 | conversation.data.listUserss.items[0].conversations.items 110 | ); 111 | setOpen(false); 112 | } catch (err) { 113 | console.log(err); 114 | } 115 | }; 116 | 117 | const handleClose = () => { 118 | setOpen(false); 119 | }; 120 | 121 | return ( 122 |
123 | handleClose()}> 124 |
125 | 126 | Explore Rooms 127 | 128 | 129 | {chatRoomList && newListMaker().length ? ( 130 | { 131 | newListMaker().map((list, idx) => ( 132 | 133 | 136 | joinGroupFun( 137 | list.chatRoomId, 138 | list.chatRoomImage, 139 | list.name, 140 | list.description 141 | ) 142 | } 143 | className="cursor-pointer" 144 | > 145 | 146 | 147 | 148 | {list.description}} 151 | /> 152 | 153 | 154 | 155 | )) 156 | } 157 | ) : ( 158 |
159 | You are already a part of all the available groups! Great going. 160 |
161 |
)} 162 |
163 |
164 |
165 |
166 | ); 167 | } -------------------------------------------------------------------------------- /src/Components/MobileApp/Sidebar/Sidebar.css: -------------------------------------------------------------------------------- 1 | /************ General CSS ****************/ 2 | 3 | .mobile_sidebar { 4 | flex: 1; 5 | display: flex; 6 | flex-direction: column; 7 | background-color: #ffffff; 8 | } 9 | 10 | 11 | /************ mobile_sidebar Header ****************/ 12 | 13 | .mobile_sidebar_header { 14 | display: flex; 15 | justify-content: space-between; 16 | padding: 20px; 17 | } 18 | 19 | .side_header_right { 20 | display: flex; 21 | align-items: center; 22 | justify-content: space-between; 23 | min-width: 5vw; 24 | } 25 | 26 | .side_header_right > .MuiSvgIcon-root { 27 | margin-right: 2vw; 28 | font-size: 24px !important; 29 | fill: #B6C8EB !important; 30 | } 31 | 32 | /************ mobile_sidebar Search Container ****************/ 33 | 34 | .mobile_sidebar_search { 35 | display: flex; 36 | align-items: center; 37 | height: 39px; 38 | padding: 10px; 39 | margin-bottom: 20px; 40 | } 41 | 42 | .mobile_sidebar_search_container { 43 | display: flex; 44 | align-items: center; 45 | background-color: #E1E7F7; 46 | width: 100%; 47 | border-radius: 20px; 48 | height: 48px; 49 | margin-left: 15px; 50 | margin-right: 15px; 51 | } 52 | .mobile_sidebar_search_container > input { 53 | border: none; 54 | width: 100%; 55 | padding: 10px; 56 | background-color: #E1E7F7; 57 | border-radius: 20px; 58 | height: 24px; 59 | margin-left: 10px; 60 | color: #5E5470; 61 | } 62 | 63 | .mobile_sidebar_search_container > input:focus-visible { 64 | outline: none; 65 | } 66 | 67 | .mobile_sidebar_search_container > .MuiSvgIcon-root { 68 | color: #A6A4AE; 69 | padding: 10px; 70 | } 71 | 72 | /************ mobile_sidebar Chats ****************/ 73 | 74 | .mobile_sidebar_chats { 75 | flex: 1; 76 | background-color: white; 77 | border-bottom-left-radius: 20px; 78 | border-bottom-right-radius: 20px; 79 | } 80 | 81 | .mobile_sidebar_chat { 82 | display: flex; 83 | padding: 20px; 84 | cursor: pointer; 85 | border-bottom: 1px solid #f6f6f6; 86 | } 87 | 88 | .mobile_sidebar_chat_selected { 89 | background-color: #E1E7F7; 90 | -webkit-box-shadow: none; 91 | -moz-box-shadow: none; 92 | box-shadow: none; 93 | border-right: #E1E7F7; 94 | border-left: 3px solid #DE2B64; 95 | } 96 | 97 | .mobile_sidebar_chat:hover { 98 | background-color: #B6C8EB; 99 | } 100 | 101 | .mobile_sidebar_chat_info > h2 { 102 | font-size: 16px; 103 | margin-bottom: 8px; 104 | color: #5E5470; 105 | } 106 | 107 | .mobile_sidebar_chat_info { 108 | margin-left: 15px; 109 | font-size: 12px; 110 | color: #A6A4AE; 111 | } 112 | 113 | .mobile_sidebar_chat_elipse { 114 | overflow: hidden; 115 | display: -webkit-box; 116 | -webkit-line-clamp: 1; 117 | -webkit-box-orient: vertical; 118 | } -------------------------------------------------------------------------------- /src/Components/MobileApp/Sidebar/Sidebar.js: -------------------------------------------------------------------------------- 1 | import "./Sidebar.css"; 2 | import { Avatar, IconButton } from "@material-ui/core"; 3 | import ExitToAppIcon from "@material-ui/icons/ExitToApp"; 4 | import SearchOutlinedIcon from "@material-ui/icons/SearchOutlined"; 5 | import AddIcon from "@material-ui/icons/Add"; 6 | import SidebarChat from "./SidebarChat"; 7 | import { Auth } from "aws-amplify"; 8 | import { useHistory } from "react-router-dom"; 9 | import { toast } from "react-toastify"; 10 | import { Fragment, useState } from "react"; 11 | import CreateGroup from "../CreateGroup"; 12 | import JoinGroup from "./JoinGroup"; 13 | toast.configure(); 14 | 15 | const Sidebar = ({ auth }) => { 16 | const history = useHistory(); 17 | const [open, setOpen] = useState(false); 18 | const [openJoinGroup, setOpenJoinGroup] = useState(false); 19 | // Logout Function 20 | const handleLogout = async (e) => { 21 | e.preventDefault(); 22 | try { 23 | Auth.signOut(); 24 | auth.setAuthenticated(false); 25 | auth.setUser(null); 26 | auth.setConversations(null); 27 | let message = "Logged Out Successfully"; 28 | toast.success(message, { 29 | position: "top-right", 30 | autoClose: 0, 31 | hideProgressBar: false, 32 | closeOnClick: true, 33 | pauseOnHover: true, 34 | draggable: true, 35 | }); 36 | history.push("/"); 37 | } catch (err) { 38 | console.error(err.message); 39 | } 40 | }; 41 | return ( 42 | 43 | {auth.conversations && auth.subscriptionArray && ( 44 |
45 |
46 | 47 |
48 | setOpenJoinGroup(true)}> 49 | 50 | 51 | setOpen(true)}> 52 | 53 | 54 | handleLogout(e)}> 55 | 56 | 57 |
58 |
59 |
60 | {auth.subscriptionArray.length > 0 ? ( 61 | 62 | {" "} 63 | {auth.subscriptionArray 64 | .sort( 65 | (a, b) => 66 | new Date(b.lastMessageAt) - new Date(a.lastMessageAt) 67 | ) 68 | .map((chat, idx) => ( 69 | 81 | ))}{" "} 82 | 83 | ) : ( 84 |
85 | {" "} 86 | No chats yet. {" "} 87 |
88 | )} 89 |
90 | 91 | 92 |
93 | )} 94 |
95 | ); 96 | }; 97 | 98 | export default Sidebar; 99 | -------------------------------------------------------------------------------- /src/Components/MobileApp/Sidebar/SidebarChat.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import { Avatar, Chip } from "@material-ui/core"; 3 | import moment from "moment"; 4 | import { Link } from "react-router-dom"; 5 | import { API, graphqlOperation } from "aws-amplify"; 6 | import { useEffect } from "react"; 7 | 8 | const SidebarChat = ({ 9 | lastMessageAt, 10 | conversationImage, 11 | chatRoomId, 12 | name, 13 | lastMessage, 14 | description, 15 | auth, 16 | newMessages, 17 | }) => { 18 | const updateSubscriptionArray = (subscriptionDetails) => { 19 | auth.subscriptionArray.forEach((data) => { 20 | if (data.conversationId === subscriptionDetails.conversationId) { 21 | data.lastMessage = subscriptionDetails.message; 22 | data.lastMessageAt = subscriptionDetails.sentAt; 23 | if ( 24 | subscriptionDetails.conversationId !== 25 | auth.currentConversationMessages.conversationId 26 | ) 27 | data.newMessages = 28 | "newMessages" in data ? (data.newMessages += 1) : 1; 29 | } 30 | }); 31 | }; 32 | 33 | const updateCurrentConversations = (subscriptionDetails) => { 34 | if ( 35 | subscriptionDetails.conversationId === 36 | auth.currentConversationMessages.conversationId 37 | ) { 38 | auth.currentConversationMessages.items.push(subscriptionDetails); 39 | const temp = auth.currentConversationMessages; 40 | auth.setCurrentConversationMessages(temp); 41 | } 42 | }; 43 | 44 | const subscriptionRequest = ` 45 | subscription MySubscription { 46 | subscribeToNewMessage(conversationId: "${chatRoomId}") { 47 | authorId 48 | authorName 49 | message 50 | sentAt 51 | conversationId 52 | } 53 | } 54 | `; 55 | 56 | let subscriptionOnCreate; 57 | 58 | const subscription = () => { 59 | subscriptionOnCreate = API.graphql( 60 | graphqlOperation(subscriptionRequest) 61 | ).subscribe({ 62 | next: (res) => { 63 | updateCurrentConversations(res.value.data.subscribeToNewMessage); 64 | updateSubscriptionArray(res.value.data.subscribeToNewMessage); 65 | auth.setSubscriptionArray([]); 66 | auth.setSubscriptionArray(auth.subscriptionArray); 67 | }, 68 | }); 69 | }; 70 | 71 | const resetNewMessages = (conversationId) => { 72 | const temp = auth.subscriptionArray.map((data) => { 73 | if (data.conversationId === conversationId) data.newMessages = 0; 74 | return data; 75 | }); 76 | auth.setSubscriptionArray([]); 77 | auth.setSubscriptionArray(temp); 78 | }; 79 | 80 | useEffect(() => { 81 | subscription(); 82 | return () => { 83 | subscriptionOnCreate.unsubscribe(); 84 | }; 85 | }, []); 86 | 87 | useEffect(() => { 88 | auth.setSubscriptionArray(auth.subscriptionArray); 89 | }, [auth.subscriptionArray]); 90 | 91 | return ( 92 | 98 |
resetNewMessages(chatRoomId)} 101 | > 102 | 103 |
104 |

105 | {" "} 106 | {name}  {" "} 107 | {newMessages > 0 && ( 108 | 114 | )}{" "} 115 |

116 |

{lastMessage}

117 |

118 | {" "} 119 | Last Active: {moment(lastMessageAt).format("lll")}{" "} 120 |

121 |
122 |
123 | 124 | ); 125 | }; 126 | 127 | export default SidebarChat; 128 | -------------------------------------------------------------------------------- /src/Images/Module.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 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 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /src/Images/chat2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Images/chat3.svg: -------------------------------------------------------------------------------- 1 | things_to_say -------------------------------------------------------------------------------- /src/aws-exports.js: -------------------------------------------------------------------------------- 1 | const AWSCONSTANTS = { 2 | "aws_project_region": "ap-south-1", 3 | "aws_appsync_graphqlEndpoint": "https://r7lr2ac7xrdvzcn2tjirorp2tq.appsync-api.ap-south-1.amazonaws.com/graphql", 4 | "aws_appsync_region": "ap-south-1", 5 | "aws_appsync_authenticationType": "API_KEY", 6 | "aws_appsync_apiKey": "da2-syi2aomf7bgtjf5n7au4stajri", 7 | "aws_user_pools_id": "ap-south-1_zQTcU5coK", 8 | "aws_user_pools_web_client_id": "71b5287m8kfm3o5c393tu6a3u0", 9 | }; 10 | 11 | export default AWSCONSTANTS; 12 | -------------------------------------------------------------------------------- /src/dummychat.js: -------------------------------------------------------------------------------- 1 | export const subscriptionArray = [ 2 | { 3 | "conversationId": "a37395f8-3129-4b8f-be6a-3eabe604dc67", 4 | "conversationName":"KonfHub Team", 5 | "description":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae pretium sapien. Proin facilisis lacus sit amet", 6 | "lastMessage":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae pretium sapien. Proin facilisis lacus sit amet", 7 | "lastMessageAt":"2021-05-06T19:36:04Z" 8 | }, 9 | { 10 | "conversationId": "600ebd95-cde4-47a8-85e6-2c820776ac12", 11 | "conversationName":"GirlScript BLR", 12 | "description":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae pretium sapien. Proin facilisis lacus sit amet", 13 | "lastMessage":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae pretium sapien. Proin facilisis lacus sit amet", 14 | "lastMessageAt":"2021-05-06T19:37:04Z" 15 | }, 16 | { 17 | "conversationId": "5e981744-43e8-4f94-bff4-f9c6dabf71ee", 18 | "conversationName":"Team Tanay", 19 | "description":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae pretium sapien. Proin facilisis lacus sit amet", 20 | "lastMessage":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae pretium sapien. Proin facilisis lacus sit amet", 21 | "lastMessageAt":"2021-05-06T19:26:04Z" 22 | }, 23 | { 24 | "conversationId": "1a9f79b3-d357-4f4d-b4ee-856dc02ced08", 25 | "conversationName":"2 Nerds In Love", 26 | "description":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae pretium sapien. Proin facilisis lacus sit amet", 27 | "lastMessage":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae pretium sapien. Proin facilisis lacus sit amet", 28 | "lastMessageAt":"2021-05-06T19:33:04Z" 29 | } 30 | ] 31 | 32 | 33 | const subscriptionDetails = { 34 | "authorId": "72c700a3-d5d7-4a7c-b8ae-40631c8cfc92", 35 | "authorName": "Smile Gupta", 36 | "conversationId": "1998bd49-9af3-4745-a037-d670a0757ea3", 37 | "message": "hi", 38 | "sentAt": "2021-05-07T18:06:23Z" 39 | } 40 | 41 | export const updateSubscriptionArray = subscriptionDetails => { 42 | subscriptionArray.forEach(data => { 43 | if (data.conversationId === subscriptionDetails.conversationId) { 44 | data.lastMessage = subscriptionDetails.message; 45 | data.lastMessageAt = subscriptionDetails.sentAt; 46 | } 47 | }) 48 | }; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /************ Default CSS ****************/ 2 | 3 | @import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;900&display=swap"); 4 | 5 | * { 6 | margin: 0; 7 | } 8 | 9 | body { 10 | font-family: "Roboto", sans-serif; 11 | } 12 | 13 | ::placeholder { 14 | color: #b6c8eb; 15 | } 16 | 17 | a { 18 | text-decoration: none; 19 | } 20 | 21 | .cursor-pointer { 22 | cursor: pointer; 23 | } 24 | 25 | /************ Mobile App CSS ****************/ 26 | 27 | .app_mobile { 28 | height: 100vh; 29 | } 30 | 31 | .app_mobile_body { 32 | display: flex; 33 | background-color: #3855aa; 34 | min-height: 100vh; 35 | width: 100vw; 36 | } 37 | 38 | /************ Desktop App CSS ****************/ 39 | 40 | .app { 41 | display: grid; 42 | place-items: center; 43 | background-color: #3855aa; 44 | height: 100vh; 45 | } 46 | 47 | .app_body { 48 | display: flex; 49 | height: 95vh; 50 | width: 95vw; 51 | background-color: #e1e7f7; 52 | border-radius: 20px; 53 | box-shadow: -1px 4px 20px -6px rgba(94, 84, 112, 1); 54 | } 55 | 56 | .login { 57 | height: 100vh; 58 | display: grid; 59 | place-items: center; 60 | background-image: linear-gradient( 61 | to right top, 62 | #3855aa, 63 | #4c539a, 64 | #57538b, 65 | #5c537d, 66 | #5e5470 67 | ); 68 | } 69 | 70 | .login_container { 71 | display: flex; 72 | height: 60vh; 73 | width: 60vw; 74 | background-color: #e1e7f7; 75 | border-radius: 20px; 76 | box-shadow: -1px 4px 20px -6px rgba(94, 84, 112, 1); 77 | } 78 | 79 | .background2 { 80 | background-image: url("./Images/Module.svg"); 81 | background-size: cover; 82 | } 83 | 84 | .mobile_login_container { 85 | flex: 1; 86 | display: flex; 87 | flex-direction: column; 88 | background-color: #ffffff; 89 | } 90 | 91 | .MuiTab-root { 92 | width: 50%; 93 | } 94 | 95 | .error_texts{ 96 | color: red; 97 | margin-bottom: 20px; 98 | display: block; 99 | font-size: 12px; 100 | } 101 | 102 | 103 | :root:-webkit-scrollbar 104 | { 105 | display: none; 106 | width:0px; 107 | } 108 | 109 | 110 | -------------------------------------------------------------------------------- /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 "react-toastify/dist/ReactToastify.css"; 6 | 7 | ReactDOM.render(, document.getElementById("root")); 8 | --------------------------------------------------------------------------------