diff --git a/frontend-next/package-lock.json b/frontend-next/package-lock.json index 7d4b26b..3a0c916 100644 --- a/frontend-next/package-lock.json +++ b/frontend-next/package-lock.json @@ -13,6 +13,7 @@ "next": "^14.1.0", "pigeon-maps": "^0.21.3", "react": "^18.2.0", + "react-beforeunload": "^2.6.0", "react-dom": "^18.2.0", "react-hook-form": "^7.50.1" }, @@ -5739,6 +5740,14 @@ "node": ">=0.10.0" } }, + "node_modules/react-beforeunload": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-beforeunload/-/react-beforeunload-2.6.0.tgz", + "integrity": "sha512-aKrGaRNc7fZQlDnmSYrXu4cbz9QEPhScA4A2mLxhjcULDy4VILLyLhSEjg2goIw3o5LQ1zss44kmQh5LXWYGCw==", + "peerDependencies": { + "react": "^16.8.0 || 17 || 18" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", diff --git a/frontend-next/package.json b/frontend-next/package.json index 1912fa3..544379a 100644 --- a/frontend-next/package.json +++ b/frontend-next/package.json @@ -14,6 +14,7 @@ "next": "^14.1.0", "pigeon-maps": "^0.21.3", "react": "^18.2.0", + "react-beforeunload": "^2.6.0", "react-dom": "^18.2.0", "react-hook-form": "^7.50.1" }, diff --git a/frontend-next/src/app/app/page.js b/frontend-next/src/app/app/page.js index ad74268..def0b53 100644 --- a/frontend-next/src/app/app/page.js +++ b/frontend-next/src/app/app/page.js @@ -1,12 +1,16 @@ "use client" -import { useState, useEffect, createContext, useContext } from 'react' +import { useState, useEffect } from 'react' import {Map, Marker, ZoomControl} from "pigeon-maps" import { Form, useForm } from "react-hook-form"; import { app } from "../api/firebase-config"; -import { getDatabase, ref, onValue, get, set} from "firebase/database"; +import { getDatabase, ref, onValue, get, set, remove} from "firebase/database"; +import { useBeforeunload } from 'react-beforeunload'; var database = getDatabase(app) -// Data types + +// Data Types + +// Chat Message function Chat({chatObj}) { let dateOptions = { weekday: 'long', @@ -28,9 +32,45 @@ function Chat({chatObj}) { ) } +// System Chat Message +function SystemMessage({chatObj}) { + let dateOptions = { + weekday: 'long', + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }; + return ( +
+
+ {chatObj.user} has {chatObj.body} the room. +
+
+ {new Date(chatObj.timestamp).toLocaleString(dateOptions)} +
+
+ ) +} + +// Member for Active/Room members in sidebar +function Member({memberObj}) { + return ( +
+ {memberObj.username} +
+ ) +} + +// Chat Room for myRooms and Nearby in sidebar function ChatRoomSidebar({roomObj, click}) { + // TODO: Gross fix but it works +function clicker() { + click(roomObj.name+"-"+roomObj.timestamp, roomObj) +} return ( -
+
{roomObj.name}
{roomObj.description}
@@ -39,9 +79,27 @@ return ( ) } +// Map module for main page and chat room sidebar +// TODO: MAKE NOT MOVABLE +function Geo({loc, zoom, movable, locMarker, markers}) { + if (loc) { + return ( + + {markers && markers} + {locMarker && } + {zoom && } + + ) + } else { + return ( + + ) + } + +} +// Module for Welcome Message on main tab landing page function WelcomeMessage() { - //TODO: REALLY GROSS WAY TO GET COOKIES, NEED NEW WAY TO STORE USER DATA WITHOUT API CALLS. THIS PAGE HAS TO BE CLIENT SIDE DUE TO MAPS / GEOLOCATION const [data, setData] = useState(null) const [isLoading, setLoading] = useState(true) useEffect(() => { @@ -68,55 +126,36 @@ function WelcomeMessage() { } -function Geo({loc}) { - if (loc) { - return ( - - - - - ) - } else { - return ( - - ) - } - -} - - - // Main Tabs -function MainTabHome({loc}) { +// Primary App Landing Page +function MainTabHome({loc, markers}) { return ( <>
- +
) } -function MainTabChatRoom({room}) { +// Chatroom Module for Primary Tab +function MainTabChatRoom({roomObj}) { var { register, control, reset, handleSubmit} = useForm() const [chats, setData] = useState(null) const [isLoading, setLoading] = useState(true) - var user - fetch('/api/user') - .then((res) => res.json()) - .then((data) => { - user = data - }) - - var unsubscribeUpdater + // Message updater useEffect(() => { - unsubscribeUpdater = onValue(ref(database, `/rooms/${room}/chats`), (snapshot) => { + onValue(ref(database, `/rooms/${roomObj.path+"/"+roomObj.name+"-"+roomObj.timestamp}/chats`), (snapshot) => { var chatsArr = [] var messages = snapshot.val() for (var message in messages) { - chatsArr.push() + if (messages[message].isSystem) { + chatsArr.push() + } else { + chatsArr.push() + } } setData(chatsArr.reverse()) setLoading(false) @@ -125,15 +164,18 @@ function MainTabChatRoom({room}) { function sendMessage(data) { reset() - var payload = { - body: data.message, - user: user.username, - timestamp: new Date().getTime() - } - set(ref(database,`/rooms/${room}/chats/${user.username}-${new Date().getTime()}`), payload) + fetch('/api/user').then((res) => res.json()) + .then((user) => { + var payload = { + body: data.message, + user: user.username, + isSystem: false, + timestamp: new Date().getTime() + } + set(ref(database,`/rooms/${roomObj.path+"/"+roomObj.name+"-"+roomObj.timestamp}/chats/${new Date().getTime()}-${user.username}`), payload) + }) } - if (isLoading) return
Loading
if (!chats) return
No Chats
return ( @@ -151,65 +193,52 @@ function MainTabChatRoom({room}) { ) } -function CreateRoom({loc}) { - var { register, control, reset, handleSubmit} = useForm() - - function createRoom(data) { - reset() - var path = String(loc.latitude.toFixed(2)).replace(".","")+"/"+String(loc.longitude.toFixed(2)).replace(".","") - var timestamp = new Date().getTime() - var payload = { - name: data.name, - description: data.description, - timestamp: timestamp, - latitude: loc.latitude, - longitude: loc.longitude, - path: path - } - set(ref(database,`/rooms/${path}/${data.name}-${timestamp}`), payload) - } - - return ( -
-
- -
-
- Creating room near ({loc.latitude.toFixed(2)}, {loc.longitude.toFixed(2)}) -
- -
-
- ) -} - +// Contains most everything for the app homepage +// function Home() { - var [tab, setTab] = useState("nearby") - var [mainTab, setMainTab] = useState("home") - var [chatRoom, setChatRoom] = useState("Dev") + // It's time to document and change these awful variable names + // State variables for app page + const [mainTab, setMainTab] = useState("home") // Primary tab + const [tab, setTab] = useState("nearby") // Sidebar Tab + const [chatRoomObj, setChatRoomObj] = useState(null) // Current chatroom object + const [myRoomsObj, setMyRoomsObj] = useState(null) // My Rooms Object + const [myRooms, setRoomData] = useState(null) // Current user saved rooms list + const [isRoomLoading, setRoomLoading] = useState(true) // myRooms loading variable, true = myRooms loading, false = finished loading + const [isMyRoom, setIsMyRoom] = useState(false) // Is current room in myRooms? true, false + const [location, setLocation] = useState(null) // location variable [lat,long] + const [loadingLoc, setLoadingLoc] = useState(true) // location variable loading, true = loading, false = finished loading + const [nearby, setNearby] = useState(null); // nearby rooms array + const [loadingNearby, setLoadingNearby] = useState(true); // loading nearby rooms array, true = loading, false = finished loading + const [chatroomOnline, setChatRoomOnline] = useState(null) // holds online users + const [chatroomUsers, setChatroomUsers] = useState(null) // holds all chatroom users + const [chatroomUsersLoading ,setChatroomUsersLoading] = useState(true) + const [users, setUsers] = useState(null) // all users from firebase + const [alreadyLeft, setAlreadyLeft] = useState(false) // if already left from room + const [markers, setMarkers] = useState([]) - const [myRooms, setRoomData] = useState(null) - const [isRoomLoading, setRoomLoading] = useState(true) + // Grabs user data, saves to user, then lists the users saved rooms useEffect(() => { - fetch('/api/user').then((res) => res.json()) - .then((user) => { - get(ref(database, '/users/'+user.uid+'/rooms')).then((snapshot) => { - var rooms = snapshot.val() - var roomArr = [] - for (var room in rooms) { - roomArr.push( {setChatRoom(rooms[room].path+"/"+rooms[room].name+"-"+rooms[room].timestamp);setMainTab("chat")}}/>) - } - setRoomData(roomArr) - setRoomLoading(false) - }) - }) - }, []) + fetch('/api/user').then((res) => res.json()) + .then((user) => { + onValue(ref(database, '/users/'+user.uid+'/rooms'),(snapshot) => { + setRoomLoading(true) + var rooms = snapshot.val() + setMyRoomsObj(rooms) + var roomArr = [] + var markerArr = markers + for (var room in rooms) { + var newRoom = + markerArr.push() + roomArr.push(newRoom) + } + setMarkers(markerArr) + setRoomData(roomArr) + setRoomLoading(false) + }) + }) +}, []) - - const [location, setLocation] = useState(null); - const [loadingLoc, setLoadingLoc] = useState(true) - const [nearby, setNearby] = useState(null); - const [loadingNearby, setLoadingNearby] = useState(true); + // Grabs the user location useEffect(() => { if('geolocation' in navigator) { // Retrieve latitude & longitude coordinates from `navigator.geolocation` Web API @@ -218,12 +247,16 @@ function Home() { setLoadingLoc(false) var nearbyArr = [] var path = String(coords.latitude.toFixed(2)).replace(".","")+"/"+String(coords.longitude.toFixed(2)).replace(".","") + var markersArr = markers get(ref(database, `/rooms/${path}`)).then((snapshot) => { if (snapshot.exists()) { var data = snapshot.val() for (var room in data) { - nearbyArr.push( {setChatRoom(data[room].path+"/"+data[room].name+"-"+data[room].timestamp);setMainTab("chat")}}/>) + nearbyArr.push() + // TODO: RANDOM LAST DIGIT TO MOVE AROUND THE MAP + markersArr.push() } + setMarkers(markersArr) setLoadingNearby(false) setNearby(nearbyArr) } else { @@ -234,25 +267,204 @@ function Home() { } }, []); + // Grab list of all users + useEffect(() => { + get(ref(database, `/users`)).then((snapshot) => { + setUsers(snapshot.val()) + }) + + }, []); + + // Dont Double Send Leaving Message + useEffect(() => { + if (myRoomsObj && chatRoomObj) { + var roomName = chatRoomObj.name+"-"+chatRoomObj.timestamp + if (myRooms != null && roomName in myRoomsObj) { + // its in there + setIsMyRoom(true) + } else { + // its not in there + setIsMyRoom(false) + } + } + }, [chatRoomObj]) + + // CreateRoom Module for Sidebar Create Tab + function CreateRoom({loc}) { + var { register, control, reset, handleSubmit} = useForm() + + function createRoom(data) { + reset() + var path = String(loc.latitude.toFixed(2)).replace(".","")+"/"+String(loc.longitude.toFixed(2)).replace(".","") + var timestamp = new Date().getTime() + var payload = { + name: data.name, + description: data.description, + timestamp: timestamp, + latitude: loc.latitude, + longitude: loc.longitude, + path: path + } + set(ref(database,`/rooms/${path}/${data.name}-${timestamp}`), payload) + } + + return ( +
+
+ +
+
+ Creating room near ({loc.latitude.toFixed(2)}, {loc.longitude.toFixed(2)}) +
+ +
+
+ ) + } + + // Selects chat room + function selectChatRoom(roomName, roomObj) { + fetch('/api/user').then((res) => res.json()) + .then((user) => { + // Path of chatroom + var path = roomObj.path+"/"+roomObj.name+"-"+roomObj.timestamp + + setChatRoomObj(roomObj) + + // Send entered message + var payload = { + body: "entered", + user: user.username, + isSystem: true, + timestamp: new Date().getTime() + } + set(ref(database,`/rooms/${path}/chats/${new Date().getTime()}-${user.username}`), payload) + + // Code for Room Data + set(ref(database, `/rooms/${path}/users/online/${user.uid}`), user) + onValue(ref(database, `/rooms/${path}`), (snapshot) => { + + setChatRoomOnline(null) + setChatroomUsers(null) + + // Active users list + if (snapshot.val().hasOwnProperty("users") && snapshot.val().users.hasOwnProperty("online")) { + var activeUsers = [] + var activeUsersJSON = snapshot.val().users.online + for (var user in activeUsersJSON) + activeUsers.push() + setChatRoomOnline(activeUsers) + } + + // Users who added to "my rooms" + console.log(snapshot.val().hasOwnProperty("users") && snapshot.val().users.hasOwnProperty("all")) + if (snapshot.val().hasOwnProperty("users") && snapshot.val().users.hasOwnProperty("all")) { + setChatroomUsersLoading(true) + var allUsers = [] + var allUsersJSON = snapshot.val().users.all + for (var user in allUsersJSON) + allUsers.push() + setChatroomUsers(allUsers) + setChatroomUsersLoading(false) + + } + + }) + setMainTab("chat") + setAlreadyLeft(false) + }) + } + + // Closes chat room + function closeChatRoom(roomObj) { + fetch('/api/user').then((res) => res.json()) + .then((user) => { + var path = roomObj.path+"/"+roomObj.name+"-"+roomObj.timestamp + var payload = { + body: "left", + user: user.username, + isSystem: true, + timestamp: new Date().getTime() + } + set(ref(database,`/rooms/${path}/chats/${new Date().getTime()}-${user.username}`), payload) + remove(ref(database, `/rooms/${path}/users/online/${user.uid}`)) + setChatRoomObj(null) + setAlreadyLeft(true) + setMainTab("home") + }) + } + + // Adds room to myRooms + function addToMyRooms() { + fetch('/api/user').then((res) => res.json()) + .then((user) => { + set(ref(database,`/users/${user.uid}/rooms/${chatRoomObj.name}-${chatRoomObj.timestamp}`), { + name: chatRoomObj.name, + path: chatRoomObj.path, + timestamp: chatRoomObj.timestamp, + description: chatRoomObj.description, + longitude: chatRoomObj.longitude, + latitude: chatRoomObj.latitude, + }) + var path = chatRoomObj.path+"/"+chatRoomObj.name+"-"+chatRoomObj.timestamp + set(ref(database, `/rooms/${path}/users/all/${user.uid}`), user) + }) + setIsMyRoom(true) + } + + // Deletes saved room from myRooms + function removeFromMyRooms() { + fetch('/api/user').then((res) => res.json()) + .then((user) => { + var path = chatRoomObj.path+"/"+chatRoomObj.name+"-"+chatRoomObj.timestamp + remove(ref(database,`/users/${user.uid}/rooms/${chatRoomObj.name}-${chatRoomObj.timestamp}`)) + remove(ref(database, `/rooms/${path}/users/all/${user.uid}`)) + }) + setIsMyRoom(false) + } + + // Fires to tell other uses that you are leaving the room + useBeforeunload(() => { + fetch('/api/user').then((res) => res.json()) + .then((user) => { + if (chatRoomObj && mainTab == "chat") { + var payload = { + body: "left", + user: user.username, + isSystem: true, + timestamp: new Date().getTime() + } + set(ref(database,`/rooms/${chatRoomObj.path+"/"+chatRoomObj.name+"-"+chatRoomObj.timestamp}/chats/${new Date().getTime()}-${user.username}`), payload) + remove(ref(database, `/rooms/${chatRoomObj.path+"/"+chatRoomObj.name+"-"+chatRoomObj.timestamp}/users/online/${user.uid}`)) + } + }) + }); return (
+ {/* Left Side of Page */}
+ {/* Header */} + {/* Main Page Section */}
- {(mainTab == "home" && !loadingLoc) && } - {(mainTab == "home" && loadingLoc) && } - {mainTab == "chat" && } + {(mainTab == "home" && !loadingLoc) && } + {(mainTab == "home" && loadingLoc) && } + {mainTab == "chat" && }
+ {/* Sidebar (Right Side of Page) */} + {mainTab == "home" &&
@@ -262,7 +474,7 @@ function Home() {
{setTab("create")}}>Create
- {tab == "nearby" &&
+ {(tab == "nearby") &&
{(!nearby && !loadingNearby) &&
No Nearby Rooms
Create One?
} {loadingNearby &&
Loading...
} @@ -279,7 +491,41 @@ function Home() { {(tab == "create" && !loadingLoc) && } {(tab == "create" && loadingLoc) &&
Loading...
}
+
} + {(mainTab == "chat") && +
+
+
+
+ +
+
+ {chatRoomObj.name}
+ {chatRoomObj.description} +
+
+
+
+ Online Members +
+ {chatroomOnline} +
+
+
+ All Members +
+ {!chatroomUsersLoading && chatroomUsers} +
+
+ } + {(mainTab == "profile") && +
+
+ Profile +
+
+ }
) } diff --git a/frontend-next/src/app/login/page.js b/frontend-next/src/app/login/page.js index 52637ce..96f2a1a 100644 --- a/frontend-next/src/app/login/page.js +++ b/frontend-next/src/app/login/page.js @@ -35,7 +35,7 @@ function Login() {