Refactor Authentication Flow #44

Merged
LAX18 merged 8 commits from npease-auth-refactor into main 2024-03-23 11:54:19 -09:00
26 changed files with 1481 additions and 3095 deletions
+6 -1
View File
@@ -1,3 +1,8 @@
{
"extends": "next/core-web-vitals"
"extends": "next/core-web-vitals",
"rules": {
"no-unused-vars": ["warn", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }],
"jsx-a11y/alt-text": "off",
"@next/next/no-img-element": "off"
}
}
+708 -2058
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -10,7 +10,6 @@
},
"dependencies": {
"firebase": "^10.6.0",
"firebase-admin": "^12.0.0",
"next": "^14.1.0",
"pigeon-maps": "^0.21.3",
"react": "^18.2.0",
@@ -1,22 +0,0 @@
import { initializeApp, getApps, cert } from "firebase-admin/app";
import admin from "firebase-admin";
export function customInitApp() {
if (getApps().length <= 0) {
initializeApp({
credential: admin.credential.cert({
type: process.env.FIREBASE_ADMIN_TYPE,
projectId: process.env.FIREBASE_ADMIN_PROJECT_ID,
privateKeyId: process.env.FIREBASE_ADMIN_PRIV_KEY_ID,
privateKey: process.env.FIREBASE_ADMIN_PRIV_KEY?.replace(/\\n/g, "\n"),
clientEmail: process.env.FIREBASE_ADMIN_CLIENT_EMAIL,
clientId: process.env.FIREBASE_ADMIN_CLIENT_ID,
authUri: process.env.FIREBASE_ADMIN_AUTH_URI,
tokenUri: process.env.FIREBASE_ADMIN_TOKEN_URL,
authProviderX509CertUrl: process.env.FIREBASE_ADMIN_AUTH_PROVIDER_X509_CERT_URL,
clientC509CertUrl: process.env.FIREBASE_ADMIN_CLIENT_X509_CERT_URL,
universe_domain: process.env.FIREBASE_ADMIN_UNIVERSE_DOMAIN,
}),
});
}
}
+3 -1
View File
@@ -1,5 +1,6 @@
import { initializeApp, getApps, getApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getDatabase} from "firebase/database"
var config = {
apiKey: "AIzaSyDbDPjQGt-lIjNPeTG-Q5AECM1m0vtOr2c",
@@ -13,5 +14,6 @@ var config = {
var app = getApps().length > 0 ? getApp() : initializeApp(config);
var auth = getAuth(app);
var database = getDatabase(app);
export { auth, app };
export { auth, app, database };
-95
View File
@@ -1,95 +0,0 @@
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
// Firebase Imports
import { auth } from "firebase-admin";
import { signInWithEmailAndPassword } from "firebase/auth";
// Lib Imports
import { app, auth as authConfig } from "../firebase-config";
import { customInitApp } from "../firebase-admin";
import { getDatabase, ref, get as firebaseGet } from "firebase/database";
// Needs to "init" on each call to the API
customInitApp();
// Login with Email/Password
async function handleEmailAndPassword(email, password) {
try {
var userCredential = await signInWithEmailAndPassword(authConfig,email,password);
if (userCredential.user.accessToken) {
var token = await auth().verifyIdToken(userCredential.user.accessToken);
var expiresIn = 20 * 60 * 1000; // 20 minutes
var sessionCookie = await auth().createSessionCookie(userCredential.user.accessToken, {expiresIn,});
if (token) {
var database = getDatabase(app)
var user = await firebaseGet(ref(database, `users/${userCredential.user.uid}`));
if (!user.exists()) {
var userOptions = {
name: "user",
value: JSON.stringify({defined: false, uid: userCredential.user.uid}),
maxAge: expiresIn, // 20 mins
httpOnly: true,
secure: true,
};
} else {
var userData = user.val()
userData.uid = userCredential.user.uid
userData.defined = true
var userOptions = {
name: "user",
value: JSON.stringify(userData),
maxAge: expiresIn, // 20 mins
httpOnly: true,
secure: true,
};
}
cookies().set(userOptions);
var options = {
name: "session",
value: sessionCookie,
maxAge: expiresIn, // 20 mins
httpOnly: true,
secure: true,
};
cookies().set(options);
cookies().set({
name: "uid",
value: userCredential.user.uid,
maxAge: expiresIn, // 20 mins
httpOnly: true,
secure: true,
});
return NextResponse.json({ options }, { status: 200 });
}
}
} catch (error) {
return NextResponse.json({ error: error.code }, { status: 401 });
}
}
// Handles POST requests (login requests)
export async function POST(req, res) {
try {
var { email, password } = await req?.json()
return await handleEmailAndPassword(email, password); // need session token
} catch (error) {
return NextResponse.json({ error: "Internal Server Error" },{ status: 500 });
}
}
// Handles GET requests (is session still valid requests)
export async function GET(req) {
var session = cookies().get("session")?.value || "";
//Validate if the cookie exist in the request
if (!session) {
return NextResponse.json({ isLogged: false }, { status: 401 });
} else {
// Validate session cookie
try {
var validation = await auth().verifySessionCookie(session, true);
return NextResponse.json({ isLogged: true, uid: validation.uid, email: validation.email }, { status: 200 });
} catch (error) {
return NextResponse.json({ isLogged: false}, { status: 401 });
}
}
}
@@ -1,48 +0,0 @@
import { NextResponse } from "next/server";
// Lib Imports
import { app } from "../firebase-config";
import { getDatabase, ref, set as firebaseSet } from "firebase/database";
import { cookies } from "next/headers";
async function onboard(onboardingJSON, req) {
var session = req.cookies.get("session");
//Call the authentication endpoint
var res = await fetch(new URL("/api/login", req.url), {headers: {Cookie: `session=${session?.value}`}})
// Login if unauthorized
if (res.status !== 200) {
return NextResponse.json({}, { status: 401 });
}
try {
var expiresIn = 20 * 60 * 1000; // 20 minutes
var { uid, email } = await res.json()
onboardingJSON.email = email
onboardingJSON.uid = uid
onboardingJSON.defined = true
var database = getDatabase(app)
await firebaseSet(ref(database, `users/${uid}`), onboardingJSON);
var userOptions = {
name: "user",
value: JSON.stringify(onboardingJSON),
maxAge: expiresIn, // 20 mins
httpOnly: true,
secure: true,
};
cookies().set(userOptions);
return NextResponse.json({}, { status: 200 });
} catch(error) {
return NextResponse.json({ error: "Internal Server Error: "+error },{ status: 500 });
}
}
// Handles POST requests (login requests)
export async function POST(req, res) {
try {
var onboardingJSON = await req?.json()
return await onboard(onboardingJSON, req);
} catch (error) {
return NextResponse.json({ error: "Internal Server Error" },{ status: 500 });
}
}
@@ -1,42 +0,0 @@
// Import necessary functions
import { createUserWithEmailAndPassword } from "firebase/auth";
import { auth } from "../firebase-config";
import { NextResponse } from "next/server";
// Function to register a new user using Firebase Authentication
export async function registerUser(email, password) {
try {
var userCredential = await createUserWithEmailAndPassword(auth,email,password);
// You can perform additional actions after successful registration, if needed.
return { success: true, userCredential };
} catch (error) {
return { success: false, error: error.message };
}
}
// POST request handler
export async function POST(req, res) {
try {
// Extract email and password from the request body
var { email, password } = await req?.json();
// Check if email and password are provided
if (!email || !password) {
return NextResponse.json(
{ error: "Email and password are required." },
{ status: 400 }
);
}
// Register the user
try {
var userCredential = await createUserWithEmailAndPassword(auth,email,password);
return NextResponse.json({message: "Registration successful.",user: userCredential.user,});
} catch {
return NextResponse.json({ error: registrationResult.error },{ status: 500 });
}
} catch (error) {
// Handle unexpected errors
return NextResponse.json({ error: "Internal Server Error" },{ status: 500 });
}
}
@@ -1,10 +0,0 @@
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
export async function GET(req) {
cookies().delete('user')
cookies().delete('session')
cookies().delete('uid')
return NextResponse.redirect(new URL("/",req.url))
}
-7
View File
@@ -1,7 +0,0 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
export async function GET(req) {
var userData = cookies().get("user")?.value || false
return userData != false? NextResponse.json(JSON.parse(userData)): NextResponse.json({},{status: 203})
}
+105 -632
View File
@@ -1,293 +1,30 @@
"use client";
// System Imports
import { useState, useEffect } from "react";
import { Map, Marker, ZoomControl } from "pigeon-maps";
import { Form, useForm } from "react-hook-form";
import { app } from "../api/firebase-config";
import { getDatabase, ref, onValue, get, set, remove } from "firebase/database";
import { auth, database } from "../api/firebase-config";
import { ref, onValue, set, remove, get } from "firebase/database";
import { useBeforeunload } from "react-beforeunload";
var database = getDatabase(app);
import {useRouter} from "next/navigation";
import {Marker} from "pigeon-maps";
import {onAuthStateChanged, signOut} from "firebase/auth"
// This shouldn't be up here at all but fix later
const userColors = [
"#ff80ed",
"#065535",
"#133337",
"#ffc0cb",
"#e6e6fa",
"#ffd700",
"#ffa500",
"#0000ff",
"#1b85b8",
"#5a5255",
"#559e83",
"#ae5a41",
"#c3cb71",
];
// Refactored Component Imports
// Data Structure Imports
import { ChatRoomSidebar, Member } from "../../components/app/datatypes";
// Data Types
// Header Import
import { Header } from "../../components/app/header";
// Chat Message
function Chat({ chatObj }) {
let dateOptions = {
weekday: "long",
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
};
// Main Tab Imports
import { MainTabChatRoom } from "../../components/app/main_tab/chat";
import { MainTabHome } from "../../components/app/main_tab/home";
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 (
<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)] }}>
{chatObj.user}
</span>
: {chatObj.body}
</div>
<div className="text-right text-[#d1d1d1]">
{new Date(chatObj.timestamp).toLocaleString(dateOptions)}
</div>
</div>
);
}
// System Chat Message
function SystemMessage({ chatObj }) {
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;
};
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]">
<span style={{ color: userColors[generateColor(chatObj.user)] }}>
{chatObj.user}
</span>{" "}
has {chatObj.body} the room.
</div>
<div className="text-right text-[#d1d1d1]">
{new Date(chatObj.timestamp).toLocaleString(dateOptions)}
</div>
</div>
);
}
// Member for Active/Room members in sidebar
function Member({ memberObj }) {
return (
<div className="cursor-pointer g-[aliceblue] rounded-lg m-3 shadow-xl p-2">
{memberObj.username}
</div>
);
}
// Chat Room for myRooms and Nearby in sidebar
function ChatRoomSidebar({ roomObj, click }) {
// TODO: Gross fix but it works
function clicker() {
click(roomObj.name + "-" + roomObj.timestamp, roomObj);
}
return (
<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>
);
}
// Map module for main page and chat room sidebar
// TODO: MAKE NOT MOVABLE
function Geo({ loc, zoom, movable, 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} />
);
}
}
// Module for Welcome Message on main tab landing page
function WelcomeMessage() {
const [data, setData] = useState(null);
const [isLoading, setLoading] = useState(true);
useEffect(() => {
fetch("/api/user")
.then((res) => res.json())
.then((data) => {
setData(data);
setLoading(false);
});
}, []);
if (isLoading) return <div></div>;
if (!data) return <div></div>;
return (
<div className="bg-white rounded-lg m-2 mt-4 text-left p-2 pl-5">
<div>
Welcome, {data.firstName} {data.lastName} ({data.username})
</div>
<div>Lets see what&apos;s happening in your area.</div>
</div>
);
}
// Main Tabs
// Primary App Landing Page
function MainTabHome({ loc, markers }) {
return (
<>
<WelcomeMessage />
<div className="h-[calc(100%-110px)] m-5 rounded-lg">
<Geo
loc={loc}
zoom={14}
movable={true}
locMarker={true}
markers={markers}
/>
</div>
</>
);
}
// Chatroom Module for Primary Tab
function MainTabChatRoom({ roomObj }) {
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();
fetch("/api/user")
.then((res) => res.json())
.then((user) => {
var payload = {
body: data.message,
user: user.username,
isSystem: false,
timestamp: new Date().getTime(),
};
set(
ref(
database,
`/rooms/${
roomObj.path + "/" + roomObj.name + "-" + roomObj.timestamp
}/chats/${new Date().getTime()}-${user.username}`
),
payload
);
});
}
if (isLoading) return <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>
);
}
// Sidebar Imports
import {Home_Sidebar} from "../../components/app/sidebar/home";
import { Chat_Sidebar } from "../../components/app/sidebar/chat";
import { Profile_Sidebar } from "../../components/app/sidebar/profile";
// Contains most everything for the app homepage
//
function Home() {
// It's time to document and change these awful variable names
// State variables for app page
@@ -305,48 +42,70 @@ function Home() {
const [chatroomOnline, setChatRoomOnline] = useState(null); // holds online users
const [chatroomUsers, setChatroomUsers] = useState(null); // holds all chatroom users
const [chatroomUsersLoading, setChatroomUsersLoading] = useState(true);
const [users, setUsers] = useState(null); // all users from firebase
const [alreadyLeft, setAlreadyLeft] = useState(false); // if already left from room
const [markers, setMarkers] = useState([]);
const [isAuthenticated, setAuth] = useState(false)
const [user, setUser] = useState(null)
// Authentication
useEffect(() => {
onAuthStateChanged(auth, (user) => {
if (user) {
get(ref(database, `users/${user.uid}`))
.then((userData) => {
userData = userData.val()
console.log(userData)
if (userData) {
setUser(userData)
setAuth(true)
} else {
window.location.href = "/onboarding"
}
})
} else {
setAuth(false)
window.location.href = "/login"
}
})
}, [])
// Grabs user data, saves to user, then lists the users saved rooms
useEffect(() => {
fetch("/api/user")
.then((res) => res.json())
.then((user) => {
onValue(ref(database, "/users/" + user.uid + "/rooms"), (snapshot) => {
setRoomLoading(true);
var rooms = snapshot.val();
setMyRoomsObj(rooms);
var roomArr = [];
var markerArr = markers;
for (var room in rooms) {
var newRoom = (
<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) => {
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);
});
}
},[user]);
// Grabs the user location
useEffect(() => {
if ("geolocation" in navigator) {
if ("geolocation" in navigator && user) {
// Retrieve latitude & longitude coordinates from `navigator.geolocation` Web API
navigator.geolocation.getCurrentPosition(({ coords }) => {
@@ -380,14 +139,8 @@ function Home() {
});
});
}
}, []);
},[user]);
// Grab list of all users
useEffect(() => {
get(ref(database, `/users`)).then((snapshot) => {
setUsers(snapshot.val());
});
}, []);
// Dont Double Send Leaving Message
useEffect(() => {
@@ -403,59 +156,8 @@ function Home() {
}
}, [chatRoomObj]);
// CreateRoom Module for Sidebar Create Tab
function CreateRoom({ loc }) {
var { register, control, reset, handleSubmit } = useForm();
function createRoom(data) {
reset();
var path =
String(loc.latitude.toFixed(2)).replace(".", "") +
"/" +
String(loc.longitude.toFixed(2)).replace(".", "");
var timestamp = new Date().getTime();
var payload = {
name: data.name,
description: data.description,
timestamp: timestamp,
latitude: loc.latitude,
longitude: loc.longitude,
path: path,
};
set(ref(database, `/rooms/${path}/${data.name}-${timestamp}`), payload);
}
return (
<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>
);
}
// Selects chat room
function selectChatRoom(roomName, roomObj) {
fetch("/api/user")
.then((res) => res.json())
.then((user) => {
function selectChatRoom(roomObj) {
// Path of chatroom
var path = roomObj.path + "/" + roomObj.name + "-" + roomObj.timestamp;
@@ -513,93 +215,10 @@ function Home() {
}
});
setMainTab("chat");
setAlreadyLeft(false);
});
}
// Closes chat room
function closeChatRoom(roomObj) {
fetch("/api/user")
.then((res) => res.json())
.then((user) => {
var path = roomObj.path + "/" + roomObj.name + "-" + roomObj.timestamp;
var payload = {
body: "left",
user: user.username,
isSystem: true,
timestamp: new Date().getTime(),
};
set(
ref(
database,
`/rooms/${path}/chats/${new Date().getTime()}-${user.username}`
),
payload
);
remove(ref(database, `/rooms/${path}/users/online/${user.uid}`));
setChatRoomObj(null);
setAlreadyLeft(true);
setMainTab("home");
});
}
// Adds room to myRooms
function addToMyRooms() {
fetch("/api/user")
.then((res) => res.json())
.then((user) => {
set(
ref(
database,
`/users/${user.uid}/rooms/${chatRoomObj.name}-${chatRoomObj.timestamp}`
),
{
name: chatRoomObj.name,
path: chatRoomObj.path,
timestamp: chatRoomObj.timestamp,
description: chatRoomObj.description,
longitude: chatRoomObj.longitude,
latitude: chatRoomObj.latitude,
}
);
var path =
chatRoomObj.path +
"/" +
chatRoomObj.name +
"-" +
chatRoomObj.timestamp;
set(ref(database, `/rooms/${path}/users/all/${user.uid}`), user);
});
setIsMyRoom(true);
}
// Deletes saved room from myRooms
function removeFromMyRooms() {
fetch("/api/user")
.then((res) => res.json())
.then((user) => {
var path =
chatRoomObj.path +
"/" +
chatRoomObj.name +
"-" +
chatRoomObj.timestamp;
remove(
ref(
database,
`/users/${user.uid}/rooms/${chatRoomObj.name}-${chatRoomObj.timestamp}`
)
);
remove(ref(database, `/rooms/${path}/users/all/${user.uid}`));
});
setIsMyRoom(false);
}
// Fires to tell other uses that you are leaving the room
useBeforeunload(() => {
fetch("/api/user")
.then((res) => res.json())
.then((user) => {
if (chatRoomObj && mainTab == "chat") {
var payload = {
body: "left",
@@ -629,191 +248,45 @@ function Home() {
chatRoomObj.name +
"-" +
chatRoomObj.timestamp
}/users/online/${user.uid}`
}/users/online/${userID}`
)
);
}
});
});
return (
<div className="grid grid-cols-4 auto-cols-max overflow-hidden">
{/* Left Side of Page */}
<div className="col-span-3 h-dvh">
{/* Header */}
<div className="m-2 rounded-lg h-[63px] bg-white shadow-2xl grid grid-cols-2 p-1">
<div className="h-[60px]">
<a href="/">
<img
src="logos/logo_transparent_inverse.png"
className="h-[60px]"
/>
</a>
<div>
{isAuthenticated && (
<div className="grid grid-cols-4 auto-cols-max overflow-hidden">
{/* 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}/>
{/* Main Page Section */}
<div className="mr-2 h-[calc(100%-110px)]">
{mainTab == "home" && !loadingLoc && (
<MainTabHome loc={location} markers={markers} user={user}/>
)}
{mainTab == "home" && loadingLoc && (
<MainTabHome loc={null} markers={markers} user={user}/>
)}
{mainTab == "chat" && <MainTabChatRoom roomObj={chatRoomObj} user={user}/>}
</div>
</div>
<div className="h-[60px] p-4">
{mainTab == "chat" && isMyRoom == false && (
<a
onClick={() => {
addToMyRooms();
}}
className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full mr-5"
>
Add to &quot;My Rooms&quot;
</a>
)}
{mainTab == "chat" && isMyRoom == true && (
<a
onClick={() => {
removeFromMyRooms();
}}
className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full mr-5"
>
Remove from &quot;My Rooms&quot;
</a>
)}
{mainTab == "chat" && (
<a
onClick={() => {
closeChatRoom(chatRoomObj);
}}
className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full mr-5"
>
Close Chat
</a>
)}
<a
href="/api/signout"
className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full"
>
Sign Out
</a>
</div>
</div>
{/* Main Page Section */}
<div className="mr-2 h-[calc(100%-110px)]">
{mainTab == "home" && !loadingLoc && (
<MainTabHome loc={location} markers={markers} />
{/* 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}/>
)}
{mainTab == "home" && loadingLoc && (
<MainTabHome loc={null} markers={markers} />
{mainTab == "chat" && (
<Chat_Sidebar chatRoomObj={chatRoomObj} chatroomOnline={chatroomOnline} chatroomUsersLoading={chatroomUsersLoading} chatroomUsers={chatroomUsers}/>
)}
{mainTab == "profile" && (
<Profile_Sidebar/>
)}
{mainTab == "chat" && <MainTabChatRoom roomObj={chatRoomObj} />}
</div>
</div>
{/* Sidebar (Right Side of Page) */}
{mainTab == "home" && (
<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>
</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>
)}
{mainTab == "chat" && (
<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>
)}
{mainTab == "profile" && (
<div className="h-dvh">
<div className=" bg-white m-2 h-[98%]">Profile</div>
</div>
)}
</div>
</div>
);
}
export default Home;
export default Home;
+24 -11
View File
@@ -3,10 +3,32 @@ import { useForm, Form } from "react-hook-form";
import { useRouter } from "next/navigation";
import "../globals.css"
// Firebase imports
import {auth} from "../api/firebase-config";
import { setPersistence, signInWithEmailAndPassword, browserSessionPersistence } from "firebase/auth";
function Login() {
var router = useRouter();
//var { register, handleSubmit } = useForm();
var { register, control, setError, formState: { errors, isSubmitting, isSubmitted } } = useForm()
var { register, control, setError, handleSubmit, formState: { errors, isSubmitting, isSubmitted } } = useForm()
function authenticate(data) {
setPersistence(auth, browserSessionPersistence)
.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 (
<div>
<div className="grid h-screen place-items-center">
@@ -18,16 +40,7 @@ function Login() {
<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 action="/api/login" encType={'application/json'}
onSuccess={() => {
router.push("/app");
}}
onError={() => {
const formError = { type: "server", message: "Username or Password Incorrect" }
// set same error in both:
setError('password', formError)
setError('email', formError)
}}
<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/>
+25 -12
View File
@@ -2,22 +2,35 @@
import "../globals.css"
import { useForm } from "react-hook-form";
import { useRouter } from "next/navigation";
import { ref, set } from "firebase/database";
import {auth, database} from "../api/firebase-config"
import {onAuthStateChanged} from "firebase/auth"
function createUser(data) {
onAuthStateChanged(auth, (user) => {
if (user.uid) {
console.log(user)
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();
async function Onboard(data) {
const res = await fetch("/api/onboard", {
method: "POST",
body: JSON.stringify(data ? data : {}),
});
if (res.ok) {
router.push("/app");
} else {
router.push("/login");
}
function Onboard(data) {
createUser(data)
router.push("/app");
}
return (
<div>
@@ -34,7 +47,7 @@ function Onboarding() {
<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="bg-[#dee0e0] m-5">Save</button>
<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>
+19 -16
View File
@@ -1,25 +1,28 @@
"use client"
import { useState, useEffect } from 'react'
import { app } from "./api/firebase-config";
import { getDatabase, ref, get} from "firebase/database";
var database = getDatabase(app)
import { auth, database } from "./api/firebase-config";
import { ref, get} from "firebase/database";
import {onAuthStateChanged} from "firebase/auth"
function Home() {
const [statusCode, setData] = useState(null)
const [isLoading, setLoading] = useState(true)
const [isLoadingLoc, setLoadingLoc] = useState(true)
const [roomCount, setRoomCount] = useState(null)
useEffect(() => {
fetch('/api/user')
.then((res) => res.status)
.then((status) => {
setData(status)
setLoading(false)
})
}, [])
const [isAuthenticated, setAuth] = useState(false)
const [userID, setUserID] = useState(null)
// Authentication
useEffect(() => {
onAuthStateChanged(auth, (user) => {
if (user) {
setUserID(user.uid)
setAuth(true)
} else {
setAuth(false)
}
})
}, [])
useEffect(() => {
if('geolocation' in navigator) {
@@ -51,13 +54,13 @@ function Home() {
Chat with friends!
</span>
<div className="m-5">
{(statusCode == 203 || isLoading) &&
{(!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>
}
{statusCode == 200 && <a href="/app"><button className="bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">Continue to App</button></a>}
{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>}
+26 -10
View File
@@ -4,21 +4,41 @@ import { useForm, Form } from "react-hook-form";
import "../globals.css"
import { useState } from "react";
import { createUserWithEmailAndPassword, signInWithEmailAndPassword, setPersistence, browserSessionPersistence } from "firebase/auth";
import {auth} from "../api/firebase-config";
async function Signup(data) {
var userCredential = await createUserWithEmailAndPassword(auth,data.email,data.password);
if (userCredential.user) {
setPersistence(auth, browserSessionPersistence)
.then(() => {
signInWithEmailAndPassword(auth,data.email,data.password)
.then((res) => {
console.log(res)
return true
})
})
} else {
return false
}
}
function Register() {
var { register, control, setError, handleSubmit, formState: { errors } } = useForm()
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;
};
const onSubmit = (data) => {
function onSubmit({data}) {
if (passwordMatch(data)) {
setPasswordMismatch(false);
router.push("/success");
if (Signup(data)) {
router.push("/onboarding");
}
} else{
setPasswordMismatch(true);
@@ -36,11 +56,7 @@ function Register() {
</span>
<div>
<h3 className="text-[24px] mt-[15px]">Register</h3>
<Form onSubmit={handleSubmit(onSubmit)}
onSuccess={() => {
router.push("/app");
}}
action="/api/register"
<Form onSubmit={onSubmit}
encType={'application/json'}
control={control}
>
@@ -1,26 +0,0 @@
import { Inter } from "next/font/google";
import "../../globals.css";
import { Header, Sidebar } from "./shared"
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "ChatMaps",
description: "ChatMaps: Social Media for College Students",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
<div className="grid grid-cols-4 auto-cols-max">
<div className="col-span-3 h-dvh">
<Header/>
{children}
</div>
<Sidebar/>
</div>
</body>
</html>
);
}
-28
View File
@@ -1,28 +0,0 @@
function MessagesDisplayBox() {
return (
<div className="h-[98%]">
Messages Stream in Here
</div>
)
}
function MessageSendBox() {
return (
<div className="bg-white rounded-lg shadow-2xl w-[98%] m-2">
Message Sender
</div>
)
}
function Home() {
return (
<div className="">
<MessagesDisplayBox/>
<MessageSendBox/>
</div>
)
}
export default Home;
@@ -1,18 +0,0 @@
export function Header() {
return (
<div className="m-2 rounded-lg h-[60px] bg-white shadow-2xl">
<img src="/logos/logo_transparent_inverse.png" className="h-[60px]"></img>
</div>
)
}
export function Sidebar() {
return (
<div className="h-dvh">
<div className="bg-white shadow-2xl rounded-lg m-2 h-[98%]">
Sidebar
</div>
</div>
)
}
@@ -0,0 +1,142 @@
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",
];
// Chat Message
export function Chat({ chatObj }) {
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;
};
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)] }}>
{chatObj.user}
</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 }) {
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;
};
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]">
<span style={{ color: userColors[generateColor(chatObj.user)] }}>
{chatObj.user}
</span>{" "}
has {chatObj.body} the room.
</div>
<div className="text-right text-[#d1d1d1]">
{new Date(chatObj.timestamp).toLocaleString(dateOptions)}
</div>
</div>
);
}
// Member for Active/Room members in sidebar
export function Member({ memberObj }) {
return (
<div className="cursor-pointer g-[aliceblue] rounded-lg m-3 shadow-xl p-2">
{memberObj.username}
</div>
);
}
// 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>
);
}
// 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} />
);
}
}
+127
View File
@@ -0,0 +1,127 @@
import { auth, database } from "../../app/api/firebase-config";
import { ref, set, remove } from "firebase/database";
import {signOut} from "firebase/auth";
function logout() {
console.log("Fire")
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(),
};
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);
}
// 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="m-2 rounded-lg h-[63px] bg-white shadow-2xl grid grid-cols-2 p-1">
<div className="h-[60px]">
<a href="/">
<img
src="logos/logo_transparent_inverse.png"
className="h-[60px]"
/>
</a>
</div>
<div className="h-[60px] p-4">
{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"
>
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"
>
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"
>
Close Chat
</a>
)}
<a
onClick={logout}
href="/"
className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full"
>
Sign Out
</a>
</div>
</div>
)
}
@@ -0,0 +1,94 @@
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 { database } from "../../../app/api/firebase-config";
// 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}
/>
);
}
}
setData(chatsArr.reverse());
setLoading(false);
}
);
});
function sendMessage(data) {
reset();
var payload = {
body: data.message,
user: user.username,
isSystem: false,
timestamp: new Date().getTime(),
};
set(
ref(
database,
`/rooms/${
roomObj.path + "/" + roomObj.name + "-" + roomObj.timestamp
}/chats/${new Date().getTime()}-${user.username}`
),
payload
);
}
if (isLoading) return <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>
);
}
@@ -0,0 +1,31 @@
import {Geo} from "../datatypes"
// 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>
</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>
</>
);
}
@@ -0,0 +1,38 @@
import { Geo } from "../datatypes";
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>
)
}
@@ -0,0 +1,126 @@
import { Form, useForm } from "react-hook-form";
import { database } from "../../../app/api/firebase-config";
import { ref, set } from "firebase/database";
// CreateRoom Module for Sidebar Create Tab
function CreateRoom({ loc }) {
var { register, control, reset, handleSubmit } = useForm();
function createRoom(data) {
reset();
var path =
String(loc.latitude.toFixed(2)).replace(".", "") +
"/" +
String(loc.longitude.toFixed(2)).replace(".", "");
var timestamp = new Date().getTime();
var payload = {
name: data.name,
description: data.description,
timestamp: timestamp,
latitude: loc.latitude,
longitude: loc.longitude,
path: path,
};
set(ref(database, `/rooms/${path}/${data.name}-${timestamp}`), payload);
}
return (
<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>
);
}
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>
</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>
)
}
@@ -0,0 +1,7 @@
export function Profile_Sidebar() {
return (
<div className="h-dvh">
<div className=" bg-white m-2 h-[98%]">Profile</div>
</div>
)
}
-57
View File
@@ -1,57 +0,0 @@
// src/middleware.js
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
export async function middleware(req, res) {
const session = await req.cookies.get("session");
if (req.nextUrl.pathname !== "/login" && req.nextUrl.pathname != "/register") {
// Login if not logged in
if (!session) {
return NextResponse.redirect(new URL("/login", req.url));
}
//Call the authentication endpoint
const responseAPI = await fetch(new URL("/api/login", req.url), {
headers: {
Cookie: `session=${session?.value}`,
},
});
// Login if unauthorized
if (responseAPI.status !== 200) {
return NextResponse.redirect(new URL("/login", req.url));
}
// If new user, redirect to onboarding
var user = JSON.parse(req.cookies.get("user").value)
if (user.defined) {
return NextResponse.next();
} else {
return NextResponse.redirect(new URL("/onboarding", req.url));
}
} else {
// Currently in the /login or /register, if user is authenticated, go ahead and direct them to the app
if (session) {
const responseAPI = await fetch(new URL("/api/login", req.url), {
headers: {
Cookie: `session=${session?.value}`,
},
});
if (responseAPI.status == 200) {
return NextResponse.redirect(new URL("/app", req.url))
} else {
return NextResponse.next() // Unauthenticated, continue
}
} else {
return NextResponse.next() // Not logged in, direct to login
}
}
}
//Protected routes
export const config = {
matcher: ['/((?!onboarding|api|_next/static|_next/image|auth|favicon.ico|robots.txt|images|logo|$).*)',],
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
};