Restructing Changes + Bugfixes #51

Merged
ClarkLach merged 7 commits from clarkl-mar29 into main 2024-03-30 15:26:15 -09:00
29 changed files with 1365 additions and 1153 deletions
+2
View File
@@ -0,0 +1,2 @@
{
}
+4 -2
View File
@@ -1,9 +1,9 @@
![](/frontend-next/public/logos/logo_transparent.png)
Main repo for ChatMaps, our COS420 Project.
ChatMaps is a web-based social networking service that allows users to connect to others in their local geographic area. It will implement an interactable mapping utility to show general user locations relative to other users, as well as a chat room feature that allows users to start public conversations based on a specified topic. ChatMaps is primarily intended for use in densely populated areas, such as college campuses or metropolitan areas, so people of similar interests can start conversations. The goal of this project is to create a web app that plots locations, gives a radius of the local area, and connects users into different topic-based chat rooms.
ChatMaps is a web-based social networking service that allows users to connect to others in their local geographic area. It implements an interactable mapping utility to show general user locations relative to other users, as well as a chat room feature that allows users to start public conversations based on a specified topic. ChatMaps is primarily intended for use in densely populated areas, such as college campuses or metropolitan areas, so people of similar interests can start conversations. The goal of this project is to create a web app that plots locations, gives a radius of the local area, and connects users into different topic-based chat rooms.
This service will implement user login and profiles, allowing users to add each other as friends and start private conversations. There will be several default chat rooms of varying topics, but users will also have the ability to create their own topics that will be viewable by other users. For example, a user at the University of Maine could create a joinable chat room titled “COS420”, which would be visible to others near this campus.
This service implements user login and profiles, allowing users to add each other as friends and start private conversations. There are several default chat rooms of varying topics, but users also have the ability to create their own topics that will be viewable by other users. For example, a user at the University of Maine could create a joinable chat room titled “COS420”, which would be visible to others near this campus.
This app shares some similarities to other social networks that implement location-based content. ChatMaps novel approach is to utilize user location to facilitate real-time communication with others within a given radius.
@@ -15,6 +15,8 @@ A local version can be run with:
cd frontend-next/
npm install
npm run build
npm run dev
+1 -3
View File
@@ -11,9 +11,7 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
{children}
</body>
<body className={inter.className}>{children}</body>
</html>
);
}
+201 -160
View File
@@ -4,9 +4,8 @@ import { useState, useEffect } from "react";
import { auth, database } from "../../../firebase-config";
import { ref, onValue, set, remove, get } from "firebase/database";
import { useBeforeunload } from "react-beforeunload";
import {Marker} from "pigeon-maps";
import {onAuthStateChanged} from "firebase/auth"
import { Marker } from "pigeon-maps";
import { onAuthStateChanged } from "firebase/auth";
// Refactored Component Imports
// Data Structure Imports
@@ -20,7 +19,7 @@ import { MainTabChatRoom } from "../../components/app/main_tab/chat";
import { MainTabHome } from "../../components/app/main_tab/home";
// Sidebar Imports
import {Home_Sidebar} from "../../components/app/sidebar/home";
import { Home_Sidebar } from "../../components/app/sidebar/home";
import { Chat_Sidebar } from "../../components/app/sidebar/chat";
import { Profile_Sidebar } from "../../components/app/sidebar/profile";
@@ -32,100 +31,111 @@ function Home() {
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 [roomData, setRoomData] = useState(null); // Current user saved rooms list
const [isRoomLoading, setIsRoomLoading] = 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 [chatroomOnline, setChatroomOnline] = useState(null); // holds online users
const [chatroomUsers, setChatroomUsers] = useState(null); // holds all chatroom users
const [chatroomUsersLoading, setChatroomUsersLoading] = useState(true);
const [markers, setMarkers] = useState([]);
const [isAuthenticated, setAuth] = useState(false)
const [user, setUser] = useState(null)
const [usingSearchParams, setUsingSearchParams] = useState(true)
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState(null);
const [usingSearchParams, setUsingSearchParams] = useState(true);
useEffect(() => {
const searchParams = new URLSearchParams(document.location.search);
var roomSwitch = null
var roomSwitch = null;
if (searchParams.has("room") && usingSearchParams && user) {
roomSwitch = searchParams.get("room")
setUsingSearchParams(false)
get(ref(database, `rooms/${searchParams.get("room")}`)).then((snapshot) => {
selectChatRoom(snapshot.val())
});
roomSwitch = searchParams.get("room");
setUsingSearchParams(false);
get(ref(database, `rooms/${searchParams.get("room")}`)).then(
(snapshot) => {
selectChatRoom(snapshot.val());
}
);
}
}, [user])
}, [user]);
// Authentication
useEffect(() => {
onAuthStateChanged(auth, (user) => {
if (user) {
get(ref(database, `users/${user.uid}`))
.then((userData) => {
userData = userData.val()
get(ref(database, `users/${user.uid}`)).then((userData) => {
userData = userData.val();
if (userData) {
setUser(userData)
setAuth(true)
setUser(userData);
setIsAuthenticated(true);
} else {
window.location.href = "/onboarding"
window.location.href = "/onboarding";
}
})
});
} else {
setAuth(false)
window.location.href = "/login"
setIsAuthenticated(false);
window.location.href = "/login";
}
})
}, [])
});
}, []);
// Grabs user data, saves to user, then lists the users saved rooms
useEffect(() => {
if (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 = (
<ChatRoomSidebar
roomObj={rooms[room]}
key={rooms[room].timestamp}
click={selectChatRoom}
/>
);
markerArr.push(
<Marker
width={30}
anchor={[rooms[room].latitude, rooms[room].longitude]}
color="blue"
/>
);
roomArr.push(newRoom);
}
setMarkers(markerArr);
setRoomData(roomArr);
setRoomLoading(false);
});
if (user) {
onValue(ref(database, "/users/" + user.uid + "/rooms"), (snapshot) => {
setIsRoomLoading(true);
var rooms = snapshot.val();
setMyRoomsObj(rooms);
var roomArr = [];
var markerArr = markers;
for (var room in rooms) {
var newRoom = (
<ChatRoomSidebar
roomObj={rooms[room]}
key={rooms[room].timestamp}
click={selectChatRoom}
/>
);
markerArr.push(
<Marker
width={30}
anchor={[rooms[room].latitude, rooms[room].longitude]}
color="blue"
onClick={() =>
(window.location.href =
"/app?room=" +
rooms[room].path +
"/" +
rooms[room].name +
"-" +
rooms[room].timestamp)
}
/>
);
roomArr.push(newRoom);
}
},[user]);
setMarkers(markerArr);
setRoomData(roomArr);
setIsRoomLoading(false);
});
}
}, [user]);
// Grabs the user location
useEffect(() => {
if ("geolocation" in navigator && user) {
// Retrieve latitude & longitude coordinates from `navigator.geolocation` Web API
navigator.geolocation.getCurrentPosition(({ coords }) => {
setLocation(coords)
setLoadingLoc(false)
var path = String(coords.latitude.toFixed(2)).replace(".","")+"/"+String(coords.longitude.toFixed(2)).replace(".","")
var markersArr = markers
setLocation(coords);
setLoadingLoc(false);
var path =
String(coords.latitude.toFixed(2)).replace(".", "") +
"/" +
String(coords.longitude.toFixed(2)).replace(".", "");
var markersArr = markers;
onValue(ref(database, `/rooms/${path}`), (snapshot) => {
var nearbyArr = []
var nearbyArr = [];
if (snapshot.exists()) {
var data = snapshot.val();
for (var room in data) {
@@ -138,6 +148,15 @@ function Home() {
width={30}
anchor={[data[room].latitude, data[room].longitude]}
color="blue"
onClick={() =>
(window.location.href =
"/app?room=" +
data[room].path +
"/" +
data[room].name +
"-" +
data[room].timestamp)
}
/>
);
}
@@ -150,13 +169,13 @@ function Home() {
});
});
}
},[user]);
}, [user]);
// Dont Double Send Leaving Message
useEffect(() => {
if (myRoomsObj && chatRoomObj) {
var roomName = chatRoomObj.name + "-" + chatRoomObj.timestamp;
if (myRooms != null && roomName in myRoomsObj) {
if (roomData != null && roomName in myRoomsObj) {
// its in there
setIsMyRoom(true);
} else {
@@ -168,98 +187,98 @@ function Home() {
// Selects chat room
function selectChatRoom(roomObj) {
// Path of chatroom
var path = roomObj.path + "/" + roomObj.name + "-" + roomObj.timestamp;
// Path of chatroom
var path = roomObj.path + "/" + roomObj.name + "-" + roomObj.timestamp;
setChatRoomObj(roomObj);
setChatRoomObj(roomObj);
// 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
);
// 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
);
// Code for Room Data
set(ref(database, `/rooms/${path}/users/online/${user.uid}`), user);
onValue(ref(database, `/rooms/${path}`), (snapshot) => {
setChatRoomOnline(null);
setChatroomUsers(null);
// 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(<Member memberObj={activeUsersJSON[user]} />);
setChatRoomOnline(activeUsers);
}
// 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(<Member memberObj={activeUsersJSON[user]} />);
setChatroomOnline(activeUsers);
}
// Users who added to "my rooms"
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(<Member memberObj={allUsersJSON[user]} />);
setChatroomUsers(allUsers);
setChatroomUsersLoading(false);
}
});
setMainTab("chat");
// Users who added to "my rooms"
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(<Member memberObj={allUsersJSON[user]} />);
setChatroomUsers(allUsers);
setChatroomUsersLoading(false);
}
});
setMainTab("chat");
}
// Fires to tell other uses that you are leaving the room
useBeforeunload(() => {
if (chatRoomObj && mainTab == "chat") {
var payload = {
body: "left",
user: user.username,
isSystem: true,
timestamp: new Date().getTime(),
uid: user.uid
};
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/${userID}`
)
);
}
if (chatRoomObj && mainTab == "chat") {
var payload = {
body: "left",
user: user.username,
isSystem: true,
timestamp: new Date().getTime(),
uid: user.uid,
};
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/${userID}`
)
);
}
});
return (
@@ -269,32 +288,54 @@ function Home() {
{/* Left Side of Page */}
<div className="col-span-3 h-dvh">
{/* Header */}
<Header mainTab={mainTab} isMyRoom={isMyRoom} chatRoomObj={chatRoomObj} setChatRoomObj={setChatRoomObj} setMainTab={setMainTab} setIsMyRoom={setIsMyRoom} user={user}/>
<Header
mainTab={mainTab}
isMyRoom={isMyRoom}
chatRoomObj={chatRoomObj}
setChatRoomObj={setChatRoomObj}
setMainTab={setMainTab}
setIsMyRoom={setIsMyRoom}
user={user}
/>
{/* Main Page Section */}
<div className="mr-2 h-[calc(100%-110px)]">
{mainTab == "home" && !loadingLoc && (
<MainTabHome loc={location} markers={markers} user={user}/>
<MainTabHome loc={location} markers={markers} user={user} />
)}
{mainTab == "home" && loadingLoc && (
<MainTabHome loc={null} markers={markers} user={user}/>
<MainTabHome loc={null} markers={markers} user={user} />
)}
{mainTab == "chat" && (
<MainTabChatRoom roomObj={chatRoomObj} user={user} />
)}
{mainTab == "chat" && <MainTabChatRoom roomObj={chatRoomObj} user={user}/>}
</div>
</div>
{/* Sidebar (Right Side of Page) */}
{mainTab == "home" && (
<Home_Sidebar tab={tab} nearby={nearby} loadingNearby={loadingNearby} setTab={setTab} isRoomLoading={isRoomLoading} myRooms={myRooms} loadingLoc={loadingLoc} location={location}/>
<Home_Sidebar
tab={tab}
nearby={nearby}
loadingNearby={loadingNearby}
setTab={setTab}
isRoomLoading={isRoomLoading}
myRooms={roomData}
loadingLoc={loadingLoc}
location={location}
/>
)}
{mainTab == "chat" && (
<Chat_Sidebar chatRoomObj={chatRoomObj} chatroomOnline={chatroomOnline} chatroomUsersLoading={chatroomUsersLoading} chatroomUsers={chatroomUsers} setTab={setTab}/>
)}
{mainTab == "profile" && (
<Profile_Sidebar/>
<Chat_Sidebar
chatRoomObj={chatRoomObj}
chatroomOnline={chatroomOnline}
chatroomUsersLoading={chatroomUsersLoading}
chatroomUsers={chatroomUsers}
/>
)}
{mainTab == "profile" && <Profile_Sidebar />}
</div>
)}
</div>
</div>
);
}
export default Home;
export default Home;
+1 -1
View File
@@ -5,7 +5,7 @@
body {
background-color: aliceblue;
text-align: center;
text-wrap:pretty;
text-wrap: pretty;
}
button {
+2 -4
View File
@@ -5,15 +5,13 @@ const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "ChatMaps",
description: "ChatMaps: Social Media for College Students"
description: "ChatMaps: Social Media for College Students",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
{children}
</body>
<body className={inter.className}>{children}</body>
</html>
);
}
+1 -3
View File
@@ -11,9 +11,7 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
{children}
</body>
<body className={inter.className}>{children}</body>
</html>
);
}
+109 -53
View File
@@ -1,66 +1,122 @@
"use client";
import { useForm, Form } from "react-hook-form";
import { useRouter } from "next/navigation";
import "../globals.css"
import "../globals.css";
// Firebase imports
import {auth} from "../../../firebase-config";
import { setPersistence, signInWithEmailAndPassword, indexedDBLocalPersistence } from "firebase/auth";
import { auth } from "../../../firebase-config";
import {
setPersistence,
signInWithEmailAndPassword,
indexedDBLocalPersistence,
} from "firebase/auth";
function Login() {
var router = useRouter();
//var { register, handleSubmit } = useForm();
var { register, control, setError, handleSubmit, formState: { errors, isSubmitting, isSubmitted } } = useForm()
var router = useRouter();
//var { register, handleSubmit } = useForm();
var {
register,
control,
setError,
handleSubmit,
formState: { errors, isSubmitting, isSubmitted },
} = useForm();
function authenticate(data) {
setPersistence(auth, indexedDBLocalPersistence)
.then(() => {
signInWithEmailAndPassword(auth,data.email,data.password)
.then((userCredential) => {
if (userCredential.user) {
router.push("/app")
} else {
const formError = { type: "server", message: "Username or Password Incorrect" }
// set same error in both:
setError('password', formError)
setError('email', formError)
}
})
})
}
function authenticate(data) {
setPersistence(auth, indexedDBLocalPersistence).then(() => {
signInWithEmailAndPassword(auth, data.email, data.password).then(
(userCredential) => {
if (userCredential.user) {
router.push("/app");
} else {
const formError = {
type: "server",
message: "Username or Password Incorrect",
};
// set same error in both:
setError("password", formError);
setError("email", formError);
}
}
);
});
}
return (
return (
<div>
<div className="grid h-screen place-items-center">
<div>
<div className="grid h-screen place-items-center">
<div>
<a href="/"><img src="logos/logo_transparent_inverse.png"/></a>
<span className="text-[36px]">
Chat with friends!
</span>
<div>
<h3 className="text-[24px] mt-[25px] mb-2">Login</h3>
{(errors.email && errors.password) && <div className="text-[red] mb-2 text-[18px] text-bold">Invalid Email or Password.</div>}
<Form onSubmit={handleSubmit(authenticate)} encType={'application/json'}
control={control}
>
<input type="email" id="email" className={(errors.email && errors.password) && "err"} {...register("email", { required: true })} placeholder="Enter Email Address"/><br/>
<input type="password" id="password" name="password" className={(errors.email && errors.password) && "err"} {...register("password", { required: true })} placeholder="Enter Password"/><br/>
<button type="submit" className="inline-flex items-center px-4 py-2 transition ease-in-out duration-150 bg-[#dee0e0] m-5 bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">
{(isSubmitting || isSubmitted) && <span className="inline-block">
<svg class="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" cx="12" cy="12" r="10" strokeWidth="4"></circle>
<path class="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>
</svg>
</span> }
Log In
</button>
<br/>Need an account? <a href="/register">Sign Up</a><br/>
</Form>
</div>
</div>
</div>
<a href="/">
<img src="logos/logo_transparent_inverse.png" />
</a>
<span className="text-[36px]">Chat with friends!</span>
<div>
<h3 className="text-[24px] mt-[25px] mb-2">Login</h3>
{errors.email && errors.password && (
<div className="text-[red] mb-2 text-[18px] text-bold">
Invalid Email or Password.
</div>
)}
<Form
onSubmit={handleSubmit(authenticate)}
encType={"application/json"}
control={control}
>
<input
type="email"
id="email"
className={errors.email && errors.password && "err"}
{...register("email", { required: true })}
placeholder="Enter Email Address"
/>
<br />
<input
type="password"
id="password"
name="password"
className={errors.email && errors.password && "err"}
{...register("password", { required: true })}
placeholder="Enter Password"
/>
<br />
<button
type="submit"
className="inline-flex items-center transition ease-in-out duration-150 m-5 bg-cyan-500 text-white font-bold py-2 px-4 rounded-full"
>
{(isSubmitting || isSubmitted) && (
<span className="inline-block">
<svg
class="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"
cx="12"
cy="12"
r="10"
strokeWidth="4"
></circle>
<path
class="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>
</svg>
</span>
)}
Log In
</button>
<br />
Need an account? <a href="/register">Sign Up</a>
<br />
</Form>
</div>
</div>
)
</div>
</div>
);
}
export default Login;
export default Login;
+2 -4
View File
@@ -5,15 +5,13 @@ const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "ChatMaps: Onboarding",
description: "ChatMaps: Social Media for College Students"
description: "ChatMaps: Social Media for College Students",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
{children}
</body>
<body className={inter.className}>{children}</body>
</html>
);
}
+61 -43
View File
@@ -1,57 +1,75 @@
"use client";
import "../globals.css"
import "../globals.css";
import { useForm } from "react-hook-form";
import { useRouter } from "next/navigation";
import { ref, set } from "firebase/database";
import {auth, database} from "../../../firebase-config"
import {onAuthStateChanged} from "firebase/auth"
import { auth, database } from "../../../firebase-config";
import { onAuthStateChanged } from "firebase/auth";
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
}
})
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;
}
});
}
function Onboarding() {
var router = useRouter();
var { register, handleSubmit } = useForm();
var router = useRouter();
var { register, handleSubmit } = useForm();
function Onboard(data) {
createUser(data)
router.push("/app");
}
return (
function Onboard(data) {
createUser(data);
router.push("/app");
}
return (
<div>
<div className="grid h-screen place-items-center">
<div>
<div className="grid h-screen place-items-center">
<div>
<img src="logos/logo_transparent_inverse.png"/>
<span className="text-[36px]">
Chat with friends!
</span>
<div className="m-5">
Welcome to ChatMaps! We are excited to have you join our community!<br/>First we just need a little bit of information from you to get started.
</div>
<form action="#" onSubmit={handleSubmit(Onboard)}>
<input type="text" {...register("username")} placeholder="Display Name"/><br/>
<input type="text" {...register("firstName")} placeholder="First Name"/><br/>
<input type="text" {...register("lastName")} placeholder="Last Name"/><br/>
<button type="submit" className="inline-flex items-center px-4 py-2 transition ease-in-out duration-150 bg-[#dee0e0] m-5 bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">Save</button>
</form>
</div>
</div>
<img src="logos/logo_transparent_inverse.png" />
<span className="text-[36px]">Chat with friends!</span>
<div className="m-5">
Welcome to ChatMaps! We are excited to have you join our community!
<br />
First we just need a little bit of information from you to get
started.
</div>
<form action="#" onSubmit={handleSubmit(Onboard)}>
<input
type="text"
{...register("username")}
placeholder="Display Name"
/>
<br />
<input
type="text"
{...register("firstName")}
placeholder="First Name"
/>
<br />
<input
type="text"
{...register("lastName")}
placeholder="Last Name"
/>
<br />
<button
type="submit"
className="inline-flex items-center transition ease-in-out duration-150 m-5 bg-cyan-500 text-white font-bold py-2 px-4 rounded-full"
>
Save
</button>
</form>
</div>
)
</div>
</div>
);
}
export default Onboarding;
export default Onboarding;
+83 -57
View File
@@ -1,73 +1,99 @@
"use client"
import { useState, useEffect } from 'react'
"use client";
import { useState, useEffect } from "react";
import { auth, database } from "../../firebase-config";
import { ref, get} from "firebase/database";
import {onAuthStateChanged} from "firebase/auth"
import { ref, get } from "firebase/database";
import { onAuthStateChanged } from "firebase/auth";
function Home() {
const [isLoadingLoc, setLoadingLoc] = useState(true)
const [roomCount, setRoomCount] = useState(null)
const [isAuthenticated, setAuth] = useState(false)
const [userID, setUserID] = useState(null)
const [isLoadingLoc, setLoadingLoc] = useState(true);
const [roomCount, setRoomCount] = useState(null);
const [isAuthenticated, setAuth] = useState(false);
const [userID, setUserID] = useState(null);
// Authentication
// Authentication
useEffect(() => {
onAuthStateChanged(auth, (user) => {
if (user) {
setUserID(user.uid)
setAuth(true)
setUserID(user.uid);
setAuth(true);
} else {
setAuth(false)
setAuth(false);
}
})
}, [])
});
}, []);
useEffect(() => {
if('geolocation' in navigator) {
// Retrieve latitude & longitude coordinates from `navigator.geolocation` Web API
navigator.geolocation.getCurrentPosition(({ coords }) => {
var path = String(coords.latitude.toFixed(2)).replace(".","")+"/"+String(coords.longitude.toFixed(2)).replace(".","")
get(ref(database, `/rooms/${path}`)).then((snapshot) => {
if (snapshot.exists()) {
var count = 0
for (var room in snapshot.val()) {
count += 1
}
setRoomCount(count)
} else {
setRoomCount(0)
}
setLoadingLoc(false)
});
});
useEffect(() => {
if ("geolocation" in navigator) {
// Retrieve latitude & longitude coordinates from `navigator.geolocation` Web API
navigator.geolocation.getCurrentPosition(({ coords }) => {
var path =
String(coords.latitude.toFixed(2)).replace(".", "") +
"/" +
String(coords.longitude.toFixed(2)).replace(".", "");
get(ref(database, `/rooms/${path}`)).then((snapshot) => {
if (snapshot.exists()) {
var count = 0;
for (var room in snapshot.val()) {
count += 1;
}
setRoomCount(count);
} else {
setRoomCount(0);
}
})
return (
setLoadingLoc(false);
});
});
}
});
return (
<div>
<div className="grid h-screen place-items-center">
<div>
<div className="grid h-screen place-items-center">
<div>
<img src="logos/logo_transparent_inverse.png"/>
<span className="text-[36px]">
Chat with friends!
</span>
<div className="m-5">
{(!isAuthenticated) &&
<div>
<a href="/login"><button className="bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">Login</button></a>
<a href="/register"><button className="bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">Sign Up</button></a>
</div>
}
{isAuthenticated && <a href="/app"><button className="bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">Continue to App</button></a>}
{(!isLoadingLoc && roomCount == 1) && <div className='text-[24px] pt-10'>Join others in {roomCount} room near you!</div>}
{(!isLoadingLoc && roomCount != 1 && roomCount != 0) && <div className='text-[24px] pt-10'>Join others in {roomCount} rooms near you!</div>}
{(isLoadingLoc || (roomCount == 0 && !isLoadingLoc)) && <div className='text-[24px] pt-10'>Start the conversation today!</div>}
</div>
</div>
</div>
<img src="logos/logo_transparent_inverse.png" />
<span className="text-[36px]">Chat with friends!</span>
<div className="m-5">
{!isAuthenticated && (
<div>
<a href="/login">
<button className="bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">
Login
</button>
</a>
<a href="/register">
<button className="bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">
Sign Up
</button>
</a>
</div>
)}
{isAuthenticated && (
<a href="/app">
<button className="bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">
Continue to App
</button>
</a>
)}
{!isLoadingLoc && roomCount == 1 && (
<div className="text-[24px] pt-10">
Join others in {roomCount} room near you!
</div>
)}
{!isLoadingLoc && roomCount != 1 && roomCount != 0 && (
<div className="text-[24px] pt-10">
Join others in {roomCount} rooms near you!
</div>
)}
{(isLoadingLoc || (roomCount == 0 && !isLoadingLoc)) && (
<div className="text-[24px] pt-10">
Start the conversation today!
</div>
)}
</div>
</div>
)
</div>
</div>
);
}
export default Home;
export default Home;
+1 -3
View File
@@ -11,9 +11,7 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
{children}
</body>
<body className={inter.className}>{children}</body>
</html>
);
}
+104 -55
View File
@@ -1,73 +1,122 @@
"use client";
import { useRouter } from "next/navigation";
import { useForm, Form } from "react-hook-form";
import "../globals.css"
import "../globals.css";
import { useState } from "react";
import { createUserWithEmailAndPassword, signInWithEmailAndPassword, setPersistence, indexedDBLocalPersistence } from "firebase/auth";
import {auth} from "../../../firebase-config";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
setPersistence,
indexedDBLocalPersistence,
} from "firebase/auth";
import { auth } from "../../../firebase-config";
async function Signup(data) {
var userCredential = await createUserWithEmailAndPassword(auth,data.email,data.password);
if (userCredential.user) {
setPersistence(auth, indexedDBLocalPersistence )
.then(() => {
signInWithEmailAndPassword(auth,data.email,data.password)
.then((res) => {
return true
})
})
} else {
return false
}
var userCredential = await createUserWithEmailAndPassword(
auth,
data.email,
data.password
);
if (userCredential.user) {
setPersistence(auth, indexedDBLocalPersistence).then(() => {
signInWithEmailAndPassword(auth, data.email, data.password).then(
(res) => {
return true;
}
);
});
} else {
return false;
}
}
function Register() {
var router = useRouter();
var { register, control, formState: { errors } } = useForm()
var emailRegex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/
var [passwordMismatch, setPasswordMismatch] = useState(false);
const passwordMatch = (data) => {
return data.password === data.passwordCheck;
};
var router = useRouter();
var {
register,
control,
formState: { errors },
} = useForm();
var emailRegex =
/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
var [passwordMismatch, setPasswordMismatch] = useState(false);
const passwordMatch = (data) => {
return data.password === data.passwordCheck;
};
function onSubmit({data}) {
if (passwordMatch(data)) {
setPasswordMismatch(false);
if (Signup(data)) {
router.push("/onboarding");
}
} else{
setPasswordMismatch(true);
return;
}
function onSubmit({ data }) {
if (passwordMatch(data)) {
setPasswordMismatch(false);
if (Signup(data)) {
router.push("/onboarding");
}
} else {
setPasswordMismatch(true);
return;
}
}
return (
return (
<div>
<div className="grid h-screen place-items-center">
<div>
<div className="grid h-screen place-items-center">
<div>
<a href="/"><img src="logos/logo_transparent_inverse.png"/></a>
<span className="text-[36px]">
Chat with friends!
</span>
<div>
<h3 className="text-[24px] mt-[15px]">Register</h3>
<Form onSubmit={onSubmit} encType={'application/json'} control={control}>
<input type="email" {...register("email", {required: true, pattern: emailRegex})} className={errors.email && "err"} placeholder="Enter Email Address"/><br/>
<input type="password" {...register("password", {required: true})} className={errors.password && errors.password.type == 'required' && "err"} placeholder="Enter Password"/><br/>
<input type ="password" {...register("passwordCheck", {required: false})} className ={errors.passwordCheck && errors.passwordCheck.type == 'required' && "err"} placeholder="Re-enter Password"/><br/>
{passwordMismatch && <p className="text-red-500">Passwords do not match</p>}
<button type="submit" className="bg-[#dee0e0] m-5 bg-cyan-500 text-white font-bold py-2 px-4 rounded-full"> Register</button><br/>
Have an account? <a href="/login">Log In</a>
</Form>
</div>
</div>
</div>
<a href="/">
<img src="logos/logo_transparent_inverse.png" />
</a>
<span className="text-[36px]">Chat with friends!</span>
<div>
<h3 className="text-[24px] mt-[15px]">Register</h3>
<Form
onSubmit={onSubmit}
encType={"application/json"}
control={control}
>
<input
type="email"
{...register("email", { required: true, pattern: emailRegex })}
className={errors.email && "err"}
placeholder="Enter Email Address"
/>
<br />
<input
type="password"
{...register("password", { required: true })}
className={
errors.password && errors.password.type == "required" && "err"
}
placeholder="Enter Password"
/>
<br />
<input
type="password"
{...register("passwordCheck", { required: false })}
className={
errors.passwordCheck &&
errors.passwordCheck.type == "required" &&
"err"
}
placeholder="Re-enter Password"
/>
<br />
{passwordMismatch && (
<p className="text-red-500">Passwords do not match</p>
)}
<button
type="submit"
className=" m-5 bg-cyan-500 text-white font-bold py-2 px-4 rounded-full"
>
{" "}
Register
</button>
<br />
Have an account? <a href="/login">Log In</a>
</Form>
</div>
</div>
)
</div>
</div>
);
}
export default Register;
+1 -3
View File
@@ -11,9 +11,7 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
{children}
</body>
<body className={inter.className}>{children}</body>
</html>
);
}
+86 -122
View File
@@ -2,108 +2,89 @@
// System Imports
import { useState, useEffect } from "react";
import { auth, database, storage } from "../../../../firebase-config";
import { ref, onValue, get, update } from "firebase/database";
import { ref as sRef, getDownloadURL } from "firebase/storage";
import {onAuthStateChanged} from "firebase/auth"
import { useForm, Form } from "react-hook-form";
import { ref, onValue, get, update } from "firebase/database";
import { onAuthStateChanged } from "firebase/auth";
// Refactored Component Imports
// Data Structure Imports
import { Interest, ProfileRoom } from "../../../components/app/datatypes";
import { ProfileRoom } from "../../../components/app/profile/ProfileRoom";
import { ProfileEdit } from "../../../components/app/profile/ProfileEdit";
import { Interest } from "../../../components/app/profile/Interest";
// Header Import
import { Header } from "../../../components/app/header";
import { uploadBytes } from "firebase/storage";
// Contains most everything for the app homepage
function Home({ params }) {
// User Profile Page
function UserProfile({ params }) {
// It's time to document and change these awful variable names
// State variables for app page
const [profileData, setProfileData] = useState(null)
const [isAuthenticated, setAuth] = useState(false)
const [user, setUser] = useState(null)
const [userInterestArray, setUserInterestArray] = useState(null)
const [userRoomsArray, setUserRoomsArray] = useState(null)
const [isOwner, setOwn] = useState(false)
const [isEditing, setEdit] = useState(false)
const [profileData, setProfileData] = useState(null);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState(null);
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
var { register, control} = useForm()
// Handles Edit State in Component, shares useState with ProfileEdit
const [isEditing, setIsEditing] = useState(false);
const handleIsEditing = (newValue) => {
setIsEditing(newValue);
};
// Authentication
useEffect(() => {
onAuthStateChanged(auth, (user) => {
if (user) {
get(ref(database, `users/${user.uid}`))
.then((userData) => {
userData = userData.val()
get(ref(database, `users/${user.uid}`)).then((userData) => {
userData = userData.val();
if (userData) {
if (userData.uid == params.stub) {
setOwn(true)
setIsOwner(true);
}
setUser(userData)
setAuth(true)
setUser(userData);
setIsAuthenticated(true);
} else {
window.location.href = "/onboarding"
window.location.href = "/onboarding";
}
})
});
} else {
setAuth(false)
window.location.href = "/login"
setIsAuthenticated(false);
window.location.href = "/login";
}
})
}, [])
});
}, []);
// Grabs profile user data
useEffect(() => {
onValue(ref(database, "/users/" + params.stub), (snapshot) => {
setProfileData(snapshot.val());
var interests = snapshot.val().interests || null;
interests = interests.split(",");
var interestArray = []
var i = 0
for (var interest in interests) {
if (i < 4)
interestArray.push(<Interest interest={interests[interest]}/>)
i++;
}
setUserInterestArray(interestArray)
var rooms = snapshot.val().rooms;
var roomArray = []
for (var room in rooms) {
roomArray.push(<ProfileRoom room={rooms[room]}/>)
}
setUserRoomsArray(roomArray);
});
},[]);
onValue(ref(database, "/users/" + params.stub), (snapshot) => {
setProfileData(snapshot.val());
function save({data}) {
if (data.pfp[0]) {
// image stuff
uploadBytes(sRef(storage, `users/${user.uid}/pfp`), data.pfp[0]).then(() => {
getDownloadURL(sRef(storage, `users/${user.uid}/pfp`)).then((url) => {
data.pfp = url
for (var key in data) {
if (data[key] == "") {
data[key] = profileData[key]
}
}
setEdit(false)
update(ref(database, `users/${user.uid}`), data)
})
})
} else {
for (var key in data) {
if (data[key] == "") {
data[key] = profileData[key]
}
// Populates array with user's interests
var interests = snapshot.val().interests || null;
if (interests == null) {
// Placeholder for no interests specified, will be replaced with default interests
interests = "Music, Sports, Movies";
}
data.pfp = profileData.pfp
setEdit(false)
update(ref(database, `users/${user.uid}`), data)
}
}
interests = interests.split(",");
var interestArray = [];
var i = 0;
for (var interest in interests) {
if (i < 4)
interestArray.push(<Interest interest={interests[interest]} />);
i++;
}
setUserInterestArray(interestArray);
// Populates array with user's rooms
var rooms = snapshot.val().rooms;
var roomArray = [];
for (var room in rooms) {
roomArray.push(<ProfileRoom room={rooms[room]} />);
}
setUserRoomsArray(roomArray);
});
}, []);
return (
<div>
@@ -112,67 +93,50 @@ function Home({ params }) {
{/* Left Side of Page */}
<div className="h-dvh overflow-hidden">
{/* Header */}
<Header user={user}/>
<Header user={user} />
{/* Main Page Section */}
<div className="grid grid-cols-3 mr-2 h-[calc(100%-110px)] pl-5 pr-5 pt-2">
<div className="cols-span-1 bg-white shadow-2xl rounded-xl pt-5">
{!isEditing && (
<div>
<img src={profileData.pfp} width="300px" className="relative mx-auto rounded-2xl overflow-hidden"/>
<img
src={profileData.pfp}
width="300px"
className="relative mx-auto rounded-2xl overflow-hidden"
/>
<div className="font-bold text-[30px]">
{profileData.firstName} {profileData.lastName}
{profileData.firstName} {profileData.lastName}
</div>
<div className="text-[20px]">@{profileData.username}</div>
<div className="pt-5">{profileData.bio}</div>
<div className="grid grid-cols-3 p-3">
{userInterestArray}
{userInterestArray}
</div>
<div className="grid grid-cols-1 auto-cols-min justify-items-center">
{isOwner && ( <a onClick={() => {setEdit(true)}} className="w-[120px] p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full text-center"> Edit Profile </a> )}
{!isOwner && ( <a className="w-[120px] p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full text-center"> Add Friend </a> )}
{isOwner && (
<a
onClick={() => {
setIsEditing(true);
}}
className="w-[120px] p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full text-center"
>
Edit Profile{" "}
</a>
)}
{!isOwner && (
<a className="w-[120px] p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full text-center">
Add Friend{" "}
</a>
)}
</div>
</div>
)}
{isEditing && (
<div>
<Form onSubmit={save} encType={'application/json'} control={control}>
<div className="grid grid-cols-2">
<div>
<img src={profileData.pfp} width="150px" className="relative mx-auto rounded-2xl overflow-hidden"/>
Current Profile Picture
</div>
<div className="flex content-center">
<input type="file" {...register("pfp")} className="w-[80%]" accept=".jpg,.png,.jpeg"/>
</div>
</div>
<div className="grid grid-cols-2 pl-2 w-[90%]">
<div className="pt-5">
<div className="font-bold">First Name</div>
<input className="w-[80%] border-2 border-gray-300 p-2 rounded-lg" type="text" {...register("firstName")} placeholder={profileData.firstName}/>
</div>
<div className="pt-5">
<div className="font-bold">Last Name</div>
<input className="w-[80%] border-2 border-gray-300 p-2 rounded-lg" type="text" {...register("lastName")} placeholder={profileData.lastName}/>
</div>
<div className="pt-5">
<div className="font-bold">Username</div>
<input className="w-[80%] border-2 border-gray-300 p-2 rounded-lg" type="text" {...register("username")} placeholder={profileData.username}/>
</div>
<div className="pt-5">
<div className="font-bold">Interests (Comma Seperated)</div>
<input className="w-[80%] border-2 border-gray-300 p-2 rounded-lg" type="text" {...register("interests")} placeholder={profileData.interests}/>
</div>
<div className="pt-5 col-span-2">
<div className="font-bold">Bio</div>
<textarea className="w-[92%] border-2 border-gray-300 p-2 rounded-lg" {...register("bio")} type="text" placeholder={profileData.bio}/>
</div>
<div className="justify-items-center pt-5 col-span-2">
<button type="submit" className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full text-center"> Save Changes </button>
</div>
</div>
</Form>
</div>
<ProfileEdit
profileData={profileData}
user={user}
onSave={handleIsEditing}
/>
)}
</div>
<div className="col-span-2">
@@ -184,8 +148,8 @@ function Home({ params }) {
</div>
</div>
)}
</div>
</div>
);
}
export default Home;
export default UserProfile;
+89 -129
View File
@@ -1,31 +1,64 @@
import { Map, Marker, ZoomControl } from "pigeon-maps";
// Colors for Messages
const userColors = [
"#ff80ed",
"#065535",
"#133337",
"#ffc0cb",
"#e6e6fa",
"#ffd700",
"#ffa500",
"#0000ff",
"#1b85b8",
"#5a5255",
"#559e83",
"#ae5a41",
"#c3cb71",
];
"#ff80ed",
"#065535",
"#133337",
"#ffc0cb",
"#e6e6fa",
"#ffd700",
"#ffa500",
"#0000ff",
"#1b85b8",
"#5a5255",
"#559e83",
"#ae5a41",
"#c3cb71",
];
let dateOptions = {
weekday: "long",
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
};
let dateOptions = {
weekday: "long",
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
};
const generateColor = (user_str) => {
// hashes username for consistent colors, maybe all functionality to pick color later
let hash = 0;
for (let i = 0; i < user_str.length; i++) {
hash = user_str.charCodeAt(i) + (hash * 32 - hash);
}
const index = Math.abs(hash) % userColors.length;
return index;
};
// Chat Message
export function Chat({ chatObj }) {
return (
<div className="width-[100%] bg-white rounded-lg mt-1 text-left p-1 grid grid-cols-2 mr-2">
<div>
<span style={{ color: userColors[generateColor(chatObj.user)] }}>
<a
href={chatObj.uid && "/user/" + chatObj.uid}
className="hover:font-bold cursor-pointer"
target="_blank"
>
{chatObj.user}
</a>
</span>
: {chatObj.body}
</div>
<div className="text-right text-[#d1d1d1]">
{new Date(chatObj.timestamp).toLocaleString(dateOptions)}
</div>
</div>
);
}
// System Chat Message
export function SystemMessage({ chatObj }) {
const generateColor = (user_str) => {
// hashes username for consistent colors, maybe all functionality to pick color later
let hash = 0;
@@ -36,129 +69,56 @@ const userColors = [
return index;
};
// Chat Message
export function Chat({ chatObj }) {
return (
<div className="width-[100%] bg-white rounded-lg mt-1 text-left p-1 grid grid-cols-2 mr-2">
<div>
<span style={{ color: userColors[generateColor(chatObj.user)] }}>
<a href={chatObj.uid && ("/user/"+chatObj.uid)} className="hover:font-bold cursor-pointer" target="_blank">{chatObj.user}</a>
</span>
: {chatObj.body}
</div>
<div className="text-right text-[#d1d1d1]">
{new Date(chatObj.timestamp).toLocaleString(dateOptions)}
</div>
</div>
);
}
// System Chat Message
export function SystemMessage({ chatObj }) {
const generateColor = (user_str) => {
// hashes username for consistent colors, maybe all functionality to pick color later
let hash = 0;
for (let i = 0; i < user_str.length; i++) {
hash = user_str.charCodeAt(i) + (hash * 32 - hash);
}
const index = Math.abs(hash) % userColors.length;
return index;
};
return (
return (
<div className="width-[100%] bg-white rounded-lg mt-1 text-left p-1 grid grid-cols-2 mr-2">
<div className="text-[#d1d1d1]">
<div className="text-[#d1d1d1]">
<span style={{ color: userColors[generateColor(chatObj.user)] }}>
<a href={chatObj.uid && ("/user/"+chatObj.uid)} className="hover:font-bold cursor-pointer" target="_blank">{chatObj.user}</a>
<a
href={chatObj.uid && "/user/" + chatObj.uid}
className="hover:font-bold cursor-pointer"
target="_blank"
>
{chatObj.user}
</a>
</span>{" "}
has {chatObj.body} the room.
</div>
<div className="text-right text-[#d1d1d1]">
</div>
<div className="text-right text-[#d1d1d1]">
{new Date(chatObj.timestamp).toLocaleString(dateOptions)}
</div>
</div>
</div>
);
);
}
// Member for Active/Room members in sidebar
export function Member({ memberObj }) {
return (
<a href={"/user/"+memberObj.uid} target="_blank">
<div className="cursor-pointer g-[aliceblue] rounded-lg m-3 shadow-xl p-2" >
return (
<a href={"/user/" + memberObj.uid} target="_blank">
<div className="cursor-pointer g-[aliceblue] rounded-lg m-3 shadow-xl p-2">
{memberObj.username}
</div>
</a>
);
}
);
}
// Chat Room Object for myRooms and Nearby in sidebar
export function ChatRoomSidebar({ roomObj, click }) {
// TODO: Gross fix but it works
function clicker() {
click(roomObj);
}
return (
<div
onClick={clicker}
className="border-[black] border-1 shadow-lg p-2 m-2 rounded-lg cursor-pointer"
>
<div className="col-span-2">
<div className="font-bold">{roomObj.name}</div>
<div className="italic">{roomObj.description}</div>
</div>
</div>
);
// TODO: Gross fix but it works
function clicker() {
click(roomObj);
}
// Map module for main page and chat room sidebar
// TODO: MAKE NOT MOVABLE
export function Geo({ loc, zoom, locMarker, markers }) {
if (loc) {
return (
<Map center={[loc.latitude, loc.longitude]} defaultZoom={zoom}>
{markers && markers}
{locMarker && (
<Marker
width={30}
anchor={[loc.latitude, loc.longitude]}
color="red"
/>
)}
{zoom && <ZoomControl />}
</Map>
);
} else {
return (
<Map className="rounded-lg" defaultCenter={[0, 0]} defaultZoom={zoom} />
);
}
}
// Interest for Profile
export function Interest({interest}) {
return (
<div>
<div className="rounded-lg m-2 p-2 shadow-xl">
{interest}
<div
onClick={clicker}
className="border-[black] border-1 shadow-lg p-2 m-2 rounded-lg cursor-pointer"
>
<div className="col-span-2">
<div className="font-bold">{roomObj.name}</div>
<div className="italic">{roomObj.description}</div>
</div>
</div>
)
);
}
export function ProfileRoom({room}) {
return (
<div className="rounded-lg p-2 shadow-xl bg-white h-[250px] w-[325px]">
<div className="relative z-1 h-[235px] opacity-50">
<Geo loc={{"latitude": room.latitude, "longitude": room.longitude}} zoom={12} locMarker={false}/>
</div>
<div className="relative z-2 top-[-235px] text-left p-2">
<div className="text-2xl font-bold">{room.name}</div>
<div>{room.description}</div>
<div>Created on {new Date(room.timestamp).toLocaleString(dateOptions)}</div>
<a href={"/app?room="+room.path+"/"+room.name+"-"+room.timestamp} className="absolute z-2 top-[190px] w-[108px] p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full flex items-center">Open Room</a>
</div>
</div>
)
}
// This will be removed once dateOptions is no longer used in this file
export { dateOptions };
+146 -137
View File
@@ -1,144 +1,153 @@
import { auth, database } from "../../../firebase-config";
import { ref, set, remove } from "firebase/database";
import {signOut} from "firebase/auth";
import { Popover } from '@headlessui/react'
import { signOut } from "firebase/auth";
import { Popover } from "@headlessui/react";
function logout() {
signOut(auth)
}
function logout() {
signOut(auth);
}
// Closes chat room
function closeChatRoom(roomObj, setChatRoomObj, setMainTab, user) {
var path = roomObj.path + "/" + roomObj.name + "-" + roomObj.timestamp;
var payload = {
body: "left",
user: user.username,
isSystem: true,
timestamp: new Date().getTime(),
uid: user.uid
};
set(
ref(
database,
`/rooms/${path}/chats/${new Date().getTime()}-${user.username}`
),
payload
);
remove(ref(database, `/rooms/${path}/users/online/${user.uid}`));
setChatRoomObj(null);
setMainTab("home");
}
// Closes chat room
function closeChatRoom(roomObj, setChatRoomObj, setMainTab, user) {
var path = roomObj.path + "/" + roomObj.name + "-" + roomObj.timestamp;
var payload = {
body: "left",
user: user.username,
isSystem: true,
timestamp: new Date().getTime(),
uid: user.uid,
};
set(
ref(
database,
`/rooms/${path}/chats/${new Date().getTime()}-${user.username}`
),
payload
);
remove(ref(database, `/rooms/${path}/users/online/${user.uid}`));
setChatRoomObj(null);
setMainTab("home");
}
// Adds room to myRooms
function addToMyRooms(chatRoomObj, setIsMyRoom, 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);
}
// Adds room to myRooms
function addToMyRooms(chatRoomObj, setIsMyRoom, 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(chatRoomObj, setIsMyRoom, 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);
}
export function Header({mainTab, isMyRoom, chatRoomObj, setChatRoomObj, setMainTab, setIsMyRoom, user}) {
return (
<div className="flex m-2 rounded-lg h-[63px] bg-white shadow-2xl p-1">
<div className="flex shrink h-[60px]">
<a href="/app">
<img
src="/logos/logo_transparent_inverse.png"
className="h-[60px]"
/>
</a>
</div>
<div className="grow grid grid-rows-1 grid-flow-col auto-cols-max justify-end gap-2 h-[60px] p-2">
{mainTab == "chat" && isMyRoom == false && (
<a
onClick={() => {
addToMyRooms(chatRoomObj, setIsMyRoom, user);
}}
className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full mr-5 flex items-center"
>
Add to &quot;My Rooms&quot;
</a>
)}
{mainTab == "chat" && isMyRoom == true && (
<a
onClick={() => {
removeFromMyRooms(chatRoomObj, setIsMyRoom, user);
}}
className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full mr-5 flex items-center"
>
Remove from &quot;My Rooms&quot;
</a>
)}
{mainTab == "chat" && (
<a
onClick={() => {
closeChatRoom(chatRoomObj, setChatRoomObj, setMainTab, user);
}}
className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full mr-5 flex items-center"
>
Close Chat
</a>
)}
<Popover className="relative">
<Popover.Button as="div">
<div className="mr-5 h-[44px] p-[2px] pr-[15px] cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full shadow-2xl flex">
<div className="flex items-center pl-1">
Nicholas
</div>
<div className="ml-3 rounded-lg">
<img src={user.pfp} width="40px" className="relative mx-auto rounded-xl overflow-hidden"/>
</div>
</div>
</Popover.Button>
<Popover.Panel className="absolute z-10 bg-white mt-[4px] rounded-xl ml-3 shadow-2xl">
<div className="grid grid-cols-1">
<a className="rounded-xl p-4 hover:bg-[#C0C0C0]" href={"/user/"+user.uid}>View Profile</a>
<a className="rounded-xl p-4 hover:bg-[#C0C0C0]" onClick={logout} href="/">Sign Out</a>
</div>
<img src="/solutions.jpg" alt="" />
</Popover.Panel>
</Popover>
</div>
</div>
// Deletes saved room from myRooms
function removeFromMyRooms(chatRoomObj, setIsMyRoom, 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);
}
export function Header({
mainTab,
isMyRoom,
chatRoomObj,
setChatRoomObj,
setMainTab,
setIsMyRoom,
user,
}) {
return (
<div className="flex m-2 rounded-lg h-[63px] bg-white shadow-2xl p-1">
<div className="flex shrink h-[60px]">
<a href="/app">
<img src="/logos/logo_transparent_inverse.png" className="h-[60px]" />
</a>
</div>
<div className="grow grid grid-rows-1 grid-flow-col auto-cols-max justify-end gap-2 h-[60px] p-2">
{mainTab == "chat" && isMyRoom == false && (
<a
onClick={() => {
addToMyRooms(chatRoomObj, setIsMyRoom, user);
}}
className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-5 flex items-center"
>
Add to &quot;My Rooms&quot;
</a>
)}
{mainTab == "chat" && isMyRoom == true && (
<a
onClick={() => {
removeFromMyRooms(chatRoomObj, setIsMyRoom, user);
}}
className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-5 flex items-center"
>
Remove from &quot;My Rooms&quot;
</a>
)}
{mainTab == "chat" && (
<a
onClick={() => {
closeChatRoom(chatRoomObj, setChatRoomObj, setMainTab, user);
}}
className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-5 flex items-center"
>
Close Chat
</a>
)}
<Popover className="relative">
<Popover.Button as="div">
<div className="mr-5 h-[44px] p-[2px] pr-[15px] cursor-pointer bg-cyan-500 text-white font-bold rounded-full shadow-2xl flex">
<div className="flex items-center pl-1">{user.firstName}</div>
<div className="ml-3 rounded-lg">
<img
src={user.pfp}
width="40px"
className="relative mx-auto rounded-xl overflow-hidden"
/>
</div>
</div>
</Popover.Button>
<Popover.Panel className="absolute z-10 bg-white mt-[4px] rounded-xl ml-3 shadow-2xl">
<div className="grid grid-cols-1">
<a
className="rounded-xl p-4 hover:bg-[#C0C0C0]"
href={"/user/" + user.uid}
>
View Profile
</a>
<a
className="rounded-xl p-4 hover:bg-[#C0C0C0]"
onClick={logout}
href="/"
>
Sign Out
</a>
</div>
<img src="/solutions.jpg" alt="" />
</Popover.Panel>
</Popover>
</div>
</div>
);
}
@@ -1,95 +1,94 @@
import { Chat, SystemMessage} from "../datatypes"
import { Chat, SystemMessage } from "../datatypes";
import { useState, useEffect } from "react";
import { Form, useForm } from "react-hook-form";
import { ref, onValue, set} from "firebase/database";
import { ref, onValue, set } from "firebase/database";
import { database } from "../../../../firebase-config";
// Chatroom Module for Primary Tab
// Chatroom Module for Primary Tab
export function MainTabChatRoom({ roomObj, user }) {
var { register, control, reset, handleSubmit } = useForm();
const [chats, setData] = useState(null);
const [isLoading, setLoading] = useState(true);
// Message updater
useEffect(() => {
onValue(
ref(
database,
`/rooms/${
roomObj.path + "/" + roomObj.name + "-" + roomObj.timestamp
}/chats`
),
(snapshot) => {
var chatsArr = [];
var messages = snapshot.val();
for (var message in messages) {
if (messages[message].isSystem) {
chatsArr.push(
<SystemMessage
chatObj={messages[message]}
key={messages[message].timestamp}
/>
);
} else {
chatsArr.push(
<Chat
chatObj={messages[message]}
key={messages[message].timestamp}
/>
);
}
var { register, control, reset, handleSubmit } = useForm();
const [chats, setData] = useState(null);
const [isLoading, setLoading] = useState(true);
// Message updater
useEffect(() => {
onValue(
ref(
database,
`/rooms/${
roomObj.path + "/" + roomObj.name + "-" + roomObj.timestamp
}/chats`
),
(snapshot) => {
var chatsArr = [];
var messages = snapshot.val();
for (var message in messages) {
if (messages[message].isSystem) {
chatsArr.push(
<SystemMessage
chatObj={messages[message]}
key={messages[message].timestamp}
/>
);
} else {
chatsArr.push(
<Chat
chatObj={messages[message]}
key={messages[message].timestamp}
/>
);
}
setData(chatsArr.reverse());
setLoading(false);
}
);
});
function sendMessage(data) {
reset();
var payload = {
body: data.message,
user: user.username,
uid: user.uid,
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 <div>Loading</div>;
if (!chats) return <div>No Chats</div>;
return (
<div className="m-1 h-[100%] rounded-lg">
<div className="h-[90%] m-4 overflow-y-auto flex flex-col-reverse">
{chats}
</div>
<div className="m-2 h-[10%] w-[100%] bg-white rounded-lg">
<Form
onSubmit={handleSubmit(sendMessage)}
control={control}
className="w-[100%] p-[0px]"
>
<input
type="text"
{...register("message")}
placeholder="Enter message"
className="w-[83%] border-[0px] mt-[8px] mb-[8px]"
/>
<button className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full mr-5 w-[8%]">
Send
</button>
</Form>
</div>
</div>
setData(chatsArr.reverse());
setLoading(false);
}
);
}
});
function sendMessage(data) {
reset();
var payload = {
body: data.message,
user: user.username,
uid: user.uid,
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 <div>Loading</div>;
if (!chats) return <div>No Chats</div>;
return (
<div className="m-1 h-[100%] rounded-lg">
<div className="h-[90%] m-4 overflow-y-auto flex flex-col-reverse">
{chats}
</div>
<div className="m-2 h-[10%] w-[100%] bg-white rounded-lg">
<Form
onSubmit={handleSubmit(sendMessage)}
control={control}
className="w-[100%] p-[0px]"
>
<input
type="text"
{...register("message")}
placeholder="Enter message"
className="w-[83%] border-[0px] mt-[8px] mb-[8px]"
/>
<button className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-5 w-[8%]">
Send
</button>
</Form>
</div>
</div>
);
}
@@ -1,31 +1,31 @@
import {Geo} from "../datatypes"
import { Geo } from "../map/geo";
// Module for Welcome Message on main tab landing page
function WelcomeMessage({user}) {
return (
<div className="bg-white rounded-lg m-2 mt-4 text-left p-2 pl-5">
<div>
Welcome, {user.firstName} {user.lastName} ({user.username})
</div>
<div>Lets see what&apos;s happening in your area.</div>
function WelcomeMessage({ user }) {
return (
<div className="bg-white rounded-lg m-2 mt-4 text-left p-2 pl-5">
<div>
Welcome, {user.firstName} {user.lastName} ({user.username})
</div>
);
}
<div>Lets see what&apos;s happening in your area.</div>
</div>
);
}
// Primary App Landing Page
export function MainTabHome({ loc, markers, user }) {
return (
<>
<WelcomeMessage user={user}/>
<div className="h-[calc(100%-110px)] m-5 rounded-lg">
<Geo
loc={loc}
zoom={14}
movable={true}
locMarker={true}
markers={markers}
/>
</div>
</>
);
}
return (
<>
<WelcomeMessage user={user} />
<div className="h-[calc(100%-110px)] m-5 rounded-lg">
<Geo
loc={loc}
zoom={14}
movable={true}
locMarker={true}
markers={markers}
/>
</div>
</>
);
}
@@ -0,0 +1,26 @@
import { Map, Marker, ZoomControl } from "pigeon-maps";
// Map module for main page and chat room sidebar (and eventually user profile)
// Constructs Map and Markers
// TODO: Need to get rest of marker handling here or in marker file.
export function Geo({ loc, zoom, locMarker, markers }) {
if (loc) {
return (
<Map center={[loc.latitude, loc.longitude]} defaultZoom={zoom}>
{markers && markers}
{locMarker && (
<Marker
width={30}
anchor={[loc.latitude, loc.longitude]}
color="red"
/>
)}
{zoom && <ZoomControl />}
</Map>
);
} else {
return (
<Map className="rounded-lg" defaultCenter={[0, 0]} defaultZoom={zoom} />
);
}
}
@@ -0,0 +1,9 @@
// Interests for Profile
// Making this its own file since we could do a bit more with this in the future
export function Interest({ interest }) {
return (
<div>
<div className="rounded-lg m-2 p-2 shadow-xl">{interest}</div>
</div>
);
}
@@ -0,0 +1,127 @@
import { useForm, Form } from "react-hook-form";
import { database, storage } from "../../../../firebase-config";
import { ref as sRef, getDownloadURL } from "firebase/storage";
import { ref, update } from "firebase/database";
import { uploadBytes } from "firebase/storage";
export function ProfileEdit({ profileData, user, onSave }) {
var { register, control } = useForm();
const handleEditState = () => {
onSave(false);
};
// Handles clicking save button
function save({ data }) {
// Profile pic handling
if (data.pfp[0]) {
// image stuff
uploadBytes(sRef(storage, `users/${user.uid}/pfp`), data.pfp[0]).then(
() => {
getDownloadURL(sRef(storage, `users/${user.uid}/pfp`)).then((url) => {
data.pfp = url;
for (var key in data) {
if (data[key] == "") {
data[key] = profileData[key];
}
}
handleEditState(false);
update(ref(database, `users/${user.uid}`), data);
});
}
);
} else {
for (var key in data) {
if (data[key] == "") {
data[key] = profileData[key];
}
}
data.pfp = profileData.pfp;
handleEditState(false);
update(ref(database, `users/${user.uid}`), data);
}
}
return (
<div>
<Form onSubmit={save} encType={"application/json"} control={control}>
<div className="grid grid-cols-2">
<div>
<img
src={profileData.pfp}
width="150px"
className="relative mx-auto rounded-2xl overflow-hidden"
/>
Current Profile Picture
</div>
<div className="flex content-center">
<input
type="file"
{...register("pfp")}
className="w-[80%]"
accept=".jpg,.png,.jpeg"
/>
</div>
</div>
<div className="grid grid-cols-2 pl-2 w-[90%]">
<div className="pt-5">
<div className="font-bold">First Name</div>
<input
className="w-[80%] border-2 border-gray-300 p-2 rounded-lg"
type="text"
{...register("firstName")}
placeholder={profileData.firstName}
/>
</div>
<div className="pt-5">
<div className="font-bold">Last Name</div>
<input
className="w-[80%] border-2 border-gray-300 p-2 rounded-lg"
type="text"
{...register("lastName")}
placeholder={profileData.lastName}
/>
</div>
<div className="pt-5">
<div className="font-bold">Username</div>
<input
className="w-[80%] border-2 border-gray-300 p-2 rounded-lg"
type="text"
{...register("username")}
placeholder={profileData.username}
/>
</div>
<div className="pt-5">
<div className="font-bold">Interests (Comma Seperated)</div>
<input
className="w-[80%] border-2 border-gray-300 p-2 rounded-lg"
type="text"
{...register("interests")}
placeholder={profileData.interests}
/>
</div>
<div className="pt-5 col-span-2">
<div className="font-bold">Bio</div>
<textarea
className="w-[92%] border-2 border-gray-300 p-2 rounded-lg"
{...register("bio")}
type="text"
placeholder={profileData.bio}
/>
</div>
<div className="justify-items-center pt-5 col-span-2">
<button
type="submit"
className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full text-center"
>
{" "}
Save Changes{" "}
</button>
</div>
</div>
</Form>
</div>
);
}
@@ -0,0 +1,32 @@
import { Geo } from "../map/geo";
import { dateOptions } from "../datatypes";
// Display of Rooms on user profile
export function ProfileRoom({ room }) {
return (
<div className="rounded-lg p-2 shadow-xl bg-white h-[250px] w-[325px]">
<div className="relative z-1 h-[235px] opacity-50">
<Geo
loc={{ latitude: room.latitude, longitude: room.longitude }}
zoom={12}
locMarker={false}
/>
</div>
<div className="relative z-2 top-[-235px] text-left p-2">
<div className="text-2xl font-bold">{room.name}</div>
<div>{room.description}</div>
<div>
Created on {new Date(room.timestamp).toLocaleString(dateOptions)}
</div>
<a
href={
"/app?room=" + room.path + "/" + room.name + "-" + room.timestamp
}
className="absolute z-2 top-[190px] w-[108px] p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full flex items-center"
>
Open Room
</a>
</div>
</div>
);
}
@@ -1,38 +1,42 @@
import { Geo } from "../datatypes";
import { Geo } from "../map/geo";
export function Chat_Sidebar({chatRoomObj, chatroomOnline, chatroomUsersLoading, chatroomUsers, setTab}) {
return (
<div className="h-dvh">
<div className="m-2 h-[98%] grid grid-cols-1">
<div className="bg-white rounded-lg m-2 shadow-2xl relative">
<div className="w-[100%] h-[100%] opacity-50 absolute rounded-lg z-10">
<Geo
loc={{
latitude: parseFloat(chatRoomObj.latitude.toFixed(2)),
longitude: parseFloat(chatRoomObj.longitude.toFixed(2)),
}}
zoom={12}
movable={false}
marker={false}
/>
</div>
<div className="z-10 top-0 left-0 w-[100%] h-[100%] absolute text-left pl-3 pt-2">
<span className="font-bold text-[24px]">
{chatRoomObj.name}
</span>
<br />
{chatRoomObj.description}
</div>
</div>
<div className="bg-white rounded-lg m-2 shadow-2xl">
<div>Online Members</div>
{chatroomOnline}
</div>
<div className="bg-white rounded-lg m-2 shadow-2xl">
<div>All Members</div>
{!chatroomUsersLoading && chatroomUsers}
</div>
// Sidebar when in a Chatrooms
export function Chat_Sidebar({
chatRoomObj,
chatroomOnline,
chatroomUsersLoading,
chatroomUsers,
}) {
return (
<div className="h-dvh">
<div className="m-2 h-[98%] grid grid-cols-1">
<div className="bg-white rounded-lg m-2 shadow-2xl relative">
<div className="w-[100%] h-[100%] opacity-50 absolute rounded-lg z-10">
<Geo
loc={{
latitude: parseFloat(chatRoomObj.latitude.toFixed(2)),
longitude: parseFloat(chatRoomObj.longitude.toFixed(2)),
}}
zoom={12}
movable={false}
marker={false}
/>
</div>
<div className="z-10 top-0 left-0 w-[100%] h-[100%] absolute text-left pl-3 pt-2">
<span className="font-bold text-[24px]">{chatRoomObj.name}</span>
<br />
{chatRoomObj.description}
</div>
</div>
)
}
<div className="bg-white rounded-lg m-2 shadow-2xl">
<div>Online Members</div>
{chatroomOnline}
</div>
<div className="bg-white rounded-lg m-2 shadow-2xl">
<div>All Members</div>
{!chatroomUsersLoading && chatroomUsers}
</div>
</div>
</div>
);
}
+121 -114
View File
@@ -2,125 +2,132 @@ import { Form, useForm } from "react-hook-form";
import { database } from "../../../../firebase-config";
import { ref, set } from "firebase/database";
// Sidebar on Home Page, with various functionality (create, nearby, my rooms)
// CreateRoom Module for Sidebar Create Tab
function CreateRoom({ loc }) {
var { register, control, reset, handleSubmit } = useForm();
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 (
<div className="overflow-y-auto h-[90%]">
<Form control={control} onSubmit={handleSubmit(createRoom)}>
<input
{...register("name")}
placeholder="Room Name"
className="mt-2"
/>
<input
{...register("description")}
placeholder="Room Description"
className="mt-2"
/>
<br />
<div className="mt-3 mb-2">
Creating room near ({loc.latitude.toFixed(2)},{" "}
{loc.longitude.toFixed(2)})
</div>
<button className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full mr-5">
Create
</button>
</Form>
</div>
);
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);
}
export function Home_Sidebar({tab, nearby, loadingNearby, setTab, isRoomLoading, myRooms, loadingLoc, location}) {
return (
<div className="h-dvh">
<div className="bg-white shadow-2xl rounded-lg m-2 h-[98%]">
<div className="p-2">
<div className="p-1 rounded-lg grid grid-cols-3 bg-white">
<div
className={
tab == "nearby"
? "select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0] bg-[#D3D3D3]"
: "select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0]"
}
onClick={() => {
setTab("nearby");
}}
>
Nearby
</div>
<div
className={
tab == "rooms"
? "select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0] bg-[#D3D3D3]"
: "select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0]"
}
onClick={() => {
setTab("rooms");
}}
>
My Rooms
</div>
<div
className={
tab == "create"
? "select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0] bg-[#D3D3D3]"
: "select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0]"
}
onClick={() => {
setTab("create");
}}
>
Create
</div>
</div>
return (
<div className="overflow-y-auto h-[90%]">
<Form control={control} onSubmit={handleSubmit(createRoom)}>
<input {...register("name")} placeholder="Room Name" className="mt-2" />
<input
{...register("description")}
placeholder="Room Description"
className="mt-2"
/>
<br />
<div className="mt-3 mb-2">
Creating room near ({loc.latitude.toFixed(2)},{" "}
{loc.longitude.toFixed(2)})
</div>
<button className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-5">
Create
</button>
</Form>
</div>
);
}
export function Home_Sidebar({
tab,
nearby,
loadingNearby,
setTab,
isRoomLoading,
myRooms,
loadingLoc,
location,
}) {
return (
<div className="h-dvh">
<div className="bg-white shadow-2xl rounded-lg m-2 h-[98%]">
<div className="p-2">
<div className="p-1 rounded-lg grid grid-cols-3 bg-white">
<div
className={
tab == "nearby"
? "select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0] bg-[#D3D3D3]"
: "select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0]"
}
onClick={() => {
setTab("nearby");
}}
>
Nearby
</div>
<div
className={
tab == "rooms"
? "select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0] bg-[#D3D3D3]"
: "select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0]"
}
onClick={() => {
setTab("rooms");
}}
>
My Rooms
</div>
<div
className={
tab == "create"
? "select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0] bg-[#D3D3D3]"
: "select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0]"
}
onClick={() => {
setTab("create");
}}
>
Create
</div>
{tab == "nearby" && (
<div className="overflow-y-auto h-[90%]">
<div>
{!nearby && !loadingNearby && (
<div>
No Nearby Rooms
<br />
Create One?
</div>
)}
{loadingNearby && <div>Loading...</div>}
{nearby}
</div>
</div>
)}
{tab == "rooms" && (
<div className="overflow-y-auto h-[90%]">
<div>
{isRoomLoading && <div>Loading</div>}
{!myRooms && !isRoomLoading && <div>No User Saved Rooms</div>}
{myRooms}
</div>
</div>
)}
{tab == "create" && !loadingLoc && <CreateRoom loc={location} />}
{tab == "create" && loadingLoc && <div>Loading...</div>}
</div>
</div>
)
}
{tab == "nearby" && (
<div className="overflow-y-auto h-[90%]">
<div>
{!nearby && !loadingNearby && (
<div>
No Nearby Rooms
<br />
Create One?
</div>
)}
{loadingNearby && <div>Loading...</div>}
{nearby}
</div>
</div>
)}
{tab == "rooms" && (
<div className="overflow-y-auto h-[90%]">
<div>
{isRoomLoading && <div>Loading</div>}
{!myRooms && !isRoomLoading && <div>No User Saved Rooms</div>}
{myRooms}
</div>
</div>
)}
{tab == "create" && !loadingLoc && <CreateRoom loc={location} />}
{tab == "create" && loadingLoc && <div>Loading...</div>}
</div>
</div>
);
}
@@ -1,7 +1,7 @@
export function Profile_Sidebar() {
return (
<div className="h-dvh">
<div className=" bg-white m-2 h-[98%]">Profile</div>
</div>
)
}
return (
<div className="h-dvh">
<div className=" bg-white m-2 h-[98%]">Profile</div>
</div>
);
}
-102
View File
@@ -1,102 +0,0 @@
{
"name": "ChatMaps",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"react-router-dom": "^6.22.3"
}
},
"node_modules/@remix-run/router": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz",
"integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"peer": true
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"peer": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
},
"node_modules/react-router": {
"version": "6.22.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz",
"integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==",
"dependencies": {
"@remix-run/router": "1.15.3"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "6.22.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz",
"integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==",
"dependencies": {
"@remix-run/router": "1.15.3",
"react-router": "6.22.3"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
}
}
}
}
-5
View File
@@ -1,5 +0,0 @@
{
"dependencies": {
"react-router-dom": "^6.22.3"
}
}