Skip to content

Commit

Permalink
Complete App
Browse files Browse the repository at this point in the history
  • Loading branch information
remy727 committed Jan 14, 2021
1 parent 4211c07 commit c4610d5
Show file tree
Hide file tree
Showing 20 changed files with 507 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .eslintcache
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[{"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reportWebVitals.js":"1","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/App.js":"2","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/index.js":"3","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/store/index.js":"4","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/index.js":"5","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/alert.reducer.js":"6","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/authentication.reducer.js":"7","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/users.reducer.js":"8","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/constants/index.js":"9","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/constants/alert.constants.js":"10"},{"size":354,"mtime":1610628389225,"results":"11","hashOfConfig":"12"},{"size":93,"mtime":1610628310066,"results":"13","hashOfConfig":"12"},{"size":531,"mtime":1610632564902,"results":"14","hashOfConfig":"12"},{"size":513,"mtime":1610632616964,"results":"15","hashOfConfig":"12"},{"size":283,"mtime":1610632295949,"results":"16","hashOfConfig":"12"},{"size":435,"mtime":1610633233420,"results":"17","hashOfConfig":"12"},{"size":606,"mtime":1610633371356,"results":"18","hashOfConfig":"12"},{"size":418,"mtime":1610633463017,"results":"19","hashOfConfig":"12"},{"size":66,"mtime":1610633043021,"results":"20","hashOfConfig":"12"},{"size":145,"mtime":1610632986395,"results":"21","hashOfConfig":"12"},{"filePath":"22","messages":"23","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"gwrit2",{"filePath":"24","messages":"25","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"26","messages":"27","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"28","messages":"29","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"30","messages":"31","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"32","messages":"33","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"34","messages":"35","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"36","messages":"37","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"38","messages":"39","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"40","messages":"41","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reportWebVitals.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/App.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/index.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/store/index.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/index.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/alert.reducer.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/authentication.reducer.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/users.reducer.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/constants/index.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/constants/alert.constants.js",[]]
[{"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reportWebVitals.js":"1","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/App.js":"2","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/index.js":"3","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/store/index.js":"4","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/index.js":"5","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/alert.reducer.js":"6","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/authentication.reducer.js":"7","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/users.reducer.js":"8","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/constants/index.js":"9","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/constants/alert.constants.js":"10","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/helpers/history.js":"11","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/actions/index.js":"12","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/actions/user.actions.js":"13","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/route/PrivateRoute.js":"14","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/pages/home/index.jsx":"15","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/pages/login/index.jsx":"16","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/services/user.service.js":"17","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/helpers/auth-header.js":"18","/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/helpers/fake-backend.js":"19"},{"size":354,"mtime":1610628389225,"results":"20","hashOfConfig":"21"},{"size":1320,"mtime":1610636587367,"results":"22","hashOfConfig":"21"},{"size":630,"mtime":1610637085102,"results":"23","hashOfConfig":"21"},{"size":513,"mtime":1610632616964,"results":"24","hashOfConfig":"21"},{"size":283,"mtime":1610632295949,"results":"25","hashOfConfig":"21"},{"size":435,"mtime":1610633233420,"results":"26","hashOfConfig":"21"},{"size":606,"mtime":1610633371356,"results":"27","hashOfConfig":"21"},{"size":417,"mtime":1610633692257,"results":"28","hashOfConfig":"21"},{"size":66,"mtime":1610633043021,"results":"29","hashOfConfig":"21"},{"size":145,"mtime":1610632986395,"results":"30","hashOfConfig":"21"},{"size":93,"mtime":1610633762502,"results":"31","hashOfConfig":"21"},{"size":62,"mtime":1610634014769,"results":"32","hashOfConfig":"21"},{"size":1363,"mtime":1610634870997,"results":"33","hashOfConfig":"21"},{"size":356,"mtime":1610635268369,"results":"34","hashOfConfig":"21"},{"size":1231,"mtime":1610637277674,"results":"35","hashOfConfig":"21"},{"size":3373,"mtime":1610636623295,"results":"36","hashOfConfig":"21"},{"size":1389,"mtime":1610634600640,"results":"37","hashOfConfig":"21"},{"size":247,"mtime":1610634345658,"results":"38","hashOfConfig":"21"},{"size":2081,"mtime":1610637013709,"results":"39","hashOfConfig":"21"},{"filePath":"40","messages":"41","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"gwrit2",{"filePath":"42","messages":"43","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"44","messages":"45","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"46","messages":"47","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"48","messages":"49","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"50","messages":"51","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"52","messages":"53","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"54","messages":"55","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"56","messages":"57","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"58","messages":"59","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"60","messages":"61","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"62","messages":"63","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"64","messages":"65","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"66","messages":"67","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"68","messages":"69","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"70","messages":"71","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"72","messages":"73","errorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"74","messages":"75","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"76","messages":"77","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reportWebVitals.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/App.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/index.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/store/index.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/index.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/alert.reducer.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/authentication.reducer.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/reducers/users.reducer.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/constants/index.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/constants/alert.constants.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/helpers/history.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/actions/index.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/actions/user.actions.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/route/PrivateRoute.js",["78"],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/pages/home/index.jsx",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/pages/login/index.jsx",["79"],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/services/user.service.js",["80"],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/helpers/auth-header.js",[],"/Users/admin/work/Norio.Umata/Own/react-redux-jwt-authentication/src/helpers/fake-backend.js",[],{"ruleId":"81","severity":1,"message":"82","line":1,"column":17,"nodeType":"83","messageId":"84","endLine":1,"endColumn":26},{"ruleId":"85","severity":1,"message":"86","line":67,"column":15,"nodeType":"87","endLine":67,"endColumn":950},{"ruleId":"88","severity":2,"message":"89","line":44,"column":9,"nodeType":"83","messageId":"90","endLine":44,"endColumn":17},"no-unused-vars","'Component' is defined but never used.","Identifier","unusedVar","jsx-a11y/alt-text","img elements must have an alt prop, either with meaningful text, or an empty string for decorative images.","JSXOpeningElement","no-restricted-globals","Unexpected use of 'location'.","defaultMessage"]
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"history": "^5.0.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.2.2",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.1",
"redux": "^4.0.5",
"redux-logger": "^3.0.6",
Expand Down
5 changes: 5 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<style>
a { cursor: pointer; }
.help-block { font-size: 12px; }
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
56 changes: 50 additions & 6 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,52 @@
function App() {
return (
<div className="App">
</div>
)
import React from 'react'
import { Router, Route } from 'react-router-dom'
import { connect } from 'react-redux'

import { history } from './helpers'
import { alertActions } from './actions'
import { PrivateRoute } from './route'
import { HomePage } from './pages/home'
import { LoginPage } from './pages/login'

class App extends React.Component {
constructor(props) {
super(props)

const { dispatch } = this.props
history.listen((location, action) => {
// clear alert on location change
dispatch(alertActions.clear())
})
}

render() {
const { alert } = this.props
return (
<div className="jumbotron">
<div className="container">
<div className="col-sm-8 col-sm-offset-2">
{alert.message &&
<div className={`alert ${alert.type}`}>{alert.message}</div>
}
<Router history={history}>
<div>
<PrivateRoute exact path="/" component={HomePage} />
<Route path="/login" component={LoginPage} />
</div>
</Router>
</div>
</div>
</div>
)
}
}

function mapStateToProps(state) {
const { alert } = state
return {
alert
}
}

export default App
const connectedApp = connect(mapStateToProps)(App)
export { connectedApp as App }
19 changes: 19 additions & 0 deletions src/actions/alert.actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { alertConstants } from '../constants'

function success(message) {
return { type: alertConstants.SUCCESS, message }
}

function error(message) {
return { type: alertConstants.ERROR, message }
}

function clear() {
return { type: alertConstants.CLEAR }
}

export const alertActions = {
success,
error,
clear
}
2 changes: 2 additions & 0 deletions src/actions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './alert.actions'
export * from './user.actions'
53 changes: 53 additions & 0 deletions src/actions/user.actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { userConstants } from '../constants'
import { userService } from '../services'
import { alertActions } from './'
import { history } from '../helpers'

function login(username, password) {
return dispatch => {
dispatch(request({ username }))

userService.login(username, password)
.then(
user => {
dispatch(success(user))
history.push('/')
},
error => {
dispatch(failure(error))
dispatch(alertActions.error(error))
}
)
}

function request(user) { return { type: userConstants.LOGIN_REQUEST, user } }
function success(user) { return { type: userConstants.LOGIN_SUCCESS, user } }
function failure(error) { return { type: userConstants.LOGIN_FAILURE, error } }
}

function logout() {
userService.logout()
return { type: userConstants.LOGOUT }
}

function getAll() {
return dispatch => {
dispatch(request())

userService.getAll()
.then(
users => dispatch(success(users)),
error => dispatch(failure(error))
)
}

function request() { return { type: userConstants.GETALL_REQUEST } }
function success(users) { return { type: userConstants.GETALL_SUCCESS, users } }
function failure(error) { return { type: userConstants.GETALL_FAILURE, error } }
}

export const userActions = {
login,
logout,
getAll
}
10 changes: 10 additions & 0 deletions src/helpers/auth-header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function authHeader() {
// return authorization header with jwt token
let user = JSON.parse(localStorage.getItem('user'))

if (user && user.token) {
return { 'Authorization': 'Bearer ' + user.token }
} else {
return {}
}
}
57 changes: 57 additions & 0 deletions src/helpers/fake-backend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
export function configureFakeBackend() {
let users = [{ id: 1, username: 'test', password: 'test', firstName: 'Test', lastName: 'User' }]
let realFetch = window.fetch
window.fetch = function (url, opts) {
return new Promise((resolve, reject) => {
// wrap in timeout to simulate server api call
setTimeout(() => {

// authenticate
if (url.endsWith('/users/authenticate') && opts.method === 'POST') {
// get parameters from post request
let params = JSON.parse(opts.body)

// find if any user matches login credentials
let filteredUsers = users.filter(user => {
return user.username === params.username && user.password === params.password
})

if (filteredUsers.length) {
// if login details are valid return user details and fake jwt token
let user = filteredUsers[0]
let responseJson = {
id: user.id,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
token: 'fake-jwt-token'
}
resolve({ ok: true, text: () => Promise.resolve(JSON.stringify(responseJson)) })
} else {
// else return error
reject('Username or password is incorrect')
}

return
}

// get users
if (url.endsWith('/users') && opts.method === 'GET') {
// check for fake auth token in header and return users if valid, this security is implemented server side in a real application
if (opts.headers && opts.headers.Authorization === 'Bearer fake-jwt-token') {
resolve({ ok: true, text: () => Promise.resolve(JSON.stringify(users))})
} else {
// return 401 not authorised if token is null or invalid
reject('Unauthorised')
}

return
}

// pass through any requests not handled above
realFetch(url, opts).then(response => resolve(response))

}, 500)
})
}
}
3 changes: 3 additions & 0 deletions src/helpers/history.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createBrowserHistory } from 'history'

export const history = createBrowserHistory()
3 changes: 3 additions & 0 deletions src/helpers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './history'
export * from './auth-header'
export * from './fake-backend'
6 changes: 5 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { render } from 'react-dom'
import { Provider } from 'react-redux'

import store from './store'
import App from './App'
import { App } from './App'
import reportWebVitals from './reportWebVitals'

// setup fake backend
import { configureFakeBackend } from './helpers'
configureFakeBackend()

render(
<Provider store={store}>
<App />
Expand Down
48 changes: 48 additions & 0 deletions src/pages/home/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'

import { userActions } from '../../actions'

class HomePage extends React.Component {
componentDidMount() {
this.props.dispatch(userActions.getAll())
}

render() {
const { user, users } = this.props
return (
<div className="col-md-6 col-md-offset-3">
<h1>Hi {user.firstName}!</h1>
<p>You're logged in with React & JWT!!</p>
<h3>Users from secure api end point:</h3>
{users.loading && <em>Loading users...</em>}
{users.error && <span className="text-danger">ERROR: {users.error}</span>}
{users.items &&
<ul>
{users.items.map((user, index) =>
<li key={user.id}>
{user.firstName + ' ' + user.lastName}
</li>
)}
</ul>
}
<p>
<Link to="/login">Logout</Link>
</p>
</div>
)
}
}

function mapStateToProps(state) {
const { users, authentication } = state
const { user } = authentication
return {
user,
users
}
}

const connectedHomePage = connect(mapStateToProps)(HomePage)
export { connectedHomePage as HomePage }
Loading

0 comments on commit c4610d5

Please sign in to comment.