Skip to content

Commit fed46bc

Browse files
committed
Add Login and Auth components
1 parent b129809 commit fed46bc

17 files changed

Lines changed: 1479 additions & 63 deletions

Frontend/package-lock.json

Lines changed: 1107 additions & 37 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Frontend/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13+
"@chakra-ui/react": "^2.10.9",
14+
"@emotion/react": "^11.14.0",
15+
"@emotion/styled": "^11.14.1",
16+
"axios": "^1.13.2",
17+
"framer-motion": "^12.23.25",
1318
"react": "^19.2.0",
1419
"react-dom": "^19.2.0",
1520
"react-router-dom": "^7.9.6"

Frontend/src/App.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,26 @@ import { Home } from './Pages/Home'
55
import { Login } from './Pages/Login'
66
import { Register } from './Pages/Register'
77
import { Navbar } from './Components/Navbar'
8+
import { Tasks } from './Pages/Tasks'
9+
import { ChakraProvider } from '@chakra-ui/react'
10+
import { AuthProvider } from './Contexts/useAuth'
11+
import PrivateRoute from './Components/PrivateRoute'
812

913
function App () {
1014
return(
11-
<>
12-
<Navbar/>
13-
<Routes>
14-
<Route path='/' element={<Home/>}/>
15-
<Route path='/login' element={<Login/>}/>
16-
<Route path='/Register' element={<Register/>}/>
17-
</Routes>
18-
</>
15+
16+
<ChakraProvider>
17+
<Navbar/>
18+
19+
<AuthProvider>
20+
<Routes>
21+
<Route path='/' element={<Home/>}/>
22+
<Route path='/login' element={<Login/>}/>
23+
<Route path='/Register' element={<Register/>}/>
24+
<Route path='/tasks' element={<PrivateRoute><Tasks/></PrivateRoute>}/>
25+
</Routes>
26+
</AuthProvider>
27+
</ChakraProvider>
1928
)
2029
}
2130

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from 'react';
2+
import { Navigate } from 'react-router-dom';
3+
import { useAuth } from '../Contexts/useAuth';
4+
import { Heading } from '@chakra-ui/react';
5+
6+
import { useNavigate } from 'react-router-dom';
7+
8+
const PrivateRoute = ({ children }) => {
9+
const { user, loading } = useAuth();
10+
const nav = useNavigate();
11+
12+
if (loading) return <Heading>Loading...</Heading>;
13+
14+
if (user) {
15+
return children
16+
}
17+
else {
18+
nav('/login')
19+
}
20+
21+
};
22+
23+
export default PrivateRoute;

Frontend/src/Contexts/useAuth.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React, { createContext, useContext, useState, useEffect } from 'react'
2+
import { useNavigate, useLocation } from 'react-router-dom';
3+
import { userName, loginUser, logOut } from '../api/loginapi';
4+
5+
6+
const AuthContext = createContext();
7+
8+
export const AuthProvider = ({children}) => {
9+
const [user, setUser] = useState(null)
10+
const [loading, setLoading] = useState(true)
11+
const nav = useNavigate()
12+
const location = useLocation();
13+
14+
const getAuthenticatedUser = async () => {
15+
try{
16+
const user = await userName();
17+
setUser(user);
18+
} catch (err) {
19+
20+
setUser(null);
21+
}finally{
22+
setLoading(false);
23+
}
24+
}
25+
26+
const logoutUser = async () => {
27+
try{
28+
await logOut();
29+
} catch (err) {
30+
console.log(err)
31+
} finally{
32+
setUser(null)
33+
nav('/home')
34+
}
35+
}
36+
37+
// not yet used
38+
const registerUser = async (username, email, password, confirm_password) => {
39+
try {
40+
if (password === confirm_password) {
41+
await register(username, email, password)
42+
alert('User successfully registered')
43+
nav('/login')
44+
}
45+
} catch {
46+
alert('error registering user')
47+
}
48+
}
49+
50+
const login = async (username, password) => {
51+
const user = await loginUser(username, password)
52+
if (user) {
53+
setUser(user)
54+
nav('/tasks')
55+
} else {
56+
alert('Incorrect username or password')
57+
}
58+
}
59+
60+
useEffect(() => {
61+
getAuthenticatedUser();
62+
}, [location.pathname])
63+
64+
return (
65+
<AuthContext.Provider value={{ user, loading, login, logoutUser }}>
66+
{children}
67+
</AuthContext.Provider>
68+
);
69+
70+
71+
}
72+
export const useAuth = () => useContext(AuthContext);

Frontend/src/Pages/Login.tsx

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,49 @@
1+
import { useState } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import { loginUser } from "../api/loginapi";
4+
import { VStack, Button, FormControl, FormLabel } from "@chakra-ui/react";
5+
import { useAuth } from "../Contexts/useAuth";
16

2-
export function Login () {
3-
return(
4-
<>
5-
<h2>Login Page</h2>
6-
<input type="text" placeholder="Username"/>
7-
<input type="text" placeholder="Password"/>
8-
<button>Login</button>
9-
</>
10-
)
11-
}
7+
export function Login() {
8+
const [Username, setUsername] = useState("");
9+
const [Password, setPassword] = useState("");
10+
const [err, setErr] = useState("");
11+
const nav = useNavigate();
12+
const { login } = useAuth()
13+
14+
const handlelogin = async (username, password) => {
15+
try{
16+
await login(username, password)
17+
} catch (err) {
18+
console.log(err)
19+
}
20+
};
21+
22+
return (
23+
<VStack>
24+
<h2>Login Page</h2>
25+
<br></br>
26+
<FormControl>
27+
<FormLabel>Username</FormLabel>
28+
<input
29+
type="text"
30+
value={Username}
31+
placeholder="Username"
32+
onChange={(e) => setUsername(e.target.value)}
33+
/>
34+
</FormControl>
35+
36+
<FormControl>
37+
<FormLabel>Password</FormLabel>
38+
<input
39+
type="password"
40+
value={Password}
41+
placeholder="Password"
42+
onChange={(e) => setPassword(e.target.value)}
43+
/>
44+
{err && <div style={{ color: "red" }}>{err}</div>}
45+
</FormControl>
46+
47+
<Button onClick={() => {handlelogin(Username, Password)}}>Login</Button>
48+
</VStack>
49+
)}

Frontend/src/Pages/Register.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect, useState } from "react"
22
import { registerUser } from "../api/loginapi"
3+
import { Button } from "@chakra-ui/react"
34

45
export function Register () {
56
const [username, setUsername] = useState("")
@@ -83,6 +84,7 @@ export function Register () {
8384
<>
8485
<h2>Register Page</h2>
8586
<label>
87+
<br></br>
8688
<h4>Username</h4>
8789
<input
8890
type="text"
@@ -113,11 +115,11 @@ export function Register () {
113115
{errors.confirm && <div style={{ color: "red" }}>{errors.confirm}</div>}
114116
{serverError && <div style={{ color: "red", marginTop: 8 }}>{serverError}</div>}
115117
<br></br>
116-
<button
118+
<Button
117119
onClick={handleRegister}
118120
disabled={!isFormValid || isSubmitting}
119121
style={{ marginTop: 12 }}>
120-
{isSubmitting ? "Registering..." : "Register"}</button>
122+
{isSubmitting ? "Registering..." : "Register"}</Button>
121123

122124
</>
123125
)

Frontend/src/Pages/Tasks.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { useState, useEffect } from "react"
2+
import { logOut, userName } from "../api/loginapi"
3+
import { Button } from "@chakra-ui/react"
4+
import { useNavigate } from "react-router-dom"
5+
import { path } from "framer-motion/client"
6+
7+
8+
export function Tasks () {
9+
const [user, setUser] = useState("")
10+
const nav = useNavigate()
11+
12+
useEffect(() => {
13+
const fetchName = async () => {
14+
const name = await userName()
15+
setUser(name)
16+
}
17+
})
18+
19+
const logout = async () => {
20+
const wait = await logOut()
21+
if (wait) {
22+
nav('/')
23+
}
24+
25+
}
26+
27+
return(
28+
<>
29+
<h1>Tasks</h1>
30+
<h5>Welcome back {user}</h5>
31+
<Button colorScheme="red" onClick={logout} >Logout</Button>
32+
</>
33+
)
34+
35+
}

Frontend/src/api/loginapi.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import { Fade, Tr } from "@chakra-ui/react";
2+
import axios from "axios"
3+
import { tr } from "framer-motion/client";
4+
5+
16
const BASE_URL = 'http://127.0.0.1:8000/'
27

38
export const registerUser = async<DT> (user: DT) => {
@@ -15,4 +20,65 @@ export const registerUser = async<DT> (user: DT) => {
1520
console.log("Post Error", err)
1621
}
1722

23+
}
24+
25+
export async function loginUser<DT>(username: DT, password: DT) {
26+
27+
const response = await axios.post(`${BASE_URL}accounts/token/`,
28+
{username: username, password: password},
29+
{withCredentials: true}
30+
)
31+
return response.data.success
32+
}
33+
34+
35+
export async function refreshToken () {
36+
try{
37+
await axios.post(`${BASE_URL}accounts/token/refresh/`,
38+
{},
39+
{withCredentials:true}
40+
)
41+
return true
42+
} catch (err){
43+
return false
44+
}
45+
}
46+
47+
export async function userName() {
48+
try{
49+
const response = await axios.get(`${BASE_URL}accounts/me/`,
50+
{withCredentials:true}
51+
)
52+
return response.data
53+
} catch (err) {
54+
55+
return callRefresh(err, await axios.get(`${BASE_URL}accounts/me/`,
56+
{withCredentials:true}
57+
) )
58+
59+
}
60+
61+
}
62+
63+
export async function callRefresh(err, func) {
64+
if (err.response && err.response.status === 401){
65+
const tokenRefreshed = await refreshToken();
66+
if (tokenRefreshed){
67+
const retryResponse = await func()
68+
return retryResponse.data
69+
}
70+
}
71+
return false
72+
}
73+
74+
export async function logOut() {
75+
try{
76+
await axios.post(`${BASE_URL}accounts/logout/`,
77+
{},
78+
{withCredentials:true}
79+
)
80+
return true
81+
} catch (err) {
82+
return false
83+
}
1884
}
928 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)