├── .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 | [](https://github.com/smilegupta)
3 | [](https://github.com/smilegupta) [](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 | [](https://www.facebook.com/smileguptaaa) [](https://www.instagram.com/smileguptaaa/) [](https://www.linkedin.com/in/smilegupta/) [](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 |
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 |
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 |
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 |
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 |
110 |
111 |
112 | ) : (
113 |
120 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
134 |
--------------------------------------------------------------------------------
/src/Images/chat2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Images/chat3.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------