Add Initial UI and Authentication #11
@@ -18,12 +18,9 @@ jobs:
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 21.6.2
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install frontend-next/
|
||||
|
||||
- name: Lint
|
||||
run: npm --prefix frontend-next/ run lint
|
||||
|
||||
- name: Build Frontend
|
||||
run: npm --prefix frontend-next/ run build
|
||||
@@ -1,3 +1,7 @@
|
||||
# Firebase Stuff
|
||||
firebase-admin-key.json
|
||||
firebase*.json
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ChatMaps
|
||||

|
||||
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.
|
||||
|
||||
@@ -9,9 +9,13 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"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"
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.50.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.0.1",
|
||||
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 964 B |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
||||
|
Before Width: | Height: | Size: 629 B |
@@ -0,0 +1,11 @@
|
||||
// lib/firebase-admin-config.js
|
||||
import { initializeApp, getApps, cert } from "firebase-admin/app";
|
||||
import serviceAccount from "../../../../firebase-admin-key"
|
||||
|
||||
export function customInitApp() {
|
||||
if (getApps().length <= 0) {
|
||||
initializeApp({
|
||||
credential: cert(serviceAccount)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { initializeApp, getApps, getApp } from "firebase/app";
|
||||
import { getAuth } from "firebase/auth";
|
||||
import firebaseConfigFile from "../../../../firebase-config"
|
||||
|
||||
var firebaseConfig = firebaseConfigFile;
|
||||
|
||||
var app = getApps().length > 0 ? getApp() : initializeApp(firebaseConfig);
|
||||
var auth = getAuth(app);
|
||||
|
||||
export { auth, app };
|
||||
@@ -0,0 +1,64 @@
|
||||
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 { auth as authConfig } from "../firebase-config";
|
||||
import { customInitApp } from "../firebase-admin";
|
||||
|
||||
// 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);
|
||||
if (token) {
|
||||
var expiresIn = 20 * 60 * 1000; // 20 minutes
|
||||
var sessionCookie = await auth().createSessionCookie(userCredential.user.accessToken, {expiresIn,});
|
||||
var options = {
|
||||
name: "session",
|
||||
value: sessionCookie,
|
||||
maxAge: expiresIn, // 20 mins
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
};
|
||||
cookies().set(options);
|
||||
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 });
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// 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 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { cookies } from "next/headers";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
|
||||
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>
|
||||
|
||||
)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 25 KiB |
@@ -2,17 +2,20 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
main {
|
||||
body {
|
||||
background-color: aliceblue;
|
||||
text-align: center;
|
||||
text-wrap:pretty;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: rgb(205, 205, 205);
|
||||
background: #dee0e0;
|
||||
border-color: black;
|
||||
border: 5px;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
filter: drop-shadow(0 25px 25px rgb(0 0 0 / 0.15));
|
||||
}
|
||||
|
||||
button:hover {
|
||||
@@ -23,5 +26,6 @@ input {
|
||||
border: 1px solid black;
|
||||
border-radius: 4px;
|
||||
padding: 10px 10px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,15 @@ 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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Inter } from "next/font/google";
|
||||
import "../globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata = {
|
||||
title: "ChatMaps: Login",
|
||||
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,43 @@
|
||||
"use client";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useRouter } from "next/navigation";
|
||||
import "../globals.css"
|
||||
|
||||
function Login() {
|
||||
var router = useRouter();
|
||||
var { register, handleSubmit } = useForm();
|
||||
async function Login(data) {
|
||||
const res = await fetch("/api/login", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data ? data : {}),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
router.push("/app");
|
||||
}
|
||||
}
|
||||
|
||||
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">
|
||||
<h3 className="text-[24px] mt-[50px]">Login</h3>
|
||||
<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" className="bg-[#dee0e0] m-5">Login</button><br/>
|
||||
Don't have an account? <a href="/register">Sign Up</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Login;
|
||||
@@ -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,27 +1,24 @@
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col justify-between p-24">
|
||||
<h1>Welcome to ChatMaps.</h1>
|
||||
|
||||
|
||||
<div id="room">
|
||||
<label>Room </label>
|
||||
<select>
|
||||
<option>Room 1</option>
|
||||
<option>Room 2</option>
|
||||
<option>Room 3</option>
|
||||
</select>
|
||||
<button>Join Room</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="message">
|
||||
<label>Enter a message</label>
|
||||
<input />
|
||||
<button>Send</button>
|
||||
</div>
|
||||
|
||||
|
||||
</main>
|
||||
);
|
||||
function Home() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="absolute right-[6%] top-[4%]">
|
||||
<button>Download</button>
|
||||
</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">
|
||||
<a href="/login"><button>Login</button></a>
|
||||
<a href="/register"><button>Signup</button></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home;
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Inter } from "next/font/google";
|
||||
import "../globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata = {
|
||||
title: "ChatMaps: Register",
|
||||
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,43 @@
|
||||
"use client";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
import "../globals.css"
|
||||
|
||||
function Register() {
|
||||
var {register, handleSubmit } = useForm()
|
||||
var router = useRouter();
|
||||
|
||||
async function RegisterWithEmail(data) {
|
||||
const res = await fetch("/api/register", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data ? data : {}),
|
||||
});
|
||||
if (res.ok) {
|
||||
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">
|
||||
<h3 className="text-[24px] mt-[50px]">Register</h3>
|
||||
<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" className="bg-[#dee0e0] m-5">Register</button><br/>
|
||||
Have an account? <a href="/login">Log In</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Register;
|
||||
@@ -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",
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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;
|
||||
@@ -0,0 +1,18 @@
|
||||
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,49 @@
|
||||
// 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");
|
||||
// Login if not logged in
|
||||
if (!session) {
|
||||
return NextResponse.redirect(new URL("/login", req.url));
|
||||
}
|
||||
//Call the authentication endpoint
|
||||
const responseAPI = await fetch("http://localhost:3000/api/login", {
|
||||
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 { 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Protected routes
|
||||
export const config = {
|
||||
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' },
|
||||
],
|
||||
};
|
||||
@@ -5,14 +5,5 @@ module.exports = {
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||
"gradient-conic":
|
||||
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||