Add onboarding, dashboard with relevant API's
This commit is contained in:
Generated
+9
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -7,4 +7,4 @@ var firebaseConfig = firebaseConfigFile;
|
||||
var app = getApps().length > 0 ? getApp() : initializeApp(firebaseConfig);
|
||||
var auth = getAuth(app);
|
||||
|
||||
export { auth };
|
||||
export { auth, app };
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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'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;
|
||||
@@ -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>
|
||||
|
||||
)
|
||||
}
|
||||
@@ -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'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>
|
||||
);
|
||||
}
|
||||
@@ -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,4 +1,4 @@
|
||||
async function Home() {
|
||||
function Home() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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' },
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user