mirror of
https://github.com/cheveguerra/whaticket-community.git
synced 2026-04-18 19:59:20 +00:00
working
This commit is contained in:
11
frontend/src/App.css
Normal file
11
frontend/src/App.css
Normal file
@@ -0,0 +1,11 @@
|
||||
button:active {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
img:active {
|
||||
opacity: 0.5;
|
||||
}
|
||||
60
frontend/src/App.js
Normal file
60
frontend/src/App.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from "react";
|
||||
import { BrowserRouter, Route, Switch } from "react-router-dom";
|
||||
import { toast, ToastContainer } from "react-toastify";
|
||||
|
||||
import Home from "./pages/Home/Home";
|
||||
import Chat from "./pages/Chat/Chat";
|
||||
import Profile from "./pages/Profile/Profile";
|
||||
import Signup from "./pages/Signup/Signup";
|
||||
import Login from "./pages/Login/Login";
|
||||
import "./App.css";
|
||||
|
||||
const App = () => {
|
||||
const showToast = (type, message) => {
|
||||
switch (type) {
|
||||
case 0:
|
||||
toast.warning(message);
|
||||
break;
|
||||
case 1:
|
||||
toast.success(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<ToastContainer
|
||||
autoClose={2000}
|
||||
hideProgressBar={true}
|
||||
position={toast.POSITION.TOP_CENTER}
|
||||
/>
|
||||
<Switch>
|
||||
<Route exact path="/" render={props => <Home />} />
|
||||
<Route
|
||||
exact
|
||||
path="/login"
|
||||
render={props => <Login showToast={showToast} />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/profile"
|
||||
render={props => <Profile showToast={showToast} />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/signup"
|
||||
render={props => <Signup showToast={showToast} />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/chat"
|
||||
render={props => <Chat showToast={showToast} />}
|
||||
/>
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
BIN
frontend/src/Images/canove.png
Normal file
BIN
frontend/src/Images/canove.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 424 KiB |
BIN
frontend/src/Images/profile_default.png
Normal file
BIN
frontend/src/Images/profile_default.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
BIN
frontend/src/Images/send.png
Normal file
BIN
frontend/src/Images/send.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 374 B |
BIN
frontend/src/Images/upload.png
Normal file
BIN
frontend/src/Images/upload.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 651 B |
BIN
frontend/src/Images/wa-background.png
Normal file
BIN
frontend/src/Images/wa-background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 682 KiB |
37
frontend/src/components/Navbar/DefaultNavbar.js
Normal file
37
frontend/src/components/Navbar/DefaultNavbar.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
|
||||
import { Navbar, Nav, Container } from "react-bootstrap";
|
||||
import { LinkContainer } from "react-router-bootstrap";
|
||||
|
||||
const LogedinNavbar = () => {
|
||||
return (
|
||||
<div>
|
||||
<Navbar variant="dark" bg="dark" expand="lg">
|
||||
<Container>
|
||||
<LinkContainer to="/" style={{ color: "#519032" }}>
|
||||
<Navbar.Brand>EconoWhatsBot</Navbar.Brand>
|
||||
</LinkContainer>
|
||||
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
|
||||
<Navbar.Collapse id="responsive-navbar-nav">
|
||||
<Nav className="mr-auto">
|
||||
<LinkContainer to="/">
|
||||
<Nav.Link href="#home">Home</Nav.Link>
|
||||
</LinkContainer>
|
||||
<LinkContainer to="/chat">
|
||||
<Nav.Link href="#link">Chat</Nav.Link>
|
||||
</LinkContainer>
|
||||
</Nav>
|
||||
<LinkContainer to="/login">
|
||||
<Nav.Link href="#login">Login</Nav.Link>
|
||||
</LinkContainer>
|
||||
<LinkContainer to="/signup">
|
||||
<Nav.Link href="#signup">Signup</Nav.Link>
|
||||
</LinkContainer>
|
||||
</Navbar.Collapse>
|
||||
</Container>
|
||||
</Navbar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogedinNavbar;
|
||||
48
frontend/src/components/Navbar/LogedinNavbar.js
Normal file
48
frontend/src/components/Navbar/LogedinNavbar.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Navbar, Nav, Container } from "react-bootstrap";
|
||||
import { LinkContainer } from "react-router-bootstrap";
|
||||
|
||||
const DefaultNavbar = () => {
|
||||
const username = localStorage.getItem("username");
|
||||
const history = useHistory();
|
||||
|
||||
const handleLogout = e => {
|
||||
e.preventDefault();
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem("userName");
|
||||
localStorage.removeItem("userId");
|
||||
history.push("/");
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar variant="dark" bg="dark" expand="lg">
|
||||
<Container>
|
||||
<LinkContainer to="/" style={{ color: "#519032" }}>
|
||||
<Navbar.Brand>EconoWhatsBot</Navbar.Brand>
|
||||
</LinkContainer>
|
||||
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
|
||||
<Navbar.Collapse id="responsive-navbar-nav">
|
||||
<Nav className="mr-auto">
|
||||
<LinkContainer to="/">
|
||||
<Nav.Link href="#home">Home</Nav.Link>
|
||||
</LinkContainer>
|
||||
<LinkContainer to="/chat">
|
||||
<Nav.Link href="#link">Chat</Nav.Link>
|
||||
</LinkContainer>
|
||||
</Nav>
|
||||
<Navbar.Text>
|
||||
Logado como: <a href="#login">{username}</a>
|
||||
</Navbar.Text>
|
||||
<Nav.Link href="#logout" onClick={handleLogout}>
|
||||
Logout
|
||||
</Nav.Link>
|
||||
</Navbar.Collapse>
|
||||
</Container>
|
||||
</Navbar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DefaultNavbar;
|
||||
0
frontend/src/components/Navbar/Nabar.css
Normal file
0
frontend/src/components/Navbar/Nabar.css
Normal file
11
frontend/src/index.js
Normal file
11
frontend/src/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import App from "./App";
|
||||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
213
frontend/src/pages/Chat/Chat.css
Normal file
213
frontend/src/pages/Chat/Chat.css
Normal file
@@ -0,0 +1,213 @@
|
||||
.root {
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.ProfilePicture {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
left: 10px;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.ProfileHeaderText {
|
||||
font-weight: bold;
|
||||
color: #203152;
|
||||
font-size: 20px;
|
||||
/* padding: 60px; */
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
margin-top: 5px;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
/* .notificationpragraph {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
} */
|
||||
.newmessages {
|
||||
border-radius: 20%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 8px;
|
||||
|
||||
background: green;
|
||||
color: white;
|
||||
text-align: center;
|
||||
|
||||
font: 14px Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* List user */
|
||||
|
||||
.viewListUser {
|
||||
overflow-y: scroll;
|
||||
max-height: 90vh;
|
||||
min-height: 90vh;
|
||||
height: auto;
|
||||
width: 50vh;
|
||||
/* padding-top: 10px; */
|
||||
/* padding-bottom: 10px; */
|
||||
}
|
||||
|
||||
.viewListUser::-webkit-scrollbar-track {
|
||||
padding: 2px 0;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.viewListUser::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.viewListUser::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
background-color: #9999;
|
||||
}
|
||||
|
||||
.viewWrapItem {
|
||||
border: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #fff;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
padding: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* border-left: 15px solid #1ebea5; */
|
||||
/* margin-bottom: 3px; */
|
||||
border-bottom: 1px solid rgb(220, 220, 220);
|
||||
}
|
||||
|
||||
.selectedviewWrapItem {
|
||||
border: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #e2e2e2;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
padding: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* border-left: 15px solid #1ebea5; */
|
||||
/* margin-bottom: 3px; */
|
||||
border-bottom: 1px solid rgb(220, 220, 220);
|
||||
}
|
||||
|
||||
.viewWrapItemNotification {
|
||||
border: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #1de9b6;
|
||||
max-width: 44vh;
|
||||
min-width: 44vh;
|
||||
padding: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* border-left: 15px solid #1ebea5; */
|
||||
/* margin-bottom: 3px; */
|
||||
border-bottom: 1px solid rgb(220, 220, 220);
|
||||
}
|
||||
|
||||
.viewAvatarItem {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.viewWrapContentItem {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
margin-left: 15px;
|
||||
color: #203152;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.textItem {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 240px;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.viewBoard {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
/* --------------------------------SEARCH BUTTON DESIGN-------------------------------- */
|
||||
.rootsearchbar {
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.input-icons i {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.input-icons {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profileviewleftside {
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
background-color: #ededed;
|
||||
height: 64px;
|
||||
position: relative;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
/* IE10 */
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 10px;
|
||||
background: rgb(148, 167, 185);
|
||||
color: white;
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
border-radius: 10px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 100%;
|
||||
|
||||
border-radius: 10px;
|
||||
|
||||
/* margin-right: 85%; */
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
border: 2px solid dodgerblue;
|
||||
}
|
||||
141
frontend/src/pages/Chat/Chat.js
Normal file
141
frontend/src/pages/Chat/Chat.js
Normal file
@@ -0,0 +1,141 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import "./Chat.css";
|
||||
import api from "../../util/api";
|
||||
import profilePic from "../../Images/canove.png";
|
||||
import profileDefaultPic from "../../Images/profile_default.png";
|
||||
import openSocket from "socket.io-client";
|
||||
import { FiSearch } from "react-icons/fi";
|
||||
|
||||
import LogedinNavbar from "../../components/Navbar/LogedinNavbar";
|
||||
import DefaultNavbar from "../../components/Navbar/DefaultNavbar";
|
||||
|
||||
import ChatBox from "../ChatBox/ChatBox";
|
||||
|
||||
const Chat = ({ showToast }) => {
|
||||
const token = localStorage.getItem("token");
|
||||
const username = localStorage.getItem("username");
|
||||
|
||||
const [currentPeerContact, setCurrentPeerContact] = useState(null);
|
||||
const [contacts, setContacts] = useState([]);
|
||||
const [displayedContacts, setDisplayedContacts] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchContacts = async () => {
|
||||
const res = await api.get("/contacts", {
|
||||
headers: { Authorization: "Bearer " + token },
|
||||
});
|
||||
setContacts(res.data);
|
||||
setDisplayedContacts(res.data);
|
||||
};
|
||||
fetchContacts();
|
||||
const socket = openSocket("http://localhost:8080");
|
||||
|
||||
socket.on("contact", data => {
|
||||
console.log(data);
|
||||
if (data.action === "create") {
|
||||
addContact(data.contact);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.disconnect();
|
||||
};
|
||||
}, [currentPeerContact, token]);
|
||||
|
||||
const addContact = contact => {
|
||||
setContacts(prevState => [...prevState, contact]);
|
||||
console.log("adicionando contato", contact);
|
||||
setDisplayedContacts(prevState => [...prevState, contact]);
|
||||
};
|
||||
|
||||
const handleSearchContact = e => {
|
||||
let searchTerm = e.target.value.toLowerCase();
|
||||
|
||||
setDisplayedContacts(
|
||||
contacts.filter(contact =>
|
||||
contact.name.toLowerCase().includes(searchTerm)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const handleSelectContact = (e, contact) => {
|
||||
setCurrentPeerContact(contact);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!localStorage.getItem("token") ? (
|
||||
<div>
|
||||
<DefaultNavbar />
|
||||
<h1> Você não está logado </h1>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<LogedinNavbar />
|
||||
<div className="root">
|
||||
<div className="body">
|
||||
<div className="viewListUser">
|
||||
<div className="profileviewleftside">
|
||||
<img className="ProfilePicture" alt="" src={profilePic} />
|
||||
<span className="ProfileHeaderText">{username}</span>
|
||||
</div>
|
||||
<div className="rootsearchbar">
|
||||
<div className="input-container">
|
||||
<i className="fa fa-search icon">
|
||||
<FiSearch size="20px" />
|
||||
</i>
|
||||
<input
|
||||
className="input-field"
|
||||
type="text"
|
||||
placeholder="Buscar Contato"
|
||||
onChange={handleSearchContact}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{displayedContacts &&
|
||||
displayedContacts.length > 0 &&
|
||||
displayedContacts.map((contact, index) => (
|
||||
<button
|
||||
className={
|
||||
currentPeerContact &&
|
||||
currentPeerContact.id === contact.id
|
||||
? "selectedviewWrapItem"
|
||||
: "viewWrapItem"
|
||||
}
|
||||
id={contact.id}
|
||||
key={contact.id}
|
||||
onClick={e => handleSelectContact(e, contact)}
|
||||
>
|
||||
<img
|
||||
className="viewAvatarItem"
|
||||
alt=""
|
||||
src={profileDefaultPic}
|
||||
/>
|
||||
<div className="viewWrapContentItem">
|
||||
<span className="textItem">{contact.name}</span>
|
||||
</div>
|
||||
|
||||
{contact.messages && contact.messages.length > 0 && (
|
||||
<div className="notificationpragraph">
|
||||
<p className="newmessages">
|
||||
{contact.messages.length}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="viewBoard">
|
||||
{currentPeerContact ? (
|
||||
<ChatBox currentPeerContact={currentPeerContact} />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Chat;
|
||||
368
frontend/src/pages/ChatBox/ChatBox.css
Normal file
368
frontend/src/pages/ChatBox/ChatBox.css
Normal file
@@ -0,0 +1,368 @@
|
||||
/* Chat board */
|
||||
|
||||
.viewChatBoard {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
background-image: url("../../Images/wa-background.png");
|
||||
|
||||
backface-visibility: hidden;
|
||||
border-left: 1px solid #ededed;
|
||||
/* border-right: 5px solid #DFA375; */
|
||||
max-height: 104%;
|
||||
min-height: 104%;
|
||||
/* margin-right: 20px; */
|
||||
border-radius: 10px;
|
||||
/* box-shadow: 0 5px 5px #808888; */
|
||||
}
|
||||
.viewAvatarItem {
|
||||
margin-left: 20px;
|
||||
/* background-image: url('../../images/nopic.jpg'); */
|
||||
}
|
||||
|
||||
.headerChatBoard {
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
|
||||
background-color: #ededed;
|
||||
}
|
||||
.aboutme {
|
||||
padding-top: 20px;
|
||||
margin-left: 80px;
|
||||
|
||||
align-items: center;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
}
|
||||
.textHeaderChatBoard {
|
||||
font-weight: bold;
|
||||
color: #203152;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.viewListContentChat {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow-y: scroll;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.viewListContentChat::-webkit-scrollbar-track {
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.viewListContentChat::-webkit-scrollbar {
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
.viewListContentChat::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
.teste {
|
||||
border-radius: 10px;
|
||||
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
.viewBottom {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.icOpenGallery {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.viewInputGallery {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
left: 10px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.icOpenSticker {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.icSend {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-left: 5px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.viewInput {
|
||||
flex: 1;
|
||||
border-radius: 30px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border: 0px;
|
||||
height: 50px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.viewInput:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: rgb(199, 199, 199);
|
||||
}
|
||||
|
||||
/* View item message */
|
||||
|
||||
.viewItemRight {
|
||||
width: 300px;
|
||||
height: auto;
|
||||
|
||||
background-color: #dcf8c6;
|
||||
align-self: flex-end;
|
||||
margin-right: 20px;
|
||||
margin-top: 10px;
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 0px;
|
||||
color: #203152;
|
||||
text-align: left;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
box-shadow: 0 5px 5px #808888;
|
||||
}
|
||||
|
||||
.viewItemRight2 {
|
||||
width: 300px;
|
||||
height: auto;
|
||||
background-color: #e1f3fb;
|
||||
align-self: flex-end;
|
||||
margin-right: 20px;
|
||||
margin-top: 10px;
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 0px;
|
||||
color: #203152;
|
||||
text-align: left;
|
||||
box-shadow: 0 5px 5px #808888;
|
||||
}
|
||||
|
||||
.viewItemRight3 {
|
||||
width: 300px;
|
||||
height: auto;
|
||||
align-self: flex-end;
|
||||
margin-right: 20px;
|
||||
margin-top: 10px;
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 0px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.viewWrapItemLeft {
|
||||
width: 300px;
|
||||
text-align: left;
|
||||
align-self: flex-start;
|
||||
margin-left: 20px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: -15px;
|
||||
}
|
||||
|
||||
.viewWrapItemLeft2 {
|
||||
width: 300px;
|
||||
align-self: flex-start;
|
||||
text-align: left;
|
||||
margin-left: 20px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: -15px;
|
||||
}
|
||||
|
||||
.viewWrapItemLeft3 {
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
width: 340px;
|
||||
margin-bottom: -15px;
|
||||
}
|
||||
|
||||
.viewItemLeft {
|
||||
width: 300px;
|
||||
height: auto;
|
||||
margin-top: 10px;
|
||||
background-color: #ffffff;
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 8px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
color: #303030;
|
||||
box-shadow: 0 5px 5px #808888;
|
||||
}
|
||||
|
||||
.viewItemLeft2 {
|
||||
width: 300px;
|
||||
height: auto;
|
||||
background-color: #203152;
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 8px;
|
||||
color: white;
|
||||
box-shadow: 0 5px 5px #808888;
|
||||
}
|
||||
|
||||
.viewItemLeft3 {
|
||||
width: 300px;
|
||||
height: auto;
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 8px;
|
||||
color: white;
|
||||
}
|
||||
.time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin-left: 400px;
|
||||
font: italic small-caps bold 15px/30px Georgia, serif;
|
||||
width: 110px;
|
||||
background-color: #e1f3fb;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.timesetup {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.peerAvatarLeft {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 15px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
object-fit: cover;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.viewPaddingLeft {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
/* Item text */
|
||||
|
||||
.textContentItem {
|
||||
font-size: 16px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.viewListContentChat div:last-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Item image */
|
||||
|
||||
.imgItemRight {
|
||||
object-fit: cover;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
.imgItemLeft {
|
||||
object-fit: cover;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 8px;
|
||||
}
|
||||
|
||||
.textTimeLeft {
|
||||
color: #808888;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
/* Stickers */
|
||||
|
||||
.viewStickers {
|
||||
display: block;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
height: 100px;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.viewStickers::-webkit-scrollbar-track {
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.viewStickers::-webkit-scrollbar {
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
.viewStickers::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
.imgSticker {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* Say hi */
|
||||
|
||||
.viewWrapSayHi {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.textSayHi {
|
||||
color: #808888;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.imgWaveHand {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
139
frontend/src/pages/ChatBox/ChatBox.js
Normal file
139
frontend/src/pages/ChatBox/ChatBox.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Card } from "react-bootstrap";
|
||||
import profileDefaultPic from "../../Images/profile_default.png";
|
||||
import uploadPic from "../../Images/upload.png";
|
||||
import sendPic from "../../Images/send.png";
|
||||
import api from "../../util/api";
|
||||
import openSocket from "socket.io-client";
|
||||
|
||||
import ScrollToBottom from "react-scroll-to-bottom";
|
||||
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import "./ChatBox.css";
|
||||
|
||||
const ChatBox = ({ currentPeerContact }) => {
|
||||
const contactId = currentPeerContact.id;
|
||||
const unreadMessages = currentPeerContact.messages;
|
||||
const userId = localStorage.getItem("userId");
|
||||
const username = localStorage.getItem("username");
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
const [listMessages, setListMessages] = useState([]);
|
||||
const [inputMessage, setInputMessage] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMessages = async () => {
|
||||
try {
|
||||
const res = await api.get("/messages/" + contactId, {
|
||||
headers: { Authorization: "Bearer " + token },
|
||||
});
|
||||
setListMessages(res.data);
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
}
|
||||
};
|
||||
|
||||
const readMesages = async () => {
|
||||
try {
|
||||
await api.post(
|
||||
"/messages/setread",
|
||||
{ messagesToSetRead: unreadMessages },
|
||||
{ headers: { Authorization: "Bearer " + token } }
|
||||
);
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
}
|
||||
};
|
||||
|
||||
const socket = openSocket("http://localhost:8080");
|
||||
socket.on("appMessage", data => {
|
||||
if (data.action === "create") {
|
||||
if (contactId === data.message.contactId) {
|
||||
addMessage(data.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fetchMessages();
|
||||
readMesages();
|
||||
|
||||
return () => {
|
||||
socket.disconnect();
|
||||
};
|
||||
}, [contactId, unreadMessages, token]);
|
||||
|
||||
const handleChangeInput = e => {
|
||||
setInputMessage(e.target.value);
|
||||
};
|
||||
|
||||
const handleSendMessage = async () => {
|
||||
if (inputMessage.trim() === "") return;
|
||||
const message = {
|
||||
read: 1,
|
||||
userId: userId,
|
||||
messageBody: `${username}: ${inputMessage.trim()}`,
|
||||
};
|
||||
try {
|
||||
await api.post(`/messages/${contactId}`, message, {
|
||||
headers: { Authorization: "Bearer " + token },
|
||||
});
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
}
|
||||
setInputMessage("");
|
||||
};
|
||||
|
||||
const addMessage = message => {
|
||||
setListMessages(prevState => [...prevState, message]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="viewChatBoard">
|
||||
<div className="headerChatBoard">
|
||||
<img className="viewAvatarItem" src={profileDefaultPic} alt="" />
|
||||
<span className="textHeaderChatBoard">
|
||||
<p style={{ fontSize: "20px" }}>{currentPeerContact.name}</p>
|
||||
</span>
|
||||
</div>
|
||||
<ScrollToBottom className="viewListContentChat">
|
||||
<div className="viewListContentChat">
|
||||
{listMessages.map((message, index) =>
|
||||
message.userId === 0 ? (
|
||||
<div className="viewItemLeft" key={message.id}>
|
||||
<span className="textContentItem">{message.messageBody}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="viewItemRight" key={message.id}>
|
||||
<span className="textContentItem">{message.messageBody}</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</ScrollToBottom>
|
||||
<div className="viewBottom">
|
||||
<img className="icOpenGallery" src={uploadPic} alt="" />
|
||||
<input
|
||||
// ref={input => input && input.focus()}
|
||||
name="inputMessage"
|
||||
className="viewInput"
|
||||
placeholder="mensagem"
|
||||
value={inputMessage}
|
||||
onChange={handleChangeInput}
|
||||
onKeyPress={e => {
|
||||
if (e.key === "Enter") {
|
||||
handleSendMessage();
|
||||
}
|
||||
}}
|
||||
></input>
|
||||
<img
|
||||
className="icSend"
|
||||
src={sendPic}
|
||||
alt=""
|
||||
onClick={handleSendMessage}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatBox;
|
||||
0
frontend/src/pages/Home/Home.css
Normal file
0
frontend/src/pages/Home/Home.css
Normal file
20
frontend/src/pages/Home/Home.js
Normal file
20
frontend/src/pages/Home/Home.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
import LogedinNavbar from "../../components/Navbar/LogedinNavbar";
|
||||
import DefaultNavbar from "../../components/Navbar/DefaultNavbar";
|
||||
|
||||
import { Container } from "react-bootstrap";
|
||||
|
||||
import "./Home.css";
|
||||
|
||||
const Home = () => {
|
||||
return (
|
||||
<div>
|
||||
{localStorage.getItem("token") ? <LogedinNavbar /> : <DefaultNavbar />}
|
||||
<Container>
|
||||
<h1>Home</h1>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
0
frontend/src/pages/Login/Login.css
Normal file
0
frontend/src/pages/Login/Login.css
Normal file
80
frontend/src/pages/Login/Login.js
Normal file
80
frontend/src/pages/Login/Login.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import React, { useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import api from "../../util/api";
|
||||
import LogedinNavbar from "../../components/Navbar/LogedinNavbar";
|
||||
import DefaultNavbar from "../../components/Navbar/DefaultNavbar";
|
||||
|
||||
import { Container, Form, Button } from "react-bootstrap";
|
||||
|
||||
const Login = ({ showToast }) => {
|
||||
const [user, setUser] = useState({ email: "", password: "" });
|
||||
const history = useHistory();
|
||||
|
||||
// const [token, setToken] = useState(null);
|
||||
// const [userId, setUserId] = useState(null);
|
||||
|
||||
const handleLogin = async e => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const res = await api.post("/auth/login", user);
|
||||
|
||||
// setToken(res.data.token);
|
||||
// setUserId(res.data.userId);
|
||||
|
||||
localStorage.setItem("token", res.data.token);
|
||||
localStorage.setItem("username", res.data.username);
|
||||
localStorage.setItem("userId", res.data.userId);
|
||||
const remainingMilliseconds = 60 * 60 * 1000;
|
||||
const expiryDate = new Date(new Date().getTime() + remainingMilliseconds);
|
||||
localStorage.setItem("expiryDate", expiryDate.toISOString());
|
||||
showToast(1, "Login efetuado com sucesso");
|
||||
history.push("/chat");
|
||||
} catch (err) {
|
||||
alert(err.response.data.message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeInput = e => {
|
||||
setUser({ ...user, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{localStorage.getItem("token") ? <LogedinNavbar /> : <DefaultNavbar />}
|
||||
<div>
|
||||
<br></br>
|
||||
<Container>
|
||||
<Form onSubmit={e => handleLogin(e, user)}>
|
||||
<Form.Group>
|
||||
{/* <Form.Label>Email address</Form.Label> */}
|
||||
<Form.Control
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
value={user.email}
|
||||
onChange={handleChangeInput}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
{/* <Form.Label>Password</Form.Label> */}
|
||||
<Form.Control
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Senha"
|
||||
value={user.password}
|
||||
onChange={handleChangeInput}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Button variant="primary" type="submit">
|
||||
Entrar
|
||||
</Button>
|
||||
</Form>
|
||||
</Container>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
0
frontend/src/pages/Profile/Profile.css
Normal file
0
frontend/src/pages/Profile/Profile.css
Normal file
0
frontend/src/pages/Profile/Profile.js
Normal file
0
frontend/src/pages/Profile/Profile.js
Normal file
0
frontend/src/pages/Signup/SignUp.css
Normal file
0
frontend/src/pages/Signup/SignUp.css
Normal file
88
frontend/src/pages/Signup/Signup.js
Normal file
88
frontend/src/pages/Signup/Signup.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import React, { useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import api from "../../util/api";
|
||||
import LogedinNavbar from "../../components/Navbar/LogedinNavbar";
|
||||
import DefaultNavbar from "../../components/Navbar/DefaultNavbar";
|
||||
|
||||
import { Container, Form, Button } from "react-bootstrap";
|
||||
|
||||
const Signup = () => {
|
||||
const history = useHistory();
|
||||
const initialState = { name: "", email: "", password: "" };
|
||||
const [user, setUser] = useState(initialState);
|
||||
|
||||
const handleChangeInput = e => {
|
||||
setUser({ ...user, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
const handleSignUp = async e => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await api.put("/auth/signup", user);
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
}
|
||||
history.push("/login");
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{localStorage.getItem("token") ? (
|
||||
<div>
|
||||
<LogedinNavbar />
|
||||
<h1> Você está logado </h1>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<DefaultNavbar />
|
||||
<br></br>
|
||||
<Container>
|
||||
<Form onSubmit={handleSignUp}>
|
||||
<Form.Group>
|
||||
{/* <Form.Label>Nome</Form.Label> */}
|
||||
<Form.Control
|
||||
name="name"
|
||||
type="text"
|
||||
placeholder="Nome"
|
||||
value={user.name}
|
||||
onChange={handleChangeInput}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
{/* <Form.Label>Email address</Form.Label> */}
|
||||
<Form.Control
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
value={user.email}
|
||||
onChange={handleChangeInput}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
{/* <Form.Label>Password</Form.Label> */}
|
||||
<Form.Control
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Senha"
|
||||
value={user.password}
|
||||
onChange={handleChangeInput}
|
||||
/>
|
||||
<Form.Text className="text-muted">
|
||||
Mínimo de 5 caracteres.
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
|
||||
<Button variant="primary" type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</Container>
|
||||
</div>
|
||||
)}
|
||||
<br></br>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Signup;
|
||||
7
frontend/src/util/api.js
Normal file
7
frontend/src/util/api.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import axios from "axios";
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: "http://localhost:8080",
|
||||
});
|
||||
|
||||
export default api;
|
||||
Reference in New Issue
Block a user