From ec2fc15a3fbef7a63aea0e100538cae13a850cf8 Mon Sep 17 00:00:00 2001 From: Nicholas Pease Date: Tue, 20 Feb 2024 01:08:12 -0500 Subject: [PATCH] Add onboarding, dashboard with relevant API's --- frontend-next/package-lock.json | 9 +++ frontend-next/package.json | 3 +- frontend-next/src/app/api/firebase-config.js | 2 +- frontend-next/src/app/api/login/route.js | 19 +++--- frontend-next/src/app/api/onboard/route.js | 40 +++++++++++ frontend-next/src/app/api/signout/route.js | 8 ++- frontend-next/src/app/api/user/route.js | 15 ++++ frontend-next/src/app/app/layout.js | 26 +++++++ frontend-next/src/app/app/page.js | 72 ++++++++++++++++++++ frontend-next/src/app/app/shared.js | 18 +++++ frontend-next/src/app/login/page.js | 5 +- frontend-next/src/app/onboarding/layout.js | 19 ++++++ frontend-next/src/app/onboarding/page.js | 44 ++++++++++++ frontend-next/src/app/page.js | 2 +- frontend-next/src/app/register/page.js | 3 +- frontend-next/src/middleware.js | 29 +++++++- 16 files changed, 295 insertions(+), 19 deletions(-) create mode 100644 frontend-next/src/app/api/onboard/route.js create mode 100644 frontend-next/src/app/api/user/route.js create mode 100644 frontend-next/src/app/app/layout.js create mode 100644 frontend-next/src/app/app/page.js create mode 100644 frontend-next/src/app/app/shared.js create mode 100644 frontend-next/src/app/onboarding/layout.js create mode 100644 frontend-next/src/app/onboarding/page.js diff --git a/frontend-next/package-lock.json b/frontend-next/package-lock.json index 3791b85..c0721f0 100644 --- a/frontend-next/package-lock.json +++ b/frontend-next/package-lock.json @@ -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", diff --git a/frontend-next/package.json b/frontend-next/package.json index 347a17b..4ea9d2f 100644 --- a/frontend-next/package.json +++ b/frontend-next/package.json @@ -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": { diff --git a/frontend-next/src/app/api/firebase-config.js b/frontend-next/src/app/api/firebase-config.js index e1da74b..d544147 100644 --- a/frontend-next/src/app/api/firebase-config.js +++ b/frontend-next/src/app/api/firebase-config.js @@ -7,4 +7,4 @@ var firebaseConfig = firebaseConfigFile; var app = getApps().length > 0 ? getApp() : initializeApp(firebaseConfig); var auth = getAuth(app); -export { auth }; \ No newline at end of file +export { auth, app }; \ No newline at end of file diff --git a/frontend-next/src/app/api/login/route.js b/frontend-next/src/app/api/login/route.js index 2ce8f5a..0994af3 100644 --- a/frontend-next/src/app/api/login/route.js +++ b/frontend-next/src/app/api/login/route.js @@ -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 }); } + } } \ No newline at end of file diff --git a/frontend-next/src/app/api/onboard/route.js b/frontend-next/src/app/api/onboard/route.js new file mode 100644 index 0000000..0820fc5 --- /dev/null +++ b/frontend-next/src/app/api/onboard/route.js @@ -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 }); + } +} \ No newline at end of file diff --git a/frontend-next/src/app/api/signout/route.js b/frontend-next/src/app/api/signout/route.js index 333092a..e476c1e 100644 --- a/frontend-next/src/app/api/signout/route.js +++ b/frontend-next/src/app/api/signout/route.js @@ -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 }); } \ No newline at end of file diff --git a/frontend-next/src/app/api/user/route.js b/frontend-next/src/app/api/user/route.js new file mode 100644 index 0000000..d2f9aa6 --- /dev/null +++ b/frontend-next/src/app/api/user/route.js @@ -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 }); + } \ No newline at end of file diff --git a/frontend-next/src/app/app/layout.js b/frontend-next/src/app/app/layout.js new file mode 100644 index 0000000..4b40e5e --- /dev/null +++ b/frontend-next/src/app/app/layout.js @@ -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 ( + + +
+
+
+ {children} +
+ +
+ + + ); +} diff --git a/frontend-next/src/app/app/page.js b/frontend-next/src/app/app/page.js new file mode 100644 index 0000000..e7805e9 --- /dev/null +++ b/frontend-next/src/app/app/page.js @@ -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
+ if (!data) return
+ + return ( +
+
+ Welcome, {data.firstName} {data.lastName} +
+
+ Lets see what's happening in your area. +
+
+ ) + +} + +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 ( + + ) + } else { + return ( +
Loading...
+ ) + } + +} + +function Home() { + return ( +
+ +
+ +
+
+ ) +} + + + +export default Home; \ No newline at end of file diff --git a/frontend-next/src/app/app/shared.js b/frontend-next/src/app/app/shared.js new file mode 100644 index 0000000..4fd6826 --- /dev/null +++ b/frontend-next/src/app/app/shared.js @@ -0,0 +1,18 @@ +export function Header() { + return ( +
+ +
+ ) +} + +export function Sidebar() { + return ( +
+
+ Sidebar +
+
+ + ) + } diff --git a/frontend-next/src/app/login/page.js b/frontend-next/src/app/login/page.js index dad45da..aa10c06 100644 --- a/frontend-next/src/app/login/page.js +++ b/frontend-next/src/app/login/page.js @@ -13,7 +13,7 @@ function Login() { }); if (res.ok) { - router.push("/room/success"); + router.push("/app"); } } @@ -30,7 +30,8 @@ function Login() {


- +
+ Don't have an account? Sign Up
diff --git a/frontend-next/src/app/onboarding/layout.js b/frontend-next/src/app/onboarding/layout.js new file mode 100644 index 0000000..8e01519 --- /dev/null +++ b/frontend-next/src/app/onboarding/layout.js @@ -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 ( + + + {children} + + + ); +} diff --git a/frontend-next/src/app/onboarding/page.js b/frontend-next/src/app/onboarding/page.js new file mode 100644 index 0000000..1f3b195 --- /dev/null +++ b/frontend-next/src/app/onboarding/page.js @@ -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 ( +
+
+
+ + + Chat with friends! + +
+ Welcome to ChatMaps! We are excited to have you join our community!
First we just need a little bit of information from you to get started. +
+
+
+
+ +
+
+
+
+ ) +} + +export default Onboarding; \ No newline at end of file diff --git a/frontend-next/src/app/page.js b/frontend-next/src/app/page.js index 690a8ad..ad4c12d 100644 --- a/frontend-next/src/app/page.js +++ b/frontend-next/src/app/page.js @@ -1,4 +1,4 @@ -async function Home() { +function Home() { return (
diff --git a/frontend-next/src/app/register/page.js b/frontend-next/src/app/register/page.js index 14e72e7..618e974 100644 --- a/frontend-next/src/app/register/page.js +++ b/frontend-next/src/app/register/page.js @@ -30,7 +30,8 @@ function Register() {


- +
+ Have an account? Log In
diff --git a/frontend-next/src/middleware.js b/frontend-next/src/middleware.js index 12d3e1e..7029033 100644 --- a/frontend-next/src/middleware.js +++ b/frontend-next/src/middleware.js @@ -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' }, + ], }; \ No newline at end of file