├── LICENSE
├── README.md
├── backend
├── .gitignore
├── app.js
├── controllers
│ ├── bugs-controllers.js
│ ├── groups-controllers.js
│ ├── messages-controllers.js
│ └── users-controllers.js
├── models
│ ├── bug.js
│ ├── group.js
│ ├── message.js
│ └── user.js
├── package-lock.json
├── package.json
├── routes
│ ├── bugs-route.js
│ ├── groups-route.js
│ ├── messages-route.js
│ └── users-route.js
└── utils
│ └── token.js
└── frontend
├── .firebase
└── hosting.YnVpbGQ.cache
├── .firebaserc
├── .gitignore
├── build
├── asset-manifest.json
├── favicon.ico
├── index.html
└── static
│ ├── css
│ ├── main.a29129df.chunk.css
│ └── main.a29129df.chunk.css.map
│ ├── js
│ ├── 2.52aa31c1.chunk.js
│ ├── 2.52aa31c1.chunk.js.LICENSE.txt
│ ├── 2.52aa31c1.chunk.js.map
│ ├── main.1781dd80.chunk.js
│ ├── main.1781dd80.chunk.js.map
│ ├── runtime-main.c60b7f5c.js
│ └── runtime-main.c60b7f5c.js.map
│ └── media
│ ├── cropped.955e0ca1.png
│ └── gc-logo-symbol-nobg.b927fc8a.png
├── firebase.json
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.tsx
├── assets
│ ├── cookies.png
│ ├── cropped.png
│ ├── gc-logo-nobg.png
│ ├── gc-logo-symbol-nobg.png
│ ├── gc-logo-symbol.png
│ └── gc-logo.png
├── components
│ ├── Auth
│ │ ├── Login
│ │ │ ├── index.tsx
│ │ │ └── styles.module.scss
│ │ ├── Signup
│ │ │ ├── index.tsx
│ │ │ └── styles.module.scss
│ │ └── Welcome
│ │ │ ├── index.tsx
│ │ │ └── styles.module.scss
│ ├── Main
│ │ ├── Messages
│ │ │ ├── index.tsx
│ │ │ └── styles.module.scss
│ │ ├── MsgInput
│ │ │ ├── index.tsx
│ │ │ └── styles.module.scss
│ │ ├── Onboard
│ │ │ ├── index.tsx
│ │ │ └── styles.module.scss
│ │ └── TopBar
│ │ │ ├── index.tsx
│ │ │ └── styles.module.scss
│ ├── Shared
│ │ ├── Cookie
│ │ │ ├── index.tsx
│ │ │ └── styles.module.scss
│ │ ├── CustomButton
│ │ │ ├── index.tsx
│ │ │ └── styles.module.scss
│ │ ├── EditProfile
│ │ │ ├── index.tsx
│ │ │ └── styles.module.scss
│ │ └── Modal
│ │ │ ├── index.tsx
│ │ │ └── styles.module.scss
│ └── Side
│ │ ├── BottomBar
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ │ ├── GroupInfo
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ │ ├── Groups
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ │ ├── Members
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ │ ├── Search
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ │ └── TopBar
│ │ ├── index.tsx
│ │ └── styles.module.scss
├── index.scss
├── index.tsx
├── react-app-env.d.ts
├── redux
│ ├── app-reducer.ts
│ └── auth-reducer.ts
└── views
│ ├── AppView
│ ├── index.tsx
│ └── styles.module.scss
│ └── AuthView
│ ├── index.tsx
│ └── styles.module.scss
└── tsconfig.json
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Killian Frappart
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Introduction
4 | GroupChat is an instant messaging webapp built from scratch for my personnal portfolio. This project is not intended to generate profit, but the source code is open source and anyone interested can fork the project and do whatever he wants with.
5 | Have a look to the live version , this readme or its dedicated blog post to learn more about the project structure and the technologies involved.
6 | In case you want to share problems or tips, please go ahead, it is very much appreciated! 😇
7 |
8 | Table of Contents
9 |
10 |
15 |
16 | Setup
17 | In order to run the project locally, you must have node and npm installed globally.
18 | Step 1
19 | Clone the project and install dependencies in both frontend and backend directory.
20 |
21 | ```
22 | git clone https://github.com/KillianFrappartDev/GroupChat.git
23 | cd GroupChat/frontend && npm install
24 | cd GroupChat/backend && npm install
25 | ```
26 |
27 | Step 2
28 | Create a mongoDB database, a Cloudinary account and insert environement variables.
29 |
30 | ```
31 | # backend/.env
32 |
33 | DB_URL=
34 | JWT_SECRET=
35 |
36 | # frontend/.env
37 |
38 | REACT_APP_SERVER_URL=http://localhost:5000/api
39 | REACT_APP_SOCKET_URL=http://localhost:5000
40 | REACT_APP_CLOUDINARY_API=
41 | REACT_APP_CLOUDINARY_SECRET=
42 | ```
43 |
44 | Step 3
45 | Start a dev server.
46 |
47 | ```
48 | cd GroupChat/frontend && npm start
49 | cd GroupChat/backend && npm start
50 | ```
51 |
52 | Codebase Overview
53 |
54 | 
55 |
56 | Frontend
57 |
58 | build : Base directory for the live version. It gets updated when you run "npm run build"
59 | public : index.html and template page.
60 | src : JavaScript entry point and it is where all the logic lives.
61 | assets : Pictures and logos.
62 | components : Presentational and dynamic react components.
63 | views : Call it pages if you prefer, views are build by combining components together.
64 | redux : Redux related logic (global state management).
65 |
66 |
67 | Backend
68 |
69 | utils : Helper functions, data and middlewares.
70 | controllers : Database related actions.
71 | models : MongoDB / Mongoose models definition.
72 | routes : API routes.
73 | app.js : Entry point.
74 |
75 |
76 | Tech Stack
77 |
78 | 
79 |
80 | Frontend
81 |
82 | React : JavaScript library for building user interfaces.
83 | Redux : State container.
84 | TypeScript : JavaScript superset.
85 | Material UI : Beautiful UI library.
86 | SCSS : CSS preprocessor.
87 |
88 |
89 | Backend
90 |
91 | Node : JavaScript runtime.
92 | Express : Node library for building REST API.
93 | MongoDB : NoSQL Database.
94 | SocketIO : Enables real-time, bidirectional and event-based communication.
95 |
96 |
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
--------------------------------------------------------------------------------
/backend/app.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 |
3 | const cors = require('cors');
4 | const mongoose = require('mongoose');
5 | const express = require('express');
6 |
7 | // Local Imports
8 | const usersRoute = require('./routes/users-route');
9 | const groupsRoute = require('./routes/groups-route');
10 | const messagesRoute = require('./routes/messages-route');
11 | const bugsRoute = require('./routes/bugs-route');
12 |
13 | const app = express();
14 | app.use(express.json());
15 | app.use(cors());
16 |
17 | app.use('/api/users', usersRoute);
18 | app.use('/api/groups', groupsRoute);
19 | app.use('/api/messages', messagesRoute);
20 | app.use('/api/bugs', bugsRoute);
21 |
22 | // Error Handler
23 | app.use((error, req, res, next) => {
24 | console.log('An error occured:', error);
25 | res.json({ message: error.message || 'An unknown error occured.', error: true });
26 | });
27 |
28 | // Socket.io
29 | const server = require('http').createServer(app);
30 | const io = require('socket.io')(server);
31 |
32 | class User {
33 | constructor(uid, sid) {
34 | this.uid = uid;
35 | this.sid = sid;
36 | this.gid;
37 | }
38 | }
39 |
40 | let userList = [];
41 |
42 | // Establish a connection
43 | io.on('connection', socket => {
44 | // New user
45 | socket.on('new user', uid => {
46 | userList.push(new User(uid, socket.id));
47 | });
48 |
49 | // Join group
50 | socket.on('join group', (uid, gid) => {
51 | for (let i = 0; i < userList.length; i++) {
52 | if (socket.id === userList[i].sid) userList[i].gid = gid;
53 | }
54 | });
55 |
56 | // New group
57 | socket.on('create group', (uid, title) => {
58 | io.emit('fetch group');
59 | });
60 |
61 | // New message
62 | socket.on('message', (uid, gid) => {
63 | for (const user of userList) {
64 | if (gid === user.gid) io.to(user.sid).emit('fetch messages', gid);
65 | }
66 | });
67 |
68 | // Close connection
69 | socket.on('disconnect', () => {
70 | for (let i = 0; i < userList.length; i++) {
71 | if (socket.id === userList[i].sid) userList.splice(i, 1);
72 | }
73 | });
74 | });
75 |
76 | // Connect to DB && Start server
77 | mongoose
78 | .connect(process.env.DB_URL, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true })
79 | .then(() => {
80 | server.listen(process.env.PORT || 5000, () =>
81 | console.log(`Server up and running on port ${process.env.PORT || 5000}!`)
82 | );
83 | })
84 | .catch(error => console.log('Could not start server: ', error));
85 |
--------------------------------------------------------------------------------
/backend/controllers/bugs-controllers.js:
--------------------------------------------------------------------------------
1 | // Local Imports
2 | const Bug = require('../models/bug');
3 | const User = require('../models/user');
4 |
5 | const fetch = async (req, res, next) => {};
6 |
7 | const report = async (req, res, next) => {
8 | const { id, title, description } = req.body;
9 |
10 | // Find user
11 | let user;
12 | try {
13 | user = await User.findById(id);
14 | } catch (error) {
15 | return next(new Error('[ERROR][BUGS] Could not find user by id: ' + error));
16 | }
17 |
18 | // Create a bug
19 | const bug = new Bug({ title, description, user });
20 |
21 | // Save bug
22 | try {
23 | await bug.save();
24 | } catch (error) {
25 | return next(new Error('[ERROR][BUGS] Could not create bug: ' + error));
26 | }
27 |
28 | // Send response
29 | res.json({
30 | message: '[BUGS][CREATE] Bug reported..'
31 | });
32 | };
33 |
34 | exports.fetch = fetch;
35 | exports.report = report;
36 |
--------------------------------------------------------------------------------
/backend/controllers/groups-controllers.js:
--------------------------------------------------------------------------------
1 | const { validationResult } = require('express-validator');
2 |
3 | // Local Imports
4 | const Group = require('../models/group');
5 |
6 | const fetchGroups = async (req, res, next) => {
7 | // Fetch all groups
8 | let groups;
9 | try {
10 | groups = await Group.find().populate('members');
11 | } catch (error) {
12 | return next(new Error('[ERROR][GROUPS] Could not fetch groups: ' + error));
13 | }
14 |
15 | // Send Response
16 | res.json({ message: 'Groups Fetched!', groups });
17 | };
18 |
19 | const fetchGroupData = async (req, res, next) => {
20 | const id = req.params.gid;
21 |
22 | // Fetch group by id and populate members.
23 | let group;
24 | try {
25 | group = await Group.findById(id).populate('members messages');
26 | } catch (error) {
27 | return next(new Error('[ERROR][GROUPS] Could not fetch groups by id: ' + error));
28 | }
29 |
30 | const members = group.members.map(item => {
31 | return { _id: item._id, username: item.username, image: item.image };
32 | });
33 |
34 | // Send Response
35 | res.json({ message: 'Group fetched!', messages: group.messages, members });
36 | };
37 |
38 | const createGroup = async (req, res, next) => {
39 | const { title, description } = req.body;
40 |
41 | // Input validation
42 | const errors = validationResult(req);
43 | if (!errors.isEmpty()) {
44 | return next(new Error('[ERROR][GROUPS] Invalid entries: ' + error));
45 | }
46 |
47 | // Create Group
48 | const newGroup = new Group({ title, description, members: [], messages: [] });
49 | try {
50 | await newGroup.save();
51 | } catch (error) {
52 | return next(new Error('[ERROR][GROUPS] Could not save group to DB: ' + error));
53 | }
54 |
55 | // Send Response
56 | res.json({ message: 'Group Created!' });
57 | };
58 |
59 | exports.fetchGroups = fetchGroups;
60 | exports.fetchGroupData = fetchGroupData;
61 | exports.createGroup = createGroup;
62 |
--------------------------------------------------------------------------------
/backend/controllers/messages-controllers.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | // Local Imports
4 | const Message = require('../models/message');
5 | const Group = require('../models/group');
6 | const User = require('../models/user');
7 |
8 | const fetchMessages = async (req, res, next) => {
9 | const gid = req.params.gid;
10 |
11 | if (!gid) return next(new Error('[ERROR][MESSAGES] wrong group id: '));
12 |
13 | // Find group's messages
14 | let group;
15 | try {
16 | group = await Group.findById(gid).populate('messages');
17 | } catch (error) {
18 | return next(new Error('[ERROR][MESSAGES] Could not find group by id: ' + error));
19 | }
20 |
21 | res.json({ message: 'Messages fetched!', messages: group.messages });
22 | };
23 |
24 | const sendMessage = async (req, res, next) => {
25 | const { gid, username, text, image, uid, date } = req.body;
26 |
27 | // Find group
28 | let group;
29 | try {
30 | group = await Group.findById(gid);
31 | } catch (error) {
32 | return next(new Error('[ERROR][MESSAGES] Could not find group by id: ' + error));
33 | }
34 |
35 | // Add member
36 | let user;
37 | try {
38 | user = await User.findById(uid);
39 | } catch (error) {
40 | return next(new Error('[ERROR][MESSAGES] Could not find user by id: ' + error));
41 | }
42 |
43 | let isMember = false;
44 | for (const member of group.members) {
45 | if (member._id == uid) isMember = true;
46 | }
47 | if (!isMember) group.members.push(user);
48 |
49 | // Create message
50 | const newMessage = new Message({
51 | username,
52 | text,
53 | image,
54 | group,
55 | date
56 | });
57 |
58 | // Transaction
59 | try {
60 | const sess = await mongoose.startSession();
61 | sess.startTransaction();
62 | await newMessage.save({ session: sess });
63 | group.messages.push(newMessage);
64 | await group.save({ session: sess });
65 | await sess.commitTransaction();
66 | } catch (error) {
67 | return next(new Error('[ERROR][MESSAGE] Message transaction failed: ', error));
68 | }
69 |
70 | // Send Response
71 | res.json({ message: 'Message sent!' });
72 | };
73 |
74 | exports.fetchMessages = fetchMessages;
75 | exports.sendMessage = sendMessage;
76 |
--------------------------------------------------------------------------------
/backend/controllers/users-controllers.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require('bcryptjs');
2 | const { validationResult } = require('express-validator');
3 | const { AvatarGenerator } = require('random-avatar-generator');
4 | const generator = new AvatarGenerator();
5 |
6 | // Local Imports
7 | const User = require('../models/user');
8 | const { createToken, checkToken } = require('../utils/token');
9 |
10 | const findUserWithEmail = async email => {
11 | let user;
12 | try {
13 | user = await User.findOne({ email });
14 | } catch (error) {
15 | return next(new Error('[ERROR][USERS] Could not find user with email: ', +error));
16 | }
17 | return user;
18 | };
19 |
20 | const login = async (req, res, next) => {
21 | const { email, password } = req.body;
22 |
23 | // Input validation
24 | const errors = validationResult(req);
25 | if (!errors.isEmpty()) {
26 | res.json({ message: 'Access denied, invalid Entries.', access: false });
27 | return next();
28 | }
29 |
30 | // Find User with email
31 | const user = await findUserWithEmail(email);
32 | if (!user) {
33 | res.json({ message: 'Access denied, incorrect Email.', access: false });
34 | return next();
35 | }
36 |
37 | // Decrypt password & Check if password is valid
38 | const decryptedPassword = await bcrypt.compare(password, user.password);
39 | if (!decryptedPassword) {
40 | res.json({ message: 'Access denied, incorrect Password.', access: false });
41 | return next();
42 | }
43 |
44 | // Create token
45 | let token = await createToken(user.id);
46 |
47 | // Send response
48 | res.json({
49 | message: '[USER][LOGIN] Access granted.',
50 | access: true,
51 | user: { id: user.id, username: user.username, image: user.image, token }
52 | });
53 | };
54 |
55 | const signup = async (req, res, next) => {
56 | const { email, password, username } = req.body;
57 | const defaultImage = generator.generateRandomAvatar();
58 |
59 | // Input validation
60 | const errors = validationResult(req);
61 | if (!errors.isEmpty()) {
62 | res.json({ message: 'Access denied, invalid Entries.', access: false });
63 | return next();
64 | }
65 |
66 | // Check if user with this email already exists
67 | const existingUser = await findUserWithEmail(email);
68 | if (existingUser) {
69 | res.json({ message: 'Access denied, email already used.', access: false });
70 | return next();
71 | }
72 | // Encrypt password
73 | const hashedPassword = await bcrypt.hash(password, 8);
74 |
75 | // Create new user.
76 | const newUser = new User({ email, password: hashedPassword, username, image: defaultImage });
77 | try {
78 | await newUser.save();
79 | } catch (error) {
80 | return next(new Error('[ERROR][USERS] Could not save user in DB: ' + error));
81 | }
82 |
83 | // Create token
84 | let token = await createToken(newUser.id);
85 |
86 | // Send response
87 | res.json({
88 | message: '[USER][SIGNUP] Access granted.',
89 | access: true,
90 | user: { id: newUser.id, username: newUser.username, image: newUser.image, token }
91 | });
92 | };
93 |
94 | const guest = async (req, res, next) => {
95 | const randomUsername = `Guest${Math.floor(Math.random() * 99999) + 1}`;
96 | const defaultImage = generator.generateRandomAvatar();
97 |
98 | // Create Guest
99 | const newGuest = new User({ username: randomUsername, image: defaultImage });
100 | try {
101 | await newGuest.save();
102 | } catch (error) {
103 | return next(new Error('[ERROR][USERS] Could not save guest in DB: ' + error));
104 | }
105 |
106 | // Send response
107 | res.json({
108 | message: '[USER][GUEST] Access granted.',
109 | access: true,
110 | user: { id: newGuest.id, username: newGuest.username, image: newGuest.image }
111 | });
112 | };
113 |
114 | const verify = async (req, res, next) => {
115 | const { id, token } = req.body;
116 |
117 | // Find user with id
118 | let user;
119 | try {
120 | user = await User.findById(id);
121 | } catch (error) {
122 | return next(new Error('[ERROR][USERS] Could not find user by id: ' + error));
123 | }
124 |
125 | // Verify Token
126 | const tokenIsValid = await checkToken(id, token);
127 | if (!tokenIsValid) {
128 | res.json({ message: '[USER][VERIFY] Access denied, invalid token.', access: false });
129 | return next();
130 | }
131 |
132 | // Send response
133 | res.json({
134 | message: '[USER][LOGIN] Access granted.',
135 | access: true,
136 | user: { id: user.id, username: user.username, image: user.image, token }
137 | });
138 | };
139 |
140 | const edit = async (req, res, next) => {
141 | const { id, username, image } = req.body;
142 |
143 | // Input validation
144 | const errors = validationResult(req);
145 | if (!errors.isEmpty()) {
146 | return next(new Error('[ERROR][USERS] Edit invalid entries: ' + error));
147 | }
148 |
149 | // Find user by id
150 | let user;
151 | try {
152 | user = await User.findById(id);
153 | } catch (error) {
154 | return next(new Error('[ERROR][USERS] Could not find user by id: ' + error));
155 | }
156 |
157 | // Edit username and image
158 | user.username = username;
159 | user.image = image;
160 |
161 | // Save changes
162 | try {
163 | await user.save();
164 | } catch (error) {
165 | return next(new Error('[ERROR][USERS] Could not save user update: ' + error));
166 | }
167 |
168 | // Send response
169 | res.json({
170 | message: '[USER][EDIT] User updated.',
171 | access: true,
172 | user: { username: user.username, image: user.image }
173 | });
174 | };
175 |
176 | exports.login = login;
177 | exports.signup = signup;
178 | exports.edit = edit;
179 | exports.guest = guest;
180 | exports.verify = verify;
181 |
--------------------------------------------------------------------------------
/backend/models/bug.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Schema = mongoose.Schema;
4 |
5 | const schema = new Schema({
6 | title: { type: String, required: true },
7 | description: { type: String, required: true },
8 | user: { type: mongoose.Types.ObjectId, required: true, ref: 'User' }
9 | });
10 |
11 | module.exports = mongoose.model('Bug', schema);
12 |
--------------------------------------------------------------------------------
/backend/models/group.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Schema = mongoose.Schema;
4 |
5 | const schema = new Schema({
6 | title: { type: String, required: true },
7 | description: { type: String, required: true },
8 | members: [{ type: mongoose.Types.ObjectId, required: true, ref: 'User' }],
9 | messages: [{ type: mongoose.Types.ObjectId, required: true, ref: 'Message' }]
10 | });
11 |
12 | module.exports = mongoose.model('Group', schema);
13 |
--------------------------------------------------------------------------------
/backend/models/message.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Schema = mongoose.Schema;
4 |
5 | const schema = new Schema({
6 | username: { type: String, required: true },
7 | image: { type: String, required: true },
8 | text: { type: String, required: true },
9 | group: { type: mongoose.Types.ObjectId, required: true, ref: 'Group' },
10 | date: { type: String, required: true }
11 | });
12 |
13 | module.exports = mongoose.model('Message', schema);
14 |
--------------------------------------------------------------------------------
/backend/models/user.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Schema = mongoose.Schema;
4 |
5 | const schema = new Schema({
6 | username: { type: String, required: true },
7 | email: { type: String },
8 | password: { type: String },
9 | image: { type: String, required: true }
10 | });
11 |
12 | module.exports = mongoose.model('User', schema);
13 |
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "group-chat",
3 | "version": "1.0.0",
4 | "description": "Instant messaging app project",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node app.js"
9 | },
10 | "author": "Killian Frappart",
11 | "license": "ISC",
12 | "dependencies": {
13 | "bcryptjs": "^2.4.3",
14 | "cors": "^2.8.5",
15 | "dotenv": "^8.2.0",
16 | "express": "^4.17.1",
17 | "express-validator": "^6.9.2",
18 | "jsonwebtoken": "^8.5.1",
19 | "mongoose": "^5.11.8",
20 | "mongoose-unique-validator": "^2.0.3",
21 | "random-avatar-generator": "^2.0.0",
22 | "socket.io": "^3.0.4"
23 | },
24 | "devDependencies": {
25 | "nodemon": "^2.0.6"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/backend/routes/bugs-route.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | // Local Imports
4 | const controllers = require('../controllers/bugs-controllers');
5 |
6 | const router = express.Router();
7 |
8 | router.get('/', controllers.fetch);
9 | router.post('/', controllers.report);
10 |
11 | module.exports = router;
12 |
--------------------------------------------------------------------------------
/backend/routes/groups-route.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const { body } = require('express-validator');
3 |
4 | // Local Imports
5 | const controllers = require('../controllers/groups-controllers');
6 |
7 | const router = express.Router();
8 |
9 | router.get('/:gid', controllers.fetchGroupData);
10 | router.get('/', controllers.fetchGroups);
11 | router.post('/', body('title').isLength({ min: 3, max: 12 }), controllers.createGroup);
12 |
13 | module.exports = router;
14 |
--------------------------------------------------------------------------------
/backend/routes/messages-route.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | // Local Imports
4 | const controllers = require('../controllers/messages-controllers');
5 |
6 | const router = express.Router();
7 |
8 | router.get('/:gid', controllers.fetchMessages);
9 | router.post('/', controllers.sendMessage);
10 |
11 | module.exports = router;
12 |
--------------------------------------------------------------------------------
/backend/routes/users-route.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const { body } = require('express-validator');
3 |
4 | // Local Imports
5 | const controllers = require('../controllers/users-controllers');
6 |
7 | const router = express.Router();
8 |
9 | router.post('/login', body('email').isEmail(), body('password').isLength({ min: 6, max: 20 }), controllers.login);
10 | router.post(
11 | '/signup',
12 | body('email').isEmail(),
13 | body('password').isLength({ min: 6, max: 20 }),
14 | body('username').isLength({ min: 3, max: 12 }),
15 | controllers.signup
16 | );
17 | router.put('/edit', body('username').isLength({ min: 3, max: 12 }), controllers.edit);
18 | router.post('/guest', controllers.guest);
19 | router.post('/verify', controllers.verify);
20 |
21 | module.exports = router;
22 |
--------------------------------------------------------------------------------
/backend/utils/token.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 |
3 | const jwt = require('jsonwebtoken');
4 |
5 | const createToken = async id => {
6 | const token = await jwt.sign(
7 | {
8 | data: id
9 | },
10 | process.env.JWT_SECRET,
11 | { expiresIn: '72h' }
12 | );
13 | return token;
14 | };
15 |
16 | const checkToken = async (id, token) => {
17 | const decodedToken = await jwt.verify(token, process.env.JWT_SECRET);
18 | return decodedToken.data == id;
19 | };
20 |
21 | exports.createToken = createToken;
22 | exports.checkToken = checkToken;
23 |
--------------------------------------------------------------------------------
/frontend/.firebase/hosting.YnVpbGQ.cache:
--------------------------------------------------------------------------------
1 | asset-manifest.json,1609598367922,a452ea63cc4b6dcf337b7380c7fcdd0170fd184f18192d5067bc074ea9ef45b2
2 | favicon.ico,1609598360022,5c937383b8fcbd38353b7939b8812ceffaf7712c1f0c4ae6472d0d457e1fc035
3 | index.html,1609598367922,0b9050898b61ef2671a732b303e84e6070d637de610c41f2390b367572028cbc
4 | static/css/main.80af11fb.chunk.css,1609598367929,a04fcd66615daa648b3b2e9e216557eceaa9458b5d9f210c6ce6d5a0de9a63e3
5 | static/js/2.c2b801bd.chunk.js.LICENSE.txt,1609598367928,1de7915262765ca8e6fa6cee41816325d86a2966040bf3b5ce2d0350889e1043
6 | static/js/runtime-main.c60b7f5c.js,1609598367928,447192e6dc0967656e4253aa980e83f0582b742c08c83efdeb277c9353a50fbe
7 | static/css/main.80af11fb.chunk.css.map,1609598367928,dce71eaf5e50b05563282de1c0f224b0818149a818faf533d967cb524e24df8f
8 | static/js/runtime-main.c60b7f5c.js.map,1609598367928,1fca0a570a00abbfc4fa919e555a63edd74ef1aedd8ab88d5194e0a85c9037dd
9 | static/js/main.e1bd0e8d.chunk.js,1609598367929,f652dbb5d520ee2c7e7e16ba6be02648275744c9e6d5b8dc877cdb088520cd0f
10 | static/media/gc-logo-symbol-nobg.b927fc8a.png,1609598367928,ac51e7508e536756b646e910efc95bcf85c942cf5d1a4bae3f2f4a02fabd4f30
11 | static/media/cropped.955e0ca1.png,1609598367923,9898898ac106c9da1e1b3e59b659c62fc1507ec5467bad7dec41b8fbe0ebe2c6
12 | static/js/main.e1bd0e8d.chunk.js.map,1609598367928,1819bc7335895328ece4ce87c6c65bb6a94e9031a6a2726ae3601e411f3dacb6
13 | static/js/2.c2b801bd.chunk.js,1609598367928,6174e18d9dd9f1b0aa5901120fa6c0b2189a33a645a39419dd0042246c50c875
14 | static/js/2.c2b801bd.chunk.js.map,1609598367928,2c17d8c16702a1121148016bdd144e020cc317ccb5544755bf3e982ad9f0990d
15 |
--------------------------------------------------------------------------------
/frontend/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "groupchat-26a29"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | # /build
13 |
14 | # misc
15 | .DS_Store
16 | .env
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | .eslintcache
27 |
--------------------------------------------------------------------------------
/frontend/build/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "main.css": "/static/css/main.a29129df.chunk.css",
4 | "main.js": "/static/js/main.1781dd80.chunk.js",
5 | "main.js.map": "/static/js/main.1781dd80.chunk.js.map",
6 | "runtime-main.js": "/static/js/runtime-main.c60b7f5c.js",
7 | "runtime-main.js.map": "/static/js/runtime-main.c60b7f5c.js.map",
8 | "static/js/2.52aa31c1.chunk.js": "/static/js/2.52aa31c1.chunk.js",
9 | "static/js/2.52aa31c1.chunk.js.map": "/static/js/2.52aa31c1.chunk.js.map",
10 | "index.html": "/index.html",
11 | "static/css/main.a29129df.chunk.css.map": "/static/css/main.a29129df.chunk.css.map",
12 | "static/js/2.52aa31c1.chunk.js.LICENSE.txt": "/static/js/2.52aa31c1.chunk.js.LICENSE.txt",
13 | "static/media/cropped.955e0ca1.png": "/static/media/cropped.955e0ca1.png",
14 | "static/media/gc-logo-symbol-nobg.b927fc8a.png": "/static/media/gc-logo-symbol-nobg.b927fc8a.png"
15 | },
16 | "entrypoints": [
17 | "static/js/runtime-main.c60b7f5c.js",
18 | "static/js/2.52aa31c1.chunk.js",
19 | "static/css/main.a29129df.chunk.css",
20 | "static/js/main.1781dd80.chunk.js"
21 | ]
22 | }
--------------------------------------------------------------------------------
/frontend/build/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KillianFrappartDev/GroupChat/f395b4819f5c3178757b7c21b70e909677b7c778/frontend/build/favicon.ico
--------------------------------------------------------------------------------
/frontend/build/index.html:
--------------------------------------------------------------------------------
1 | GroupChat You need to enable JavaScript to run this app.
--------------------------------------------------------------------------------
/frontend/build/static/css/main.a29129df.chunk.css:
--------------------------------------------------------------------------------
1 | .styles_container__22UZb{flex:1 1;display:flex;align-items:center;justify-content:center}@media screen and (max-width:767px){.styles_title__Ej-Me{margin-bottom:20px}}.styles_wrapper__jOH-a{display:flex;flex-direction:column;align-items:center;justify-content:center;width:75%}@media screen and (max-width:767px){.styles_wrapper__jOH-a{width:90%}}.styles_list__15TjS{list-style:none;font-size:14px}@media screen and (max-width:767px){.styles_list__15TjS li{font-size:12px}}.styles_description__2v6wa{text-align:center;margin:20px 0}@media screen and (max-width:767px){.styles_description__2v6wa{display:none}}.styles_logo__6xdUQ{width:120px}.styles_container__3Niza{flex:1 1;display:flex;justify-content:center;overflow-y:auto}.styles_wrapper__1LXHj{width:95%;padding:20px}.styles_messageContainer__2BOhR{width:100%;display:flex;align-items:center}.styles_image__3lhi_{width:65px;height:65px;border-radius:50%}@media screen and (max-width:350px){.styles_image__3lhi_{width:50px;height:50px}}.styles_textBox__3ZgkF{margin:30px 0 30px 20px}.styles_username__3MNjK{color:#828282;font-size:18px;font-weight:700}@media screen and (max-width:350px){.styles_username__3MNjK{font-size:15px}}.styles_message__2EenM{color:#e0e0e0;font-size:14px}@media screen and (max-width:350px){.styles_message__2EenM{font-size:12px}}.styles_date__2uT6J{font-size:11px;margin-left:30px;font-weight:100}@media screen and (max-width:500px){.styles_date__2uT6J{display:none}}.styles_container__82uG4,.styles_loading__2yPfW{display:flex;align-items:center}.styles_container__82uG4{padding:8px;margin-bottom:20px;width:95%;background-color:#3c393f;align-self:center;border-radius:8px}.styles_input__3eA4I{margin-left:5px;flex:1 1;color:#e0e0e0!important}.styles_iconButton__2vq75{padding:8px;background-color:#8103ff!important;background:linear-gradient(90deg,#8103ff,#e002ff)!important;border-radius:8px!important}.styles_send__3iZ-6{color:#fff}.styles_container__DqthX{width:100%;height:60px;box-shadow:0 4px 4px rgba(0,0,0,.2);display:flex;align-items:center;justify-content:center}.styles_wrapper__tQxOT{width:95%;display:flex;align-items:center}.styles_title__3NY4r{font-size:18px}.styles_iconButton__3PNcZ{display:none!important}@media (max-width:767px){.styles_iconButton__3PNcZ{display:inline-block!important}}.styles_menu__6okpy{color:#e0e0e0}.styles_container__3BXkG{width:100%;height:60px;box-shadow:0 4px 4px rgba(0,0,0,.2);display:flex;align-items:center;justify-content:center}.styles_wrapperInChannel__htkyy,.styles_wrapperOutChannel__2VRY2{width:95%;display:flex;align-items:center}.styles_wrapperOutChannel__2VRY2{justify-content:space-between}.styles_title__2clG8{font-size:18px}.styles_addButton__12tTJ{order:2}.styles_add__2wScA,.styles_arrow__2vgBi{color:#e0e0e0}.styles_add__2wScA:hover,.styles_arrow__2vgBi:hover{color:#8103ff;color:linear-gradient(90deg,#8103ff,#c311ff)}.styles_container__1i7Qt{width:100%;height:90px;background-color:#0b090c;display:flex;align-items:center;justify-content:center}.styles_wrapper__2qbpV{width:95%;justify-content:space-between}.styles_userBox__3a5u9,.styles_wrapper__2qbpV{display:flex;align-items:center}.styles_image__11Yot{width:55px;height:55px;border-radius:50%;margin-right:15px}.styles_image__11Yot:hover{cursor:pointer}@media screen and (max-width:350px){.styles_exitButton__3BCit{padding:5px}}.styles_username__3LXNR{color:#828282;font-size:18px}.styles_exit__2YFO5{color:#e0e0e0}.styles_exit__2YFO5:hover{color:#8103ff;color:linear-gradient(90deg,#8103ff,#c311ff)}.styles_buttonContainer__3GfD6{display:flex}.styles_container__1BDXC{padding:6px;margin-top:20px;display:flex;align-items:center;width:95%;background-color:#3c393f;align-self:center;border-radius:8px}.styles_input__pfyyU{margin-left:5px;flex:1 1;color:#e0e0e0!important}.styles_iconButton__EtfuS{padding:8px;border-radius:8px!important}.styles_search__2jlGM{color:#e0e0e0}.styles_search__2jlGM:hover{color:#8103ff;color:linear-gradient(90deg,#8103ff,#c311ff)}.styles_container__18Kjq{overflow-y:auto;display:flex;justify-content:center}.styles_wrapper__2Xr1l{width:95%}.styles_group__2c-j8{display:flex;align-items:center;margin:20px 0}.styles_group__2c-j8:hover{cursor:pointer}.styles_group__2c-j8:hover .styles_tag___JKYL,.styles_group__2c-j8:hover .styles_title__1CSj3{color:#8103ff;color:linear-gradient(90deg,#8103ff,#c311ff)}.styles_tag___JKYL{width:40px;height:40px;display:flex;justify-content:center;align-items:center;background-color:#252329;border-radius:10px;margin-right:15px}.styles_container__1LU86{display:flex;flex-direction:column;align-items:center}.styles_wrapper__iBB-9{width:95%}.styles_title__3l4Z9{color:#e0e0e0;font-size:20px;margin:20px 0}.styles_description__2OoUl{font-size:13px;color:#e0e0e0}.styles_container__1eC4c{display:flex;flex-direction:column;align-items:center;overflow-y:auto;margin:20px 0}.styles_wrapper__yKduZ{width:95%}.styles_member__3fRCJ{display:flex;align-items:center;margin:20px 0}.styles_title__2XsWg{font-size:20px;color:#e0e0e0;margin:20px 0;width:95%}.styles_username__2eb-_{color:#828282}.styles_image__J8vpo{width:50px;height:50px;border-radius:50%;margin-right:20px}.styles_loading__2VQz7{margin-top:50px}.styles_black__3Tk7k:focus,.styles_purple__3xh8b:focus,.styles_smallPurple__Wpznm:focus{outline:none}.styles_black__3Tk7k,.styles_purple__3xh8b{width:350px;height:50px;border-style:none;margin:20px 0}.styles_black__3Tk7k:hover,.styles_purple__3xh8b:hover{cursor:pointer}@media screen and (max-width:350px){.styles_black__3Tk7k,.styles_purple__3xh8b{width:300px}}.styles_smallPurple__Wpznm{width:150px;height:40px;font-size:14px;margin:20px 0;border:2px solid #8103ff}.styles_smallPurple__Wpznm:hover{cursor:pointer}.styles_black__3Tk7k{background-color:#19161b;color:#e0e0e0}.styles_black__3Tk7k:hover{background-color:#e0e0e0;color:#19161b}.styles_purple__3xh8b,.styles_smallPurple__Wpznm{background-color:transparent;color:#8103ff;color:linear-gradient(90deg,#8103ff,#c311ff);border:2px solid #8103ff}.styles_purple__3xh8b:hover,.styles_smallPurple__Wpznm:hover{background-color:#8103ff;background:linear-gradient(90deg,#8103ff,#e002ff)!important;border-width:0;color:#252329}.styles_smallPurple__Wpznm{border-width:1px}.styles_backdrop__3YpAz{width:100%;height:100vh;background-color:rgba(0,0,0,.3);position:fixed;top:0}.styles_modal__3y1tw{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:600px;background-color:#120f13;border-radius:10px;padding:30px}@media screen and (max-width:600px){.styles_modal__3y1tw{width:100%;padding:10px}}.styles_form__V8Jh9{display:flex;flex-direction:column}.styles_input__32pb9{margin:20px 0!important}.styles_error__YbLa9{font-size:12px;color:#f44336}.styles_backdrop__vFnbO{width:100%;height:100vh;background-color:rgba(0,0,0,.3);position:fixed;top:0}.styles_modal__2ypNE{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:600px;background-color:#120f13;border-radius:10px;padding:30px}@media screen and (max-width:600px){.styles_modal__2ypNE{width:100%;padding:10px}}.styles_form__35WkX{display:flex;flex-direction:column;margin-top:20px}.styles_input__2PV0Q{margin:20px 0!important}.styles_error__36mV2{font-size:12px;color:#f44336}.styles_image__11FlE{align-self:flex-start;width:130px;height:130px;border-radius:50%}.styles_file__38Q9g{display:none}.styles_container__4Nu-6{width:100%;height:100vh;display:flex}.styles_container__4Nu-6 .styles_main__1s-dM{flex:3 1;background-color:#252329;display:flex;flex-direction:column;justify-content:space-between}.styles_container__4Nu-6 .styles_mobile__36sqR,.styles_container__4Nu-6 .styles_side__1Sb-a{flex:1 1;background-color:#19161b;display:flex;flex-direction:column;justify-content:space-between}@media screen and (max-width:767px){.styles_container__4Nu-6 .styles_main__1s-dM{flex:1 1}.styles_container__4Nu-6 .styles_side__1Sb-a{display:none}}.styles_sideContent__2RRNI{flex:1 1;display:flex;flex-direction:column;justify-content:flex-start;overflow-y:hidden}.styles_mobile__36sqR{position:absolute;height:100vh;width:80%;z-index:20;-webkit-animation:styles_slide-in__3o5Wp .5s linear 1 forwards;animation:styles_slide-in__3o5Wp .5s linear 1 forwards}@-webkit-keyframes styles_slide-in__3o5Wp{0%{transform:translateX(-100%)}to{transform:translateX(0)}}@keyframes styles_slide-in__3o5Wp{0%{transform:translateX(-100%)}to{transform:translateX(0)}}.styles_container__1tJbr{position:absolute;right:20px;bottom:20px;display:flex;align-items:flex-end}.styles_box__ogdW_{margin-right:10px;background-color:#19161b;border-radius:10px;display:flex;flex-direction:column;align-items:center;padding:20px 20px 0}@media screen and (max-width:424px){.styles_box__ogdW_{padding-bottom:20px}}.styles_cookie__1R2HK{width:60px;-webkit-animation:styles_wobble-ver-right__2X2Nx 2s 0s infinite forwards;animation:styles_wobble-ver-right__2X2Nx 2s 0s infinite forwards}.styles_cookie__1R2HK:hover{cursor:pointer}.styles_title__1tY9N{margin-bottom:10px}.styles_actions__1utNg{display:flex;align-items:center}@media screen and (max-width:424px){.styles_actions__1utNg{flex-direction:column}}.styles_info__n4On0{color:#006ebd;font-size:12px;margin-top:20px;margin-bottom:20px;margin-left:20px}@media screen and (max-width:424px){.styles_info__n4On0{margin:0}}@-webkit-keyframes styles_wobble-ver-right__2X2Nx{0%,to{transform:translateY(0) rotate(0);transform-origin:50% 50%}15%{transform:translateY(-30px) rotate(6deg)}30%{transform:translateY(15px) rotate(-6deg)}45%{transform:translateY(-15px) rotate(3.6deg)}60%{transform:translateY(9px) rotate(-2.4deg)}75%{transform:translateY(-6px) rotate(1.2deg)}}@keyframes styles_wobble-ver-right__2X2Nx{0%,to{transform:translateY(0) rotate(0);transform-origin:50% 50%}15%{transform:translateY(-30px) rotate(6deg)}30%{transform:translateY(15px) rotate(-6deg)}45%{transform:translateY(-15px) rotate(3.6deg)}60%{transform:translateY(9px) rotate(-2.4deg)}75%{transform:translateY(-6px) rotate(1.2deg)}}.styles_container__3710A{width:100%;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;background-color:#252329;overflow:hidden}.styles_logo__3EFlQ{width:350px}@media screen and (max-width:350px){.styles_logo__3EFlQ{width:300px}}.styles_guest__5Ltnc{color:#006ebd;font-size:12px;margin-top:20px}.styles_guest__5Ltnc:hover{cursor:pointer}.styles_container__1o0Fe{background-color:#252329;width:100%;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;overflow:hidden}.styles_logo__-42WF{width:150px;margin-bottom:30px}.styles_form__3iops{display:flex;flex-direction:column}.styles_input__3SIu9{width:350px;height:80px}@media screen and (max-width:350px){.styles_input__3SIu9{width:300px}}.styles_submit__2tz8r{width:350px;height:50px;margin:20px 0;background-color:transparent;color:#8103ff;color:linear-gradient(90deg,#8103ff,#c311ff);border:2px solid linear-gradient(90deg,#8103ff,#c311ff)}.styles_submit__2tz8r:hover{background-color:#8103ff;background-color:linear-gradient(90deg,#8103ff,#c311ff);color:#252329;cursor:pointer}.styles_guest__1fzKS{color:#006ebd;font-size:12px;margin-top:20px;margin-bottom:30px}.styles_guest__1fzKS:hover{cursor:pointer}.styles_error__ZnYsY{font-size:12px;color:#f44336}.styles_container__2xNfY{background-color:#252329;width:100%;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;overflow:hidden}.styles_logo__BRvPe{width:150px;margin-bottom:30px}.styles_form__1o0MN{display:flex;flex-direction:column}.styles_input__1kiHw{width:350px;height:80px}@media screen and (max-width:350px){.styles_input__1kiHw{width:300px}}.styles_submit__36CgC{width:350px;height:50px;margin:20px 0;background-color:transparent;color:#8103ff;color:linear-gradient(90deg,#8103ff,#c311ff);border:2px solid linear-gradient(90deg,#8103ff,#c311ff)}.styles_submit__36CgC:hover{background-color:#8103ff;background:linear-gradient(90deg,#8103ff,#e002ff)!important;color:#252329;cursor:pointer}.styles_guest__23uPH{color:#006ebd;font-size:12px;margin-top:20px;margin-bottom:30px}.styles_guest__23uPH:hover{cursor:pointer}.styles_error__1Wx2V{font-size:12px;color:#f44336}*,:after,:before{box-sizing:border-box}ol[class],ul[class]{padding:0}blockquote,body,dd,dl,figcaption,figure,h1,h2,h3,h4,li,ol[class],p,ul[class]{margin:0}body{min-height:100vh;scroll-behavior:smooth;text-rendering:optimizeSpeed;line-height:1.5;padding:0;color:#e0e0e0;font-family:"Roboto",sans-serif;letter-spacing:1.2px;background-color:#252329}ol[class],ul[class]{list-style:none}a:not([class]){-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto}img{max-width:100%;display:block}article>*+*{margin-top:1em}button,input,select,textarea{font:inherit}a{text-decoration:none}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-button{width:0;height:0}::-webkit-scrollbar-thumb{background:#5a5a5a;border:0 #fff;border-radius:50px}::-webkit-scrollbar-thumb:hover{background:#fff}::-webkit-scrollbar-thumb:active{background:#000}::-webkit-scrollbar-track{background:#3c3c3c;border:0 #fff;border-radius:50px}::-webkit-scrollbar-track:hover{background:#3c3c3c}::-webkit-scrollbar-track:active{background:#333}::-webkit-scrollbar-corner{background:transparent}
2 | /*# sourceMappingURL=main.a29129df.chunk.css.map */
--------------------------------------------------------------------------------
/frontend/build/static/css/main.a29129df.chunk.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack://src/components/Main/Onboard/styles.module.scss","webpack://src/components/Main/Messages/styles.module.scss","webpack://src/components/Main/MsgInput/styles.module.scss","webpack://src/components/Main/TopBar/styles.module.scss","webpack://src/components/Side/TopBar/styles.module.scss","webpack://src/components/Side/BottomBar/styles.module.scss","webpack://src/components/Side/Search/styles.module.scss","webpack://src/components/Side/Groups/styles.module.scss","webpack://src/components/Side/GroupInfo/styles.module.scss","webpack://src/components/Side/Members/styles.module.scss","webpack://src/components/Shared/CustomButton/styles.module.scss","webpack://src/components/Shared/Modal/styles.module.scss","webpack://src/components/Shared/EditProfile/styles.module.scss","webpack://src/views/AppView/styles.module.scss","webpack://src/components/Shared/Cookie/styles.module.scss","webpack://src/components/Auth/Welcome/styles.module.scss","webpack://src/components/Auth/Login/styles.module.scss","webpack://src/components/Auth/Signup/styles.module.scss","webpack://src/index.scss"],"names":[],"mappings":"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","file":"main.a29129df.chunk.css","sourcesContent":[".container {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.title {\n @media screen and (max-width: 767px) {\n margin-bottom: 20px;\n }\n}\n\n.wrapper {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 75%;\n @media screen and (max-width: 767px) {\n width: 90%;\n }\n}\n\n.list {\n list-style: none;\n font-size: 14px;\n @media screen and (max-width: 767px) {\n li {\n font-size: 12px;\n }\n }\n}\n\n.description {\n text-align: center;\n margin: 20px 0;\n @media screen and (max-width: 767px) {\n display: none;\n }\n}\n\n.logo {\n width: 120px;\n}\n","// List\n.container {\n flex: 1;\n display: flex;\n justify-content: center;\n overflow-y: auto;\n}\n\n.wrapper {\n width: 95%;\n padding: 20px;\n}\n\n// Message\n\n.messageContainer {\n width: 100%;\n display: flex;\n align-items: center;\n}\n\n.image {\n width: 65px;\n height: 65px;\n border-radius: 50%;\n @media screen and (max-width: 350px) {\n width: 50px;\n height: 50px;\n }\n}\n\n.textBox {\n margin: 30px 0px 30px 20px;\n}\n\n.username {\n color: #828282;\n font-size: 18px;\n font-weight: bold;\n @media screen and (max-width: 350px) {\n font-size: 15px;\n }\n}\n\n.message {\n color: #e0e0e0;\n font-size: 14px;\n @media screen and (max-width: 350px) {\n font-size: 12px;\n }\n}\n\n.date {\n font-size: 11px;\n margin-left: 30px;\n font-weight: 100;\n @media screen and (max-width: 500px) {\n display: none;\n }\n}\n\n.loading {\n display: flex;\n align-items: center;\n}\n",".container {\n padding: 8px;\n margin-bottom: 20px;\n display: flex;\n align-items: center;\n width: 95%;\n background-color: #3c393f;\n align-self: center;\n border-radius: 8px;\n}\n\n.input {\n margin-left: 5px;\n flex: 1;\n color: #e0e0e0 !important;\n}\n\n.iconButton {\n padding: 8px;\n background-color: rgb(129, 3, 255) !important;\n background: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(224, 2, 255, 1) 100%) !important;\n border-radius: 8px !important;\n}\n\n.send {\n color: #fff;\n}\n",".container {\n width: 100%;\n height: 60px;\n box-shadow: 0px 4px 4px rgba($color: #000, $alpha: 0.2);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.wrapper {\n width: 95%;\n display: flex;\n align-items: center;\n}\n\n.title {\n font-size: 18px;\n}\n\n.iconButton {\n display: none !important;\n @media (max-width: 767px) {\n display: inline-block !important;\n }\n}\n\n.menu {\n color: #e0e0e0;\n}\n",".container {\n width: 100%;\n height: 60px;\n box-shadow: 0px 4px 4px rgba($color: #000, $alpha: 0.2);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.wrapperInChannel,\n.wrapperOutChannel {\n width: 95%;\n display: flex;\n align-items: center;\n}\n\n.wrapperOutChannel {\n justify-content: space-between;\n}\n\n.title {\n font-size: 18px;\n}\n\n.addButton {\n order: 2;\n}\n\n.arrow,\n.add {\n color: #e0e0e0;\n &:hover {\n color: rgb(129, 3, 255);\n color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);\n }\n}\n",".container {\n width: 100%;\n height: 90px;\n background-color: #0b090c;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.wrapper {\n width: 95%;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.userBox {\n display: flex;\n align-items: center;\n}\n\n.image {\n width: 55px;\n height: 55px;\n border-radius: 50%;\n margin-right: 15px;\n &:hover {\n cursor: pointer;\n }\n}\n\n.exitButton {\n @media screen and (max-width: 350px) {\n padding: 5px;\n }\n}\n\n.username {\n color: #828282;\n font-size: 18px;\n}\n\n.exit {\n color: #e0e0e0;\n &:hover {\n color: rgb(129, 3, 255);\n color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);\n }\n}\n\n.buttonContainer {\n display: flex;\n}\n",".container {\n padding: 6px;\n margin-top: 20px;\n display: flex;\n align-items: center;\n width: 95%;\n background-color: #3c393f;\n align-self: center;\n border-radius: 8px;\n}\n\n.input {\n margin-left: 5px;\n flex: 1;\n color: #e0e0e0 !important;\n}\n\n.iconButton {\n padding: 8px;\n border-radius: 8px !important;\n}\n\n.search {\n color: #e0e0e0;\n &:hover {\n color: rgb(129, 3, 255);\n color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);\n }\n}\n",".container {\n overflow-y: auto;\n display: flex;\n justify-content: center;\n}\n\n.wrapper {\n width: 95%;\n}\n\n.group {\n display: flex;\n align-items: center;\n margin: 20px 0;\n &:hover {\n cursor: pointer;\n .tag,\n .title {\n color: rgb(129, 3, 255);\n color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);\n }\n }\n}\n\n.tag {\n width: 40px;\n height: 40px;\n display: flex;\n justify-content: center;\n align-items: center;\n background-color: #252329;\n border-radius: 10px;\n margin-right: 15px;\n}\n",".container {\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.wrapper {\n width: 95%;\n}\n\n.title {\n color: #e0e0e0;\n font-size: 20px;\n margin: 20px 0;\n}\n\n.description {\n font-size: 13px;\n color: #e0e0e0;\n}\n",".container {\n display: flex;\n flex-direction: column;\n align-items: center;\n overflow-y: auto;\n margin: 20px 0;\n}\n\n.wrapper {\n width: 95%;\n}\n\n.member {\n display: flex;\n align-items: center;\n margin: 20px 0;\n}\n\n.title {\n font-size: 20px;\n color: #e0e0e0;\n margin: 20px 0;\n width: 95%;\n}\n\n.username {\n color: #828282;\n}\n\n.image {\n width: 50px;\n height: 50px;\n border-radius: 50%;\n margin-right: 20px;\n}\n\n.loading {\n margin-top: 50px;\n}\n",".black,\n.purple,\n.smallPurple {\n &:focus {\n outline: none;\n }\n}\n\n.purple,\n.black {\n width: 350px;\n height: 50px;\n border-style: none;\n margin: 20px 0;\n &:hover {\n cursor: pointer;\n }\n\n @media screen and (max-width: 350px) {\n width: 300px;\n }\n}\n\n.smallPurple {\n width: 150px;\n height: 40px;\n font-size: 14px;\n margin: 20px 0;\n border: solid 2px rgb(129, 3, 255);\n &:hover {\n cursor: pointer;\n }\n}\n\n.black {\n background-color: #19161b;\n color: #e0e0e0;\n &:hover {\n background-color: #e0e0e0;\n color: #19161b;\n }\n}\n\n.purple,\n.smallPurple {\n background-color: rgba($color: #000000, $alpha: 0);\n color: rgb(129, 3, 255);\n color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);\n border: solid 2px rgb(129, 3, 255);\n &:hover {\n background-color: rgb(129, 3, 255);\n background: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(224, 2, 255, 1) 100%) !important;\n border-width: 0px;\n color: #252329;\n }\n}\n\n.smallPurple {\n border-width: 1px;\n}\n",".backdrop {\n width: 100%;\n height: 100vh;\n background-color: rgba($color: #000000, $alpha: 0.3);\n position: fixed;\n top: 0;\n}\n\n.modal {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 600px;\n background-color: #120f13;\n border-radius: 10px;\n padding: 30px;\n @media screen and (max-width: 600px) {\n width: 100%;\n padding: 10px;\n }\n}\n\n.form {\n display: flex;\n flex-direction: column;\n}\n\n.input {\n margin: 20px 0 !important;\n}\n\n.error {\n font-size: 12px;\n color: #f44336;\n}\n",".backdrop {\n width: 100%;\n height: 100vh;\n background-color: rgba($color: #000000, $alpha: 0.3);\n position: fixed;\n top: 0;\n}\n\n.modal {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 600px;\n background-color: #120f13;\n border-radius: 10px;\n padding: 30px;\n @media screen and (max-width: 600px) {\n width: 100%;\n padding: 10px;\n }\n}\n\n.form {\n display: flex;\n flex-direction: column;\n margin-top: 20px;\n}\n\n.input {\n margin: 20px 0 !important;\n}\n\n.error {\n font-size: 12px;\n color: #f44336;\n}\n\n.image {\n align-self: flex-start;\n width: 130px;\n height: 130px;\n border-radius: 50%;\n}\n\n.file {\n display: none;\n}\n","@mixin columnLayout {\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n}\n\n.container {\n width: 100%;\n height: 100vh;\n display: flex;\n\n .main {\n flex: 3;\n background-color: #252329;\n @include columnLayout;\n }\n\n .side,\n .mobile {\n flex: 1;\n background-color: #19161b;\n @include columnLayout;\n }\n\n @media screen and (max-width: 767px) {\n .main {\n flex: 1;\n }\n\n .side {\n display: none;\n }\n }\n}\n\n.sideContent {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n overflow-y: hidden;\n}\n\n.mobile {\n position: absolute;\n height: 100vh;\n width: 80%;\n z-index: 20;\n animation: slide-in 0.5s linear 1 forwards;\n}\n\n@keyframes slide-in {\n 0% {\n transform: translateX(-100%);\n }\n 100% {\n transform: translateX(0);\n }\n}\n",".container {\n position: absolute;\n right: 20px;\n bottom: 20px;\n display: flex;\n align-items: flex-end;\n}\n\n.box {\n margin-right: 10px;\n background-color: #19161b;\n border-radius: 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 20px 20px 0px;\n @media screen and (max-width: 424px) {\n padding-bottom: 20px;\n }\n}\n\n.cookie {\n width: 60px;\n -webkit-animation: wobble-ver-right 2s 0s infinite forwards;\n animation: wobble-ver-right 2s 0s infinite forwards;\n &:hover {\n cursor: pointer;\n }\n}\n\n.title {\n margin-bottom: 10px;\n}\n\n.actions {\n display: flex;\n align-items: center;\n @media screen and (max-width: 424px) {\n flex-direction: column;\n }\n}\n\n.info {\n color: #006ebd;\n font-size: 12px;\n margin-top: 20px;\n margin-bottom: 20px;\n margin-left: 20px;\n @media screen and (max-width: 424px) {\n margin: 0px;\n }\n}\n\n@-webkit-keyframes wobble-ver-right {\n 0%,\n 100% {\n -webkit-transform: translateY(0) rotate(0);\n transform: translateY(0) rotate(0);\n -webkit-transform-origin: 50% 50%;\n transform-origin: 50% 50%;\n }\n 15% {\n -webkit-transform: translateY(-30px) rotate(6deg);\n transform: translateY(-30px) rotate(6deg);\n }\n 30% {\n -webkit-transform: translateY(15px) rotate(-6deg);\n transform: translateY(15px) rotate(-6deg);\n }\n 45% {\n -webkit-transform: translateY(-15px) rotate(3.6deg);\n transform: translateY(-15px) rotate(3.6deg);\n }\n 60% {\n -webkit-transform: translateY(9px) rotate(-2.4deg);\n transform: translateY(9px) rotate(-2.4deg);\n }\n 75% {\n -webkit-transform: translateY(-6px) rotate(1.2deg);\n transform: translateY(-6px) rotate(1.2deg);\n }\n}\n\n@keyframes wobble-ver-right {\n 0%,\n 100% {\n -webkit-transform: translateY(0) rotate(0);\n transform: translateY(0) rotate(0);\n -webkit-transform-origin: 50% 50%;\n transform-origin: 50% 50%;\n }\n 15% {\n -webkit-transform: translateY(-30px) rotate(6deg);\n transform: translateY(-30px) rotate(6deg);\n }\n 30% {\n -webkit-transform: translateY(15px) rotate(-6deg);\n transform: translateY(15px) rotate(-6deg);\n }\n 45% {\n -webkit-transform: translateY(-15px) rotate(3.6deg);\n transform: translateY(-15px) rotate(3.6deg);\n }\n 60% {\n -webkit-transform: translateY(9px) rotate(-2.4deg);\n transform: translateY(9px) rotate(-2.4deg);\n }\n 75% {\n -webkit-transform: translateY(-6px) rotate(1.2deg);\n transform: translateY(-6px) rotate(1.2deg);\n }\n}\n",".container {\n width: 100%;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n background-color: #252329;\n overflow: hidden;\n}\n\n.logo {\n width: 350px;\n @media screen and (max-width: 350px) {\n width: 300px;\n }\n}\n\n.guest {\n color: #006ebd;\n font-size: 12px;\n margin-top: 20px;\n &:hover {\n cursor: pointer;\n }\n}\n",".container {\n background-color: #252329;\n width: 100%;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n}\n\n.logo {\n width: 150px;\n margin-bottom: 30px;\n}\n\n.form {\n display: flex;\n flex-direction: column;\n}\n\n.input {\n width: 350px;\n height: 80px;\n @media screen and (max-width: 350px) {\n width: 300px;\n }\n}\n\n.submit {\n width: 350px;\n height: 50px;\n border-style: none;\n margin: 20px 0;\n background-color: rgba($color: #000000, $alpha: 0);\n color: rgb(129, 3, 255);\n color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);\n border: solid 2px rgb(129, 3, 255);\n border-color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);\n &:hover {\n background-color: rgb(129, 3, 255);\n background-color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);\n color: #252329;\n cursor: pointer;\n }\n}\n\n.guest {\n color: #006ebd;\n font-size: 12px;\n margin-top: 20px;\n margin-bottom: 30px;\n &:hover {\n cursor: pointer;\n }\n}\n\n.error {\n font-size: 12px;\n color: #f44336;\n}\n",".container {\n background-color: #252329;\n width: 100%;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n}\n\n.logo {\n width: 150px;\n margin-bottom: 30px;\n}\n\n.form {\n display: flex;\n flex-direction: column;\n}\n\n.input {\n width: 350px;\n height: 80px;\n @media screen and (max-width: 350px) {\n width: 300px;\n }\n}\n\n.submit {\n width: 350px;\n height: 50px;\n border-style: none;\n margin: 20px 0;\n background-color: rgba($color: #000000, $alpha: 0);\n color: rgb(129, 3, 255);\n color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);\n border: solid 2px rgb(129, 3, 255);\n border-color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);\n &:hover {\n background-color: rgb(129, 3, 255);\n background: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(224, 2, 255, 1) 100%) !important;\n color: #252329;\n cursor: pointer;\n }\n}\n\n.guest {\n color: #006ebd;\n font-size: 12px;\n margin-top: 20px;\n margin-bottom: 30px;\n &:hover {\n cursor: pointer;\n }\n}\n\n.error {\n font-size: 12px;\n color: #f44336;\n}\n","/* Box sizing rules */\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n/* Remove default padding */\nul[class],\nol[class] {\n padding: 0;\n}\n\n/* Remove default margin */\nbody,\nh1,\nh2,\nh3,\nh4,\np,\nul[class],\nol[class],\nli,\nfigure,\nfigcaption,\nblockquote,\ndl,\ndd {\n margin: 0;\n}\n\n/* Set core body defaults */\nbody {\n min-height: 100vh;\n scroll-behavior: smooth;\n text-rendering: optimizeSpeed;\n line-height: 1.5;\n padding: 0;\n color: #e0e0e0;\n font-family: 'Roboto', sans-serif;\n letter-spacing: 1.2px;\n background-color: #252329;\n}\n\n/* Remove list styles on ul, ol elements with a class attribute */\nul[class],\nol[class] {\n list-style: none;\n}\n\n/* A elements that don't have a class get default styles */\na:not([class]) {\n text-decoration-skip-ink: auto;\n}\n\n/* Make images easier to work with */\nimg {\n max-width: 100%;\n display: block;\n}\n\n/* Natural flow and rhythm in articles by default */\narticle > * + * {\n margin-top: 1em;\n}\n\n/* Inherit fonts for inputs and buttons */\ninput,\nbutton,\ntextarea,\nselect {\n font: inherit;\n}\n\na {\n text-decoration: none;\n}\n\n::-webkit-scrollbar {\n width: 6px;\n height: 6px;\n}\n::-webkit-scrollbar-button {\n width: 0px;\n height: 0px;\n}\n::-webkit-scrollbar-thumb {\n background: #5a5a5a;\n border: 0px none #ffffff;\n border-radius: 50px;\n}\n::-webkit-scrollbar-thumb:hover {\n background: #ffffff;\n}\n::-webkit-scrollbar-thumb:active {\n background: #000000;\n}\n::-webkit-scrollbar-track {\n background: #3c3c3c;\n border: 0px none #ffffff;\n border-radius: 50px;\n}\n::-webkit-scrollbar-track:hover {\n background: #3c3c3c;\n}\n::-webkit-scrollbar-track:active {\n background: #333333;\n}\n::-webkit-scrollbar-corner {\n background: transparent;\n}\n"]}
--------------------------------------------------------------------------------
/frontend/build/static/js/2.52aa31c1.chunk.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*
2 | object-assign
3 | (c) Sindre Sorhus
4 | @license MIT
5 | */
6 |
7 | /*!
8 | * The buffer module from node.js, for the browser.
9 | *
10 | * @author Feross Aboukhadijeh
11 | * @license MIT
12 | */
13 |
14 | /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */
15 |
16 | /**
17 | * A better abstraction over CSS.
18 | *
19 | * @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present
20 | * @website https://github.com/cssinjs/jss
21 | * @license MIT
22 | */
23 |
24 | /** @license React v0.20.1
25 | * scheduler.production.min.js
26 | *
27 | * Copyright (c) Facebook, Inc. and its affiliates.
28 | *
29 | * This source code is licensed under the MIT license found in the
30 | * LICENSE file in the root directory of this source tree.
31 | */
32 |
33 | /** @license React v16.13.1
34 | * react-is.production.min.js
35 | *
36 | * Copyright (c) Facebook, Inc. and its affiliates.
37 | *
38 | * This source code is licensed under the MIT license found in the
39 | * LICENSE file in the root directory of this source tree.
40 | */
41 |
42 | /** @license React v17.0.1
43 | * react-dom.production.min.js
44 | *
45 | * Copyright (c) Facebook, Inc. and its affiliates.
46 | *
47 | * This source code is licensed under the MIT license found in the
48 | * LICENSE file in the root directory of this source tree.
49 | */
50 |
51 | /** @license React v17.0.1
52 | * react-jsx-runtime.production.min.js
53 | *
54 | * Copyright (c) Facebook, Inc. and its affiliates.
55 | *
56 | * This source code is licensed under the MIT license found in the
57 | * LICENSE file in the root directory of this source tree.
58 | */
59 |
60 | /** @license React v17.0.1
61 | * react.production.min.js
62 | *
63 | * Copyright (c) Facebook, Inc. and its affiliates.
64 | *
65 | * This source code is licensed under the MIT license found in the
66 | * LICENSE file in the root directory of this source tree.
67 | */
68 |
69 | /**!
70 | * @fileOverview Kickass library to create and place poppers near their reference elements.
71 | * @version 1.16.1-lts
72 | * @license
73 | * Copyright (c) 2016 Federico Zivolo and contributors
74 | *
75 | * Permission is hereby granted, free of charge, to any person obtaining a copy
76 | * of this software and associated documentation files (the "Software"), to deal
77 | * in the Software without restriction, including without limitation the rights
78 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
79 | * copies of the Software, and to permit persons to whom the Software is
80 | * furnished to do so, subject to the following conditions:
81 | *
82 | * The above copyright notice and this permission notice shall be included in all
83 | * copies or substantial portions of the Software.
84 | *
85 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
86 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
87 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
88 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
89 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
90 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
91 | * SOFTWARE.
92 | */
93 |
--------------------------------------------------------------------------------
/frontend/build/static/js/main.1781dd80.chunk.js:
--------------------------------------------------------------------------------
1 | (this.webpackJsonpfrontend=this.webpackJsonpfrontend||[]).push([[0],{119:function(e,t,a){e.exports={black:"styles_black__3Tk7k",purple:"styles_purple__3xh8b",smallPurple:"styles_smallPurple__Wpznm"}},120:function(e,t,a){e.exports={container:"styles_container__3710A",logo:"styles_logo__3EFlQ",guest:"styles_guest__5Ltnc"}},34:function(e,t,a){e.exports={container:"styles_container__1i7Qt",wrapper:"styles_wrapper__2qbpV",userBox:"styles_userBox__3a5u9",image:"styles_image__11Yot",exitButton:"styles_exitButton__3BCit",username:"styles_username__3LXNR",exit:"styles_exit__2YFO5",buttonContainer:"styles_buttonContainer__3GfD6"}},376:function(e,t,a){},377:function(e,t,a){"use strict";a.r(t);var r=a(2),n=a(0),s=a(12),c=a.n(s),o=a(88),i=a(14),l=a(13),u=a.n(l),p=a(24),d=a(7),j=a(21),b=a.n(j),m=a(193),g=a.n(m),h=a(417),O=a(413),x=a.p+"static/media/gc-logo-symbol-nobg.b927fc8a.png",f=a(69),v=a.n(f),y=function(e){return Object(r.jsx)("div",{className:v.a.container,onClick:e.onClick,children:Object(r.jsxs)("div",{className:v.a.wrapper,children:[Object(r.jsx)("img",{className:v.a.logo,alt:"logo",src:x}),Object(r.jsx)("h1",{className:v.a.title,children:"Hello World!"}),Object(r.jsx)("p",{className:v.a.description,children:"I really appreciate that you take time to have a look to my work. This app is an instant messaging project that offer the possibility to create and join channels and start a conversation."}),Object(r.jsxs)("ul",{className:v.a.list,children:[Object(r.jsx)("li",{children:'\u2b50\ufe0f Use the "menu" icon on top (Mobile).'}),Object(r.jsx)("li",{children:"\u2b50\ufe0f Click on any channel you want to join."}),Object(r.jsx)("li",{children:'\u2b50\ufe0f Create a channel with the "+" icon.'}),Object(r.jsx)("li",{children:"\u2b50\ufe0f Send messages with the text input."}),Object(r.jsx)("li",{children:"\u2b50\ufe0f Browse channels with the search input."}),Object(r.jsx)("li",{children:'\u2b50\ufe0f Report a bug with the "bug" icon.'}),Object(r.jsx)("li",{children:"\u2b50\ufe0f Click on your profile to edit."}),Object(r.jsx)("li",{children:'\u2b50\ufe0f Use the "exit" icon to logout.'})]})]})})},_=a(405),A=a(39),k=a.n(A),C=function(e){return Object(r.jsxs)("div",{className:k.a.messageContainer,children:[Object(r.jsx)("img",{className:k.a.image,alt:"User",src:e.image}),Object(r.jsxs)("div",{className:k.a.textBox,children:[Object(r.jsxs)("p",{className:k.a.username,children:[e.username," ",Object(r.jsx)("span",{className:k.a.date,children:e.date})]}),Object(r.jsx)("p",{className:k.a.message,children:e.text})]})]})},N=function(e){return Object(n.useEffect)((function(){var e=document.getElementById("chat");e&&(e.scrollTop=e.scrollHeight)})),Object(r.jsx)("div",{id:"chat",className:k.a.container,onClick:e.onClick,children:e.loading?Object(r.jsx)("div",{className:k.a.loading,children:Object(r.jsx)(_.a,{})}):Object(r.jsx)("div",{className:k.a.wrapper,children:e.messages.map((function(e){return Object(r.jsx)(C,{_id:e._id,username:e.username,text:e.text,image:e.image,date:e.date},e._id)}))})})},w=a(418),B=a(414),R=a(197),S=a.n(R),E=a(93),G=a.n(E),D=function(e){var t=Object(n.useState)(""),a=Object(d.a)(t,2),s=a[0],c=a[1],o=function(){e.sendClick(s,function(){var e=new Date,t=["January","February","March","April","May","June","July","August","September","October","November","December"][e.getMonth()],a=String(e.getDate()).padStart(2,"0");return"".concat(t+" "+a+","," - ").concat((new Date).getHours(),":").concat((new Date).getMinutes())}()),c("")};return Object(r.jsxs)("div",{className:G.a.container,onClick:e.onClick,children:[Object(r.jsx)(w.a,{className:G.a.input,placeholder:"Write here...",value:s,onChange:function(e){return c(e.target.value)},onKeyDown:function(e){"Enter"===e.key&&o()}}),Object(r.jsx)(B.a,{className:G.a.iconButton,onClick:o,children:Object(r.jsx)(S.a,{className:G.a.send})})]})},P=a(198),I=a.n(P),L=a(82),H=a.n(L),U=function(e){return Object(r.jsx)("div",{className:H.a.container,children:Object(r.jsxs)("div",{className:H.a.wrapper,children:[Object(r.jsx)(B.a,{className:H.a.iconButton,onClick:e.menuClick,children:Object(r.jsx)(I.a,{className:H.a.menu,fontSize:"large"})}),Object(r.jsx)("h2",{className:H.a.title,children:e.title})]})})},Y=a(419),F=a(201),T=a.n(F),J=a(200),M=a.n(J),q=a(40),Q=a.n(q),V=function(e){return Object(r.jsx)("div",{className:Q.a.container,children:e.inChannel?Object(r.jsxs)("div",{className:Q.a.wrapperInChannel,children:[Object(r.jsx)(Y.a,{title:"Back to channels list",placement:"bottom",children:Object(r.jsx)(B.a,{className:Q.a.arrowButton,onClick:e.arrowClick,children:Object(r.jsx)(M.a,{className:Q.a.arrow})})}),Object(r.jsx)("h2",{className:Q.a.title,children:"All channels"})]}):Object(r.jsxs)("div",{className:Q.a.wrapperOutChannel,children:[Object(r.jsx)("h2",{className:Q.a.title,children:"Channels"}),Object(r.jsx)(Y.a,{title:"Create Channel",placement:"bottom",children:Object(r.jsx)(B.a,{className:Q.a.addButton,onClick:e.plusClick,children:Object(r.jsx)(T.a,{className:Q.a.add})})})]})})},Z=a(203),K=a.n(Z),X=a(202),z=a.n(X),W=a(34),$=a.n(W),ee=function(e){var t=Object(i.c)((function(e){return e.auth})),a=t.username,n=t.image;return Object(r.jsx)("div",{className:$.a.container,children:Object(r.jsxs)("div",{className:$.a.wrapper,children:[Object(r.jsxs)("div",{className:$.a.userBox,children:[Object(r.jsx)(Y.a,{title:"Edit profile",placement:"top",children:Object(r.jsx)("img",{className:$.a.image,alt:"User",src:n,onClick:e.profileClick})}),Object(r.jsx)("p",{className:$.a.username,children:a})]}),Object(r.jsxs)("div",{className:$.a.buttonContainer,children:[Object(r.jsx)(Y.a,{title:"Report a bug",placement:"top",children:Object(r.jsx)(B.a,{className:$.a.exitButton,onClick:e.bugClick,children:Object(r.jsx)(z.a,{className:$.a.exit})})}),Object(r.jsx)(Y.a,{title:"Logout",placement:"top",children:Object(r.jsx)(B.a,{className:$.a.exitButton,onClick:e.exitClick,children:Object(r.jsx)(K.a,{className:$.a.exit})})})]})]})})},te=a(204),ae=a.n(te),re=a(94),ne=a.n(re),se=function(e){var t=Object(n.useState)(""),a=Object(d.a)(t,2),s=a[0],c=a[1];return Object(r.jsxs)("div",{className:ne.a.container,children:[Object(r.jsx)(B.a,{className:ne.a.iconButton,children:Object(r.jsx)(ae.a,{className:ne.a.search})}),Object(r.jsx)(w.a,{className:ne.a.input,placeholder:"Search...",onChange:function(t){return function(t){c(t.target.value);var a=e.groups.filter((function(e){return e.title.toLowerCase().includes(t.target.value.toLowerCase())}));e.update(a)}(t)},value:s})]})},ce=a(83),oe=a.n(ce),ie=function(e){return Object(r.jsxs)("div",{className:oe.a.group,onClick:function(){return e.groupClick(e._id)},children:[Object(r.jsx)("span",{className:oe.a.tag,children:e.tag}),Object(r.jsx)("p",{className:oe.a.title,children:e.title})]})},le=function(e){return Object(r.jsx)("div",{className:oe.a.container,children:Object(r.jsx)("div",{className:oe.a.wrapper,children:e.groups.map((function(t){return Object(r.jsx)(ie,{_id:t._id,title:t.title,tag:"".concat(t.title[0]).concat(t.title[1]).toUpperCase(),groupClick:function(t){return e.groupClick(t)}},t._id)}))})})},ue=a(95),pe=a.n(ue),de=function(e){var t,a;return Object(r.jsx)("div",{className:pe.a.container,children:Object(r.jsxs)("div",{className:pe.a.wrapper,children:[Object(r.jsx)("p",{className:pe.a.title,children:null===(t=e.currentGroup)||void 0===t?void 0:t.title}),Object(r.jsx)("p",{className:pe.a.description,children:null===(a=e.currentGroup)||void 0===a?void 0:a.description})]})})},je=a(55),be=a.n(je),me=function(e){return Object(r.jsxs)("div",{className:be.a.member,children:[Object(r.jsx)("img",{className:be.a.image,alt:"User",src:e.image}),Object(r.jsx)("p",{className:be.a.username,children:e.username})]})},ge=function(e){return Object(r.jsxs)("div",{className:be.a.container,children:[Object(r.jsx)("p",{className:be.a.title,children:"Members"}),e.loading?Object(r.jsx)("div",{className:be.a.loading,children:Object(r.jsx)(_.a,{})}):Object(r.jsx)("div",{className:be.a.wrapper,children:e.members.map((function(e){return Object(r.jsx)(me,{_id:null===e||void 0===e?void 0:e._id,username:null===e||void 0===e?void 0:e.username,image:null===e||void 0===e?void 0:e.image},null===e||void 0===e?void 0:e._id)}))})]})},he=a(412),Oe=a(211),xe=a(410),fe=a(119),ve=a.n(fe),ye=function(e){return e.isPurple?Object(r.jsx)("button",{onClick:e.onClick,className:e.small?ve.a.smallPurple:ve.a.purple,type:e.type?e.type:"button",children:e.title}):Object(r.jsx)("button",{onClick:e.onClick,className:ve.a.black,type:e.type?e.type:"button",children:e.title})},_e=a(70),Ae=a.n(_e),ke=Object(Oe.a)({palette:{type:"dark"}}),Ce=function(e){var t=Object(i.b)(),a=Object(n.useState)(!0),s=Object(d.a)(a,2),c=s[0],o=s[1],l=Object(n.useState)(""),u=Object(d.a)(l,2),p=u[0],j=u[1],b=Object(n.useState)(!1),m=Object(d.a)(b,2),g=m[0],h=m[1],O=Object(n.useState)(""),x=Object(d.a)(O,2),f=x[0],v=x[1],y=Object(n.useState)(""),_=Object(d.a)(y,2),A=_[0],k=_[1];return Object(r.jsxs)(r.Fragment,{children:[Object(r.jsx)("div",{className:Ae.a.backdrop,onClick:function(){return t({type:"MODAL",payload:{modal:null}})}}),Object(r.jsxs)("div",{className:Ae.a.modal,children:[Object(r.jsx)("h2",{children:e.title}),Object(r.jsx)(xe.a,{theme:ke,children:Object(r.jsxs)("form",{className:Ae.a.form,onSubmit:function(e){return e.preventDefault()},children:[Object(r.jsx)(he.a,{className:Ae.a.input,id:"title",label:"Title",variant:"outlined",onChange:function(e){return function(e){e.target.value.length<=2||e.target.value.length>12?(h(!0),v("Title should contain 3 to 12 characters.")):(h(!1),v(""),o(!0)),j(e.target.value)}(e)},helperText:f,error:g,value:p}),Object(r.jsx)(he.a,{className:Ae.a.input,id:"description",rows:3,label:"Description",variant:"outlined",multiline:!0,value:A,onChange:function(e){return k(e.target.value)}}),Object(r.jsx)(ye,{onClick:function(){return function(t,a){g?o(!1):e.onCreate(t,a)}(p,A)},isPurple:!0,title:"Create",small:!0}),!c&&Object(r.jsx)("p",{className:Ae.a.error,children:"Invalid entries."})]})})]})]})},Ne=a(205),we=a.n(Ne),Be=a(56),Re=a.n(Be),Se=Object(Oe.a)({palette:{type:"dark"}}),Ee=function(e){var t=Object(i.b)(),a=Object(i.c)((function(e){return e.auth})),s=a.username,c=a.image,o=Object(n.useRef)(null),l=Object(n.useState)(!1),j=Object(d.a)(l,2),m=j[0],g=j[1],h=Object(n.useState)(!0),O=Object(d.a)(h,2),x=O[0],f=O[1],v=Object(n.useState)(s),y=Object(d.a)(v,2),A=y[0],k=y[1],C=Object(n.useState)(!1),N=Object(d.a)(C,2),w=N[0],B=N[1],R=Object(n.useState)(""),S=Object(d.a)(R,2),E=S[0],G=S[1],D=Object(n.useState)(c),P=Object(d.a)(D,2),I=P[0],L=P[1],H=function(){var e=Object(p.a)(u.a.mark((function e(t){var a;return u.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return g(!0),e.prev=1,e.next=4,b.a.post("https://api.cloudinary.com/v1_1/djghq5xmi/image/upload",t);case 4:a=e.sent,e.next=11;break;case 7:e.prev=7,e.t0=e.catch(1),console.log("ERROR",e.t0),g(!1);case 11:if(a){e.next=13;break}return e.abrupt("return");case 13:L(a.data.secure_url),g(!1);case 15:case"end":return e.stop()}}),e,null,[[1,7]])})));return function(t){return e.apply(this,arguments)}}();return Object(r.jsxs)(r.Fragment,{children:[Object(r.jsx)("div",{className:Re.a.backdrop,onClick:function(){return t({type:"MODAL",payload:{modal:null}})}}),Object(r.jsxs)("div",{className:Re.a.modal,children:[Object(r.jsx)("h2",{children:"Profile"}),Object(r.jsx)(xe.a,{theme:Se,children:Object(r.jsxs)("form",{className:Re.a.form,onSubmit:function(e){return e.preventDefault()},children:[Object(r.jsx)("img",{className:Re.a.image,alt:"User",src:I,onClick:function(){null!==o.current&&o.current.click()}}),Object(r.jsx)("input",{className:Re.a.file,type:"file",ref:o,accept:".jpg,.png,.jpeg",onChange:function(e){var t=new FormData;t.append("file",e.target.files[0]),t.append("api_key","467699583817694"),t.append("timestamp",Math.floor(Date.now()/1e3).toString()),t.append("signature",we()("timestamp=".concat(Math.floor(Date.now()/1e3)).concat("lX7wf1sbaFwNfYjrYP08xh9TjQU"))),H(t)}}),Object(r.jsx)(he.a,{className:Re.a.input,id:"username",label:"Username",variant:"outlined",onChange:function(e){return function(e){e.target.value.length<=2||e.target.value.length>12?(B(!0),G("Username should contain 3 to 12 characters.")):(B(!1),G(""),f(!0)),k(e.target.value)}(e)},helperText:E,error:w,value:A}),Object(r.jsx)(ye,{onClick:function(){return function(t,a){w?f(!1):e.onEdit(t,a)}(A,I)},isPurple:!0,title:"Edit",small:!0}),!x&&Object(r.jsx)("p",{className:Re.a.error,children:"Invalid entries."}),m&&Object(r.jsx)(_.a,{})]})})]})]})},Ge=a(57),De=a.n(Ge),Pe=function(){var e=Object(i.b)(),t=Object(i.c)((function(e){return e.auth})),a=Object(i.c)((function(e){return e.app})),s=a.inChannel,c=a.currentGroup,o=a.displayedGroups,l=a.messages,j=a.members,m=a.groups,x=a.modal,f=Object(n.useState)(!1),v=Object(d.a)(f,2),_=v[0],A=v[1],k=Object(n.useState)(!1),C=Object(d.a)(k,2),w=C[0],B=C[1],R=Object(n.useState)({open:!1,severity:void 0,message:null}),S=Object(d.a)(R,2),E=S[0],G=S[1],P=Object(n.useState)(null),I=Object(d.a)(P,2),L=I[0],H=I[1];Object(n.useEffect)((function(){var e=g()("https://groupchat-api.herokuapp.com",{transports:["websocket"]});e.emit("new user",t.id),e.on("fetch messages",(function(e){return Q(e)})),e.on("fetch group",q),H(e),q()}),[]),Object(n.useEffect)((function(){L&&(L.emit("join group",t.id,null===c||void 0===c?void 0:c._id),Q())}),[c]);var Y,F,T=function(){var a=Object(p.a)(u.a.mark((function a(r,n){var s,c,o,i;return u.a.wrap((function(a){for(;;)switch(a.prev=a.next){case 0:if(s=t.token,c=t.id,s){a.next=4;break}return G({open:!0,severity:"error",message:"Guests are not allowed to create groups, please register."}),a.abrupt("return");case 4:return a.prev=4,a.next=7,b.a.post("".concat("https://groupchat-api.herokuapp.com/api","/users/verify"),{id:c,token:s});case 7:o=a.sent,a.next=14;break;case 10:return a.prev=10,a.t0=a.catch(4),console.log("[ERROR][AUTH][VERIFY]: ",a.t0),a.abrupt("return");case 14:if(o.data.access){a.next=17;break}return localStorage.removeItem("userData"),a.abrupt("return");case 17:return a.prev=17,a.next=20,b.a.post("".concat("https://groupchat-api.herokuapp.com/api","/groups"),{title:r,description:n||"No description."});case 20:i=a.sent,a.next=28;break;case 23:return a.prev=23,a.t1=a.catch(17),console.log("[ERROR][GROUPS][CREATE]: ",a.t1),G({open:!0,severity:"error",message:"An error occured: Could not create group."}),a.abrupt("return");case 28:if(i){a.next=30;break}return a.abrupt("return");case 30:e({type:"MODAL",payload:{modal:null}}),q(),null===L||void 0===L||L.emit("create group",t.id,r),G({open:!0,severity:"success",message:"".concat(r," channel created.")});case 34:case"end":return a.stop()}}),a,null,[[4,10],[17,23]])})));return function(e,t){return a.apply(this,arguments)}}(),J=function(){var a=Object(p.a)(u.a.mark((function a(r,n){var s,c,o,i;return u.a.wrap((function(a){for(;;)switch(a.prev=a.next){case 0:if(s=t.token,c=t.id,s){a.next=4;break}return G({open:!0,severity:"error",message:"Guests are not allowed to edit profile, please register."}),a.abrupt("return");case 4:return a.prev=4,a.next=7,b.a.post("".concat("https://groupchat-api.herokuapp.com/api","/users/verify"),{id:c,token:s});case 7:o=a.sent,a.next=14;break;case 10:return a.prev=10,a.t0=a.catch(4),console.log("[ERROR][AUTH][VERIFY]: ",a.t0),a.abrupt("return");case 14:if(o.data.access){a.next=17;break}return localStorage.removeItem("userData"),a.abrupt("return");case 17:return a.prev=17,a.next=20,b.a.put("".concat("https://groupchat-api.herokuapp.com/api","/users/edit"),{id:c,username:r,image:n});case 20:i=a.sent,a.next=28;break;case 23:return a.prev=23,a.t1=a.catch(17),console.log("[ERROR][USERS][EDIT]: ",a.t1),G({open:!0,severity:"error",message:"An error occured: Could not edit profile."}),a.abrupt("return");case 28:if(i){a.next=30;break}return a.abrupt("return");case 30:G({open:!0,severity:"success",message:"Profile updated."}),e({type:"MODAL",payload:{modal:null}}),e({type:"EDIT",payload:{username:i.data.user.username,image:i.data.user.image}});case 33:case"end":return a.stop()}}),a,null,[[4,10],[17,23]])})));return function(e,t){return a.apply(this,arguments)}}(),M=function(){var e=Object(p.a)(u.a.mark((function e(a,r){var n;return u.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(L){e.next=2;break}return e.abrupt("return");case 2:return e.prev=2,e.next=5,b.a.post("".concat("https://groupchat-api.herokuapp.com/api","/messages"),{gid:null===c||void 0===c?void 0:c._id,text:a,username:t.username,image:t.image,uid:t.id,date:r});case 5:n=e.sent,e.next=13;break;case 8:return e.prev=8,e.t0=e.catch(2),console.log("[ERROR][GROUPS][CREATE]: ",e.t0),G({open:!0,severity:"error",message:"An error occured: Could not send message."}),e.abrupt("return");case 13:if(n){e.next=15;break}return e.abrupt("return");case 15:null===L||void 0===L||L.emit("message",t.id,null===c||void 0===c?void 0:c._id);case 16:case"end":return e.stop()}}),e,null,[[2,8]])})));return function(t,a){return e.apply(this,arguments)}}(),q=function(){var t=Object(p.a)(u.a.mark((function t(){var a;return u.a.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.prev=0,t.next=3,b.a.get("".concat("https://groupchat-api.herokuapp.com/api","/groups"));case 3:a=t.sent,t.next=11;break;case 6:return t.prev=6,t.t0=t.catch(0),console.log("[ERROR][GROUPS][FETCH]: ",t.t0),G({open:!0,severity:"error",message:"An error occured: Could not fetch groups."}),t.abrupt("return");case 11:if(a){t.next=13;break}return t.abrupt("return");case 13:e({type:"FETCH GROUPS",payload:{displayedGroups:a.data.groups,groups:a.data.groups}});case 14:case"end":return t.stop()}}),t,null,[[0,6]])})));return function(){return t.apply(this,arguments)}}(),Q=function(){var t=Object(p.a)(u.a.mark((function t(){var a,r,n=arguments;return u.a.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:if(a=n.length>0&&void 0!==n[0]?n[0]:null===c||void 0===c?void 0:c._id){t.next=3;break}return t.abrupt("return");case 3:return t.prev=3,t.next=6,b.a.get("".concat("https://groupchat-api.herokuapp.com/api","/groups/").concat(a));case 6:r=t.sent,t.next=15;break;case 9:return t.prev=9,t.t0=t.catch(3),console.log("[ERROR][MESSAGES][FETCH]: ",t.t0),G({open:!0,severity:"error",message:"An error occured: Could not fetch messages and members."}),B(!1),t.abrupt("return");case 15:if(B(!1),!r.data.error){t.next=19;break}return G({open:!0,severity:"error",message:"An error occured: Could not fetch messages and members."}),t.abrupt("return");case 19:e({type:"FETCH MESSAGES",payload:{messages:r.data.messages,members:r.data.members}});case 20:case"end":return t.stop()}}),t,null,[[3,9]])})));return function(){return t.apply(this,arguments)}}(),Z=function(){var a=Object(p.a)(u.a.mark((function a(r,n){var s,c;return u.a.wrap((function(a){for(;;)switch(a.prev=a.next){case 0:return s=t.id,a.prev=1,a.next=4,b.a.post("".concat("https://groupchat-api.herokuapp.com/api","/bugs"),{id:s,title:r,description:n||"No description."});case 4:c=a.sent,a.next=12;break;case 7:return a.prev=7,a.t0=a.catch(1),console.log("[ERROR][BUGS][CREATE]: ",a.t0),G({open:!0,severity:"error",message:"An error occured: Could not report bug."}),a.abrupt("return");case 12:if(c){a.next=14;break}return a.abrupt("return");case 14:e({type:"MODAL",payload:{modal:null}}),G({open:!0,severity:"success",message:"Bug reported, thank you!"});case 16:case"end":return a.stop()}}),a,null,[[1,7]])})));return function(e,t){return a.apply(this,arguments)}}();return s?(Y=Object(r.jsxs)("div",{className:De.a.sideContent,children:[Object(r.jsx)(de,{currentGroup:c}),Object(r.jsx)(ge,{members:j,loading:w})]}),F=Object(r.jsxs)("div",{className:De.a.main,children:[Object(r.jsx)(U,{title:null===c||void 0===c?void 0:c.title,menuClick:function(){return A(!0)}}),Object(r.jsx)(N,{messages:l,onClick:function(){return A(!1)},loading:w}),Object(r.jsx)(D,{sendClick:M,onClick:function(){return A(!1)}})]})):(Y=Object(r.jsxs)("div",{className:De.a.sideContent,children:[Object(r.jsx)(se,{groups:m,update:function(t){return e({type:"SEARCH",payload:{displayedGroups:t}})}}),Object(r.jsx)(le,{groups:o,groupClick:function(t){return function(t){B(!0);var a=m.filter((function(e){return e._id===t}));a.length>0&&e({type:"CHANGE GROUP",payload:{currentGroup:a[0]}})}(t)}})]}),F=Object(r.jsxs)("div",{className:De.a.main,children:[Object(r.jsx)(U,{title:"",menuClick:function(){return A(!0)}}),Object(r.jsx)(y,{onClick:function(){return A(!1)}})]})),Object(r.jsxs)("div",{className:De.a.container,children:[Object(r.jsxs)("div",{className:_?De.a.mobile:De.a.side,children:[Object(r.jsx)(V,{inChannel:s,arrowClick:function(){e({type:"EXIT"})},plusClick:function(){e({type:"MODAL",payload:{modal:"create"}}),A(!1)}}),Y,Object(r.jsx)(ee,{exitClick:function(){null===L||void 0===L||L.disconnect(),localStorage.removeItem("userData"),e({type:"LOGOUT"})},profileClick:function(){e({type:"MODAL",payload:{modal:"edit"}}),A(!1)},bugClick:function(){e({type:"MODAL",payload:{modal:"bug"}}),A(!1)}})]}),F,"create"===x&&Object(r.jsx)(Ce,{onCreate:T,title:"New Channel"}),"edit"===x&&Object(r.jsx)(Ee,{onEdit:J}),"bug"===x&&Object(r.jsx)(Ce,{onCreate:Z,title:"Bug Report"}),Object(r.jsx)(h.a,{open:E.open,onClose:function(){return G({open:!1,severity:E.severity,message:null})},autoHideDuration:5e3,children:Object(r.jsx)(O.a,{variant:"filled",onClose:function(){return G({open:!1,severity:E.severity,message:null})},severity:E.severity,children:E.message})})]})},Ie=a(8),Le=a(29),He=a(17),Ue=a(71),Ye=a.n(Ue),Fe=function(e){var t=Object(n.useState)(!0),a=Object(d.a)(t,2),s=a[0],c=a[1];return Object(r.jsxs)("div",{className:Ye.a.container,children:[s&&Object(r.jsxs)("div",{className:Ye.a.box,children:[Object(r.jsx)("h3",{className:Ye.a.title,children:"Are you hungry ?"}),Object(r.jsxs)("div",{className:Ye.a.actions,children:[Object(r.jsx)(ye,{isPurple:!0,small:!0,title:"Accept cookies",onClick:e.onAccept}),Object(r.jsx)("a",{className:Ye.a.info,target:"_blank",href:"https://ec.europa.eu/info/cookies_en",children:"More information."})]})]}),Object(r.jsx)("img",{className:Ye.a.cookie,onClick:function(){return c((function(e){return!e}))},src:"data:image/png;base64,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"})]})},Te=a.p+"static/media/cropped.955e0ca1.png",Je=a(120),Me=a.n(Je),qe=function(e){var t=Object(i.b)(),a=function(){var e=Object(p.a)(u.a.mark((function e(){var a;return u.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,b.a.post("".concat("https://groupchat-api.herokuapp.com/api","/users/guest"));case 3:a=e.sent,e.next=10;break;case 6:return e.prev=6,e.t0=e.catch(0),console.log("[ERROR][AUTH][GUEST]: ",e.t0),e.abrupt("return");case 10:if(a.data.access){e.next=12;break}return e.abrupt("return");case 12:t({type:"GUEST",payload:Object(Ie.a)({},a.data.user)});case 13:case"end":return e.stop()}}),e,null,[[0,6]])})));return function(){return e.apply(this,arguments)}}();return Object(r.jsxs)("div",{className:Me.a.container,children:[Object(r.jsx)("img",{className:Me.a.logo,alt:"GroupChat Logo",src:Te}),Object(r.jsx)(Le.b,{to:"/login",children:Object(r.jsx)(ye,{onClick:function(){},isPurple:!1,title:"Login",small:!1})}),Object(r.jsx)(Le.b,{to:"/signup",children:Object(r.jsx)(ye,{onClick:function(){},isPurple:!0,title:"Signup",small:!1})}),Object(r.jsx)("p",{className:Me.a.guest,onClick:a,children:"Continue as guest"})]})},Qe=a(420),Ve=a(415),Ze=a(118),Ke=a(48),Xe=a(58),ze=a.n(Xe),We=function(e){var t=Object(i.b)(),a=Object(He.g)(),s=Object(n.useState)(!1),c=Object(d.a)(s,2),o=c[0],l=c[1],j=Object(n.useState)(!1),m=Object(d.a)(j,2),g=m[0],f=m[1],v=Object(n.useState)({open:!1,message:null}),y=Object(d.a)(v,2),A=y[0],k=y[1],C=function(){var e=Object(p.a)(u.a.mark((function e(r,n,s){var c;return u.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return l(!0),e.prev=1,e.next=4,b.a.post("".concat("https://groupchat-api.herokuapp.com/api","/users/login"),{checked:r,email:n.toLowerCase(),password:s.toLowerCase()});case 4:c=e.sent,e.next=12;break;case 7:return e.prev=7,e.t0=e.catch(1),console.log("[ERROR][AUTH][LOGIN]: ",e.t0),l(!1),e.abrupt("return");case 12:if(c.data.access){e.next=16;break}return k({open:!0,message:c.data.message}),l(!1),e.abrupt("return");case 16:r&&localStorage.setItem("userData",JSON.stringify({id:c.data.user.id,token:c.data.user.token})),t({type:"LOGIN",payload:Object(Ie.a)({},c.data.user)}),a.push(""),l(!1);case 20:case"end":return e.stop()}}),e,null,[[1,7]])})));return function(t,a,r){return e.apply(this,arguments)}}(),N=Object(Ze.a)({initialValues:{email:"",password:""},validationSchema:Ke.a({email:Ke.b().email("Invalid email address").required("Required"),password:Ke.b().min(6,"Must be 6 characters at least").required("Required").max(20,"Can not exceed 20 characters")}),onSubmit:function(e){return C(g,e.email,e.password)}});return Object(r.jsxs)("div",{className:ze.a.container,children:[Object(r.jsx)(Le.b,{to:"/",children:Object(r.jsx)("img",{className:ze.a.logo,alt:"logo",src:x})}),Object(r.jsxs)("form",{className:ze.a.form,children:[Object(r.jsx)(he.a,Object(Ie.a)({className:ze.a.input,id:"email",label:"Email",variant:"outlined",type:"text",helperText:N.touched.email&&N.errors.email,error:N.touched.email&&!!N.errors.email},N.getFieldProps("email"))),Object(r.jsx)(he.a,Object(Ie.a)(Object(Ie.a)({className:ze.a.input,id:"password",label:"Password",variant:"outlined",type:"password"},N.getFieldProps("password")),{},{helperText:N.touched.password&&N.errors.password,error:N.touched.password&&!!N.errors.password})),Object(r.jsx)(Qe.a,{className:ze.a.check,control:Object(r.jsx)(Ve.a,{checked:g,onChange:function(){return f((function(e){return!e}))},name:"checked",color:"primary"}),label:"Remember me"}),Object(r.jsx)(ye,{type:"submit",onClick:N.handleSubmit,isPurple:!0,title:"Login",small:!1})]}),Object(r.jsx)(Le.b,{to:"/signup",children:Object(r.jsx)("p",{className:ze.a.guest,children:"Don't have an account? Sign Up"})}),o&&Object(r.jsx)(_.a,{}),Object(r.jsx)(h.a,{open:A.open,onClose:function(){return k({open:!1,message:null})},autoHideDuration:5e3,children:Object(r.jsx)(O.a,{variant:"filled",onClose:function(){return k({open:!1,message:null})},severity:"error",children:A.message})})]})},$e=a(49),et=a.n($e),tt=function(e){var t=Object(i.b)(),a=Object(He.g)(),s=Object(n.useState)(!1),c=Object(d.a)(s,2),o=c[0],l=c[1],j=Object(n.useState)(!1),m=Object(d.a)(j,2),g=m[0],f=m[1],v=Object(n.useState)({open:!1,message:null}),y=Object(d.a)(v,2),A=y[0],k=y[1],C=function(){var e=Object(p.a)(u.a.mark((function e(r,n,s,c){var o;return u.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return l(!0),e.prev=1,e.next=4,b.a.post("".concat("https://groupchat-api.herokuapp.com/api","/users/signup"),{checked:r,email:n.toLowerCase(),password:s.toLowerCase(),username:c});case 4:o=e.sent,e.next=12;break;case 7:return e.prev=7,e.t0=e.catch(1),console.log("[ERROR][AUTH][SIGNUP]: ",e.t0),l(!1),e.abrupt("return");case 12:if(o.data.access){e.next=16;break}return k({open:!0,message:o.data.message}),l(!1),e.abrupt("return");case 16:r&&localStorage.setItem("userData",JSON.stringify({id:o.data.user.id,token:o.data.user.token})),t({type:"LOGIN",payload:Object(Ie.a)({},o.data.user)}),a.push(""),l(!1);case 20:case"end":return e.stop()}}),e,null,[[1,7]])})));return function(t,a,r,n){return e.apply(this,arguments)}}(),N=Object(Ze.a)({initialValues:{username:"",email:"",password:""},validationSchema:Ke.a({username:Ke.b().min(2,"Must be 2 characters at least").required("Required").max(12,"Can not exceed 12 characters"),email:Ke.b().email("Invalid email address").required("Required"),password:Ke.b().min(6,"Must be 6 characters at least").required("Required").max(20,"Can not exceed 20 characters")}),onSubmit:function(e){return C(g,e.email,e.password,e.username)}});return Object(r.jsxs)("div",{className:et.a.container,children:[Object(r.jsx)(Le.b,{to:"/",children:Object(r.jsx)("img",{className:et.a.logo,alt:"logo",src:x})}),Object(r.jsxs)("form",{className:et.a.form,children:[Object(r.jsx)(he.a,Object(Ie.a)({className:et.a.input,id:"username",label:"Username",variant:"outlined",helperText:N.touched.username&&N.errors.username,error:N.touched.username&&!!N.errors.username},N.getFieldProps("username"))),Object(r.jsx)(he.a,Object(Ie.a)({className:et.a.input,id:"email",label:"Email",variant:"outlined",helperText:N.touched.email&&N.errors.email,error:N.touched.email&&!!N.errors.email},N.getFieldProps("email"))),Object(r.jsx)(he.a,Object(Ie.a)({className:et.a.input,id:"password",label:"Password",type:"password",variant:"outlined",helperText:N.touched.password&&N.errors.password,error:N.touched.password&&!!N.errors.password},N.getFieldProps("password"))),Object(r.jsx)(Qe.a,{className:et.a.check,control:Object(r.jsx)(Ve.a,{checked:g,onChange:function(){return f((function(e){return!e}))},name:"checkedB",color:"primary"}),label:"Remember me"}),Object(r.jsx)(ye,{type:"submit",onClick:N.handleSubmit,isPurple:!0,title:"Signup",small:!1})]}),Object(r.jsx)(Le.b,{to:"/login",children:Object(r.jsx)("p",{className:et.a.guest,children:"Already a member ? Login"})}),o&&Object(r.jsx)(_.a,{}),Object(r.jsx)(h.a,{open:A.open,onClose:function(){return k({open:!1,message:null})},autoHideDuration:5e3,children:Object(r.jsx)(O.a,{variant:"filled",onClose:function(){return k({open:!1,message:null})},severity:"error",children:A.message})})]})},at=Object(Oe.a)({palette:{type:"dark"}}),rt=function(e){var t=Object(i.b)(),a=Object(n.useState)(!0),s=Object(d.a)(a,2),c=s[0],o=s[1];Object(n.useEffect)((function(){localStorage.getItem("cookieData")&&o(!1);var e=localStorage.getItem("userData");if(e){var t=JSON.parse(e);l(t.id,t.token)}}),[]);var l=function(){var e=Object(p.a)(u.a.mark((function e(a,r){var n;return u.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,b.a.post("".concat("https://groupchat-api.herokuapp.com/api","/users/verify"),{id:a,token:r});case 3:n=e.sent,e.next=10;break;case 6:return e.prev=6,e.t0=e.catch(0),console.log("[ERROR][AUTH][VERIFY]: ",e.t0),e.abrupt("return");case 10:if(n.data.access){e.next=13;break}return localStorage.removeItem("userData"),e.abrupt("return");case 13:t({type:"LOGIN",payload:Object(Ie.a)({},n.data.user)});case 14:case"end":return e.stop()}}),e,null,[[0,6]])})));return function(t,a){return e.apply(this,arguments)}}();return Object(r.jsxs)(xe.a,{theme:at,children:[c&&Object(r.jsx)(Fe,{onAccept:function(){localStorage.setItem("cookieData","accepted"),o(!1)}}),Object(r.jsx)(Le.a,{children:Object(r.jsxs)(He.d,{children:[Object(r.jsx)(He.b,{path:"/login",exact:!0,component:We}),Object(r.jsx)(He.b,{path:"/signup",exact:!0,component:tt}),Object(r.jsx)(He.b,{path:"/",exact:!0,component:qe}),Object(r.jsx)(He.a,{to:"/"})]})})]})},nt=function(){return Object(i.c)((function(e){return e.auth.isLogged}))?Object(r.jsx)(Pe,{}):Object(r.jsx)(rt,{})},st={inChannel:!1,messages:[],members:[],displayedGroups:[],groups:[],currentGroup:null,modal:null},ct=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:st,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case"CHANGE GROUP":return Object(Ie.a)(Object(Ie.a)({},e),{},{currentGroup:t.payload.currentGroup,inChannel:!0});case"SEARCH":return Object(Ie.a)(Object(Ie.a)({},e),{},{displayedGroups:t.payload.displayedGroups});case"FETCH GROUPS":return Object(Ie.a)(Object(Ie.a)({},e),{},{displayedGroups:t.payload.displayedGroups,groups:t.payload.groups});case"FETCH MESSAGES":return Object(Ie.a)(Object(Ie.a)({},e),{},{messages:t.payload.messages,members:t.payload.members});case"MODAL":return Object(Ie.a)(Object(Ie.a)({},e),{},{modal:t.payload.modal});case"EXIT":return Object(Ie.a)(Object(Ie.a)({},e),{},{inChannel:!1,currentGroup:null,displayedGroups:e.groups,members:[],messages:[]});default:return e}},ot={isLogged:!1,username:null,image:null,token:null,id:null},it=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:ot,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case"LOGIN":return Object(Ie.a)(Object(Ie.a)({},e),{},{isLogged:!0,username:t.payload.username,image:t.payload.image,token:t.payload.token,id:t.payload.id});case"LOGOUT":return Object(Ie.a)(Object(Ie.a)({},e),{},{isLogged:!1,username:null,image:null,token:null,id:null});case"GUEST":return Object(Ie.a)(Object(Ie.a)({},e),{},{isLogged:!0,username:t.payload.username,image:t.payload.image,token:null,id:t.payload.id});case"EDIT":return Object(Ie.a)(Object(Ie.a)({},e),{},{isLogged:!0,username:t.payload.username,image:t.payload.image});default:return e}},lt=(a(376),Object(o.b)({auth:it,app:ct})),ut=Object(o.c)(lt);c.a.render(Object(r.jsx)(i.a,{store:ut,children:Object(r.jsx)(nt,{})}),document.getElementById("root"))},39:function(e,t,a){e.exports={container:"styles_container__3Niza",wrapper:"styles_wrapper__1LXHj",messageContainer:"styles_messageContainer__2BOhR",image:"styles_image__3lhi_",textBox:"styles_textBox__3ZgkF",username:"styles_username__3MNjK",message:"styles_message__2EenM",date:"styles_date__2uT6J",loading:"styles_loading__2yPfW"}},40:function(e,t,a){e.exports={container:"styles_container__3BXkG",wrapperInChannel:"styles_wrapperInChannel__htkyy",wrapperOutChannel:"styles_wrapperOutChannel__2VRY2",title:"styles_title__2clG8",addButton:"styles_addButton__12tTJ",arrow:"styles_arrow__2vgBi",add:"styles_add__2wScA"}},49:function(e,t,a){e.exports={container:"styles_container__2xNfY",logo:"styles_logo__BRvPe",form:"styles_form__1o0MN",input:"styles_input__1kiHw",submit:"styles_submit__36CgC",guest:"styles_guest__23uPH",error:"styles_error__1Wx2V"}},55:function(e,t,a){e.exports={container:"styles_container__1eC4c",wrapper:"styles_wrapper__yKduZ",member:"styles_member__3fRCJ",title:"styles_title__2XsWg",username:"styles_username__2eb-_",image:"styles_image__J8vpo",loading:"styles_loading__2VQz7"}},56:function(e,t,a){e.exports={backdrop:"styles_backdrop__vFnbO",modal:"styles_modal__2ypNE",form:"styles_form__35WkX",input:"styles_input__2PV0Q",error:"styles_error__36mV2",image:"styles_image__11FlE",file:"styles_file__38Q9g"}},57:function(e,t,a){e.exports={container:"styles_container__4Nu-6",main:"styles_main__1s-dM",side:"styles_side__1Sb-a",mobile:"styles_mobile__36sqR",sideContent:"styles_sideContent__2RRNI","slide-in":"styles_slide-in__3o5Wp"}},58:function(e,t,a){e.exports={container:"styles_container__1o0Fe",logo:"styles_logo__-42WF",form:"styles_form__3iops",input:"styles_input__3SIu9",submit:"styles_submit__2tz8r",guest:"styles_guest__1fzKS",error:"styles_error__ZnYsY"}},69:function(e,t,a){e.exports={container:"styles_container__22UZb",title:"styles_title__Ej-Me",wrapper:"styles_wrapper__jOH-a",list:"styles_list__15TjS",description:"styles_description__2v6wa",logo:"styles_logo__6xdUQ"}},70:function(e,t,a){e.exports={backdrop:"styles_backdrop__3YpAz",modal:"styles_modal__3y1tw",form:"styles_form__V8Jh9",input:"styles_input__32pb9",error:"styles_error__YbLa9"}},71:function(e,t,a){e.exports={container:"styles_container__1tJbr",box:"styles_box__ogdW_",cookie:"styles_cookie__1R2HK","wobble-ver-right":"styles_wobble-ver-right__2X2Nx",title:"styles_title__1tY9N",actions:"styles_actions__1utNg",info:"styles_info__n4On0"}},82:function(e,t,a){e.exports={container:"styles_container__DqthX",wrapper:"styles_wrapper__tQxOT",title:"styles_title__3NY4r",iconButton:"styles_iconButton__3PNcZ",menu:"styles_menu__6okpy"}},83:function(e,t,a){e.exports={container:"styles_container__18Kjq",wrapper:"styles_wrapper__2Xr1l",group:"styles_group__2c-j8",tag:"styles_tag___JKYL",title:"styles_title__1CSj3"}},93:function(e,t,a){e.exports={container:"styles_container__82uG4",input:"styles_input__3eA4I",iconButton:"styles_iconButton__2vq75",send:"styles_send__3iZ-6"}},94:function(e,t,a){e.exports={container:"styles_container__1BDXC",input:"styles_input__pfyyU",iconButton:"styles_iconButton__EtfuS",search:"styles_search__2jlGM"}},95:function(e,t,a){e.exports={container:"styles_container__1LU86",wrapper:"styles_wrapper__iBB-9",title:"styles_title__3l4Z9",description:"styles_description__2OoUl"}}},[[377,1,2]]]);
2 | //# sourceMappingURL=main.1781dd80.chunk.js.map
--------------------------------------------------------------------------------
/frontend/build/static/js/runtime-main.c60b7f5c.js:
--------------------------------------------------------------------------------
1 | !function(e){function r(r){for(var n,f,l=r[0],i=r[1],a=r[2],c=0,s=[];c0.2%",
55 | "not dead",
56 | "not op_mini all"
57 | ],
58 | "development": [
59 | "last 1 chrome version",
60 | "last 1 firefox version",
61 | "last 1 safari version"
62 | ]
63 | },
64 | "devDependencies": {
65 | "@types/react-router-dom": "^5.1.6"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KillianFrappartDev/GroupChat/f395b4819f5c3178757b7c21b70e909677b7c778/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 | GroupChat
12 |
13 |
14 |
15 | You need to enable JavaScript to run this app.
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/frontend/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSelector } from 'react-redux';
3 |
4 | // Local Imports
5 | import AppView from './views/AppView/index';
6 | import AuthView from './views/AuthView/index';
7 |
8 | interface IRootState {
9 | auth: {
10 | isLogged: boolean;
11 | id: string | null;
12 | username: string | null;
13 | image: string | null;
14 | token: string | null;
15 | };
16 | }
17 |
18 | const App: React.FC = () => {
19 | const isAuth = useSelector((state: IRootState) => state.auth.isLogged);
20 |
21 | return isAuth ? : ;
22 | };
23 |
24 | export default App;
25 |
--------------------------------------------------------------------------------
/frontend/src/assets/cookies.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KillianFrappartDev/GroupChat/f395b4819f5c3178757b7c21b70e909677b7c778/frontend/src/assets/cookies.png
--------------------------------------------------------------------------------
/frontend/src/assets/cropped.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KillianFrappartDev/GroupChat/f395b4819f5c3178757b7c21b70e909677b7c778/frontend/src/assets/cropped.png
--------------------------------------------------------------------------------
/frontend/src/assets/gc-logo-nobg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KillianFrappartDev/GroupChat/f395b4819f5c3178757b7c21b70e909677b7c778/frontend/src/assets/gc-logo-nobg.png
--------------------------------------------------------------------------------
/frontend/src/assets/gc-logo-symbol-nobg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KillianFrappartDev/GroupChat/f395b4819f5c3178757b7c21b70e909677b7c778/frontend/src/assets/gc-logo-symbol-nobg.png
--------------------------------------------------------------------------------
/frontend/src/assets/gc-logo-symbol.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KillianFrappartDev/GroupChat/f395b4819f5c3178757b7c21b70e909677b7c778/frontend/src/assets/gc-logo-symbol.png
--------------------------------------------------------------------------------
/frontend/src/assets/gc-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KillianFrappartDev/GroupChat/f395b4819f5c3178757b7c21b70e909677b7c778/frontend/src/assets/gc-logo.png
--------------------------------------------------------------------------------
/frontend/src/components/Auth/Login/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Link } from 'react-router-dom';
3 | import axios from 'axios';
4 | import { TextField, FormControlLabel, Checkbox, Snackbar, CircularProgress } from '@material-ui/core';
5 | import MuiAlert from '@material-ui/lab/Alert';
6 | import { useDispatch } from 'react-redux';
7 | import { useFormik } from 'formik';
8 | import * as Yup from 'yup';
9 | import { useHistory } from 'react-router-dom';
10 |
11 | // Local Imports
12 | import logo from '../../../assets/gc-logo-symbol-nobg.png';
13 | import CustomButton from '../../Shared/CustomButton/index';
14 | import styles from './styles.module.scss';
15 |
16 | type Props = {};
17 |
18 | type SnackData = {
19 | open: boolean;
20 | message: string | null;
21 | };
22 |
23 | const Login: React.FC = props => {
24 | const dispatch = useDispatch();
25 | const history = useHistory();
26 |
27 | const [isLoading, setIsLoading] = useState(false);
28 | const [checked, setChecked] = useState(false);
29 | const [snack, setSnack] = useState({ open: false, message: null });
30 |
31 | // Async Requests
32 | const loginSubmit = async (checked: boolean, email: string, password: string) => {
33 | setIsLoading(true);
34 | let response;
35 | try {
36 | response = await axios.post(`${process.env.REACT_APP_SERVER_URL}/users/login`, {
37 | checked,
38 | email: email.toLowerCase(),
39 | password: password.toLowerCase()
40 | });
41 | } catch (error) {
42 | console.log('[ERROR][AUTH][LOGIN]: ', error);
43 | setIsLoading(false);
44 | return;
45 | }
46 | if (!response.data.access) {
47 | setSnack({ open: true, message: response.data.message });
48 | setIsLoading(false);
49 | return;
50 | }
51 | if (checked) {
52 | localStorage.setItem('userData', JSON.stringify({ id: response.data.user.id, token: response.data.user.token }));
53 | }
54 | dispatch({ type: 'LOGIN', payload: { ...response.data.user } });
55 | history.push('');
56 | setIsLoading(false);
57 | };
58 |
59 | const formik = useFormik({
60 | initialValues: {
61 | email: '',
62 | password: ''
63 | },
64 | validationSchema: Yup.object({
65 | email: Yup.string().email('Invalid email address').required('Required'),
66 | password: Yup.string()
67 | .min(6, 'Must be 6 characters at least')
68 | .required('Required')
69 | .max(20, 'Can not exceed 20 characters')
70 | }),
71 | onSubmit: values => loginSubmit(checked, values.email, values.password)
72 | });
73 |
74 | return (
75 |
76 |
77 |
78 |
79 |
109 |
110 |
Don't have an account? Sign Up
111 |
112 | {isLoading &&
}
113 |
setSnack({ open: false, message: null })} autoHideDuration={5000}>
114 | setSnack({ open: false, message: null })} severity="error">
115 | {snack.message}
116 |
117 |
118 |
119 | );
120 | };
121 |
122 | export default Login;
123 |
--------------------------------------------------------------------------------
/frontend/src/components/Auth/Login/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | background-color: #252329;
3 | width: 100%;
4 | min-height: 100vh;
5 | display: flex;
6 | flex-direction: column;
7 | align-items: center;
8 | justify-content: center;
9 | overflow: hidden;
10 | }
11 |
12 | .logo {
13 | width: 150px;
14 | margin-bottom: 30px;
15 | }
16 |
17 | .form {
18 | display: flex;
19 | flex-direction: column;
20 | }
21 |
22 | .input {
23 | width: 350px;
24 | height: 80px;
25 | @media screen and (max-width: 350px) {
26 | width: 300px;
27 | }
28 | }
29 |
30 | .submit {
31 | width: 350px;
32 | height: 50px;
33 | border-style: none;
34 | margin: 20px 0;
35 | background-color: rgba($color: #000000, $alpha: 0);
36 | color: rgb(129, 3, 255);
37 | color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);
38 | border: solid 2px rgb(129, 3, 255);
39 | border-color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);
40 | &:hover {
41 | background-color: rgb(129, 3, 255);
42 | background-color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);
43 | color: #252329;
44 | cursor: pointer;
45 | }
46 | }
47 |
48 | .guest {
49 | color: #006ebd;
50 | font-size: 12px;
51 | margin-top: 20px;
52 | margin-bottom: 30px;
53 | &:hover {
54 | cursor: pointer;
55 | }
56 | }
57 |
58 | .error {
59 | font-size: 12px;
60 | color: #f44336;
61 | }
62 |
--------------------------------------------------------------------------------
/frontend/src/components/Auth/Signup/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import axios from 'axios';
3 | import { TextField, FormControlLabel, Checkbox, Snackbar, CircularProgress } from '@material-ui/core';
4 | import { Link } from 'react-router-dom';
5 | import { useDispatch } from 'react-redux';
6 | import MuiAlert from '@material-ui/lab/Alert';
7 | import { useFormik } from 'formik';
8 | import * as Yup from 'yup';
9 | import { useHistory } from 'react-router-dom';
10 |
11 | // Local Imports
12 | import logo from '../../../assets/gc-logo-symbol-nobg.png';
13 | import CustomButton from '../../Shared/CustomButton/index';
14 | import styles from './styles.module.scss';
15 |
16 | type Props = {};
17 |
18 | type SnackData = {
19 | open: boolean;
20 | message: string | null;
21 | };
22 |
23 | const Signup: React.FC = props => {
24 | const dispatch = useDispatch();
25 | const history = useHistory();
26 |
27 | const [isLoading, setIsLoading] = useState(false);
28 | const [checked, setChecked] = useState(false);
29 | const [snack, setSnack] = useState({ open: false, message: null });
30 |
31 | // Async Requests
32 | const signupSubmit = async (checked: boolean, email: string, password: string, username: string) => {
33 | setIsLoading(true);
34 | let response;
35 | try {
36 | response = await axios.post(`${process.env.REACT_APP_SERVER_URL}/users/signup`, {
37 | checked,
38 | email: email.toLowerCase(),
39 | password: password.toLowerCase(),
40 | username
41 | });
42 | } catch (error) {
43 | console.log('[ERROR][AUTH][SIGNUP]: ', error);
44 | setIsLoading(false);
45 | return;
46 | }
47 | if (!response.data.access) {
48 | setSnack({ open: true, message: response.data.message });
49 | setIsLoading(false);
50 | return;
51 | }
52 | if (checked) {
53 | localStorage.setItem('userData', JSON.stringify({ id: response.data.user.id, token: response.data.user.token }));
54 | }
55 | dispatch({ type: 'LOGIN', payload: { ...response.data.user } });
56 | history.push('');
57 | setIsLoading(false);
58 | };
59 |
60 | const formik = useFormik({
61 | initialValues: {
62 | username: '',
63 | email: '',
64 | password: ''
65 | },
66 | validationSchema: Yup.object({
67 | username: Yup.string()
68 | .min(2, 'Must be 2 characters at least')
69 | .required('Required')
70 | .max(12, 'Can not exceed 12 characters'),
71 | email: Yup.string().email('Invalid email address').required('Required'),
72 | password: Yup.string()
73 | .min(6, 'Must be 6 characters at least')
74 | .required('Required')
75 | .max(20, 'Can not exceed 20 characters')
76 | }),
77 | onSubmit: values => signupSubmit(checked, values.email, values.password, values.username)
78 | });
79 |
80 | return (
81 |
82 |
83 |
84 |
85 |
123 |
124 |
Already a member ? Login
125 |
126 | {isLoading &&
}
127 |
setSnack({ open: false, message: null })} autoHideDuration={5000}>
128 | setSnack({ open: false, message: null })} severity="error">
129 | {snack.message}
130 |
131 |
132 |
133 | );
134 | };
135 |
136 | export default Signup;
137 |
--------------------------------------------------------------------------------
/frontend/src/components/Auth/Signup/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | background-color: #252329;
3 | width: 100%;
4 | min-height: 100vh;
5 | display: flex;
6 | flex-direction: column;
7 | align-items: center;
8 | justify-content: center;
9 | overflow: hidden;
10 | }
11 |
12 | .logo {
13 | width: 150px;
14 | margin-bottom: 30px;
15 | }
16 |
17 | .form {
18 | display: flex;
19 | flex-direction: column;
20 | }
21 |
22 | .input {
23 | width: 350px;
24 | height: 80px;
25 | @media screen and (max-width: 350px) {
26 | width: 300px;
27 | }
28 | }
29 |
30 | .submit {
31 | width: 350px;
32 | height: 50px;
33 | border-style: none;
34 | margin: 20px 0;
35 | background-color: rgba($color: #000000, $alpha: 0);
36 | color: rgb(129, 3, 255);
37 | color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);
38 | border: solid 2px rgb(129, 3, 255);
39 | border-color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);
40 | &:hover {
41 | background-color: rgb(129, 3, 255);
42 | background: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(224, 2, 255, 1) 100%) !important;
43 | color: #252329;
44 | cursor: pointer;
45 | }
46 | }
47 |
48 | .guest {
49 | color: #006ebd;
50 | font-size: 12px;
51 | margin-top: 20px;
52 | margin-bottom: 30px;
53 | &:hover {
54 | cursor: pointer;
55 | }
56 | }
57 |
58 | .error {
59 | font-size: 12px;
60 | color: #f44336;
61 | }
62 |
--------------------------------------------------------------------------------
/frontend/src/components/Auth/Welcome/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import axios from 'axios';
3 | import { Link } from 'react-router-dom';
4 | import { useDispatch } from 'react-redux';
5 |
6 | // Local Imports
7 | import logo from '../../../assets/cropped.png';
8 | import CustomButton from '../../Shared/CustomButton/index';
9 | import styles from './styles.module.scss';
10 |
11 | type Props = {};
12 |
13 | const Welcome: React.FC = props => {
14 | const dispatch = useDispatch();
15 |
16 | // Async Requests
17 | const guestRequest = async () => {
18 | let response;
19 | try {
20 | response = await axios.post(`${process.env.REACT_APP_SERVER_URL}/users/guest`);
21 | } catch (error) {
22 | console.log('[ERROR][AUTH][GUEST]: ', error);
23 | return;
24 | }
25 | if (!response.data.access) return;
26 | dispatch({ type: 'GUEST', payload: { ...response.data.user } });
27 | };
28 |
29 | return (
30 |
31 |
32 |
33 |
{}} isPurple={false} title="Login" small={false} />
34 |
35 |
36 | {}} isPurple={true} title="Signup" small={false} />
37 |
38 |
39 | Continue as guest
40 |
41 |
42 | );
43 | };
44 |
45 | export default Welcome;
46 |
--------------------------------------------------------------------------------
/frontend/src/components/Auth/Welcome/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 100%;
3 | min-height: 100vh;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: center;
8 | background-color: #252329;
9 | overflow: hidden;
10 | }
11 |
12 | .logo {
13 | width: 350px;
14 | @media screen and (max-width: 350px) {
15 | width: 300px;
16 | }
17 | }
18 |
19 | .guest {
20 | color: #006ebd;
21 | font-size: 12px;
22 | margin-top: 20px;
23 | &:hover {
24 | cursor: pointer;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/components/Main/Messages/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { CircularProgress } from '@material-ui/core';
3 |
4 | // Local Imports
5 | import styles from './styles.module.scss';
6 |
7 | type PropsMessage = {
8 | username: string;
9 | text: string;
10 | image: string;
11 | _id: string;
12 | date: string;
13 | };
14 |
15 | const Message: React.FC = props => {
16 | return (
17 |
18 |
19 |
20 |
21 | {props.username} {props.date}
22 |
23 |
{props.text}
24 |
25 |
26 | );
27 | };
28 |
29 | type PropsMessages = {
30 | messages: PropsMessage[];
31 | onClick: () => void;
32 | loading: boolean;
33 | };
34 |
35 | const Messages: React.FC = props => {
36 | useEffect(() => {
37 | const chatElement = document.getElementById('chat');
38 | if (chatElement) {
39 | chatElement.scrollTop = chatElement.scrollHeight;
40 | }
41 | });
42 |
43 | return (
44 |
45 | {props.loading ? (
46 |
47 |
48 |
49 | ) : (
50 |
51 | {props.messages.map(message => (
52 |
60 | ))}
61 |
62 | )}
63 |
64 | );
65 | };
66 |
67 | export default Messages;
68 |
--------------------------------------------------------------------------------
/frontend/src/components/Main/Messages/styles.module.scss:
--------------------------------------------------------------------------------
1 | // List
2 | .container {
3 | flex: 1;
4 | display: flex;
5 | justify-content: center;
6 | overflow-y: auto;
7 | }
8 |
9 | .wrapper {
10 | width: 95%;
11 | padding: 20px;
12 | }
13 |
14 | // Message
15 |
16 | .messageContainer {
17 | width: 100%;
18 | display: flex;
19 | align-items: center;
20 | }
21 |
22 | .image {
23 | width: 65px;
24 | height: 65px;
25 | border-radius: 50%;
26 | @media screen and (max-width: 350px) {
27 | width: 50px;
28 | height: 50px;
29 | }
30 | }
31 |
32 | .textBox {
33 | margin: 30px 0px 30px 20px;
34 | }
35 |
36 | .username {
37 | color: #828282;
38 | font-size: 18px;
39 | font-weight: bold;
40 | @media screen and (max-width: 350px) {
41 | font-size: 15px;
42 | }
43 | }
44 |
45 | .message {
46 | color: #e0e0e0;
47 | font-size: 14px;
48 | @media screen and (max-width: 350px) {
49 | font-size: 12px;
50 | }
51 | }
52 |
53 | .date {
54 | font-size: 11px;
55 | margin-left: 30px;
56 | font-weight: 100;
57 | @media screen and (max-width: 500px) {
58 | display: none;
59 | }
60 | }
61 |
62 | .loading {
63 | display: flex;
64 | align-items: center;
65 | }
66 |
--------------------------------------------------------------------------------
/frontend/src/components/Main/MsgInput/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { InputBase, IconButton } from '@material-ui/core';
3 | import SendIcon from '@material-ui/icons/Send';
4 |
5 | // Local Imports
6 | import styles from './styles.module.scss';
7 |
8 | type Props = {
9 | sendClick: (msg: string, date: string) => void;
10 | onClick: () => void;
11 | };
12 |
13 | const MsgInput: React.FC = props => {
14 | const [msg, setMsg] = useState('');
15 |
16 | const getDateString = () => {
17 | const monthNames = [
18 | 'January',
19 | 'February',
20 | 'March',
21 | 'April',
22 | 'May',
23 | 'June',
24 | 'July',
25 | 'August',
26 | 'September',
27 | 'October',
28 | 'November',
29 | 'December'
30 | ];
31 | let dateObj = new Date();
32 | let month = monthNames[dateObj.getMonth()];
33 | let day = String(dateObj.getDate()).padStart(2, '0');
34 | let output = month + ' ' + day + ',';
35 |
36 | return `${output} - ${new Date().getHours()}:${new Date().getMinutes()}`;
37 | };
38 |
39 | const sendHandler = () => {
40 | props.sendClick(msg, getDateString());
41 | setMsg('');
42 | };
43 |
44 | return (
45 |
46 | setMsg(e.target.value)}
51 | onKeyDown={e => {
52 | if (e.key === 'Enter') sendHandler();
53 | }}
54 | />
55 |
56 |
57 |
58 |
59 | );
60 | };
61 |
62 | export default MsgInput;
63 |
--------------------------------------------------------------------------------
/frontend/src/components/Main/MsgInput/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 8px;
3 | margin-bottom: 20px;
4 | display: flex;
5 | align-items: center;
6 | width: 95%;
7 | background-color: #3c393f;
8 | align-self: center;
9 | border-radius: 8px;
10 | }
11 |
12 | .input {
13 | margin-left: 5px;
14 | flex: 1;
15 | color: #e0e0e0 !important;
16 | }
17 |
18 | .iconButton {
19 | padding: 8px;
20 | background-color: rgb(129, 3, 255) !important;
21 | background: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(224, 2, 255, 1) 100%) !important;
22 | border-radius: 8px !important;
23 | }
24 |
25 | .send {
26 | color: #fff;
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/src/components/Main/Onboard/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // Local Imports
4 | import logo from '../../../assets/gc-logo-symbol-nobg.png';
5 | import styles from './styles.module.scss';
6 |
7 | type Props = {
8 | onClick: () => void;
9 | };
10 |
11 | const Onboard: React.FC = props => {
12 | return (
13 |
14 |
15 |
16 |
Hello World!
17 |
18 | I really appreciate that you take time to have a look to my work. This app is an instant messaging project
19 | that offer the possibility to create and join channels and start a conversation.
20 |
21 |
22 | ⭐️ Use the "menu" icon on top (Mobile).
23 | ⭐️ Click on any channel you want to join.
24 | ⭐️ Create a channel with the "+" icon.
25 | ⭐️ Send messages with the text input.
26 | ⭐️ Browse channels with the search input.
27 | ⭐️ Report a bug with the "bug" icon.
28 | ⭐️ Click on your profile to edit.
29 | ⭐️ Use the "exit" icon to logout.
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default Onboard;
37 |
--------------------------------------------------------------------------------
/frontend/src/components/Main/Onboard/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | flex: 1;
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | }
7 |
8 | .title {
9 | @media screen and (max-width: 767px) {
10 | margin-bottom: 20px;
11 | }
12 | }
13 |
14 | .wrapper {
15 | display: flex;
16 | flex-direction: column;
17 | align-items: center;
18 | justify-content: center;
19 | width: 75%;
20 | @media screen and (max-width: 767px) {
21 | width: 90%;
22 | }
23 | }
24 |
25 | .list {
26 | list-style: none;
27 | font-size: 14px;
28 | @media screen and (max-width: 767px) {
29 | li {
30 | font-size: 12px;
31 | }
32 | }
33 | }
34 |
35 | .description {
36 | text-align: center;
37 | margin: 20px 0;
38 | @media screen and (max-width: 767px) {
39 | display: none;
40 | }
41 | }
42 |
43 | .logo {
44 | width: 120px;
45 | }
46 |
--------------------------------------------------------------------------------
/frontend/src/components/Main/TopBar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { IconButton } from '@material-ui/core';
3 | import MenuIcon from '@material-ui/icons/Menu';
4 |
5 | // Local Imports
6 | import styles from './styles.module.scss';
7 |
8 | type Props = {
9 | title?: String;
10 | menuClick: () => void;
11 | };
12 |
13 | const TopBar: React.FC = props => {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
{props.title}
21 |
22 |
23 | );
24 | };
25 |
26 | export default TopBar;
27 |
--------------------------------------------------------------------------------
/frontend/src/components/Main/TopBar/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 100%;
3 | height: 60px;
4 | box-shadow: 0px 4px 4px rgba($color: #000, $alpha: 0.2);
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | }
9 |
10 | .wrapper {
11 | width: 95%;
12 | display: flex;
13 | align-items: center;
14 | }
15 |
16 | .title {
17 | font-size: 18px;
18 | }
19 |
20 | .iconButton {
21 | display: none !important;
22 | @media (max-width: 767px) {
23 | display: inline-block !important;
24 | }
25 | }
26 |
27 | .menu {
28 | color: #e0e0e0;
29 | }
30 |
--------------------------------------------------------------------------------
/frontend/src/components/Shared/Cookie/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | // Local Imports
4 | import image from '../../../assets/cookies.png';
5 | import CustomButton from '../CustomButton/index';
6 | import styles from './styles.module.scss';
7 |
8 | type Props = {
9 | onAccept: () => void;
10 | };
11 |
12 | const Cookie: React.FC = props => {
13 | const [open, setOpen] = useState(true);
14 | return (
15 |
16 | {open && (
17 |
18 |
Are you hungry ?
19 |
25 |
26 | )}
27 |
setOpen(prev => !prev)} src={image} />
28 |
29 | );
30 | };
31 |
32 | export default Cookie;
33 |
--------------------------------------------------------------------------------
/frontend/src/components/Shared/Cookie/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | position: absolute;
3 | right: 20px;
4 | bottom: 20px;
5 | display: flex;
6 | align-items: flex-end;
7 | }
8 |
9 | .box {
10 | margin-right: 10px;
11 | background-color: #19161b;
12 | border-radius: 10px;
13 | display: flex;
14 | flex-direction: column;
15 | align-items: center;
16 | padding: 20px 20px 0px;
17 | @media screen and (max-width: 424px) {
18 | padding-bottom: 20px;
19 | }
20 | }
21 |
22 | .cookie {
23 | width: 60px;
24 | -webkit-animation: wobble-ver-right 2s 0s infinite forwards;
25 | animation: wobble-ver-right 2s 0s infinite forwards;
26 | &:hover {
27 | cursor: pointer;
28 | }
29 | }
30 |
31 | .title {
32 | margin-bottom: 10px;
33 | }
34 |
35 | .actions {
36 | display: flex;
37 | align-items: center;
38 | @media screen and (max-width: 424px) {
39 | flex-direction: column;
40 | }
41 | }
42 |
43 | .info {
44 | color: #006ebd;
45 | font-size: 12px;
46 | margin-top: 20px;
47 | margin-bottom: 20px;
48 | margin-left: 20px;
49 | @media screen and (max-width: 424px) {
50 | margin: 0px;
51 | }
52 | }
53 |
54 | @-webkit-keyframes wobble-ver-right {
55 | 0%,
56 | 100% {
57 | -webkit-transform: translateY(0) rotate(0);
58 | transform: translateY(0) rotate(0);
59 | -webkit-transform-origin: 50% 50%;
60 | transform-origin: 50% 50%;
61 | }
62 | 15% {
63 | -webkit-transform: translateY(-30px) rotate(6deg);
64 | transform: translateY(-30px) rotate(6deg);
65 | }
66 | 30% {
67 | -webkit-transform: translateY(15px) rotate(-6deg);
68 | transform: translateY(15px) rotate(-6deg);
69 | }
70 | 45% {
71 | -webkit-transform: translateY(-15px) rotate(3.6deg);
72 | transform: translateY(-15px) rotate(3.6deg);
73 | }
74 | 60% {
75 | -webkit-transform: translateY(9px) rotate(-2.4deg);
76 | transform: translateY(9px) rotate(-2.4deg);
77 | }
78 | 75% {
79 | -webkit-transform: translateY(-6px) rotate(1.2deg);
80 | transform: translateY(-6px) rotate(1.2deg);
81 | }
82 | }
83 |
84 | @keyframes wobble-ver-right {
85 | 0%,
86 | 100% {
87 | -webkit-transform: translateY(0) rotate(0);
88 | transform: translateY(0) rotate(0);
89 | -webkit-transform-origin: 50% 50%;
90 | transform-origin: 50% 50%;
91 | }
92 | 15% {
93 | -webkit-transform: translateY(-30px) rotate(6deg);
94 | transform: translateY(-30px) rotate(6deg);
95 | }
96 | 30% {
97 | -webkit-transform: translateY(15px) rotate(-6deg);
98 | transform: translateY(15px) rotate(-6deg);
99 | }
100 | 45% {
101 | -webkit-transform: translateY(-15px) rotate(3.6deg);
102 | transform: translateY(-15px) rotate(3.6deg);
103 | }
104 | 60% {
105 | -webkit-transform: translateY(9px) rotate(-2.4deg);
106 | transform: translateY(9px) rotate(-2.4deg);
107 | }
108 | 75% {
109 | -webkit-transform: translateY(-6px) rotate(1.2deg);
110 | transform: translateY(-6px) rotate(1.2deg);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/frontend/src/components/Shared/CustomButton/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // Local Imports
4 | import styles from './styles.module.scss';
5 |
6 | type Props = {
7 | isPurple: boolean;
8 | title: string;
9 | small: boolean;
10 | type?: 'button' | 'submit' | 'reset' | undefined;
11 | onClick: () => void;
12 | };
13 |
14 | const CustomButton: React.FC = props => {
15 | if (!props.isPurple)
16 | return (
17 |
18 | {props.title}
19 |
20 | );
21 | else
22 | return (
23 |
28 | {props.title}
29 |
30 | );
31 | };
32 |
33 | export default CustomButton;
34 |
--------------------------------------------------------------------------------
/frontend/src/components/Shared/CustomButton/styles.module.scss:
--------------------------------------------------------------------------------
1 | .black,
2 | .purple,
3 | .smallPurple {
4 | &:focus {
5 | outline: none;
6 | }
7 | }
8 |
9 | .purple,
10 | .black {
11 | width: 350px;
12 | height: 50px;
13 | border-style: none;
14 | margin: 20px 0;
15 | &:hover {
16 | cursor: pointer;
17 | }
18 |
19 | @media screen and (max-width: 350px) {
20 | width: 300px;
21 | }
22 | }
23 |
24 | .smallPurple {
25 | width: 150px;
26 | height: 40px;
27 | font-size: 14px;
28 | margin: 20px 0;
29 | border: solid 2px rgb(129, 3, 255);
30 | &:hover {
31 | cursor: pointer;
32 | }
33 | }
34 |
35 | .black {
36 | background-color: #19161b;
37 | color: #e0e0e0;
38 | &:hover {
39 | background-color: #e0e0e0;
40 | color: #19161b;
41 | }
42 | }
43 |
44 | .purple,
45 | .smallPurple {
46 | background-color: rgba($color: #000000, $alpha: 0);
47 | color: rgb(129, 3, 255);
48 | color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);
49 | border: solid 2px rgb(129, 3, 255);
50 | &:hover {
51 | background-color: rgb(129, 3, 255);
52 | background: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(224, 2, 255, 1) 100%) !important;
53 | border-width: 0px;
54 | color: #252329;
55 | }
56 | }
57 |
58 | .smallPurple {
59 | border-width: 1px;
60 | }
61 |
--------------------------------------------------------------------------------
/frontend/src/components/Shared/EditProfile/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useLayoutEffect } from 'react';
2 | import { TextField, CircularProgress } from '@material-ui/core';
3 | import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
4 | import { useSelector } from 'react-redux';
5 | import axios from 'axios';
6 | import sha1 from 'sha1';
7 | import { useDispatch } from 'react-redux';
8 |
9 | // Local Imports
10 | import CustomButton from '../CustomButton/index';
11 | import styles from './styles.module.scss';
12 |
13 | const darkTheme = createMuiTheme({
14 | palette: {
15 | type: 'dark'
16 | }
17 | });
18 |
19 | type Props = {
20 | onEdit: (username: string, image: string) => void;
21 | };
22 |
23 | interface IRootState {
24 | auth: {
25 | username: string;
26 | image: string;
27 | };
28 | }
29 |
30 | const EditProfile: React.FC = props => {
31 | const dispatch = useDispatch();
32 |
33 | const { username, image } = useSelector((state: IRootState) => state.auth);
34 | const imagePickerRef = useRef(null);
35 |
36 | const [isLoading, setIsLoading] = useState(false);
37 | const [isValid, setIsValid] = useState(true);
38 | const [newUsername, setUsername] = useState(username);
39 | const [usernameError, setUsernameError] = useState(false);
40 | const [usernameHelper, setUsernameHelper] = useState('');
41 | const [newImage, setImage] = useState(image);
42 |
43 | const editHandler = (newUsername: string, newImage: string) => {
44 | if (usernameError) {
45 | setIsValid(false);
46 | return;
47 | }
48 |
49 | props.onEdit(newUsername, newImage);
50 | };
51 |
52 | const usernameHandler = (e: React.ChangeEvent) => {
53 | if (e.target.value.length <= 2 || e.target.value.length > 12) {
54 | setUsernameError(true);
55 | setUsernameHelper('Username should contain 3 to 12 characters.');
56 | } else {
57 | setUsernameError(false);
58 | setUsernameHelper('');
59 | setIsValid(true);
60 | }
61 |
62 | setUsername(e.target.value);
63 | };
64 |
65 | const postImage = async (data: FormData) => {
66 | setIsLoading(true);
67 | let response;
68 | try {
69 | response = await axios.post('https://api.cloudinary.com/v1_1/djghq5xmi/image/upload', data);
70 | } catch (error) {
71 | console.log('ERROR', error);
72 | setIsLoading(false);
73 | }
74 | if (!response) return;
75 | setImage(response.data.secure_url);
76 | setIsLoading(false);
77 | };
78 |
79 | const uploadHandler = (e: any) => {
80 | const formData = new FormData();
81 | formData.append('file', e.target.files[0]);
82 | formData.append('api_key', process.env.REACT_APP_CLOUDINARY_API!);
83 | formData.append('timestamp', Math.floor(Date.now() / 1000).toString());
84 | formData.append(
85 | 'signature',
86 | sha1(`timestamp=${Math.floor(Date.now() / 1000)}${process.env.REACT_APP_CLOUDINARY_SECRET}`)
87 | );
88 | postImage(formData);
89 | };
90 |
91 | return (
92 | <>
93 | dispatch({ type: 'MODAL', payload: { modal: null } })}>
94 |
95 |
Profile
96 |
97 |
127 |
128 |
129 | >
130 | );
131 | };
132 |
133 | export default EditProfile;
134 |
--------------------------------------------------------------------------------
/frontend/src/components/Shared/EditProfile/styles.module.scss:
--------------------------------------------------------------------------------
1 | .backdrop {
2 | width: 100%;
3 | height: 100vh;
4 | background-color: rgba($color: #000000, $alpha: 0.3);
5 | position: fixed;
6 | top: 0;
7 | }
8 |
9 | .modal {
10 | position: absolute;
11 | top: 50%;
12 | left: 50%;
13 | transform: translate(-50%, -50%);
14 | width: 600px;
15 | background-color: #120f13;
16 | border-radius: 10px;
17 | padding: 30px;
18 | @media screen and (max-width: 600px) {
19 | width: 100%;
20 | padding: 10px;
21 | }
22 | }
23 |
24 | .form {
25 | display: flex;
26 | flex-direction: column;
27 | margin-top: 20px;
28 | }
29 |
30 | .input {
31 | margin: 20px 0 !important;
32 | }
33 |
34 | .error {
35 | font-size: 12px;
36 | color: #f44336;
37 | }
38 |
39 | .image {
40 | align-self: flex-start;
41 | width: 130px;
42 | height: 130px;
43 | border-radius: 50%;
44 | }
45 |
46 | .file {
47 | display: none;
48 | }
49 |
--------------------------------------------------------------------------------
/frontend/src/components/Shared/Modal/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { TextField } from '@material-ui/core';
3 | import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
4 | import { useDispatch } from 'react-redux';
5 |
6 | // Local Imports
7 | import CustomButton from '../CustomButton/index';
8 | import styles from './styles.module.scss';
9 |
10 | const darkTheme = createMuiTheme({
11 | palette: {
12 | type: 'dark'
13 | }
14 | });
15 |
16 | type Props = {
17 | onCreate: (title: string, description: string) => void;
18 | title: string;
19 | };
20 |
21 | const Modal: React.FC = props => {
22 | const dispatch = useDispatch();
23 |
24 | const [isValid, setIsValid] = useState(true);
25 | const [title, setTitle] = useState('');
26 | const [titleError, setTitleEror] = useState(false);
27 | const [titleHelper, setTitleHelper] = useState('');
28 | const [description, setDescription] = useState('');
29 |
30 | const createHandler = (title: string, description: string) => {
31 | if (titleError) {
32 | setIsValid(false);
33 | return;
34 | }
35 |
36 | props.onCreate(title, description);
37 | };
38 |
39 | const titleHandler = (e: React.ChangeEvent) => {
40 | if (e.target.value.length <= 2 || e.target.value.length > 12) {
41 | setTitleEror(true);
42 | setTitleHelper('Title should contain 3 to 12 characters.');
43 | } else {
44 | setTitleEror(false);
45 | setTitleHelper('');
46 | setIsValid(true);
47 | }
48 |
49 | setTitle(e.target.value);
50 | };
51 |
52 | return (
53 | <>
54 | dispatch({ type: 'MODAL', payload: { modal: null } })}>
55 |
56 |
{props.title}
57 |
58 |
82 |
83 |
84 | >
85 | );
86 | };
87 |
88 | export default Modal;
89 |
--------------------------------------------------------------------------------
/frontend/src/components/Shared/Modal/styles.module.scss:
--------------------------------------------------------------------------------
1 | .backdrop {
2 | width: 100%;
3 | height: 100vh;
4 | background-color: rgba($color: #000000, $alpha: 0.3);
5 | position: fixed;
6 | top: 0;
7 | }
8 |
9 | .modal {
10 | position: absolute;
11 | top: 50%;
12 | left: 50%;
13 | transform: translate(-50%, -50%);
14 | width: 600px;
15 | background-color: #120f13;
16 | border-radius: 10px;
17 | padding: 30px;
18 | @media screen and (max-width: 600px) {
19 | width: 100%;
20 | padding: 10px;
21 | }
22 | }
23 |
24 | .form {
25 | display: flex;
26 | flex-direction: column;
27 | }
28 |
29 | .input {
30 | margin: 20px 0 !important;
31 | }
32 |
33 | .error {
34 | font-size: 12px;
35 | color: #f44336;
36 | }
37 |
--------------------------------------------------------------------------------
/frontend/src/components/Side/BottomBar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { IconButton, Tooltip } from '@material-ui/core';
3 | import ExitToAppIcon from '@material-ui/icons/ExitToApp';
4 | import BugReportIcon from '@material-ui/icons/BugReport';
5 | import { useSelector } from 'react-redux';
6 |
7 | // Local Imports
8 | import styles from './styles.module.scss';
9 |
10 | type Props = {
11 | exitClick: () => void;
12 | profileClick: () => void;
13 | bugClick: () => void;
14 | };
15 |
16 | interface IRootState {
17 | auth: {
18 | username: string;
19 | image: string;
20 | };
21 | }
22 |
23 | const BottomBar: React.FC = props => {
24 | const { username, image } = useSelector((state: IRootState) => state.auth);
25 |
26 | return (
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
{username}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | export default BottomBar;
54 |
--------------------------------------------------------------------------------
/frontend/src/components/Side/BottomBar/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 100%;
3 | height: 90px;
4 | background-color: #0b090c;
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | }
9 |
10 | .wrapper {
11 | width: 95%;
12 | display: flex;
13 | align-items: center;
14 | justify-content: space-between;
15 | }
16 |
17 | .userBox {
18 | display: flex;
19 | align-items: center;
20 | }
21 |
22 | .image {
23 | width: 55px;
24 | height: 55px;
25 | border-radius: 50%;
26 | margin-right: 15px;
27 | &:hover {
28 | cursor: pointer;
29 | }
30 | }
31 |
32 | .exitButton {
33 | @media screen and (max-width: 350px) {
34 | padding: 5px;
35 | }
36 | }
37 |
38 | .username {
39 | color: #828282;
40 | font-size: 18px;
41 | }
42 |
43 | .exit {
44 | color: #e0e0e0;
45 | &:hover {
46 | color: rgb(129, 3, 255);
47 | color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);
48 | }
49 | }
50 |
51 | .buttonContainer {
52 | display: flex;
53 | }
54 |
--------------------------------------------------------------------------------
/frontend/src/components/Side/GroupInfo/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // Local Imports
4 | import styles from './styles.module.scss';
5 |
6 | type Props = {
7 | currentGroup: {
8 | title: string;
9 | description: string;
10 | } | null;
11 | };
12 |
13 | const GroupInfo: React.FC = props => {
14 | return (
15 |
16 |
17 |
{props.currentGroup?.title}
18 |
{props.currentGroup?.description}
19 |
20 |
21 | );
22 | };
23 |
24 | export default GroupInfo;
25 |
--------------------------------------------------------------------------------
/frontend/src/components/Side/GroupInfo/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
7 | .wrapper {
8 | width: 95%;
9 | }
10 |
11 | .title {
12 | color: #e0e0e0;
13 | font-size: 20px;
14 | margin: 20px 0;
15 | }
16 |
17 | .description {
18 | font-size: 13px;
19 | color: #e0e0e0;
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/components/Side/Groups/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // Local Imports
4 | import styles from './styles.module.scss';
5 |
6 | type PropsGroup = {
7 | title: string;
8 | key?: string;
9 | tag?: string;
10 | _id: string;
11 | groupClick: (id: string) => void;
12 | };
13 |
14 | const Group: React.FC = props => {
15 | return (
16 | props.groupClick(props._id)}>
17 |
{props.tag}
18 |
{props.title}
19 |
20 | );
21 | };
22 |
23 | type PropsGroups = {
24 | groups: PropsGroup[];
25 | groupClick: (id: string) => void;
26 | };
27 |
28 | const Groups: React.FC = props => {
29 | return (
30 |
31 |
32 | {props.groups.map(group => (
33 | props.groupClick(id)}
39 | />
40 | ))}
41 |
42 |
43 | );
44 | };
45 |
46 | export default Groups;
47 |
--------------------------------------------------------------------------------
/frontend/src/components/Side/Groups/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | overflow-y: auto;
3 | display: flex;
4 | justify-content: center;
5 | }
6 |
7 | .wrapper {
8 | width: 95%;
9 | }
10 |
11 | .group {
12 | display: flex;
13 | align-items: center;
14 | margin: 20px 0;
15 | &:hover {
16 | cursor: pointer;
17 | .tag,
18 | .title {
19 | color: rgb(129, 3, 255);
20 | color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);
21 | }
22 | }
23 | }
24 |
25 | .tag {
26 | width: 40px;
27 | height: 40px;
28 | display: flex;
29 | justify-content: center;
30 | align-items: center;
31 | background-color: #252329;
32 | border-radius: 10px;
33 | margin-right: 15px;
34 | }
35 |
--------------------------------------------------------------------------------
/frontend/src/components/Side/Members/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CircularProgress from '@material-ui/core/CircularProgress';
3 |
4 | // Local Imports
5 | import styles from './styles.module.scss';
6 |
7 | type MemberProps = {
8 | _id: string;
9 | username: string;
10 | image: string;
11 | };
12 |
13 | const Member: React.FC = props => {
14 | return (
15 |
16 |
17 |
{props.username}
18 |
19 | );
20 | };
21 |
22 | type MembersProps = {
23 | members: MemberProps[];
24 | loading: boolean;
25 | };
26 |
27 | const Members: React.FC = props => {
28 | return (
29 |
30 |
Members
31 | {props.loading ? (
32 |
33 |
34 |
35 | ) : (
36 |
37 | {props.members.map(member => (
38 |
39 | ))}
40 |
41 | )}
42 |
43 | );
44 | };
45 |
46 | export default Members;
47 |
--------------------------------------------------------------------------------
/frontend/src/components/Side/Members/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | overflow-y: auto;
6 | margin: 20px 0;
7 | }
8 |
9 | .wrapper {
10 | width: 95%;
11 | }
12 |
13 | .member {
14 | display: flex;
15 | align-items: center;
16 | margin: 20px 0;
17 | }
18 |
19 | .title {
20 | font-size: 20px;
21 | color: #e0e0e0;
22 | margin: 20px 0;
23 | width: 95%;
24 | }
25 |
26 | .username {
27 | color: #828282;
28 | }
29 |
30 | .image {
31 | width: 50px;
32 | height: 50px;
33 | border-radius: 50%;
34 | margin-right: 20px;
35 | }
36 |
37 | .loading {
38 | margin-top: 50px;
39 | }
40 |
--------------------------------------------------------------------------------
/frontend/src/components/Side/Search/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { InputBase, IconButton } from '@material-ui/core';
3 | import SearchIcon from '@material-ui/icons/Search';
4 |
5 | // Local Imports
6 | import styles from './styles.module.scss';
7 |
8 | type Props = {
9 | groups: Group[];
10 | update: (groups: Group[]) => void;
11 | };
12 |
13 | type Group = {
14 | title: string;
15 | _id: string;
16 | description: string;
17 | members: any;
18 | groupClick: () => {};
19 | };
20 |
21 | const Search: React.FC = props => {
22 | const [searchValue, setSearchValue] = useState('');
23 |
24 | const searchHandler = (e: React.ChangeEvent) => {
25 | setSearchValue(e.target.value);
26 | const allGroups = props.groups;
27 | const filteredGroups: Group[] = allGroups.filter(grp =>
28 | grp.title.toLowerCase().includes(e.target.value.toLowerCase())
29 | );
30 | props.update(filteredGroups);
31 | };
32 |
33 | return (
34 |
35 |
36 |
37 |
38 | searchHandler(e)}
42 | value={searchValue}
43 | />
44 |
45 | );
46 | };
47 |
48 | export default Search;
49 |
--------------------------------------------------------------------------------
/frontend/src/components/Side/Search/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 6px;
3 | margin-top: 20px;
4 | display: flex;
5 | align-items: center;
6 | width: 95%;
7 | background-color: #3c393f;
8 | align-self: center;
9 | border-radius: 8px;
10 | }
11 |
12 | .input {
13 | margin-left: 5px;
14 | flex: 1;
15 | color: #e0e0e0 !important;
16 | }
17 |
18 | .iconButton {
19 | padding: 8px;
20 | border-radius: 8px !important;
21 | }
22 |
23 | .search {
24 | color: #e0e0e0;
25 | &:hover {
26 | color: rgb(129, 3, 255);
27 | color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/frontend/src/components/Side/TopBar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { IconButton, Tooltip } from '@material-ui/core';
3 | import AddIcon from '@material-ui/icons/Add';
4 | import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos';
5 |
6 | // Local Imports
7 | import styles from './styles.module.scss';
8 |
9 | type Props = {
10 | arrowClick: () => void;
11 | plusClick: () => void;
12 | inChannel: boolean;
13 | };
14 |
15 | const TopBar: React.FC = props => {
16 | return (
17 |
18 | {props.inChannel ? (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
All channels
26 |
27 | ) : (
28 |
29 |
Channels
30 |
31 |
32 |
33 |
34 |
35 |
36 | )}
37 |
38 | );
39 | };
40 |
41 | export default TopBar;
42 |
--------------------------------------------------------------------------------
/frontend/src/components/Side/TopBar/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 100%;
3 | height: 60px;
4 | box-shadow: 0px 4px 4px rgba($color: #000, $alpha: 0.2);
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | }
9 |
10 | .wrapperInChannel,
11 | .wrapperOutChannel {
12 | width: 95%;
13 | display: flex;
14 | align-items: center;
15 | }
16 |
17 | .wrapperOutChannel {
18 | justify-content: space-between;
19 | }
20 |
21 | .title {
22 | font-size: 18px;
23 | }
24 |
25 | .addButton {
26 | order: 2;
27 | }
28 |
29 | .arrow,
30 | .add {
31 | color: #e0e0e0;
32 | &:hover {
33 | color: rgb(129, 3, 255);
34 | color: linear-gradient(90deg, rgba(129, 3, 255, 1) 0%, rgba(195, 17, 255, 1) 100%);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/frontend/src/index.scss:
--------------------------------------------------------------------------------
1 | /* Box sizing rules */
2 | *,
3 | *::before,
4 | *::after {
5 | box-sizing: border-box;
6 | }
7 |
8 | /* Remove default padding */
9 | ul[class],
10 | ol[class] {
11 | padding: 0;
12 | }
13 |
14 | /* Remove default margin */
15 | body,
16 | h1,
17 | h2,
18 | h3,
19 | h4,
20 | p,
21 | ul[class],
22 | ol[class],
23 | li,
24 | figure,
25 | figcaption,
26 | blockquote,
27 | dl,
28 | dd {
29 | margin: 0;
30 | }
31 |
32 | /* Set core body defaults */
33 | body {
34 | min-height: 100vh;
35 | scroll-behavior: smooth;
36 | text-rendering: optimizeSpeed;
37 | line-height: 1.5;
38 | padding: 0;
39 | color: #e0e0e0;
40 | font-family: 'Roboto', sans-serif;
41 | letter-spacing: 1.2px;
42 | background-color: #252329;
43 | }
44 |
45 | /* Remove list styles on ul, ol elements with a class attribute */
46 | ul[class],
47 | ol[class] {
48 | list-style: none;
49 | }
50 |
51 | /* A elements that don't have a class get default styles */
52 | a:not([class]) {
53 | text-decoration-skip-ink: auto;
54 | }
55 |
56 | /* Make images easier to work with */
57 | img {
58 | max-width: 100%;
59 | display: block;
60 | }
61 |
62 | /* Natural flow and rhythm in articles by default */
63 | article > * + * {
64 | margin-top: 1em;
65 | }
66 |
67 | /* Inherit fonts for inputs and buttons */
68 | input,
69 | button,
70 | textarea,
71 | select {
72 | font: inherit;
73 | }
74 |
75 | a {
76 | text-decoration: none;
77 | }
78 |
79 | ::-webkit-scrollbar {
80 | width: 6px;
81 | height: 6px;
82 | }
83 | ::-webkit-scrollbar-button {
84 | width: 0px;
85 | height: 0px;
86 | }
87 | ::-webkit-scrollbar-thumb {
88 | background: #5a5a5a;
89 | border: 0px none #ffffff;
90 | border-radius: 50px;
91 | }
92 | ::-webkit-scrollbar-thumb:hover {
93 | background: #ffffff;
94 | }
95 | ::-webkit-scrollbar-thumb:active {
96 | background: #000000;
97 | }
98 | ::-webkit-scrollbar-track {
99 | background: #3c3c3c;
100 | border: 0px none #ffffff;
101 | border-radius: 50px;
102 | }
103 | ::-webkit-scrollbar-track:hover {
104 | background: #3c3c3c;
105 | }
106 | ::-webkit-scrollbar-track:active {
107 | background: #333333;
108 | }
109 | ::-webkit-scrollbar-corner {
110 | background: transparent;
111 | }
112 |
--------------------------------------------------------------------------------
/frontend/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { createStore, combineReducers } from 'redux';
4 | import { Provider } from 'react-redux';
5 |
6 | // Local imports
7 | import App from './App';
8 | import appReducer from './redux/app-reducer';
9 | import authReducer from './redux/auth-reducer';
10 | import './index.scss';
11 |
12 | const rootReducer = combineReducers({ auth: authReducer, app: appReducer });
13 |
14 | const store = createStore(rootReducer);
15 |
16 | ReactDOM.render(
17 | //
18 |
19 |
20 | ,
21 | document.getElementById('root')
22 | );
23 |
--------------------------------------------------------------------------------
/frontend/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/frontend/src/redux/app-reducer.ts:
--------------------------------------------------------------------------------
1 | type AppState = {
2 | inChannel: boolean;
3 | displayedGroups: [];
4 | messages: [];
5 | members: [];
6 | groups: [];
7 | currentGroup: null;
8 | modal: null | 'bug' | 'edit' | 'create';
9 | };
10 |
11 | type AppAction = {
12 | type: string;
13 | payload: {
14 | currentGroup: {};
15 | displayedGroups: [];
16 | groups: [];
17 | messages: [];
18 | members: [];
19 | modal: null | 'bug' | 'edit' | 'create';
20 | };
21 | };
22 |
23 | const initialState: AppState = {
24 | inChannel: false,
25 | messages: [],
26 | members: [],
27 | displayedGroups: [],
28 | groups: [],
29 | currentGroup: null,
30 | modal: null
31 | };
32 |
33 | const reducer = (state = initialState, action: AppAction) => {
34 | switch (action.type) {
35 | case 'CHANGE GROUP':
36 | return { ...state, currentGroup: action.payload.currentGroup, inChannel: true };
37 |
38 | case 'SEARCH':
39 | return { ...state, displayedGroups: action.payload.displayedGroups };
40 |
41 | case 'FETCH GROUPS':
42 | return { ...state, displayedGroups: action.payload.displayedGroups, groups: action.payload.groups };
43 |
44 | case 'FETCH MESSAGES':
45 | return { ...state, messages: action.payload.messages, members: action.payload.members };
46 |
47 | case 'MODAL':
48 | return { ...state, modal: action.payload.modal };
49 |
50 | case 'EXIT':
51 | return {
52 | ...state,
53 | inChannel: false,
54 | currentGroup: null,
55 | displayedGroups: state.groups,
56 | members: [],
57 | messages: []
58 | };
59 |
60 | default:
61 | return state;
62 | }
63 | };
64 |
65 | export default reducer;
66 |
--------------------------------------------------------------------------------
/frontend/src/redux/auth-reducer.ts:
--------------------------------------------------------------------------------
1 | type UserState = {
2 | isLogged: boolean;
3 | id: string | null;
4 | username: string | null;
5 | image: string | null;
6 | token: string | null;
7 | };
8 |
9 | type UserAction = {
10 | type: string;
11 | payload: {
12 | id: string;
13 | username: string;
14 | image: string;
15 | token: string | null;
16 | }
17 | }
18 |
19 | const initialState: UserState = { isLogged: false, username: null, image: null, token: null, id: null };
20 |
21 | const reducer = ( state = initialState, action: UserAction) => {
22 | switch(action.type) {
23 | case 'LOGIN':
24 | return {...state, isLogged: true, username: action.payload.username, image: action.payload.image, token: action.payload.token, id: action.payload.id}
25 |
26 | case 'LOGOUT':
27 | return {...state, isLogged: false, username: null, image: null, token: null, id: null}
28 |
29 | case 'GUEST':
30 | return {...state, isLogged: true, username: action.payload.username, image: action.payload.image, token: null, id: action.payload.id}
31 |
32 | case 'EDIT':
33 | return {...state, isLogged: true, username: action.payload.username, image: action.payload.image}
34 |
35 | default:
36 | return state;
37 | }
38 | }
39 |
40 | export default reducer;
--------------------------------------------------------------------------------
/frontend/src/views/AppView/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import axios from 'axios';
4 | import socketIOClient from 'socket.io-client';
5 | import { Snackbar } from '@material-ui/core';
6 | import MuiAlert from '@material-ui/lab/Alert';
7 |
8 | // Local Imports
9 | import Onboard from '../../components/Main/Onboard/index';
10 | import Messages from '../../components/Main/Messages/index';
11 | import MsgInput from '../../components/Main/MsgInput/index';
12 | import MainTopBar from '../../components/Main/TopBar/index';
13 | import SideTopBar from '../../components/Side/TopBar/index';
14 | import BottomBar from '../../components/Side/BottomBar/index';
15 | import Search from '../../components/Side/Search/index';
16 | import Groups from '../../components/Side/Groups/index';
17 | import GroupInfo from '../../components/Side/GroupInfo/index';
18 | import Members from '../../components/Side/Members/index';
19 | import Modal from '../../components/Shared/Modal/index';
20 | import EditProfile from '../../components/Shared/EditProfile/index';
21 | import styles from './styles.module.scss';
22 |
23 | type GroupData = {
24 | _id: string;
25 | title: string;
26 | description: string;
27 | groupClick: () => void;
28 | };
29 |
30 | type SnackData = {
31 | open: boolean;
32 | message: string | null;
33 | severity: 'success' | 'error' | undefined;
34 | };
35 |
36 | interface IRootState {
37 | auth: {
38 | isLogged: boolean;
39 | id: string | null;
40 | username: string | null;
41 | image: string | null;
42 | token: string | null;
43 | };
44 | app: {
45 | inChannel: boolean;
46 | currentGroup: GroupData;
47 | displayedGroups: GroupData[];
48 | messages: [];
49 | members: [];
50 | groups: [];
51 | modal: null | 'bug' | 'edit' | 'create';
52 | };
53 | }
54 |
55 | const AppView: React.FC = () => {
56 | const dispatch = useDispatch();
57 | const userData = useSelector((state: IRootState) => state.auth);
58 | const { inChannel, currentGroup, displayedGroups, messages, members, groups, modal } = useSelector(
59 | (state: IRootState) => state.app
60 | );
61 |
62 | const [mobile, setMobile] = useState(false);
63 | const [loading, setLoading] = useState(false);
64 | const [snack, setSnack] = useState({ open: false, severity: undefined, message: null });
65 | const [socket, setSocket] = useState(null);
66 |
67 | useEffect(() => {
68 | const socket = socketIOClient(process.env.REACT_APP_SOCKET_URL!, { transports: ['websocket'] });
69 | socket.emit('new user', userData.id);
70 | socket.on('fetch messages', (id: string) => fetchMessages(id));
71 | socket.on('fetch group', fetchGroups);
72 | setSocket(socket);
73 | fetchGroups();
74 | }, []);
75 |
76 | useEffect(() => {
77 | if (!socket) return;
78 | socket.emit('join group', userData.id, currentGroup?._id);
79 |
80 | fetchMessages();
81 | }, [currentGroup]);
82 |
83 | // Handlers
84 | const logoutHandler = () => {
85 | socket?.disconnect();
86 | localStorage.removeItem('userData');
87 | dispatch({ type: 'LOGOUT' });
88 | };
89 |
90 | const groupHandler = (id: string) => {
91 | setLoading(true);
92 | const current = groups.filter((item: GroupData) => item._id === id);
93 | if (current.length > 0) {
94 | dispatch({ type: 'CHANGE GROUP', payload: { currentGroup: current[0] } });
95 | }
96 | };
97 |
98 | // Async Requests
99 | const createGroup = async (title: string, description: string) => {
100 | const { token, id } = userData;
101 | if (!token) {
102 | setSnack({ open: true, severity: 'error', message: `Guests are not allowed to create groups, please register.` });
103 | return;
104 | }
105 |
106 | let verifiedToken;
107 | try {
108 | verifiedToken = await axios.post(`${process.env.REACT_APP_SERVER_URL}/users/verify`, {
109 | id,
110 | token
111 | });
112 | } catch (error) {
113 | console.log('[ERROR][AUTH][VERIFY]: ', error);
114 | return;
115 | }
116 | if (!verifiedToken.data.access) {
117 | localStorage.removeItem('userData');
118 | return;
119 | }
120 |
121 | let response;
122 | try {
123 | response = await axios.post(`${process.env.REACT_APP_SERVER_URL}/groups`, {
124 | title,
125 | description: description ? description : 'No description.'
126 | });
127 | } catch (error) {
128 | console.log('[ERROR][GROUPS][CREATE]: ', error);
129 | setSnack({ open: true, severity: 'error', message: `An error occured: Could not create group.` });
130 | return;
131 | }
132 | if (!response) return;
133 | dispatch({ type: 'MODAL', payload: { modal: null } });
134 | fetchGroups();
135 | socket?.emit('create group', userData.id, title);
136 | setSnack({ open: true, severity: 'success', message: `${title} channel created.` });
137 | };
138 |
139 | const editProfileRequest = async (username: string, image: string) => {
140 | const { token, id } = userData;
141 | if (!token) {
142 | setSnack({ open: true, severity: 'error', message: `Guests are not allowed to edit profile, please register.` });
143 | return;
144 | }
145 |
146 | let verifiedToken;
147 | try {
148 | verifiedToken = await axios.post(`${process.env.REACT_APP_SERVER_URL}/users/verify`, {
149 | id,
150 | token
151 | });
152 | } catch (error) {
153 | console.log('[ERROR][AUTH][VERIFY]: ', error);
154 | return;
155 | }
156 | if (!verifiedToken.data.access) {
157 | localStorage.removeItem('userData');
158 | return;
159 | }
160 |
161 | let response;
162 | try {
163 | response = await axios.put(`${process.env.REACT_APP_SERVER_URL}/users/edit`, {
164 | id,
165 | username,
166 | image
167 | });
168 | } catch (error) {
169 | console.log('[ERROR][USERS][EDIT]: ', error);
170 | setSnack({ open: true, severity: 'error', message: `An error occured: Could not edit profile.` });
171 | return;
172 | }
173 | if (!response) return;
174 | setSnack({ open: true, severity: 'success', message: `Profile updated.` });
175 | dispatch({ type: 'MODAL', payload: { modal: null } });
176 | dispatch({ type: 'EDIT', payload: { username: response.data.user.username, image: response.data.user.image } });
177 | };
178 |
179 | const createMessage = async (text: string, date: string) => {
180 | if (!socket) return;
181 |
182 | let response;
183 | try {
184 | response = await axios.post(`${process.env.REACT_APP_SERVER_URL}/messages`, {
185 | gid: currentGroup?._id,
186 | text,
187 | username: userData.username,
188 | image: userData.image,
189 | uid: userData.id,
190 | date
191 | });
192 | } catch (error) {
193 | console.log('[ERROR][GROUPS][CREATE]: ', error);
194 | setSnack({ open: true, severity: 'error', message: `An error occured: Could not send message.` });
195 | return;
196 | }
197 | if (!response) return;
198 | socket?.emit('message', userData.id, currentGroup?._id);
199 | };
200 |
201 | const fetchGroups = async () => {
202 | let response;
203 | try {
204 | response = await axios.get(`${process.env.REACT_APP_SERVER_URL}/groups`);
205 | } catch (error) {
206 | console.log('[ERROR][GROUPS][FETCH]: ', error);
207 | setSnack({ open: true, severity: 'error', message: `An error occured: Could not fetch groups.` });
208 | return;
209 | }
210 | if (!response) return;
211 | dispatch({
212 | type: 'FETCH GROUPS',
213 | payload: { displayedGroups: response.data.groups, groups: response.data.groups }
214 | });
215 | };
216 |
217 | const fetchMessages = async (gid = currentGroup?._id) => {
218 | if (!gid) return;
219 | let response;
220 | try {
221 | response = await axios.get(`${process.env.REACT_APP_SERVER_URL}/groups/${gid}`);
222 | } catch (error) {
223 | console.log('[ERROR][MESSAGES][FETCH]: ', error);
224 | setSnack({ open: true, severity: 'error', message: `An error occured: Could not fetch messages and members.` });
225 | setLoading(false);
226 | return;
227 | }
228 | setLoading(false);
229 | if (response.data.error) {
230 | setSnack({ open: true, severity: 'error', message: `An error occured: Could not fetch messages and members.` });
231 | return;
232 | }
233 | dispatch({ type: 'FETCH MESSAGES', payload: { messages: response.data.messages, members: response.data.members } });
234 | };
235 |
236 | const reportBug = async (title: string, description: string) => {
237 | const { id } = userData;
238 |
239 | let response;
240 | try {
241 | response = await axios.post(`${process.env.REACT_APP_SERVER_URL}/bugs`, {
242 | id,
243 | title,
244 | description: description ? description : 'No description.'
245 | });
246 | } catch (error) {
247 | console.log('[ERROR][BUGS][CREATE]: ', error);
248 | setSnack({ open: true, severity: 'error', message: `An error occured: Could not report bug.` });
249 | return;
250 | }
251 | if (!response) return;
252 | dispatch({ type: 'MODAL', payload: { modal: null } });
253 | setSnack({ open: true, severity: 'success', message: `Bug reported, thank you!` });
254 | };
255 |
256 | // Render
257 | let sideContent;
258 | let mainContent;
259 |
260 | if (inChannel) {
261 | sideContent = (
262 |
263 |
264 |
265 |
266 | );
267 | mainContent = (
268 |
269 | setMobile(true)} />
270 | setMobile(false)} loading={loading} />
271 | setMobile(false)} />
272 |
273 | );
274 | } else {
275 | sideContent = (
276 |
277 | dispatch({ type: 'SEARCH', payload: { displayedGroups: filteredGroups } })}
280 | />
281 | groupHandler(id)} />
282 |
283 | );
284 | mainContent = (
285 |
286 | setMobile(true)} />
287 | setMobile(false)} />
288 |
289 | );
290 | }
291 |
292 | return (
293 |
294 |
295 | {
298 | dispatch({ type: 'EXIT' });
299 | }}
300 | plusClick={() => {
301 | dispatch({ type: 'MODAL', payload: { modal: 'create' } });
302 | setMobile(false);
303 | }}
304 | />
305 | {sideContent}
306 | {
309 | dispatch({ type: 'MODAL', payload: { modal: 'edit' } });
310 | setMobile(false);
311 | }}
312 | bugClick={() => {
313 | dispatch({ type: 'MODAL', payload: { modal: 'bug' } });
314 | setMobile(false);
315 | }}
316 | />
317 |
318 | {mainContent}
319 | {modal === 'create' &&
}
320 | {modal === 'edit' &&
}
321 | {modal === 'bug' &&
}
322 |
setSnack({ open: false, severity: snack.severity, message: null })}
325 | autoHideDuration={5000}
326 | >
327 | setSnack({ open: false, severity: snack.severity, message: null })}
330 | severity={snack.severity}
331 | >
332 | {snack.message}
333 |
334 |
335 |
336 | );
337 | };
338 |
339 | export default AppView;
340 |
--------------------------------------------------------------------------------
/frontend/src/views/AppView/styles.module.scss:
--------------------------------------------------------------------------------
1 | @mixin columnLayout {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: space-between;
5 | }
6 |
7 | .container {
8 | width: 100%;
9 | height: 100vh;
10 | display: flex;
11 |
12 | .main {
13 | flex: 3;
14 | background-color: #252329;
15 | @include columnLayout;
16 | }
17 |
18 | .side,
19 | .mobile {
20 | flex: 1;
21 | background-color: #19161b;
22 | @include columnLayout;
23 | }
24 |
25 | @media screen and (max-width: 767px) {
26 | .main {
27 | flex: 1;
28 | }
29 |
30 | .side {
31 | display: none;
32 | }
33 | }
34 | }
35 |
36 | .sideContent {
37 | flex: 1;
38 | display: flex;
39 | flex-direction: column;
40 | justify-content: flex-start;
41 | overflow-y: hidden;
42 | }
43 |
44 | .mobile {
45 | position: absolute;
46 | height: 100vh;
47 | width: 80%;
48 | z-index: 20;
49 | animation: slide-in 0.5s linear 1 forwards;
50 | }
51 |
52 | @keyframes slide-in {
53 | 0% {
54 | transform: translateX(-100%);
55 | }
56 | 100% {
57 | transform: translateX(0);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/frontend/src/views/AuthView/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import axios from 'axios';
3 | import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
4 | import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
5 | import { useDispatch } from 'react-redux';
6 |
7 | // Local Imports
8 | import Cookie from '../../components/Shared/Cookie/index';
9 | import Welcome from '../../components/Auth/Welcome/index';
10 | import Login from '../../components/Auth/Login/index';
11 | import Signup from '../../components/Auth/Signup/index';
12 |
13 | const darkTheme = createMuiTheme({
14 | palette: {
15 | type: 'dark'
16 | }
17 | });
18 |
19 | type Props = {};
20 |
21 | type UserData = {
22 | id: string;
23 | token: string;
24 | };
25 |
26 | const AuthView: React.FC = props => {
27 | const dispatch = useDispatch();
28 | const [cookie, setCookie] = useState(true);
29 |
30 | useEffect(() => {
31 | const cookieData = localStorage.getItem('cookieData');
32 | if (cookieData) setCookie(false);
33 | const userData = localStorage.getItem('userData');
34 | if (!userData) return;
35 | const parsedData: UserData = JSON.parse(userData);
36 | verifyRequest(parsedData.id, parsedData.token);
37 | }, []);
38 |
39 | // Async Requests
40 | const verifyRequest = async (id: string, token: string) => {
41 | let response;
42 | try {
43 | response = await axios.post(`${process.env.REACT_APP_SERVER_URL}/users/verify`, {
44 | id,
45 | token
46 | });
47 | } catch (error) {
48 | console.log('[ERROR][AUTH][VERIFY]: ', error);
49 | return;
50 | }
51 | if (!response.data.access) {
52 | localStorage.removeItem('userData');
53 | return;
54 | }
55 | dispatch({ type: 'LOGIN', payload: { ...response.data.user } });
56 | };
57 |
58 | return (
59 |
60 | {cookie && (
61 | {
63 | localStorage.setItem('cookieData', 'accepted');
64 | setCookie(false);
65 | }}
66 | />
67 | )}
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | );
78 | };
79 |
80 | export default AuthView;
81 |
--------------------------------------------------------------------------------
/frontend/src/views/AuthView/styles.module.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KillianFrappartDev/GroupChat/f395b4819f5c3178757b7c21b70e909677b7c778/frontend/src/views/AuthView/styles.module.scss
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------