-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e0fe935
Showing
26 changed files
with
3,297 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Auto detect text files and perform LF normalization | ||
* text=auto |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
api/.env | ||
client/.env | ||
.idea | ||
api/node_modules | ||
client/node_modules | ||
api/uploads/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
pnpm-debug.log* | ||
lerna-debug.log* | ||
|
||
node_modules | ||
dist | ||
dist-ssr | ||
*.local | ||
|
||
# Editor directories and files | ||
.vscode/* | ||
!.vscode/extensions.json | ||
.idea | ||
.DS_Store | ||
*.suo | ||
*.ntvs* | ||
*.njsproj | ||
*.sln | ||
*.sw? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Vite + React</title> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<script type="module" src="/src/main.jsx"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"name": "client", | ||
"private": true, | ||
"version": "0.0.0", | ||
"type": "module", | ||
"scripts": { | ||
"dev": "vite", | ||
"build": "vite build", | ||
"preview": "vite preview" | ||
}, | ||
"dependencies": { | ||
"autoprefixer": "^10.4.13", | ||
"axios": "^1.3.2", | ||
"lodash": "^4.17.21", | ||
"postcss": "^8.4.21", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"tailwindcss": "^3.2.4" | ||
}, | ||
"devDependencies": { | ||
"@types/react": "^18.0.27", | ||
"@types/react-dom": "^18.0.10", | ||
"@vitejs/plugin-react": "^3.1.0", | ||
"vite": "^4.1.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module.exports = { | ||
plugins: { | ||
tailwindcss: {}, | ||
autoprefixer: {}, | ||
}, | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import axios from "axios"; | ||
import {UserContextProvider} from "./UserContext"; | ||
import Routes from "./Routes"; | ||
|
||
function App() { | ||
axios.defaults.baseURL = 'http://localhost:4040'; | ||
axios.defaults.withCredentials = true; | ||
return ( | ||
<UserContextProvider> | ||
<Routes /> | ||
</UserContextProvider> | ||
) | ||
} | ||
|
||
export default App |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export default function Avatar({userId,username,online}) { | ||
const colors = ['bg-teal-200', 'bg-red-200', | ||
'bg-green-200', 'bg-purple-200', | ||
'bg-blue-200', 'bg-yellow-200', | ||
'bg-orange-200', 'bg-pink-200', 'bg-fuchsia-200', 'bg-rose-200']; | ||
const userIdBase10 = parseInt(userId.substring(10), 16); | ||
const colorIndex = userIdBase10 % colors.length; | ||
const color = colors[colorIndex]; | ||
return ( | ||
<div className={"w-8 h-8 relative rounded-full flex items-center "+color}> | ||
<div className="text-center w-full opacity-70">{username[0]}</div> | ||
{online && ( | ||
<div className="absolute w-3 h-3 bg-green-400 bottom-0 right-0 rounded-full border border-white"></div> | ||
)} | ||
{!online && ( | ||
<div className="absolute w-3 h-3 bg-gray-400 bottom-0 right-0 rounded-full border border-white"></div> | ||
)} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
import {useContext, useEffect, useRef, useState} from "react"; | ||
import Avatar from "./Avatar"; | ||
import Logo from "./Logo"; | ||
import {UserContext} from "./UserContext.jsx"; | ||
import {uniqBy} from "lodash"; | ||
import axios from "axios"; | ||
import Contact from "./Contact"; | ||
|
||
export default function Chat() { | ||
const [ws,setWs] = useState(null); | ||
const [onlinePeople,setOnlinePeople] = useState({}); | ||
const [offlinePeople,setOfflinePeople] = useState({}); | ||
const [selectedUserId,setSelectedUserId] = useState(null); | ||
const [newMessageText,setNewMessageText] = useState(''); | ||
const [messages,setMessages] = useState([]); | ||
const {username,id,setId,setUsername} = useContext(UserContext); | ||
const divUnderMessages = useRef(); | ||
useEffect(() => { | ||
connectToWs(); | ||
}, [selectedUserId]); | ||
function connectToWs() { | ||
const ws = new WebSocket('ws://localhost:4040'); | ||
setWs(ws); | ||
ws.addEventListener('message', handleMessage); | ||
ws.addEventListener('close', () => { | ||
setTimeout(() => { | ||
console.log('Disconnected. Trying to reconnect.'); | ||
connectToWs(); | ||
}, 1000); | ||
}); | ||
} | ||
function showOnlinePeople(peopleArray) { | ||
const people = {}; | ||
peopleArray.forEach(({userId,username}) => { | ||
people[userId] = username; | ||
}); | ||
setOnlinePeople(people); | ||
} | ||
function handleMessage(ev) { | ||
const messageData = JSON.parse(ev.data); | ||
console.log({ev,messageData}); | ||
if ('online' in messageData) { | ||
showOnlinePeople(messageData.online); | ||
} else if ('text' in messageData) { | ||
if (messageData.sender === selectedUserId) { | ||
setMessages(prev => ([...prev, {...messageData}])); | ||
} | ||
} | ||
} | ||
function logout() { | ||
axios.post('/logout').then(() => { | ||
setWs(null); | ||
setId(null); | ||
setUsername(null); | ||
}); | ||
} | ||
function sendMessage(ev, file = null) { | ||
if (ev) ev.preventDefault(); | ||
ws.send(JSON.stringify({ | ||
recipient: selectedUserId, | ||
text: newMessageText, | ||
file, | ||
})); | ||
if (file) { | ||
axios.get('/messages/'+selectedUserId).then(res => { | ||
setMessages(res.data); | ||
}); | ||
} else { | ||
setNewMessageText(''); | ||
setMessages(prev => ([...prev,{ | ||
text: newMessageText, | ||
sender: id, | ||
recipient: selectedUserId, | ||
_id: Date.now(), | ||
}])); | ||
} | ||
} | ||
function sendFile(ev) { | ||
const reader = new FileReader(); | ||
reader.readAsDataURL(ev.target.files[0]); | ||
reader.onload = () => { | ||
sendMessage(null, { | ||
name: ev.target.files[0].name, | ||
data: reader.result, | ||
}); | ||
}; | ||
} | ||
|
||
useEffect(() => { | ||
const div = divUnderMessages.current; | ||
if (div) { | ||
div.scrollIntoView({behavior:'smooth', block:'end'}); | ||
} | ||
}, [messages]); | ||
|
||
useEffect(() => { | ||
axios.get('/people').then(res => { | ||
const offlinePeopleArr = res.data | ||
.filter(p => p._id !== id) | ||
.filter(p => !Object.keys(onlinePeople).includes(p._id)); | ||
const offlinePeople = {}; | ||
offlinePeopleArr.forEach(p => { | ||
offlinePeople[p._id] = p; | ||
}); | ||
setOfflinePeople(offlinePeople); | ||
}); | ||
}, [onlinePeople]); | ||
|
||
useEffect(() => { | ||
if (selectedUserId) { | ||
axios.get('/messages/'+selectedUserId).then(res => { | ||
setMessages(res.data); | ||
}); | ||
} | ||
}, [selectedUserId]); | ||
|
||
const onlinePeopleExclOurUser = {...onlinePeople}; | ||
delete onlinePeopleExclOurUser[id]; | ||
|
||
const messagesWithoutDupes = uniqBy(messages, '_id'); | ||
|
||
return ( | ||
<div className="flex h-screen"> | ||
<div className="bg-white w-1/3 flex flex-col"> | ||
<div className="flex-grow"> | ||
<Logo /> | ||
{Object.keys(onlinePeopleExclOurUser).map(userId => ( | ||
<Contact | ||
key={userId} | ||
id={userId} | ||
online={true} | ||
username={onlinePeopleExclOurUser[userId]} | ||
onClick={() => {setSelectedUserId(userId);console.log({userId})}} | ||
selected={userId === selectedUserId} /> | ||
))} | ||
{Object.keys(offlinePeople).map(userId => ( | ||
<Contact | ||
key={userId} | ||
id={userId} | ||
online={false} | ||
username={offlinePeople[userId].username} | ||
onClick={() => setSelectedUserId(userId)} | ||
selected={userId === selectedUserId} /> | ||
))} | ||
</div> | ||
<div className="p-2 text-center flex items-center justify-center"> | ||
<span className="mr-2 text-sm text-gray-600 flex items-center"> | ||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-4 h-4"> | ||
<path fillRule="evenodd" d="M7.5 6a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM3.751 20.105a8.25 8.25 0 0116.498 0 .75.75 0 01-.437.695A18.683 18.683 0 0112 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 01-.437-.695z" clipRule="evenodd" /> | ||
</svg> | ||
{username} | ||
</span> | ||
<button | ||
onClick={logout} | ||
className="text-sm bg-blue-100 py-1 px-2 text-gray-500 border rounded-sm">logout</button> | ||
</div> | ||
</div> | ||
<div className="flex flex-col bg-blue-50 w-2/3 p-2"> | ||
<div className="flex-grow"> | ||
{!selectedUserId && ( | ||
<div className="flex h-full flex-grow items-center justify-center"> | ||
<div className="text-gray-300">← Select a person from the sidebar</div> | ||
</div> | ||
)} | ||
{!!selectedUserId && ( | ||
<div className="relative h-full"> | ||
<div className="overflow-y-scroll absolute top-0 left-0 right-0 bottom-2"> | ||
{messagesWithoutDupes.map(message => ( | ||
<div key={message._id} className={(message.sender === id ? 'text-right': 'text-left')}> | ||
<div className={"text-left inline-block p-2 my-2 rounded-md text-sm " +(message.sender === id ? 'bg-blue-500 text-white':'bg-white text-gray-500')}> | ||
{message.text} | ||
{message.file && ( | ||
<div className=""> | ||
<a target="_blank" className="flex items-center gap-1 border-b" href={axios.defaults.baseURL + '/uploads/' + message.file}> | ||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-4 h-4"> | ||
<path fillRule="evenodd" d="M18.97 3.659a2.25 2.25 0 00-3.182 0l-10.94 10.94a3.75 3.75 0 105.304 5.303l7.693-7.693a.75.75 0 011.06 1.06l-7.693 7.693a5.25 5.25 0 11-7.424-7.424l10.939-10.94a3.75 3.75 0 115.303 5.304L9.097 18.835l-.008.008-.007.007-.002.002-.003.002A2.25 2.25 0 015.91 15.66l7.81-7.81a.75.75 0 011.061 1.06l-7.81 7.81a.75.75 0 001.054 1.068L18.97 6.84a2.25 2.25 0 000-3.182z" clipRule="evenodd" /> | ||
</svg> | ||
{message.file} | ||
</a> | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
))} | ||
<div ref={divUnderMessages}></div> | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
{!!selectedUserId && ( | ||
<form className="flex gap-2" onSubmit={sendMessage}> | ||
<input type="text" | ||
value={newMessageText} | ||
onChange={ev => setNewMessageText(ev.target.value)} | ||
placeholder="Type your message here" | ||
className="bg-white flex-grow border rounded-sm p-2"/> | ||
<label className="bg-blue-200 p-2 text-gray-600 cursor-pointer rounded-sm border border-blue-200"> | ||
<input type="file" className="hidden" onChange={sendFile} /> | ||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-6 h-6"> | ||
<path fillRule="evenodd" d="M18.97 3.659a2.25 2.25 0 00-3.182 0l-10.94 10.94a3.75 3.75 0 105.304 5.303l7.693-7.693a.75.75 0 011.06 1.06l-7.693 7.693a5.25 5.25 0 11-7.424-7.424l10.939-10.94a3.75 3.75 0 115.303 5.304L9.097 18.835l-.008.008-.007.007-.002.002-.003.002A2.25 2.25 0 015.91 15.66l7.81-7.81a.75.75 0 011.061 1.06l-7.81 7.81a.75.75 0 001.054 1.068L18.97 6.84a2.25 2.25 0 000-3.182z" clipRule="evenodd" /> | ||
</svg> | ||
</label> | ||
<button type="submit" className="bg-blue-500 p-2 text-white rounded-sm"> | ||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6"> | ||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5" /> | ||
</svg> | ||
</button> | ||
</form> | ||
)} | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import Avatar from "./Avatar.jsx"; | ||
|
||
export default function Contact({id,username,onClick,selected,online}) { | ||
return ( | ||
<div key={id} onClick={() => onClick(id)} | ||
className={"border-b border-gray-100 flex items-center gap-2 cursor-pointer "+(selected ? 'bg-blue-50' : '')}> | ||
{selected && ( | ||
<div className="w-1 bg-blue-500 h-12 rounded-r-md"></div> | ||
)} | ||
<div className="flex gap-2 py-2 pl-4 items-center"> | ||
<Avatar online={online} username={username} userId={id} /> | ||
<span className="text-gray-800">{username}</span> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export default function Logo() { | ||
return ( | ||
<div className="text-blue-600 font-bold flex gap-2 p-4"> | ||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-6 h-6"> | ||
<path d="M4.913 2.658c2.075-.27 4.19-.408 6.337-.408 2.147 0 4.262.139 6.337.408 1.922.25 3.291 1.861 3.405 3.727a4.403 4.403 0 00-1.032-.211 50.89 50.89 0 00-8.42 0c-2.358.196-4.04 2.19-4.04 4.434v4.286a4.47 4.47 0 002.433 3.984L7.28 21.53A.75.75 0 016 21v-4.03a48.527 48.527 0 01-1.087-.128C2.905 16.58 1.5 14.833 1.5 12.862V6.638c0-1.97 1.405-3.718 3.413-3.979z" /> | ||
<path d="M15.75 7.5c-1.376 0-2.739.057-4.086.169C10.124 7.797 9 9.103 9 10.609v4.285c0 1.507 1.128 2.814 2.67 2.94 1.243.102 2.5.157 3.768.165l2.782 2.781a.75.75 0 001.28-.53v-2.39l.33-.026c1.542-.125 2.67-1.433 2.67-2.94v-4.286c0-1.505-1.125-2.811-2.664-2.94A49.392 49.392 0 0015.75 7.5z" /> | ||
</svg> | ||
MernChat | ||
</div> | ||
); | ||
} |
Oops, something went wrong.