33 Commits

Author SHA1 Message Date
Nicholas Pease 4ea77d8941 Update README.md with Demo Login (#105) 2024-06-15 21:39:10 -04:00
Nicholas Pease 33f42a703a Update README.md with Demo Login 2024-05-24 08:20:21 -04:00
ClarkLach 0db13e3178 Added Deliverables 4+5, Final Presentation Slides (#104) 2024-04-26 00:09:23 -04:00
Nicholas Pease aeb28b0623 Added Firebase Security Warning 2024-04-26 00:08:54 -04:00
ClarkLach 0ac733fa63 Add files via upload 2024-04-25 15:46:44 -04:00
ClarkLach e7f0855578 Delete deliverables/deliverable_5/filler.txt 2024-04-25 15:46:30 -04:00
ClarkLach 0f9a74826a Add files via upload 2024-04-25 15:46:14 -04:00
ClarkLach 67b8725f70 Create filler.txt 2024-04-25 15:45:26 -04:00
ClarkLach b9e244cf03 Delete deliverables/deliverable_4/filler.txt 2024-04-25 15:45:14 -04:00
ClarkLach 4a5078cab3 Add files via upload 2024-04-25 15:44:57 -04:00
ClarkLach 29de0c85fe Create filler.txt 2024-04-25 15:44:43 -04:00
Nicholas Pease 77c2bf9620 Nearby Room Fix / ChatMaps URL Formatting Fix (#103) 2024-04-24 09:27:28 -04:00
npease 91735beb88 Remove hanging console.log / fix linting issues 2024-04-24 01:35:23 -04:00
npease f02e74bc3a Bugfix - Fix nearby rooms on map 2024-04-24 01:25:40 -04:00
npease c3361e3141 Bugfix - RMF Chatma.ps URL Fix 2024-04-24 00:59:04 -04:00
Nicholas Pease 0cf510e95c Various Bug Fixes and Additions (#102) 2024-04-23 19:48:43 -04:00
npease 754442adfa Bugfix - Key on new Rich chatma.ps messages 2024-04-23 19:39:20 -04:00
npease 833361255b Feature - Add Rich Room URL's 2024-04-23 19:37:24 -04:00
npease 7a231c4e39 Bugfix - Cleanup Silent React Errors 2024-04-23 19:07:10 -04:00
npease 08c88fb59c Linting - Linting Complete 2024-04-23 17:52:21 -04:00
npease 4b5454ddc2 Easter Egg - Add /peter command and framework for future commands / easter eggs / Additional easter eggs (including /snoop) 2024-04-23 17:41:59 -04:00
npease 623de2877e Bugfix - URL / Image Detection and TTS Slur Detection / Blocking 2024-04-23 17:33:31 -04:00
npease ce6b23ec98 BugFix - Profile Data Updates / Status Fix 2024-04-23 14:01:24 -04:00
Nicholas Pease a83f219c02 Various Interactivity Bug Fixes (#101) 2024-04-23 11:05:17 -04:00
npease 97325623fe Bug Fix - Remove unused Jsdoc dependency (Breaks Deployment Actions) 2024-04-23 00:53:06 -04:00
npease 19466dd98f Bug Fix - Fix DM Online Not Removing On Close, Fix Onboarded Users Unable to Selectively Edit Profile Fields, Fix DM Online User Updates 2024-04-23 00:46:29 -04:00
npease 577dddd785 Bug Fix - Readd Updating Friend / Friend Requests 2024-04-23 00:17:37 -04:00
npease a58579e753 Bug Fix - Room Live Updates, Initial Account PFP, Initial Account Online Status 2024-04-23 00:07:33 -04:00
npease f5f4706123 Bug Fix - Remove Persistent Online from Added Members in Rooms 2024-04-22 23:38:05 -04:00
Nicholas Pease 0886b85847 TTS Fix (#100) 2024-04-22 20:37:10 -04:00
npease 30a40bf6b1 TTS Fix 2024-04-22 20:35:37 -04:00
Nicholas Pease ae927dea0d Add /tts (#99) 2024-04-22 20:26:47 -04:00
npease 69d2b98d50 Add /tts 2024-04-22 00:51:56 -04:00
53 changed files with 3541 additions and 1773 deletions
-27
View File
@@ -1,27 +0,0 @@
name: JSDoc to GH Pages
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Build
uses: andstor/jsdoc-action@v1
with:
source_dir: ./frontend-next
output_dir: ./jsdoc
recurse: true
template: minami
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./jsdoc
+10
View File
@@ -11,6 +11,14 @@ The live version of this app can be found at:
https://chatma.ps/
Demo Login:<br/>
Email: demo@chatma.ps<br/>
Password: demoapp<br/>
Please note. The demo is restricted to sending messages, and creating rooms. This is intented to ensure the demo remains an adequate example of the functionalities of this application. If you would like to test the full set of features, please make an account.
A local version can be run (assuming you have Node installed) with:
cd frontend-next/
@@ -23,3 +31,5 @@ then navigating to:
http://localhost:3000
NOTICE: Access to firebase from localhost has been disabled due to security considerations. To demo our app, please go to https://chatma.ps!
Binary file not shown.
+2 -1
View File
@@ -4,6 +4,7 @@
"no-unused-vars": ["warn", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }],
"jsx-a11y/alt-text": "off",
"@next/next/no-img-element": "off",
"no-console": 1
"no-console": 1,
"react-hooks/exhaustive-deps": "off"
}
}
+3304 -1525
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -30,7 +30,8 @@
"react-dom": "^18.2.0",
"react-firebase-hooks": "^5.1.1",
"react-hook-form": "^7.50.1",
"react-test-renderer": "^18.2.0"
"react-test-renderer": "^18.2.0",
"tts-react": "^3.0.6"
},
"devDependencies": {
"@capacitor/assets": "^3.0.5",
+3 -3
View File
@@ -10,8 +10,8 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
<main className={inter.className}>
{children}
</main>
);
}
+3 -3
View File
@@ -7,7 +7,7 @@ import Drawer from '@mui/material/Drawer';
// Firebase Imports
import { auth, database } from "../../../firebase-config";
import { ref, get, set } from "firebase/database";
import { ref, onValue, set } from "firebase/database";
import { useAuthState } from "react-firebase-hooks/auth"
// Component Imports
@@ -43,10 +43,10 @@ function Home() {
// Authentication Verification / Redirection if Profile Data not Filled out
useEffect(() => {
if (authUser && authLoading === false) {
get(ref(database, `users/${authUser.uid}`)).then((userData) => {
onValue(ref(database, `users/${authUser.uid}`), (userData) => {
userData = userData.val();
if (userData) {
setUser({...userData});
setUser(userData);
} else {
window.location.href = "/onboarding";
}
+4 -3
View File
@@ -10,8 +10,9 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
<main className={inter.className}>
{children}
</main>
);
}
+3 -3
View File
@@ -5,7 +5,7 @@ import { useState, useEffect } from "react";
// Firebase Imports
import { auth, database } from "../../../firebase-config";
import { ref, onValue, set, onDisconnect, get, onChildAdded, onChildRemoved } from "firebase/database";
import { ref, onValue, set, onDisconnect} from "firebase/database";
import { useAuthState } from "react-firebase-hooks/auth"
// Component Imports
@@ -39,7 +39,7 @@ function Chat() {
// Authentication Verification / Redirection if Profile Data not Filled out
useEffect(() => {
if (authUser && authLoading === false && !user) {
get(ref(database, `users/${authUser.uid}`)).then((userData) => {
onValue(ref(database, `users/${authUser.uid}`),(userData) => {
userData = userData.val();
if (userData) {
setUser(userData);
@@ -91,7 +91,7 @@ function Chat() {
})*/
// Room Object Load
get(ref(database, `/rooms/${path}`)).then((roomData) => {
onValue(ref(database, `/rooms/${path}`), (roomData) => {
roomData = roomData.val();
setChatRoomObj(roomData)
if (!doneLoading) {
+4 -3
View File
@@ -10,8 +10,9 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
<main className={inter.className}>
{children}
</main>
);
}
+6 -5
View File
@@ -5,7 +5,7 @@ import { useState, useEffect } from "react";
// Firebase Imports
import { auth, database } from "../../../firebase-config";
import { ref, onValue, set, onDisconnect, get, onChildAdded, onChildRemoved} from "firebase/database";
import { ref, onValue, set, onDisconnect} from "firebase/database";
import { useAuthState } from "react-firebase-hooks/auth"
// Component Imports
@@ -40,7 +40,7 @@ function Chat() {
// Authentication Verification / Redirection if Profile Data not Filled out
useEffect(() => {
if (authUser && authLoading === false) {
get(ref(database, `users/${authUser.uid}`)).then((userData) => {
onValue(ref(database, `users/${authUser.uid}`), (userData) => {
userData = userData.val();
if (userData) {
setUser(userData);
@@ -58,10 +58,11 @@ function Chat() {
if (user) {
const searchParams = new URLSearchParams(document.location.search);
var path = searchParams.get("dm")
if (path.includes(user.uid))
if (path.includes(user.uid)) {
setIsUserAuthed(true)
else
} else {
location.href = "/app"
}
/*// Send entered message
var payload = {
body: "entered",
@@ -95,7 +96,7 @@ function Chat() {
})*/
// Room Object Load
get(ref(database, `/dms/${path}`)).then((roomData) => {
onValue(ref(database, `/dms/${path}`), (roomData) => {
roomData = roomData.val();
setChatRoomObj(roomData)
if (!doneLoading) {
-1
View File
@@ -15,7 +15,6 @@ button {
border-radius: 5px;
padding: 5px;
margin: 5px;
filter: drop-shadow(0 25px 25px rgb(0 0 0 / 0.15));
}
button:hover {
+3 -3
View File
@@ -10,8 +10,8 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
<main className={inter.className}>
{children}
</main>
);
}
+4 -4
View File
@@ -31,7 +31,7 @@ function Login() {
}
}
).catch((error) => {
if (error = "auth/invalid-credential") {
if (error == "auth/invalid-credential") {
const formError = {
type: "server",
message: "Username or Password Incorrect",
@@ -99,20 +99,20 @@ function Login() {
{(isSubmitting || isSubmitted) && (
<span className="inline-block">
<svg
class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
className="opacity-25"
cx="12"
cy="12"
r="10"
strokeWidth="4"
></circle>
<path
class="opacity-75"
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
+4 -3
View File
@@ -10,8 +10,9 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
<main className={inter.className}>
{children}
</main>
);
}
+20 -22
View File
@@ -6,27 +6,9 @@ import { useRouter } from "next/navigation";
// Firebase Imports
import { ref, set } from "firebase/database";
import { auth, database } from "../../../firebase-config";
import { auth, database, storage } from "../../../firebase-config";
import { onAuthStateChanged } from "firebase/auth";
/**
* Creates user data in Firebase DB
* @param {JSON} data - User data to be stored in Firebase DB ( from form )
* @return {Boolean} - True if user data is stored, False if user data is not stored
*/
function createUser(data) {
onAuthStateChanged(auth, (user) => {
if (user.uid) {
data.uid = user.uid;
data.defined = true;
data.email = user.email;
set(ref(database, `users/${user.uid}`), data);
return true;
} else {
return false;
}
});
}
import { ref as sRef, getDownloadURL } from "firebase/storage";
/**
* Onboarding Page
@@ -37,8 +19,24 @@ function Onboarding() {
var { register, handleSubmit } = useForm();
function Onboard(data) {
createUser(data);
router.push("/app");
onAuthStateChanged(auth, (user) => {
if (user.uid) {
data.uid = user.uid;
data.defined = true;
data.invisibleStatus = false;
data.bio = " ";
data.interests = " , , "
getDownloadURL(sRef(storage, `/default.png`)).then((url) => {
data.pfp = url;
data.email = user.email;
set(ref(database, `users/${user.uid}`), data);
router.push("/app");
})
} else {
return false;
}
});
}
return (
<div>
+3 -3
View File
@@ -46,9 +46,9 @@ function Home() {
get(ref(database, `/rooms/${path}`)).then((snapshot) => {
if (snapshot.exists()) {
var count = 0;
for (var room in snapshot.val()) {
count += 1;
}
snapshot.forEach(() => {
count++;
});
setRoomCount(count);
} else {
setRoomCount(0);
+3 -3
View File
@@ -10,8 +10,8 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
<main className={inter.className}>
{children}
</main>
);
}
+3 -3
View File
@@ -10,8 +10,8 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
<main className={inter.className}>
{children}
</main>
);
}
+7 -7
View File
@@ -2,7 +2,7 @@
// System Imports
import { useState, useEffect } from "react";
import { auth, database } from "../../../firebase-config";
import { ref, onValue, get } from "firebase/database";
import { ref, onValue } from "firebase/database";
import { useAuthState } from "react-firebase-hooks/auth"
@@ -49,7 +49,7 @@ function UserProfile() {
if (authUser && authLoading === false) {
const searchParams = new URLSearchParams(document.location.search);
var userUID = searchParams.get("uid")
get(ref(database, `users/${authUser.uid}`)).then((userData) => {
onValue(ref(database, `users/${authUser.uid}`), (userData) => {
userData = userData.val();
if (userData) {
if (userData.uid == userUID) {
@@ -70,7 +70,7 @@ function UserProfile() {
useEffect(() => {
const searchParams = new URLSearchParams(document.location.search);
var userUID = searchParams.get("uid")
get(ref(database, "/users/" + userUID)).then((snapshot) => {
onValue(ref(database, "/users/" + userUID), (snapshot) => {
setProfileData(snapshot.val());
// Populates array with user's interests
@@ -84,7 +84,7 @@ function UserProfile() {
var i = 0;
for (var interest in interests) {
if (i < 4)
interestArray.push(<Interest interest={interests[interest]} />);
interestArray.push(<Interest interest={interests[interest]} key={interest}/>);
i++;
}
setUserInterestArray(interestArray);
@@ -93,7 +93,7 @@ function UserProfile() {
var rooms = snapshot.val().rooms;
var roomArray = [];
for (var room in rooms) {
roomArray.push(<ProfileRoom room={rooms[room]} />);
roomArray.push(<ProfileRoom room={rooms[room]} key={room}/>);
}
setUserRoomsArray(roomArray);
});
@@ -119,7 +119,7 @@ function UserProfile() {
{/* Left Side of Page */}
<div className="h-dvh md:overflow-hidden">
{/* Header */}
<Header user={user} />
<Header user={user} mainTab={"profile"} />
{/* Main Page Section */}
<div className="md:grid md:grid-cols-3 mr-2 h-[calc(100%-110px)] pl-5 pr-5 pt-2 max-md:mb-10">
<div className="cols-span-1 bg-white shadow-2xl rounded-xl pt-5 max-md:pb-5">
@@ -127,7 +127,7 @@ function UserProfile() {
<div>
<img
src={profileData.pfp}
className="relative mx-auto rounded-2xl overflow-hidden w-[90%]"
className="relative mx-auto rounded-2xl overflow-hidden max-h-[20%] max-w-[70%]"
/>
<div className="font-bold text-[30px] flex justify-center items-center">
{profileData.lastOnline == true && <CircleIcon className="text-lime-600 mr-3"/>}{profileData.firstName} {profileData.lastName}
+88 -48
View File
@@ -11,6 +11,17 @@ import PersonIcon from '@mui/icons-material/Person';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import CircleIcon from '@mui/icons-material/Circle';
import { TextToSpeech } from 'tts-react'
// Chat Commands Dictionary
const chatCommands = {
"/peter": "https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExcThtYjMxcWk2NG55YTE3OHdvNWJwcXVrZzV1ZmZkdWJ6cHBremFwdiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13w5HmyiuaZ224/giphy.gif",
"/shrug": "¯\\_(ツ)_/¯",
"/tableflip": "(╯°□°)╯︵ ┻━┻",
"/unflip": "┬─┬ ( ゜-゜ノ)",
"/snoop": "https://media1.tenor.com/m/cIUjlvgnFRgAAAAd/dog-snoop.gif"
}
// Colors for Messages
const userColors = [
"#ff80ed",
@@ -43,18 +54,17 @@ let dateOptions = {
* @returns {Boolean} - Image Loaded (True) or Not (False)
*/
function imageProcessing(url) {
var x = async () => {
var x = new Promise((resolve) => {
var img = new Image();
img.src = url;
img.onload = function() {
return true;
img.onload = () => {
resolve([true, url]);
}
img.onerror = function() {
return false;
img.onerror = () => {
resolve([false, url]);
}
}
var res = x()
return res
})
return x
}
@@ -63,28 +73,42 @@ function imageProcessing(url) {
* @param {String} message - Message to Format
* @returns {String} - Formatted Message (IN HTML)
*/
export function RMF(message) {
var URLREGEX = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
var URLmatch = message.match(URLREGEX);
var newMessage = URLmatch ? [] : message
if (URLmatch) {
for (var i = 0; i < URLmatch.length; i++) {
if (imageProcessing("https://"+URLmatch[i])) {
newMessage.push((<span className="mr-2">
{(URLmatch.length == 1) && message.split(URLmatch[i])[0].replace("https://","").replace("http://","")}
<img src={"https://"+URLmatch[i]} className="max-w-[100%]"/>
{(i == URLmatch.length || URLmatch.length == 1) && message.split(URLmatch[i])[1]}
</span>))
} else {
newMessage.push((<span className="mr-2">
{URLmatch.length == 1 && message.split(URLmatch[i])[0]}
<Link href={"https://"+URLmatch[i]} target="_blank" className="hover:underline">{URLmatch[i]}</Link>
{(i == URLmatch.length || URLmatch.length == 1) && message.split(URLmatch[i])[1]}
</span>))
export async function RMF(message) {
var x = new Promise(async (resolve) => {
var URLREGEX = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
var URLmatch = message.match(URLREGEX);
var newMessage = URLmatch ? [] : message
if (URLmatch) {
for (var i = 0; i < URLmatch.length; i++) {
if (URLmatch[i].includes("chatma.ps") ) {
// Rich Message Formatting for Chat Maps
if (URLmatch[i].includes("/chat?")) {
var roomName = URLmatch[i].split("?")[1].split("/")[2].split("-")[0].replaceAll("%20"," ")
newMessage.push((<span className="italic" key={roomName}>invites you to <Link href={"https://"+URLmatch[i]} className="underline" target="_blank">{roomName}</Link></span>))
}
} else {
await imageProcessing("https://"+URLmatch[i]).then((result) => {
if (result[0]) {
newMessage.push((<span className="mr-2" key={URLmatch[i]}>
{(URLmatch.length == 1) && message.split(result[1])[0].replace("https://","").replace("http://","")}
<img src={result[1]} className="max-w-[100%]"/>
{(i == URLmatch.length || URLmatch.length == 1) && message.split(result[1])[1]}
</span>))
} else {
newMessage.push((<span className="mr-2" key={URLmatch[i]}>
{URLmatch.length == 1 && message.split(URLmatch[i])[0]}
<Link href={result[1]} target="_blank" className="hover:underline">{URLmatch[i]}</Link>
{(i == URLmatch.length || URLmatch.length == 1) && message.split(URLmatch[i])[1]}
</span>))
}
})
}
}
}
}
return newMessage
resolve(newMessage)
});
return x
}
/**
* Grabs Window Size
@@ -132,31 +156,47 @@ const generateColor = (user_str) => {
* @returns {Object} - Chat Message Component
*/
export function Chat({ chatObj, user, path }) {
const [message, setMessage] = useState([])
function deleteMessage() {
remove(ref(database, `${path}/chats/${chatObj.timestamp}-${chatObj.user}`))
}
var messageFilterBypass = [undefined, null, '', ' ', '\'', '\"']
if (!messageFilterBypass.includes(chatObj.body) && (chatObj.body.length != 1 && !chatObj.body[0].match(/\W/))) {
var message = filter.clean(chatObj.body)
message = RMF(message)
} else {
var message = chatObj.body
if (chatObj.body in chatCommands) {
chatObj.body = chatCommands[chatObj.body]
}
useEffect(() => {
// Peter Griffin Easter Egg and others
var messageFilterBypass = [undefined, null, '', ' ', '\'', '\"']
if (!messageFilterBypass.includes(chatObj.body) && (chatObj.body.length != 1 && !chatObj.body[0].match(/\W/))) {
var settingMessage = filter.clean(chatObj.body)
RMF(settingMessage).then((result) => {
setMessage(result)
})
} else {
setMessage(chatObj.body)
}
}, [])
return (
<div className="width-[100%] bg-white rounded-lg mt-1 text-left p-1 grid grid-cols-2 mr-2">
<div>
{user.uid == chatObj.uid && <DeleteOutlineIcon fontSize="" className="ml-1 mr-1 cursor-pointer" onClick={() => {deleteMessage()}}/>}
<span className="mr-[5px]" style={{ color: userColors[generateColor(chatObj.user)] }}>
<Link href={`/user?uid=${chatObj.uid}`}
className="hover:font-bold cursor-pointer">
{chatObj.user}
</Link>
</span>
{message}
</div>
<div className="text-right text-[#d1d1d1]">
{new Date(chatObj.timestamp).toLocaleString(dateOptions)}
</div>
<div>
{message && (
<div className="width-[100%] bg-white rounded-lg mt-1 text-left p-1 grid grid-cols-2 mr-2">
<div>
{user.uid == chatObj.uid && <DeleteOutlineIcon fontSize="" className="ml-1 mr-1 cursor-pointer" onClick={() => {deleteMessage()}}/>}
<span className="mr-[5px]" style={{ color: userColors[generateColor(chatObj.user)] }}>
<Link href={`/user?uid=${chatObj.uid}`}
className="hover:font-bold cursor-pointer">
{chatObj.user}
</Link>
</span>
{(typeof message == 'string' && message.substring(0,4) == "/tts")? (<TextToSpeech autoPlay={true}> {chatObj.user + " said " + filter.clean(message.substring(5,message.length))} </TextToSpeech>): message}
</div>
<div className="text-right text-[#d1d1d1]">
{new Date(chatObj.timestamp).toLocaleString(dateOptions)}
</div>
</div>
)}
</div>
);
}
@@ -71,7 +71,7 @@ export function DMRoom({ roomObj, user }) {
chatObj={messages[message]}
user={user}
path={"/dms/" + chatRoomObj.room}
key={messages[message].timestamp}
key={messages[message].timestamp + "-" + messages[message].user}
/>
);
}
+18 -16
View File
@@ -14,6 +14,7 @@ import MenuIcon from '@mui/icons-material/Menu';
import AddIcon from '@mui/icons-material/Add';
import RemoveIcon from '@mui/icons-material/Remove';
import CloseIcon from '@mui/icons-material/Close';
import { useEffect } from "react";
/**
* Closes Chat
@@ -21,8 +22,12 @@ import CloseIcon from '@mui/icons-material/Close';
* @param {JSON} user - User Object
* @returns {void}
*/
function closeChat(chatRoomObj, user) {
function closeChat(chatRoomObj, user, mainTab) {
if (mainTab == "chat") {
remove(ref(database, `/rooms/${chatRoomObj.path}/${chatRoomObj.name}-${chatRoomObj.timestamp}/users/online/${user.uid}`))
} else {
remove(ref(database, `/dms/${chatRoomObj.room}/users/online/${user.uid}`))
}
}
@@ -49,6 +54,7 @@ function addToMyRooms(chatRoomObj, user) {
);
var path =
chatRoomObj.path + "/" + chatRoomObj.name + "-" + chatRoomObj.timestamp;
user.lastOnline = serverTimestamp();
set(ref(database, `/rooms/${path}/users/all/${user.uid}`), user);
}
@@ -90,12 +96,14 @@ export function Header({mainTab,chatRoomObj,user,sidebarControl}) {
}
// Sets User Online / Offline
// Stored in header for easy code maintenance and retains user online/offline throughout app
// Makes user online
if (user.invisibleStatus == false) {
set(ref(database, `/users/${user.uid}/lastOnline`), true)
}
useEffect(() => {
// Sets User Online / Offline
// Stored in header for easy code maintenance and retains user online/offline throughout app
// Makes user online
if (user.invisibleStatus == false) {
set(ref(database, `/users/${user.uid}/lastOnline`), true)
}
}, [])
// Makes user offline (with last time online)
onDisconnect(ref(database, `/users/${user.uid}/lastOnline`)).set(serverTimestamp())
@@ -124,7 +132,6 @@ export function Header({mainTab,chatRoomObj,user,sidebarControl}) {
<a
onClick={() => {
removeFromMyRooms(chatRoomObj, user);
}}
className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-2 flex items-center"
>
@@ -135,7 +142,7 @@ export function Header({mainTab,chatRoomObj,user,sidebarControl}) {
<Link
href="/app"
className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-2 flex items-center"
onClick={() => {closeChat(chatRoomObj,user)}}
onClick={() => {closeChat(chatRoomObj,user, mainTab)}}
>
<CloseIcon/>
</Link>
@@ -148,13 +155,8 @@ export function Header({mainTab,chatRoomObj,user,sidebarControl}) {
<ProfilePanel user={user}/>
{/* Sidebar Control (for small screens) */}
<div
className="md:hidden p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-5 flex items-center"
onClick={() => {sidebarControl()}}
>
<MenuIcon/>
</div>
</div>
{mainTab !== "profile" && (<div className="md:hidden p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-5 flex items-center" onClick={() => {sidebarControl()}}><MenuIcon/></div>)}
</div>
</div>
);
}
+23 -60
View File
@@ -1,49 +1,11 @@
import { Map, Marker, ZoomControl, Overlay } from "pigeon-maps";
import { database } from "../../../../firebase-config";
import { ref, get} from "firebase/database";
import { useState } from "react";
import { useEffect, useState } from "react";
import ChatBubbleTwoToneIcon from '@mui/icons-material/ChatBubbleTwoTone';
import PersonOutlineTwoToneIcon from '@mui/icons-material/PersonOutlineTwoTone';
import { red } from '@mui/material/colors';
/**
* Nearby Markers Grabber
* @param {JSON} location - Location Object {latitude, longitude}
* @returns {Array} - Array of Markers {<Marker>}
*/
function NearbyMarkers(location) {
const [newMarkers, setNewMarkers] = useState(null);
if (location) {
const path = String(location.latitude.toFixed(2)).replace(".", "") +"/" +String(location.longitude.toFixed(2)).replace(".", "") +"/";
get(ref(database, `/rooms/${path}`)).then((snapshot) => {
if (snapshot.exists()) {
const rooms = snapshot.val();
setNewMarkers(rooms)
}
})
}
return newMarkers;
}
/**
* Friend Markers Grabber
* @param {JSON} user - User Object
* @returns {Array} - Array of Markers {<Marker>}
*/
function FriendMarkers(user) {
var friendMarkers = []
if (user && "friends" in user && "friends" in user.friends) {
for (var friend in user.friends.friends) {
get(ref(database, `/users/${friend}`)).then((snapshot) => {
var friendData = snapshot.val();
if (friendData.location) {
friendMarkers.push(friendData);
}
});
}
}
}
/**
* Geo Component for Wrapping Map
* @constructor
@@ -57,30 +19,30 @@ export function Geo({ loc, zoom, moveable, user }) {
const [hovering, setHovering] = useState(false);
const [hoverText, setHoverText] = useState("");
const [hoverAnchor, setHoverAnchor] = useState([null,null]);
const [nearbyMarkersFinal, setNearbyMarkers] = useState(null);
if (moveable) {
if (user.rooms) {
// Load My Rooms Markers
var myRoomsMarkers = Object.values(user.rooms).map((roomObj) => {
return (<Marker
key={roomObj.path + "-" + roomObj.name}
anchor={[roomObj.latitude, roomObj.longitude]}
onClick={() => {window.location.href = "/chat?room=" + roomObj.path + "/" + roomObj.name + "-" + roomObj.timestamp;}}
style={{pointerEvents:'auto'} /* So stupid */}
onMouseOver={() => {setHoverText(roomObj.name);setHovering(true);setHoverAnchor([roomObj.latitude, roomObj.longitude])}}
onMouseOut={() => {setHovering(false)}}
>
<ChatBubbleTwoToneIcon color="primary" fontSize="large"/>
</Marker>)
})
}
if (moveable && user.rooms) {
// Load My Rooms Markers
var myRoomsMarkers = Object.values(user.rooms).map((roomObj) => {
return (<Marker
key={roomObj.path + "-" + roomObj.name}
anchor={[roomObj.latitude, roomObj.longitude]}
onClick={() => {window.location.href = "/chat?room=" + roomObj.path + "/" + roomObj.name + "-" + roomObj.timestamp;}}
style={{pointerEvents:'auto'} /* So stupid */}
onMouseOver={() => {setHoverText(roomObj.name);setHovering(true);setHoverAnchor([roomObj.latitude, roomObj.longitude])}}
onMouseOut={() => {setHovering(false)}}
>
<ChatBubbleTwoToneIcon color="primary" fontSize="large"/>
</Marker>)
})
}
useEffect(() => {
// Load Nearby Markers
var nearbyMarkers = null;
if (location) {
if (moveable && loc) {
const path = String(loc.latitude.toFixed(2)).replace(".", "") +"/" +String(loc.longitude.toFixed(2)).replace(".", "") +"/";
get(ref(database, `/rooms/${path}`)).then((snapshot) => {
console.log("ran")
if (snapshot.exists()) {
nearbyMarkers = snapshot.val();
if (nearbyMarkers) {
@@ -96,11 +58,12 @@ export function Geo({ loc, zoom, moveable, user }) {
<ChatBubbleTwoToneIcon color="secondary" fontSize="large"/>
</Marker>)
})
setNearbyMarkers(nearbyMarkers);
}
}
})
}
}
}, [])
if (!loc) {
return <div>Getting Location...</div>;
@@ -115,7 +78,7 @@ export function Geo({ loc, zoom, moveable, user }) {
attribution={false}
>
{zoom && <ZoomControl />}
{moveable && nearbyMarkers}
{moveable && nearbyMarkersFinal}
{moveable && myRoomsMarkers}
{ /* Overlay */}
@@ -56,7 +56,7 @@ export function ChatRoom({ roomObj, user }) {
chatsArr.push(
<SystemMessage
chatObj={messages[message]}
key={messages[message].timestamp}
key={messages[message].timestamp + messages[message].user}
/>
);
} else {
@@ -65,7 +65,7 @@ export function ChatRoom({ roomObj, user }) {
chatObj={messages[message]}
user={user}
path={"/rooms/" + chatRoomObj.path + "/" + chatRoomObj.name + "-" + chatRoomObj.timestamp}
key={messages[message].timestamp}
key={messages[message].timestamp + "-" + messages[message].user}
/>
);
}
@@ -17,7 +17,7 @@ export function Sidebar({chatRoomObj}) {
var activeUsers = [];
var activeUsersJSON = chatRoomObj.users.online;
for (var user in activeUsersJSON)
activeUsers.push(<Member memberObj={activeUsersJSON[user]} />);
activeUsers.push(<Member memberObj={activeUsersJSON[user]} key={user}/>);
var chatroomOnline = activeUsers
}
@@ -29,7 +29,7 @@ export function Sidebar({chatRoomObj}) {
var allUsers = [];
var allUsersJSON = chatRoomObj.users.all;
for (var user in allUsersJSON)
allUsers.push(<Member memberObj={allUsersJSON[user]} />);
allUsers.push(<Member memberObj={allUsersJSON[user]} key={user}/>);
var chatroomUsers = allUsers
}
return (
+10 -12
View File
@@ -1,24 +1,22 @@
import { Member } from "../datatypes"
import { database } from "../../../../firebase-config"
import {ref, get, onValue} from "firebase/database"
import {ref, get} from "firebase/database"
import { useState, useEffect } from "react"
export function Sidebar({user, chatRoomObj}) {
const [profileData, setProfileData] = useState(null)
const [chatroomOnline, setChatroomOnline] = useState(null)
var path = chatRoomObj.UIDs[0] < chatRoomObj.UIDs[1] ? chatRoomObj.UIDs[0] + "-" + chatRoomObj.UIDs[1] : chatRoomObj.UIDs[1] + "-" + chatRoomObj.UIDs[0];
var activeUsers = []
get(ref(database, `/dms/${path}/users/online`)).then((snapshot) => {
if (snapshot.exists()) {
var activeUsersJSON = snapshot.val();
for (var activeUser in activeUsersJSON)
activeUsers.push(<Member memberObj={activeUsersJSON[activeUser]} key={activeUser} />);
}
})
useEffect(() => {
if (chatRoomObj.users && chatRoomObj.users.online) {
var activeUsers = []
for (var activeUser in chatRoomObj.users.online)
activeUsers.push(<Member memberObj={chatRoomObj.users.online[activeUser]} key={activeUser} />);
}
setChatroomOnline(activeUsers)
}, [chatRoomObj])
useEffect(() => {
if (user) {
// Profile Information
@@ -49,7 +47,7 @@ export function Sidebar({user, chatRoomObj}) {
</div>
<div className="bg-white rounded-lg m-2 shadow-2xl">
<div>In The Chat</div>
{activeUsers}
{chatroomOnline}
</div>
</div>
</div>
@@ -156,7 +156,7 @@ export function Sidebar({user,location,loadingLoc}) {
}
})
}, [])
}, [user.friends])
return (
<div className="h-dvh bg-[aliceblue] pt-2 pb-2 pl-2 pr-1">
@@ -168,42 +168,42 @@ export function Sidebar({user,location,loadingLoc}) {
'w-[30%]',
selected
? 'bg-cyan-500 text-white font-bold shadow hover:bg-white/[0.6] hover:text-black'
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold'
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold bg-[#D3D3D3] drop-shadow-none'
)}>Nearby</Tab>
<Tab className={({ selected }) =>
classNames(
'w-[30%]',
selected
? 'bg-cyan-500 text-white font-bold shadow hover:bg-white/[0.6] hover:text-black'
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold'
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold bg-[#D3D3D3] drop-shadow-none'
)}>My Rooms</Tab>
<Tab className={({ selected }) =>
classNames(
'w-[30%]',
selected
? 'bg-cyan-500 text-white font-bold shadow hover:bg-white/[0.6] hover:text-black'
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold'
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold bg-[#D3D3D3] drop-shadow-none'
)}>Create</Tab>
<Tab className={({ selected }) =>
classNames(
'w-[30%]',
selected
? 'bg-cyan-500 text-white font-bold shadow hover:bg-white/[0.6] hover:text-black'
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold'
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold bg-[#D3D3D3] drop-shadow-none'
)}>DMs</Tab>
<Tab className={({ selected }) =>
classNames(
'w-[30%]',
selected
? 'bg-cyan-500 text-white font-bold shadow hover:bg-white/[0.6] hover:text-black'
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold'
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold bg-[#D3D3D3] drop-shadow-none'
)}>Friends</Tab>
<Tab className={({ selected }) =>
classNames(
'w-[30%]',
selected
? 'bg-cyan-500 text-white font-bold shadow hover:bg-white/[0.6] hover:text-black'
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold'
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold bg-[#D3D3D3] drop-shadow-none'
)}>Requests</Tab>
</Tab.List>
@@ -10,7 +10,7 @@ export function NearbySidebar({location}) {
const [nearbyArr, setNearbyArr] = useState([])
const [displayedRooms, setDisplayedRooms] = useState([])
const [nearbyArrReady, setNearbyArrReady] = useState(false)
const {register, watch, setFocus} = useForm({defaultValues: {search: null}})
const {register, watch, setFocus} = useForm({defaultValues: {search: ""}})
// Search Bar Value
const search = watch("search")
@@ -19,7 +19,7 @@ export function NearbySidebar({location}) {
function SearchBar() {
return (
<div className="w-[97%]">
<input type="text" placeholder="Search" {...register("search")} className="w-full p-2 border-2 border-gray-300 rounded-lg col-span-3" value={null} />
<input type="text" placeholder="Search" {...register("search")} className="w-full p-2 border-2 border-gray-300 rounded-lg col-span-3" value={" "} />
</div>
)
}