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/package-lock.json b/frontend/package-lock.json index d74e05d..b76376c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.0", "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.22.3" }, "devDependencies": { "@types/react": "^18.2.64", @@ -569,6 +570,14 @@ "node": ">= 8" } }, + "node_modules/@remix-run/router": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.13.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", @@ -2373,6 +2382,36 @@ "react": "^18.2.0" } }, + "node_modules/react-router": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "dependencies": { + "@remix-run/router": "1.15.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "dependencies": { + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index c0de196..f3ae3f2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,8 @@ }, "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.22.3" }, "devDependencies": { "@types/react": "^18.2.64", diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8d4280a..73dfb70 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,22 +1,56 @@ -import './App.css' +import "./styles/App.css"; +import { Link, useNavigate } from 'react-router-dom'; import MainSearch from './components/MainSearch'; +import { SessionContext, destroySessionCookie, getSessionCookie } from "./contexts/session"; +import { useState, useEffect, useContext } from "react"; -function App() { +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] + ); return ( <> -
- PeerNotes Logo - -
+ + +
+ PeerNotes Logo + +
+
) } +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 2558f8f..1f7fe48 100644 --- a/frontend/src/components/MainSearch.tsx +++ b/frontend/src/components/MainSearch.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import '../screens/MainScreenWrapper.css'; +import '../styles/MainScreenWrapper.css'; const MainSearch: React.FC = () => { return ( 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..14705c2 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,9 +2,33 @@ 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'; + +const router = createBrowserRouter([ + { + path: "/", + element: , + children: [ + // add more routes here + ] + }, + { + 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/styles/App.css b/frontend/src/styles/App.css new file mode 100644 index 0000000..ee9db94 --- /dev/null +++ b/frontend/src/styles/App.css @@ -0,0 +1,10 @@ +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; +} \ No newline at end of file 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