Add onboarding, dashboard with relevant API's

This commit is contained in:
2024-02-20 01:08:12 -05:00
parent d7a2382cb5
commit ec2fc15a3f
16 changed files with 295 additions and 19 deletions
+9
View File
@@ -11,6 +11,7 @@
"firebase": "^10.8.0",
"firebase-admin": "^12.0.0",
"next": "^14.1.0",
"pigeon-maps": "^0.21.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.50.1"
@@ -4949,6 +4950,14 @@
"node": ">=0.10.0"
}
},
"node_modules/pigeon-maps": {
"version": "0.21.3",
"resolved": "https://registry.npmjs.org/pigeon-maps/-/pigeon-maps-0.21.3.tgz",
"integrity": "sha512-NbzISHHvMrcYBMBJ6NSSdTC1iGshSYOvsql9AqBfsE7KXni9xFIV9dkIWAfkKGCbafZ/0JysWZSp7zfg2piOFg==",
"peerDependencies": {
"react": "*"
}
},
"node_modules/pirates": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
+2 -1
View File
@@ -10,10 +10,11 @@
},
"dependencies": {
"firebase": "^10.8.0",
"firebase-admin": "^12.0.0",
"next": "^14.1.0",
"pigeon-maps": "^0.21.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"firebase-admin": "^12.0.0",
"react-hook-form": "^7.50.1"
},
"devDependencies": {
+1 -1
View File
@@ -7,4 +7,4 @@ var firebaseConfig = firebaseConfigFile;
var app = getApps().length > 0 ? getApp() : initializeApp(firebaseConfig);
var auth = getAuth(app);
export { auth };
export { auth, app };
+10 -9
View File
@@ -1,4 +1,4 @@
import { cookies, headers } from "next/headers";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
// Firebase Imports
import { auth } from "firebase-admin";
@@ -15,14 +15,14 @@ async function handleEmailAndPassword(email, password) {
try {
var userCredential = await signInWithEmailAndPassword(authConfig,email,password);
if (userCredential.user.accessToken) {
const token = await auth().verifyIdToken(userCredential.user.accessToken);
var token = await auth().verifyIdToken(userCredential.user.accessToken);
if (token) {
var expiresIn = 300000
var expiresIn = 20 * 60 * 1000; // 20 minutes
var sessionCookie = await auth().createSessionCookie(userCredential.user.accessToken, {expiresIn,});
var options = {
name: "session",
value: sessionCookie,
maxAge: expiresIn, // 5 mins
maxAge: expiresIn, // 20 mins
httpOnly: true,
secure: true,
};
@@ -53,11 +53,12 @@ export async function GET(req) {
return NextResponse.json({ isLogged: false }, { status: 401 });
} else {
// Validate session cookie
var validation = await auth().verifySessionCookie(session, true);
if (!validation) {
return NextResponse.json({ isLogged: false }, { status: 401 });
} else {
return NextResponse.json({ isLogged: true }, { status: 200 });
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 });
}
}
}
@@ -0,0 +1,40 @@
import { NextResponse } from "next/server";
// Lib Imports
import { app } from "../firebase-config";
import { getDatabase, ref, set as firebaseSet } from "firebase/database";
async function onboard(firstName, lastName, req) {
var session = req.cookies.get("session");
//Call the authentication endpoint
var res = await fetch("http://localhost:3000/api/login", {headers: {Cookie: `session=${session?.value}`}})
// Login if unauthorized
if (res.status !== 200) {
return NextResponse.json({}, { status: 401 });
}
try {
var { uid, email } = await res.json()
var database = getDatabase(app)
await firebaseSet(ref(database, `users/${uid}`), {
firstName: firstName,
lastName: lastName,
email: email
});
return NextResponse.json({}, { status: 200 });
} catch(error) {
return NextResponse.json({ error: "Internal Server Error" },{ status: 500 });
}
}
// Handles POST requests (login requests)
export async function POST(req, res) {
try {
var { firstName, lastName } = await req?.json()
return await onboard(firstName, lastName, req);
} catch (error) {
console.log(error)
return NextResponse.json({ error: "Internal Server Error" },{ status: 500 });
}
}
+7 -1
View File
@@ -1,11 +1,17 @@
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
export async function POST(req) {
export async function GET(req) {
cookies().set({
name: "session",
value: "",
maxAge: -1,
});
cookies().set({
name: "firstName",
value: "",
maxAge: -1,
});
return NextResponse.json({}, { status: 200 });
}
+15
View File
@@ -0,0 +1,15 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
export async function GET(req) {
const session = cookies().get("session");
// Login if not logged in
if (session) {
return NextResponse.json({
firstName: cookies().get("firstName")?.value,
lastName: cookies().get("lastName")?.value,
uid: cookies().get("uid")?.value,
})
}
return NextResponse.json({}, { status: 500 });
}
+26
View File
@@ -0,0 +1,26 @@
import { Inter } from "next/font/google";
import "../globals.css";
import { Header, Sidebar } from "./shared"
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "ChatMaps: Home",
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 overflow-hidden">
<div className="col-span-3 h-page">
<Header/>
{children}
</div>
<Sidebar/>
</div>
</body>
</html>
);
}
+72
View File
@@ -0,0 +1,72 @@
"use client"
import { useState, useEffect } from 'react'
import {Map} from "pigeon-maps"
function WelcomeMessage() {
//TODO: REALLY GROSS WAY TO GET COOKIES, NEED NEW WAY TO STORE USER DATA WITHOUT API CALLS. THIS PAGE HAS TO BE CLIENT SIDE DUE TO MAPS / GEOLOCATION
const [data, setData] = useState(null)
const [isLoading, setLoading] = useState(true)
useEffect(() => {
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}
</div>
<div>
Lets see what&apos;s happening in your area.
</div>
</div>
)
}
function Geo() {
const [isLoading, setLoading] = useState(true)
const [data, setData] = useState();
useEffect(() => {
if('geolocation' in navigator) {
// Retrieve latitude & longitude coordinates from `navigator.geolocation` Web API
navigator.geolocation.getCurrentPosition(({ coords }) => {
const { latitude, longitude } = coords;
console.log(latitude, longitude)
setData(coords)
setLoading(false)
})
}
}, []);
if (!isLoading) {
return (
<Map className="rounded-lg" defaultCenter={[data.latitude, data.longitude]} defaultZoom={14}/>
)
} else {
return (
<div>Loading...</div>
)
}
}
function Home() {
return (
<div className="h-[calc(100%-75px)]">
<WelcomeMessage/>
<div className='h-[calc(100%-110px)] m-5 rounded-lg'>
<Geo/>
</div>
</div>
)
}
export default Home;
+18
View File
@@ -0,0 +1,18 @@
export function Header() {
return (
<div className="m-2 rounded-lg h-[60px] bg-white shadow-2xl">
<a href="/"><img src="/logos/logo_transparent_inverse.png" className="h-[60px]"></img></a>
</div>
)
}
export function Sidebar() {
return (
<div className="h-dvh">
<div className="bg-white shadow-2xl rounded-lg m-2 h-[98%]">
Sidebar
</div>
</div>
)
}
+3 -2
View File
@@ -13,7 +13,7 @@ function Login() {
});
if (res.ok) {
router.push("/room/success");
router.push("/app");
}
}
@@ -30,7 +30,8 @@ function Login() {
<form action="#" onSubmit={handleSubmit(Login)}>
<input type="email" {...register("email")} placeholder="Enter Email Address"/><br/>
<input type="password" {...register("password")} placeholder="Enter Password"/><br/>
<button type="submit">Login</button>
<button type="submit" className="bg-[#dee0e0] m-5">Login</button><br/>
Don&apos;t have an account? <a href="/register">Sign Up</a>
</form>
</div>
</div>
@@ -0,0 +1,19 @@
import { Inter } from "next/font/google";
import "../globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "ChatMaps: Onboarding",
description: "ChatMaps: Social Media for College Students"
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
{children}
</body>
</html>
);
}
+44
View File
@@ -0,0 +1,44 @@
"use client";
import "../globals.css"
import { useForm } from "react-hook-form";
import { useRouter } from "next/navigation";
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");
}
}
return (
<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("firstName")} placeholder="First Name"/><br/>
<input type="text" {...register("lastName")} placeholder="Last Name"/><br/>
<button type="submit" className="bg-[#dee0e0] m-5">Save</button>
</form>
</div>
</div>
</div>
)
}
export default Onboarding;
+1 -1
View File
@@ -1,4 +1,4 @@
async function Home() {
function Home() {
return (
<div>
+2 -1
View File
@@ -30,7 +30,8 @@ function Register() {
<form action="#" onSubmit={handleSubmit(RegisterWithEmail)}>
<input type="email" {...register("email")} placeholder="Enter Email Address"/><br/>
<input type="password" {...register("password")} placeholder="Enter Password"/><br/>
<button type="submit">Register</button>
<button type="submit" className="bg-[#dee0e0] m-5">Register</button><br/>
Have an account? <a href="/login">Log In</a>
</form>
</div>
</div>
+26 -3
View File
@@ -1,5 +1,7 @@
// src/middleware.js
import { NextResponse } from "next/server";
import { app } from "./app/api/firebase-config";
import { getDatabase, ref, get as firebaseGet } from "firebase/database";
export async function middleware(req, res) {
const session = req.cookies.get("session");
@@ -17,10 +19,31 @@ export async function middleware(req, res) {
if (responseAPI.status !== 200) {
return NextResponse.redirect(new URL("/login", req.url));
}
return NextResponse.next();
// If new user, redirect to onboarding
var { uid } = await responseAPI.json()
var firstName = await req.cookies.get("firstName")?.value;
if (firstName) {
return NextResponse.next();
} else {
var database = getDatabase(app)
var user = await firebaseGet(ref(database, `users/${uid}`));
if (!user.exists()) {
return NextResponse.redirect(new URL("/onboarding", req.url));
} else {
var returnedResponse = NextResponse.next();
returnedResponse.cookies.set("firstName",user.val()?.firstName)
returnedResponse.cookies.set("lastName",user.val()?.lastName)
returnedResponse.cookies.set("uid",uid)
return returnedResponse
}
}
}
//Add your protected routes
//Protected routes
export const config = {
matcher: ["/room/:path*"],
matcher: ['/((?!login|register|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' },
],
};