diff --git a/frontend-next/src/app/app/page.js b/frontend-next/src/app/app/page.js
index 635df25..521d193 100644
--- a/frontend-next/src/app/app/page.js
+++ b/frontend-next/src/app/app/page.js
@@ -51,8 +51,6 @@ function Home() {
window.location.href = "/onboarding";
}
});
- } else {
- window.location.href = "/login"
}
}, [authUser])
diff --git a/frontend-next/src/app/chat/page.js b/frontend-next/src/app/chat/page.js
index c9c13f4..9421a3c 100644
--- a/frontend-next/src/app/chat/page.js
+++ b/frontend-next/src/app/chat/page.js
@@ -47,8 +47,6 @@ function Chat() {
window.location.href = "/onboarding";
}
});
- } else {
- window.location.href = "/login"
}
}, [authUser])
diff --git a/frontend-next/src/app/dm/layout.js b/frontend-next/src/app/dm/layout.js
new file mode 100644
index 0000000..fc1a7b4
--- /dev/null
+++ b/frontend-next/src/app/dm/layout.js
@@ -0,0 +1,17 @@
+import { Inter } from "next/font/google";
+import "../globals.css";
+
+const inter = Inter({ subsets: ["latin"] });
+
+export const metadata = {
+ title: "ChatMaps: DM",
+ description: "ChatMaps: Social Media for College Students",
+};
+
+export default function RootLayout({ children }) {
+ return (
+
+
{children}
+
+ );
+}
diff --git a/frontend-next/src/app/dm/page.js b/frontend-next/src/app/dm/page.js
new file mode 100644
index 0000000..d2487a5
--- /dev/null
+++ b/frontend-next/src/app/dm/page.js
@@ -0,0 +1,139 @@
+"use client";
+// System Imports
+import Drawer from '@mui/material/Drawer';
+import { useState, useEffect } from "react";
+
+// Firebase Imports
+import { auth, database } from "../../../firebase-config";
+import { ref, onValue, set, onDisconnect } from "firebase/database";
+import { useAuthState } from "react-firebase-hooks/auth"
+
+// Component Imports
+import { Header } from "../../components/app/header";
+import { DMRoom } from "../../components/app/friends/page";
+import { Sidebar } from "../../components/app/sidebar/dm";
+import {useWindowSize} from "../../components/app/datatypes";
+
+
+/**
+ * DM Page
+ * @returns {Object} Chat Page
+ */
+function Chat() {
+ // State variables for chat page
+ const [user, setUser] = useState(null); // user data
+ const [chatRoomObj, setChatRoomObj] = useState(null); // Current chatroom object
+ const [doneLoading, setDoneLoading] = useState(false) // is the page done loading or not
+ const [authUser] = useAuthState(auth) // auth user object (used to obtain other user object)
+ const [drawerOpen, setDrawerOpen] = useState(true); // drawer open state
+
+ var windowSize = useWindowSize()
+ useEffect(() => {
+ if (windowSize.width < 767) {
+ setDrawerOpen(false)
+ } else {
+ setDrawerOpen(true)
+ }
+ }, [windowSize])
+
+ // Authentication Verification / Redirection if Profile Data not Filled out
+ useEffect(() => {
+ if (authUser) {
+ onValue(ref(database, `users/${authUser.uid}`), (userData) => {
+ userData = userData.val();
+ if (userData) {
+ setUser(userData);
+ } else {
+ window.location.href = "/onboarding";
+ }
+ });
+ }
+ }, [authUser])
+
+ // Users URL params to load proper chatroom, then logs the user into that room
+ useEffect(() => {
+ if (user) {
+ const searchParams = new URLSearchParams(document.location.search);
+ var path = searchParams.get("dm")
+
+ /*// Send entered message
+ var payload = {
+ body: "entered",
+ user: user.username,
+ isSystem: true,
+ timestamp: new Date().getTime(),
+ uid: user.uid,
+ };
+ set(
+ ref(
+ database,
+ `/rooms/${path}/chats/${new Date().getTime()}-${user.username}`
+ ),
+ payload
+ );*/
+
+ // Add user to online for room
+ set(ref(database, `/dms/${path}/users/online/${user.uid}`), user)
+
+ // Removes user from room on disconnect (reload, window close, internet lost)
+ onDisconnect(ref(database, `/dms/${path}/users/online/${user.uid}`)).remove()
+
+ // Sends leaving message on disconnect (Timestamp function used due to new onDisconnect stuff)
+ /*someRef = ref(database, `/rooms/${path}/chats/${new Date().getTime()}-${user.username}`)
+ onDisconnect(someRef).set({
+ body: "left",
+ user: user.username,
+ isSystem: true,
+ timestamp: serverTimestamp(),
+ uid: user.uid,
+ })*/
+
+ onValue(ref(database, `/dms/${path}`), (roomData) => {
+ roomData = roomData.val();
+ setChatRoomObj(roomData)
+ if (!doneLoading) {
+ setDoneLoading(true)
+ }
+ })
+ }
+ }, [user]);
+
+ return (
+
+ {(authUser && doneLoading) && (
+
+ {/* Left Side of Page */}
+
+ {/* Header */}
+
{setDrawerOpen(!drawerOpen)}}
+ />
+ {/* Main Page Section */}
+
+
+
+
+ {/* Sidebar (Right Side of Page) */}
+
767? "persistent": "temporary"} onClose={() => {setDrawerOpen(false)}} sx={{
+ width: windowSize.width > 767? 400: "80%",
+ marginTop: 10,
+ flexShrink: 0,
+ '& .MuiDrawer-paper': {
+ width: windowSize.width > 767? 400: "80%",
+ borderLeft: 0,
+ },
+ }}>
+
+
+
+
+
+ )}
+
+ );
+}
+
+export default Chat;
\ No newline at end of file
diff --git a/frontend-next/src/app/user/page.js b/frontend-next/src/app/user/page.js
index fc45735..f56df4f 100644
--- a/frontend-next/src/app/user/page.js
+++ b/frontend-next/src/app/user/page.js
@@ -15,6 +15,9 @@ import { Interest } from "../../components/app/profile/Interest";
// Header Import
import { Header } from "../../components/app/header";
+// Friend Import
+import { addFriend } from "../../components/app/friends/friends";
+
/**
* User Profile Page
* @returns {Object} - User Profile Page
@@ -26,6 +29,8 @@ function UserProfile() {
const [userInterestArray, setUserInterestArray] = useState(null); // Array of user's interests
const [userRoomsArray, setUserRoomsArray] = useState(null); // Array of user's rooms
const [isOwner, setIsOwner] = useState(false); // Determines if user is owner of profile
+ const [friends, setFriends] = useState(false); // is user a friend?
+ const [isPending, setPending] = useState(false); // is friend request pending?
// Handles Edit State in Component, shares useState with ProfileEdit
const [isEditing, setIsEditing] = useState(false);
@@ -51,9 +56,6 @@ function UserProfile() {
window.location.href = "/onboarding";
}
});
- } else {
- setIsAuthenticated(false);
- window.location.href = "/login";
}
});
}, []);
@@ -91,6 +93,18 @@ function UserProfile() {
});
}, []);
+ useEffect(() => {
+ if (user && profileData) {
+ console.log(user.uid in profileData.friends.requests)
+ if ("friends" in user) {
+ profileData.uid in user.friends.friends ? setFriends(true) : setFriends(false);
+ }
+ if ("friends" in profileData) {
+ user.uid in profileData.friends.requests ? setPending(true) : setPending(false);
+ }
+ }
+ }, [user, profileData]);
+
return (
{isAuthenticated && (
@@ -128,11 +142,13 @@ function UserProfile() {
Edit Profile
)}
- {!isOwner && (
-
- Add Friend
-
+ {(!isOwner && !friends) && (
+
)}
+ {(!isOwner && friends ) && (
Friends
)}
)}
diff --git a/frontend-next/src/components/app/friends/dm.js b/frontend-next/src/components/app/friends/dm.js
new file mode 100644
index 0000000..36c07e3
--- /dev/null
+++ b/frontend-next/src/components/app/friends/dm.js
@@ -0,0 +1,55 @@
+// Firebase Imports
+import { database } from "../../../../firebase-config"
+import { ref, set, get } from "firebase/database";
+
+import ChatIcon from '@mui/icons-material/Chat';
+
+
+export function openDM(user, uid) {
+ get(ref(database, `dms/`)).then((snapshot) => {
+ var dmsList = snapshot.val();
+ for (var dmRoom in dmsList) {
+ if (user.uid in dmsList[dmRoom].UIDs && uid in dmsList[dmRoom].UIDs) {
+ window.location.href = `/dm?dm=${dmRoom}`
+ }
+ }
+ createDM(user, uid)
+ window.location.href = `/dm?dm=${user.uid}-${uid}`
+ });
+}
+
+
+export function createDM(user, uid) {
+ set(ref(database, `dms/${user.uid}-${uid}`), {
+ initUID: user.uid,
+ targetUID: uid,
+ room: user.uid + "-" + uid,
+ UIDs: [user.uid, uid]
+ })
+}
+
+/**
+ *
+ * @param {JSON} friendObj - Friend Object (user)
+ * @returns DM Component
+ */
+export function DM({user,friendObj}) {
+ return (
+
+
+
+ {openDM(user,friendObj.uid)}}/>
+
+
+
{openDM(user,friendObj.uid)}}>
+
+
+
{friendObj.firstName} {friendObj.lastName}
+
@{friendObj.username}
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/frontend-next/src/components/app/friends/friends.js b/frontend-next/src/components/app/friends/friends.js
new file mode 100644
index 0000000..db01bd9
--- /dev/null
+++ b/frontend-next/src/components/app/friends/friends.js
@@ -0,0 +1,101 @@
+import Link from 'next/link'
+
+// Icons
+import ChatIcon from '@mui/icons-material/Chat';
+
+// Firebase Imports
+import { database } from "../../../../firebase-config"
+import { ref, set } from "firebase/database";
+
+import { openDM } from './dm';
+
+// Icons
+import CheckIcon from '@mui/icons-material/Check';
+import CloseIcon from '@mui/icons-material/Close';
+
+/**
+ * Send a friend request to a user
+ * @param {JSON} user - User Object
+ * @param {JSON} uid - User ID of the user to send a friend request to
+ */
+export function addFriend(user, uid) {
+ // Add to user's friend requests
+ set(ref(database, `users/${uid}/friends/requests/${user.uid}`), {
+ uid: user.uid
+ })
+}
+
+/**
+ *
+ * @param {JSON} friendObj - Friend Object (user)
+ * @returns Friend Component
+ */
+export function Friend({user,friendObj}) {
+ return (
+
+
+
+ {openDM(user,friendObj.uid)}}/>
+
+
+
+
+
+
{friendObj.firstName} {friendObj.lastName}
+
@{friendObj.username}
+
+
+
+
+
+ )
+}
+
+/**
+ *
+ * @prop {JSON} user - User Object
+ * @prop {JSON} requestingUser - User Object of the user requesting to be friends
+ * @returns
+ */
+export function FriendRequest({user, requestingUser}) {
+ /**
+ * Accepts Friend Request
+ * @param {JSON} user - User Object
+ * @param {JSON} uid - User ID of the user who sent the friend request
+ */
+ function acceptRequest(user, uid) {
+ // Add to user's friends
+ set(ref(database, `users/${user.uid}/friends/friends/${uid}`), {
+ uid: uid
+ })
+ removeRequest(user, uid)
+ }
+ /**
+ * Removes Friend Request
+ * @param {JSON} user - User Object
+ * @param {JSON} uid - User ID of the user who sent the friend request
+ */
+ function removeRequest(user, uid) {
+ // Remove from user's friend requests
+ set(ref(database, `users/${user.uid}/friends/requests/${uid}`), null)
+ }
+ return (
+
+
+
+ {acceptRequest(user, requestingUser.uid)}}/>
+ {removeRequest(user, requestingUser.uid)}}/>
+
+
+
+
+
+
{requestingUser.firstName} {requestingUser.lastName}
+
@{requestingUser.username}
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/frontend-next/src/components/app/friends/page.js b/frontend-next/src/components/app/friends/page.js
new file mode 100644
index 0000000..c9698a8
--- /dev/null
+++ b/frontend-next/src/components/app/friends/page.js
@@ -0,0 +1,87 @@
+// Dependency Imports
+import { Form, useForm } from "react-hook-form";
+
+// Firebase Imports
+import { ref, set } from "firebase/database";
+import { database } from "../../../../firebase-config";
+
+// Component Imports
+import { Chat, SystemMessage } from "../datatypes";
+
+// Icons
+import SendIcon from '@mui/icons-material/Send';
+
+/**
+ * Chat Room Component
+ * @prop {JSON} roomObj - Room Object
+ * @prop {JSON} user - User Object
+ * @returns {Object} - Chat Room Component
+ */
+export function DMRoom({ roomObj, user }) {
+ var { register, control, reset, handleSubmit } = useForm();
+
+ // Message updater
+ var chatsArr = [];
+ var messages = roomObj.chats;
+ for (var message in messages) {
+ if (messages[message].isSystem) {
+ chatsArr.push(
+
+ );
+ } else {
+ chatsArr.push(
+
+ );
+ }
+ }
+ var chats = chatsArr.reverse();
+
+ /**
+ * Send Message in Chatroom
+ * @param {JSON} data - Message data to send (from form)
+ * @returns {void}
+ */
+ function sendMessage(data) {
+ reset();
+ var payload = {
+ body: data.message,
+ user: user.username,
+ uid: user.uid,
+ isSystem: false,
+ timestamp: new Date().getTime(),
+ };
+ set(
+ ref(
+ database,
+ `/dms/${roomObj.room}/chats/${new Date().getTime()}-${user.username}`
+ ),
+ payload
+ );
+ }
+
+ if (!chats) return No Chats
;
+ return (
+
+ );
+}
diff --git a/frontend-next/src/components/app/header.js b/frontend-next/src/components/app/header.js
index 3120d64..aa860c8 100644
--- a/frontend-next/src/components/app/header.js
+++ b/frontend-next/src/components/app/header.js
@@ -119,7 +119,7 @@ export function Header({mainTab,chatRoomObj,user,sidebarControl}) {
)}
- {mainTab == "chat" && (
+ {(mainTab == "chat" || mainTab == "dm" ) && (
);
+ var chatroomOnline = activeUsers
+ }
+
+ useEffect(() => {
+ if (user) {
+ // Profile Information
+ if (user.uid == chatRoomObj.UIDs[0]) {
+ var profileUID = chatRoomObj.UIDs[1]
+ } else {
+ var profileUID = chatRoomObj.UIDs[0]
+ }
+
+ get(ref(database, `users/${profileUID}`)).then((snapshot) => {
+ var profileData = snapshot.val()
+ setProfileData(profileData)
+ })
+ }
+ }, [user])
+
+ return (
+
+ {profileData && (
+
+
+
+
+
+
{profileData.firstName} {profileData.lastName}
+ @{profileData.username}
+
+
+
+
In The Chat
+ {chatroomOnline}
+
+
+
+ )}
+
+ );
+ }
+
\ No newline at end of file
diff --git a/frontend-next/src/components/app/sidebar/home.js b/frontend-next/src/components/app/sidebar/home.js
index 74ad684..f7744c8 100644
--- a/frontend-next/src/components/app/sidebar/home.js
+++ b/frontend-next/src/components/app/sidebar/home.js
@@ -12,6 +12,11 @@ import { ref, set, get } from "firebase/database";
// Component Imports
import { ChatRoomSidebar } from "../datatypes";
+// Friend Imports (TEMP)
+import { Friend, FriendRequest } from "../friends/friends";
+
+// DM Imports
+import { DM } from "../friends/dm";
/**
* Create Room Component for /app Sidebar
@@ -85,6 +90,9 @@ function classNames(...classes) {
export function Sidebar({user,location,loadingLoc}) {
const [nearbyArr, setNearbyArr] = useState([])
const [nearbyArrReady, setNearbyArrReady] = useState(false)
+ const [friends, setFriends] = useState([])
+ const [friendRequests, setFriendRequests] = useState(null)
+ const [dms, setDMs] = useState(null)
// Add myRooms to Sidebar
var myRoomArr = [];
for (var room in user.rooms) {
@@ -125,6 +133,50 @@ export function Sidebar({user,location,loadingLoc}) {
}
}, [location])
+ useEffect(() => {
+ if (user && user.friends) {
+ get(ref(database, "/users/")).then((snapshot) => {
+ var users = snapshot.val();
+ var friends = [];
+ for (var friend in user.friends.friends) {
+ friends.push( );
+ }
+ setFriends(friends);
+ });
+
+ var requestArr = [];
+ for (var request in user.friends.requests) {
+ get(ref(database, `/users/${request}`)).then((snapshot) => {
+ requestArr.push( );
+ });
+ }
+ setFriendRequests(requestArr);
+ } else {
+ setFriends(No Friends
);
+ setFriendRequests(No Friend Requests
);
+ }
+
+ get(ref(database, `/dms`)).then((snapshot) => {
+ var dmsList = snapshot.val();
+ var dmArr = [];
+ for(var dmRoom in dmsList) {
+ if (user.uid == dmsList[dmRoom].UIDs[0]) {
+ get(ref(database, `/users/${dmsList[dmRoom].UIDs[1]}`)).then((snapshot) => {
+ dmArr.push();
+ })
+ } else if (user.uid == dmsList[dmRoom].UIDs[1]) {
+ get(ref(database, `/users/${dmsList[dmRoom].UIDs[1]}`)).then((snapshot) => {
+ dmArr.push();
+ })
+ }
+ }
+ if (dmArr.length == 0) {
+ dmArr.push(No DMs
);
+ }
+ setDMs(dmArr);
+ })
+ }, [user])
+
return (
@@ -151,6 +203,28 @@ export function Sidebar({user,location,loadingLoc}) {
? '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'
)}>Create
+
+ 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'
+ )} defaultIndex={1}>DMs
+
+ 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'
+ )} defaultIndex={1}>Friends
+
+ 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'
+ )} defaultIndex={1}>Requests
+
@@ -173,6 +247,15 @@ export function Sidebar({user,location,loadingLoc}) {
{!loadingLoc && }
{loadingLoc && Loading...
}
+
+ {dms}
+
+
+ {friends}
+
+
+ {friendRequests}
+