Browse Source

first commit

master
dzecevic 2 years ago
commit
46d06cd463
  1. 2
      .gitattributes
  2. 130
      .gitignore
  3. 9
      README.md
  4. 29321
      package-lock.json
  5. 40
      package.json
  6. BIN
      public/favicon.ico
  7. 43
      public/index.html
  8. BIN
      public/logo192.png
  9. BIN
      public/logo512.png
  10. 25
      public/manifest.json
  11. 3
      public/robots.txt
  12. 28
      src/App.js
  13. 23
      src/components/Header.js
  14. 97
      src/context/AuthContext.js
  15. 13
      src/index.css
  16. 11
      src/index.js
  17. 38
      src/pages/HomePage.js
  18. 19
      src/pages/LoginPage.js
  19. 11
      src/utils/PrivateRoute.js

2
.gitattributes

@ -0,0 +1,2 @@
*.ico filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text

130
.gitignore

@ -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.*

9
README.md

@ -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.

29321
package-lock.json

File diff suppressed because it is too large

40
package.json

@ -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"
]
}
}

BIN
public/favicon.ico (Stored with Git LFS)

Binary file not shown.

43
public/index.html

@ -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>

BIN
public/logo192.png (Stored with Git LFS)

Binary file not shown.

BIN
public/logo512.png (Stored with Git LFS)

Binary file not shown.

25
public/manifest.json

@ -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"
}

3
public/robots.txt

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

28
src/App.js

@ -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;

23
src/components/Header.js

@ -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

97
src/context/AuthContext.js

@ -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>
)
}

13
src/index.css

@ -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;
}

11
src/index.js

@ -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>
);

38
src/pages/HomePage.js

@ -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

19
src/pages/LoginPage.js

@ -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

11
src/utils/PrivateRoute.js

@ -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…
Cancel
Save