diff --git a/README.md b/README.md index 3c944e9..4c64133 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,30 @@ Repository for a course project of CS4675/CS6675 at Georgia Institute of Technol
  • Raise a PR once you are ready and have checked your code for errors.
  • Mention a bullet point summary for all the features you are pushing as part of the PR within the description to ease the review process
  • + +## Getting Started + +### Frontend +For our frontend we are using React + Vite + TypeScript! + +- **React** -> JavaScript Framework for creating reactive user interfaces. +- **Vite** -> Development environment / build tool. Gives us access to features like hot reload, bundling, and plugins. +- **TypeScript** -> A superset of JavaScript allowing for static types. + +Here's how to set up + run the frontend environment: +1. Download and install [Node.js](https://nodejs.org/en/download) v18+ +Check your node version: +```sh +node -v +``` +2. Clone the repo using Git +3. Install dependencies +```sh +cd frontend +npm install +``` +4. Start the development server +```sh +npm run dev +``` +5. Visit http://localhost:5173 \ No newline at end of file diff --git a/backend/api/user_login.py b/backend/api/user_login.py index 1a8becb..4cd8ff5 100644 --- a/backend/api/user_login.py +++ b/backend/api/user_login.py @@ -18,7 +18,9 @@ class LoginView(APIView): user = authenticate(username=username, password=password) if user: token, _ = Token.objects.get_or_create(user=user) - return Response({"token": token.key}) + response = Response({"token": token.key}, status=status.HTTP_200_OK) + response.set_cookie("token", token.key) + return response else: return Response( {"error": "Invalid credentials"}, diff --git a/backend/peer_notes/settings.py b/backend/peer_notes/settings.py index 474b470..2418f69 100644 --- a/backend/peer_notes/settings.py +++ b/backend/peer_notes/settings.py @@ -134,3 +134,7 @@ STATIC_URL = "static/" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" AUTH_USER_MODEL = "api.PeerUser" CORS_ALLOW_ALL_ORIGINS = True +# CORS_ALLOWED_ORIGINS = [ +# "http://localhost:5173" +# ] +CORS_ALLOW_CREDENTIALS = True \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 570d257..b4ef972 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,21 +1,49 @@ -import './App.css' -import { BrowserRouter, Routes, Route } from 'react-router-dom'; -import MainSearch from './components/MainSearch'; -import Results from './components/Results'; +import "./styles/App.css"; +import { Link, useNavigate } from 'react-router-dom'; +import { Outlet } from "react-router-dom"; +import { SessionContext, destroySessionCookie, getSessionCookie } from "./contexts/session"; +import { useState, useEffect, useContext } from "react"; + +export default function App() { + const [session, setSession] = useState(getSessionCookie()); + const navigate = useNavigate(); + + // Redirect to login if session is undefined + useEffect( + () => { + setSession(getSessionCookie()); + if (session === undefined) { + navigate("/login"); + } + }, + [session, navigate] + ); -function App() { return ( - -
    -
    -
    - - } /> - } /> - -
    -
    - ); + <> + + +
    + +
    +
    + + ) } +function Navbar() { + const session = useContext(SessionContext); -export default App + const links = session ? ( + <> + Home + Logout + + ): ( + Login + ) + return ( + + ); +} \ No newline at end of file diff --git a/frontend/src/components/MainSearch.tsx b/frontend/src/components/MainSearch.tsx index 70021cf..1a9617e 100644 --- a/frontend/src/components/MainSearch.tsx +++ b/frontend/src/components/MainSearch.tsx @@ -1,6 +1,6 @@ -import React, { useState, FormEvent } from 'react'; +import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import '../screens/MainScreenWrapper.css'; +import '../styles/MainScreenWrapper.css'; interface Filters { professor: string; @@ -27,16 +27,20 @@ const MainSearch: React.FC = () => { const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); - var input = filters.toString() + const input = filters.toString() const searchParams = new URLSearchParams(input) navigate(`/results?${searchParams}`); }; return ( <> -
    - PeerNotes Logo -
    + PeerNotes Logo
    diff --git a/frontend/src/contexts/session.ts b/frontend/src/contexts/session.ts new file mode 100644 index 0000000..ce77016 --- /dev/null +++ b/frontend/src/contexts/session.ts @@ -0,0 +1,17 @@ +import React from "react"; + +type SessionContextType = string | undefined; + +export const getSessionCookie = () => { + const sessionCookie = document.cookie + .split('; ') + .find(row => row.startsWith('token=')) + ?.split('=')[1]; + return sessionCookie; +} +export const destroySessionCookie = () => { + document.cookie = "token=; Max-Age=0;" +} + +export const SessionContext = React.createContext(getSessionCookie()); + diff --git a/frontend/src/hooks/sessionRedirect.ts b/frontend/src/hooks/sessionRedirect.ts new file mode 100644 index 0000000..c90d30a --- /dev/null +++ b/frontend/src/hooks/sessionRedirect.ts @@ -0,0 +1,14 @@ +import { useEffect, useContext } from "react"; +import { useNavigate } from "react-router-dom"; +import { SessionContext } from "../contexts/session"; + +export const useSessionRedirect = () => { + const session = useContext(SessionContext); + const navigate = useNavigate(); + // Redirect to home if session is defined + useEffect(() => { + if (session !== undefined) { + navigate('/'); + } + }, [session, navigate]) +} \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 3d7150d..88bae49 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,9 +2,44 @@ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.tsx' import './index.css' +import { + createBrowserRouter, + RouterProvider, +} from "react-router-dom"; +import Login from './screens/Login.tsx'; +import Signup from './screens/Signup.tsx'; +import Results from './components/Results.tsx'; +import MainSearch from './components/MainSearch.tsx'; + +const router = createBrowserRouter([ + { + path: "/", + element: , + children: [ + // add more routes here + { + path: "search", + element: , + }, + { + path: "results", + element: , + }, + ] + }, + + { + path: "/login", + element: , + }, + { + path: "/signup", + element: , + }, +]); ReactDOM.createRoot(document.getElementById('root')!).render( - + , ) diff --git a/frontend/src/screens/Login.tsx b/frontend/src/screens/Login.tsx new file mode 100644 index 0000000..23a1cd5 --- /dev/null +++ b/frontend/src/screens/Login.tsx @@ -0,0 +1,48 @@ +import { Link, useNavigate } from "react-router-dom" +import "../styles/Login.css" +import { FormEvent } from "react"; +import { useSessionRedirect } from "../hooks/sessionRedirect"; + +export default function Login() { + const navigate = useNavigate(); + + useSessionRedirect(); + + function handleSubmit(e: FormEvent) { + e.preventDefault(); + const formData = new FormData(e.target as HTMLFormElement); + const username = formData.get('username'); + const password = formData.get('password'); + fetch('http://localhost:8000/api/login/', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ username, password }), + }) + .then(() => { + navigate('/'); + }) + .catch((error) => { + console.error('Error:', error); + }); + } + return ( +
    + +

    Login

    +
    + + +
    +
    + + +
    + +

    or Sign up

    + +
    + ); +} \ No newline at end of file diff --git a/frontend/src/screens/Signup.tsx b/frontend/src/screens/Signup.tsx new file mode 100644 index 0000000..9022619 --- /dev/null +++ b/frontend/src/screens/Signup.tsx @@ -0,0 +1,51 @@ +import { Link, useNavigate } from "react-router-dom"; +import "../styles/Login.css" +import { FormEvent } from "react"; +import { useSessionRedirect } from "../hooks/sessionRedirect"; +export default function Signup() { + const navigate = useNavigate(); + useSessionRedirect(); + + function handleSubmit(e: FormEvent) { + e.preventDefault(); + const formData = new FormData(e.target as HTMLFormElement); + const email = formData.get('email'); + const username = formData.get('username'); + const password = formData.get('password'); + fetch('http://localhost:8000/api/signup/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email, username, password }), + }) + .then(response => response.json()) + .then(() => { + navigate('/login'); + }) + .catch((error) => { + console.error('Error:', error); + }); + } + return ( +
    +
    +

    Sign Up

    +
    + + +
    +
    + + +
    +
    + + +
    + +

    or Login

    +
    +
    + ); +} \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/styles/App.css similarity index 62% rename from frontend/src/App.css rename to frontend/src/styles/App.css index 6e19a9f..2909c89 100644 --- a/frontend/src/App.css +++ b/frontend/src/styles/App.css @@ -1,3 +1,13 @@ +nav { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 2rem; + border: 3px solid #f1f1f1; + border-radius: 5px; + color: black; + background-color: white; +} .App { display: flex; flex-direction: column; diff --git a/frontend/src/styles/Login.css b/frontend/src/styles/Login.css new file mode 100644 index 0000000..3fbe8c4 --- /dev/null +++ b/frontend/src/styles/Login.css @@ -0,0 +1,42 @@ +form { + display: flex; + flex-direction: column; + justify-content: center; + gap: 0.5rem; +} +main { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} +h1 { + text-align: center; +} + +.form-input { + display: flex; + flex-direction: column; +} + + .login-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background: #B1D4E0; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* shadow */; + width: 40vh; + } + + .submit-button { + padding: 8px 16px; + margin-top: 20px; /* Adds space above the button */ + background-color: #2E8BC0; /* Example background color */ + color: white; /* Text color */ + border: none; + border-radius: 4px; + cursor: pointer; + font-family: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif; + } \ No newline at end of file diff --git a/frontend/src/screens/MainScreenWrapper.css b/frontend/src/styles/MainScreenWrapper.css similarity index 100% rename from frontend/src/screens/MainScreenWrapper.css rename to frontend/src/styles/MainScreenWrapper.css