commit
46d06cd463
19 changed files with 29822 additions and 0 deletions
@ -0,0 +1,2 @@ |
|||
*.ico filter=lfs diff=lfs merge=lfs -text |
|||
*.png filter=lfs diff=lfs merge=lfs -text |
|||
@ -0,0 +1,130 @@ |
|||
# Logs |
|||
logs |
|||
*.log |
|||
npm-debug.log* |
|||
yarn-debug.log* |
|||
yarn-error.log* |
|||
lerna-debug.log* |
|||
.pnpm-debug.log* |
|||
|
|||
# Diagnostic reports (https://nodejs.org/api/report.html) |
|||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
|||
|
|||
# Runtime data |
|||
pids |
|||
*.pid |
|||
*.seed |
|||
*.pid.lock |
|||
|
|||
# Directory for instrumented libs generated by jscoverage/JSCover |
|||
lib-cov |
|||
|
|||
# Coverage directory used by tools like istanbul |
|||
coverage |
|||
*.lcov |
|||
|
|||
# nyc test coverage |
|||
.nyc_output |
|||
|
|||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) |
|||
.grunt |
|||
|
|||
# Bower dependency directory (https://bower.io/) |
|||
bower_components |
|||
|
|||
# node-waf configuration |
|||
.lock-wscript |
|||
|
|||
# Compiled binary addons (https://nodejs.org/api/addons.html) |
|||
build/Release |
|||
|
|||
# Dependency directories |
|||
node_modules/ |
|||
jspm_packages/ |
|||
|
|||
# Snowpack dependency directory (https://snowpack.dev/) |
|||
web_modules/ |
|||
|
|||
# TypeScript cache |
|||
*.tsbuildinfo |
|||
|
|||
# Optional npm cache directory |
|||
.npm |
|||
|
|||
# Optional eslint cache |
|||
.eslintcache |
|||
|
|||
# Optional stylelint cache |
|||
.stylelintcache |
|||
|
|||
# Microbundle cache |
|||
.rpt2_cache/ |
|||
.rts2_cache_cjs/ |
|||
.rts2_cache_es/ |
|||
.rts2_cache_umd/ |
|||
|
|||
# Optional REPL history |
|||
.node_repl_history |
|||
|
|||
# Output of 'npm pack' |
|||
*.tgz |
|||
|
|||
# Yarn Integrity file |
|||
.yarn-integrity |
|||
|
|||
# dotenv environment variable files |
|||
.env |
|||
.env.development.local |
|||
.env.test.local |
|||
.env.production.local |
|||
.env.local |
|||
|
|||
# parcel-bundler cache (https://parceljs.org/) |
|||
.cache |
|||
.parcel-cache |
|||
|
|||
# Next.js build output |
|||
.next |
|||
out |
|||
|
|||
# Nuxt.js build / generate output |
|||
.nuxt |
|||
dist |
|||
|
|||
# Gatsby files |
|||
.cache/ |
|||
# Comment in the public line in if your project uses Gatsby and not Next.js |
|||
# https://nextjs.org/blog/next-9-1#public-directory-support |
|||
# public |
|||
|
|||
# vuepress build output |
|||
.vuepress/dist |
|||
|
|||
# vuepress v2.x temp and cache directory |
|||
.temp |
|||
.cache |
|||
|
|||
# Docusaurus cache and generated files |
|||
.docusaurus |
|||
|
|||
# Serverless directories |
|||
.serverless/ |
|||
|
|||
# FuseBox cache |
|||
.fusebox/ |
|||
|
|||
# DynamoDB Local files |
|||
.dynamodb/ |
|||
|
|||
# TernJS port file |
|||
.tern-port |
|||
|
|||
# Stores VSCode versions used for testing VSCode extensions |
|||
.vscode-test |
|||
|
|||
# yarn v2 |
|||
.yarn/cache |
|||
.yarn/unplugged |
|||
.yarn/build-state.yml |
|||
.yarn/install-state.gz |
|||
.pnp.* |
|||
@ -0,0 +1,9 @@ |
|||
# React-Django JWT Authentication - Frontend |
|||
|
|||
### `npm start` |
|||
|
|||
Runs the app in the development mode.\ |
|||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser. |
|||
|
|||
The page will reload when you make changes.\ |
|||
You may also see any lint errors in the console. |
|||
File diff suppressed because it is too large
@ -0,0 +1,40 @@ |
|||
{ |
|||
"name": "frontend", |
|||
"version": "0.1.0", |
|||
"private": true, |
|||
"dependencies": { |
|||
"@testing-library/jest-dom": "^5.16.5", |
|||
"@testing-library/react": "^13.4.0", |
|||
"@testing-library/user-event": "^13.5.0", |
|||
"jwt-decode": "^3.1.2", |
|||
"react": "^18.2.0", |
|||
"react-dom": "^18.2.0", |
|||
"react-router-dom": "^6.8.1", |
|||
"react-scripts": "5.0.1", |
|||
"web-vitals": "^2.1.4" |
|||
}, |
|||
"scripts": { |
|||
"start": "react-scripts start", |
|||
"build": "react-scripts build", |
|||
"test": "react-scripts test", |
|||
"eject": "react-scripts eject" |
|||
}, |
|||
"eslintConfig": { |
|||
"extends": [ |
|||
"react-app", |
|||
"react-app/jest" |
|||
] |
|||
}, |
|||
"browserslist": { |
|||
"production": [ |
|||
">0.2%", |
|||
"not dead", |
|||
"not op_mini all" |
|||
], |
|||
"development": [ |
|||
"last 1 chrome version", |
|||
"last 1 firefox version", |
|||
"last 1 safari version" |
|||
] |
|||
} |
|||
} |
|||
Binary file not shown.
@ -0,0 +1,43 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8" /> |
|||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
|||
<meta name="theme-color" content="#000000" /> |
|||
<meta |
|||
name="description" |
|||
content="Web site created using create-react-app" |
|||
/> |
|||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> |
|||
<!-- |
|||
manifest.json provides metadata used when your web app is installed on a |
|||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ |
|||
--> |
|||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> |
|||
<!-- |
|||
Notice the use of %PUBLIC_URL% in the tags above. |
|||
It will be replaced with the URL of the `public` folder during the build. |
|||
Only files inside the `public` folder can be referenced from the HTML. |
|||
|
|||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will |
|||
work correctly both with client-side routing and a non-root public URL. |
|||
Learn how to configure a non-root public URL by running `npm run build`. |
|||
--> |
|||
<title>React App</title> |
|||
</head> |
|||
<body> |
|||
<noscript>You need to enable JavaScript to run this app.</noscript> |
|||
<div id="root"></div> |
|||
<!-- |
|||
This HTML file is a template. |
|||
If you open it directly in the browser, you will see an empty page. |
|||
|
|||
You can add webfonts, meta tags, or analytics to this file. |
|||
The build step will place the bundled scripts into the <body> tag. |
|||
|
|||
To begin the development, run `npm start` or `yarn start`. |
|||
To create a production bundle, use `npm run build` or `yarn build`. |
|||
--> |
|||
</body> |
|||
</html> |
|||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,25 @@ |
|||
{ |
|||
"short_name": "React App", |
|||
"name": "Create React App Sample", |
|||
"icons": [ |
|||
{ |
|||
"src": "favicon.ico", |
|||
"sizes": "64x64 32x32 24x24 16x16", |
|||
"type": "image/x-icon" |
|||
}, |
|||
{ |
|||
"src": "logo192.png", |
|||
"type": "image/png", |
|||
"sizes": "192x192" |
|||
}, |
|||
{ |
|||
"src": "logo512.png", |
|||
"type": "image/png", |
|||
"sizes": "512x512" |
|||
} |
|||
], |
|||
"start_url": ".", |
|||
"display": "standalone", |
|||
"theme_color": "#000000", |
|||
"background_color": "#ffffff" |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
# https://www.robotstxt.org/robotstxt.html |
|||
User-agent: * |
|||
Disallow: |
|||
@ -0,0 +1,28 @@ |
|||
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom' |
|||
|
|||
import { AuthProvider } from './context/AuthContext' |
|||
|
|||
import HomePage from './pages/HomePage' |
|||
import LoginPage from './pages/LoginPage' |
|||
import Header from './components/Header' |
|||
|
|||
import PrivateRoute from './utils/PrivateRoute' |
|||
|
|||
|
|||
function App() { |
|||
return ( |
|||
<div className="App"> |
|||
<Router> |
|||
<AuthProvider> |
|||
<Header/> |
|||
<Routes> |
|||
<Route path="/" element={<PrivateRoute><HomePage/></PrivateRoute>} /> |
|||
<Route path="/login" element={<LoginPage/>}/> |
|||
</Routes> |
|||
</AuthProvider> |
|||
</Router> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
export default App; |
|||
@ -0,0 +1,23 @@ |
|||
import React, { useContext } from 'react' |
|||
import { Link } from 'react-router-dom' |
|||
import AuthContext from '../context/AuthContext' |
|||
|
|||
const Header = () => { |
|||
let { user, logoutUser } = useContext(AuthContext) |
|||
|
|||
return ( |
|||
<div> |
|||
<Link to="/">Home</Link> |
|||
<span> | </span> |
|||
{user ? ( |
|||
<span onClick={logoutUser}>Logout</span> |
|||
) : ( |
|||
<Link to="/login" >Login</Link> |
|||
)} |
|||
{user && <p>Hello {user.username}!</p>} |
|||
|
|||
</div> |
|||
) |
|||
} |
|||
|
|||
export default Header |
|||
@ -0,0 +1,97 @@ |
|||
import { createContext, useState, useEffect } from 'react' |
|||
import jwtDecode from 'jwt-decode'; |
|||
import { useNavigate } from 'react-router-dom' |
|||
|
|||
const AuthContext = createContext() |
|||
|
|||
export default AuthContext; |
|||
|
|||
export const AuthProvider = ({children}) => { |
|||
|
|||
let [user, setUser] = useState(() => (localStorage.getItem('authTokens') ? jwtDecode(localStorage.getItem('authTokens')) : null)) |
|||
let [authTokens, setAuthTokens] = useState(() => (localStorage.getItem('authTokens') ? JSON.parse(localStorage.getItem('authTokens')) : null)) |
|||
let [loading, setLoading] = useState(true) |
|||
|
|||
const navigate = useNavigate() |
|||
|
|||
let loginUser = async (e) => { |
|||
e.preventDefault() |
|||
const response = await fetch('http://127.0.0.1:8000/api/token/', { |
|||
method: 'POST', |
|||
headers: { |
|||
'Content-Type': 'application/json' |
|||
}, |
|||
body: JSON.stringify({username: e.target.username.value, password: e.target.password.value }) |
|||
}); |
|||
|
|||
let data = await response.json(); |
|||
|
|||
if(data){ |
|||
localStorage.setItem('authTokens', JSON.stringify(data)); |
|||
setAuthTokens(data) |
|||
setUser(jwtDecode(data.access)) |
|||
navigate('/') |
|||
} else { |
|||
alert('Something went wrong while logging in the user!') |
|||
} |
|||
} |
|||
|
|||
let logoutUser = () => { |
|||
// e.preventDefault()
|
|||
localStorage.removeItem('authTokens') |
|||
setAuthTokens(null) |
|||
setUser(null) |
|||
navigate('/login') |
|||
} |
|||
|
|||
const updateToken = async () => { |
|||
const response = await fetch('http://127.0.0.1:8000/api/token/refresh/', { |
|||
method: 'POST', |
|||
headers: { |
|||
'Content-Type':'application/json' |
|||
}, |
|||
body:JSON.stringify({refresh:authTokens?.refresh}) |
|||
}) |
|||
|
|||
const data = await response.json() |
|||
if (response.status === 200) { |
|||
setAuthTokens(data) |
|||
setUser(jwtDecode(data.access)) |
|||
localStorage.setItem('authTokens',JSON.stringify(data)) |
|||
} else { |
|||
logoutUser() |
|||
} |
|||
|
|||
if(loading){ |
|||
setLoading(false) |
|||
} |
|||
} |
|||
|
|||
let contextData = { |
|||
user:user, |
|||
authTokens:authTokens, |
|||
loginUser:loginUser, |
|||
logoutUser:logoutUser, |
|||
} |
|||
|
|||
useEffect(()=>{ |
|||
if(loading){ |
|||
updateToken() |
|||
} |
|||
|
|||
const REFRESH_INTERVAL = 1000 * 60 * 4 // 4 minutes
|
|||
let interval = setInterval(()=>{ |
|||
if(authTokens){ |
|||
updateToken() |
|||
} |
|||
}, REFRESH_INTERVAL) |
|||
return () => clearInterval(interval) |
|||
|
|||
},[authTokens, loading]) |
|||
|
|||
return( |
|||
<AuthContext.Provider value={contextData}> |
|||
{children} |
|||
</AuthContext.Provider> |
|||
) |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
body { |
|||
margin: 0; |
|||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', |
|||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', |
|||
sans-serif; |
|||
-webkit-font-smoothing: antialiased; |
|||
-moz-osx-font-smoothing: grayscale; |
|||
} |
|||
|
|||
code { |
|||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', |
|||
monospace; |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
import React from 'react'; |
|||
import ReactDOM from 'react-dom/client'; |
|||
import './index.css'; |
|||
import App from './App'; |
|||
|
|||
const root = ReactDOM.createRoot(document.getElementById('root')); |
|||
root.render( |
|||
<React.StrictMode> |
|||
<App /> |
|||
</React.StrictMode> |
|||
); |
|||
@ -0,0 +1,38 @@ |
|||
import React, { useState, useEffect, useContext } from 'react' |
|||
import AuthContext from '../context/AuthContext'; |
|||
|
|||
const HomePage = () => { |
|||
const { authTokens, logoutUser } = useContext(AuthContext); |
|||
let [profile, setProfile] = useState([]) |
|||
|
|||
useEffect(() => { |
|||
getProfile() |
|||
},[]) |
|||
|
|||
const getProfile = async() => { |
|||
let response = await fetch('http://127.0.0.1:8000/api/profile', { |
|||
method: 'GET', |
|||
headers:{ |
|||
'Content-Type': 'application/json', |
|||
'Authorization':'Bearer ' + String(authTokens.access) |
|||
} |
|||
}) |
|||
let data = await response.json() |
|||
console.log(data) |
|||
if(response.status === 200){ |
|||
setProfile(data) |
|||
} else if(response.statusText === 'Unauthorized'){ |
|||
logoutUser() |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<div> |
|||
<p>You are logged in to the homepage!</p> |
|||
<p>Name: {profile.first_name} {profile.last_name}</p> |
|||
<p>Email: {profile.email}</p> |
|||
</div> |
|||
) |
|||
} |
|||
|
|||
export default HomePage |
|||
@ -0,0 +1,19 @@ |
|||
import React, {useContext} from 'react' |
|||
import AuthContext from '../context/AuthContext' |
|||
|
|||
const LoginPage = () => { |
|||
|
|||
let {loginUser} = useContext(AuthContext) |
|||
|
|||
return ( |
|||
<div> |
|||
<form onSubmit={loginUser}> |
|||
<input type="text" name="username" placeholder="Enter username"/> |
|||
<input type="password" name="password" placeholder="enter password"/> |
|||
<input type="submit"/> |
|||
</form> |
|||
</div> |
|||
) |
|||
} |
|||
|
|||
export default LoginPage |
|||
@ -0,0 +1,11 @@ |
|||
import { Navigate } from 'react-router-dom' |
|||
import { useContext } from 'react' |
|||
import AuthContext from '../context/AuthContext'; |
|||
|
|||
const PrivateRoute = ({children, ...rest}) => { |
|||
let { user } = useContext(AuthContext) |
|||
|
|||
return !user ? <Navigate to='/login'/> : children; |
|||
} |
|||
|
|||
export default PrivateRoute; |
|||
Loading…
Reference in new issue