Skip to content

Commit fc750e2

Browse files
authored
Merge pull request #235 from CodeForPhilly/156-UI
Login UI
2 parents 9d2860d + 69596de commit fc750e2

File tree

16 files changed

+660
-37
lines changed

16 files changed

+660
-37
lines changed

src/client/default.conf.template

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ server {
1111
location / {
1212
root /usr/share/nginx/html;
1313
index index.html index.htm;
14+
try_files $uri /index.html; # forward all requests to the index.html for react
1415
}
15-
16+
1617
location /api {
1718
try_files $uri @backend;
1819
}

src/client/package-lock.json

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

src/client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"@testing-library/jest-dom": "^4.2.4",
1010
"@testing-library/react": "^9.5.0",
1111
"@testing-library/user-event": "^7.2.1",
12+
"jsonwebtoken": "^8.5.1",
1213
"lodash": "^4.17.20",
1314
"moment": "^2.29.1",
1415
"react": "^16.13.1",

src/client/src/App.js

Lines changed: 120 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,142 @@
1-
import React from "react";
1+
import React from 'react'
2+
23
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom';
34

4-
import Header from "./components/Header";
5+
import Header, {AdminHeader} from "./components/Header";
56

67
import HomePage from './pages/Home';
78
import Admin from './pages/Admin';
89
import DataView from './pages/DataView360/DataView360';
910
import About from './pages/About';
11+
import Login from './components/Login/Login';
12+
import Check from './pages/Check/Check';
13+
import useToken from './components/Login/useToken';
14+
var jwt = require('jsonwebtoken');
15+
16+
// Triggers token expiration check
17+
const sleep = time => new Promise(resolve => setTimeout(resolve, time))
18+
const expTimer = () => sleep(500).then(() => ({}))
19+
20+
const AuthContext = React.createContext()
21+
22+
function AuthProvider({children}) {
23+
const [state, setState] = React.useState({
24+
status: 'pending',
25+
error: null,
26+
user: null, })
27+
28+
React.useEffect(() => {
29+
expTimer().then(
30+
user => setState({status: 'success', error: null, user}) //
31+
)
32+
}, )
33+
34+
return (
35+
<AuthContext.Provider value={state}>
36+
{state.status === 'pending' ? (
37+
'App ACP: Loading...'
38+
) : state.status === 'error' ? (
39+
<div>
40+
Oh no
41+
<div>
42+
<pre>{state.error.message}</pre>
43+
</div>
44+
</div>
45+
) : (
46+
children
47+
)}
48+
</AuthContext.Provider>
49+
)
50+
}
51+
52+
function useAuthState() {
53+
const state = React.useContext(AuthContext)
54+
const isPending = state.status === 'pending'
55+
const isError = state.status === 'error'
56+
const isSuccess = state.status === 'success'
57+
const isAuthenticated = state.user && isSuccess
58+
return {
59+
...state,
60+
isPending,
61+
isError,
62+
isSuccess,
63+
isAuthenticated,
64+
}
65+
}
66+
67+
68+
function AuthenticatedApp() {
69+
70+
const { access_token, setToken } = useToken();
71+
72+
73+
var decoded = jwt.decode(access_token, { complete: true });
74+
const userName = decoded?.payload.sub;
75+
const userRole = decoded?.payload.role;
76+
var expTime = decoded?.payload.exp - Date.now()/1000;
77+
78+
const jwtExpired = expTime <= 0
79+
80+
const hdr = userRole === 'admin' ? <AdminHeader /> : <Header /> // If we're going to display a header, which one?
1081

11-
/*basic routing of the app*/
12-
export default function App(props) {
13-
return (
82+
return (
83+
<>
1484
<Router>
15-
<Header/>
16-
<Switch>
85+
86+
{ !jwtExpired && hdr ? hdr : '' /* Above-chosen header, or if logged out, no header */ }
87+
88+
{ /* If not logged in, show login screen */
89+
(!access_token | jwtExpired) ? <Login setToken={setToken} /> : <Switch>
90+
1791
<Route exact path="/">
1892
<HomePage/>
1993
</Route>
20-
<Route path="/upload">
21-
<Admin/>
22-
</Route>
94+
95+
96+
{ /* If an admin, render Upload page */
97+
userRole === 'admin' &&
98+
<Route path="/admin">
99+
<Admin/>
100+
</Route>
101+
}
102+
103+
23104
<Route path="/about">
24105
<About/>
25106
</Route>
107+
26108
<Route path="/dataView">
27109
<DataView/>
28110
</Route>
111+
112+
<Route path="/check">
113+
<Check />
114+
</Route>
29115
</Switch>
116+
}
117+
30118
</Router>
31-
);
119+
</>
120+
)
121+
}
122+
123+
124+
function Home() {
125+
const {user} = useAuthState()
126+
const { access_token, setToken } = useToken();
127+
return user ? <AuthenticatedApp /> : <Login setToken={setToken} />
128+
}
129+
130+
function App() {
131+
const { access_token, setToken } = useToken();
132+
133+
return (
134+
<AuthProvider>
135+
<div>
136+
<Home />
137+
</div>
138+
</AuthProvider>
139+
)
32140
}
33141

142+
export default App

src/client/src/components/Header.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,36 @@ import React from "react";
22
import { Link as RouterLink } from "react-router-dom";
33
import {AppBar, Button, Toolbar, Typography } from "@material-ui/core";
44
import styles from "./styles/header.module.css";
5+
import admstyles from "./styles/adm_header.module.css";
56

6-
export default function Header(props){
7+
export function AdminHeader(props){ // This one if user has the ADMIN role
78

89
return(
9-
<AppBar position="static" id="header" className={styles.header} elevation={1}>
10+
<AppBar position="static" id="header" className={admstyles.header} elevation={1}>
1011
<Toolbar style={{"minWidth":"100", "dipslay":"flex", "justifyContent":"space-between"}}>
1112
<Typography className={styles.header_logo} variant="h6">PAWS Data Pipeline</Typography>
1213
<div style={{"display":"flex", "justifyContent":"space-between", "margin":"16px 6px 16px 16px"}}>
13-
<Button className={styles.header_link} component={RouterLink} to="/upload">Admin</Button>
14+
15+
<Button className={styles.header_link} component={RouterLink} to="/admin">Admin</Button>
1416
<Button className={styles.header_link} component={RouterLink} to="/dataView">360 DataView</Button>
15-
<Button className={styles.header_link} component={RouterLink} to="/about">About</Button>
17+
{ /* <Button className={styles.header_link} component={RouterLink} to="/check">Check</Button> */ }
1618
</div>
1719
</Toolbar>
1820
</AppBar>
1921
);
2022
}
23+
24+
export default function Header(props){ // This one if user only has USER role - no link to Admin page
25+
26+
return(
27+
<AppBar position="static" id="header" className={styles.header} elevation={1}>
28+
<Toolbar style={{"minWidth":"100", "dipslay":"flex", "justifyContent":"space-between"}}>
29+
<Typography className={styles.header_logo} variant="h6">PAWS Data Pipeline</Typography>
30+
<div style={{"display":"flex", "justifyContent":"space-between", "margin":"16px 6px 16px 16px"}}>
31+
<Button className={styles.header_link} component={RouterLink} to="/dataView">360 DataView</Button>
32+
{ /* <Button className={styles.header_link} component={RouterLink} to="/check">Check</Button> */ }
33+
</div>
34+
</Toolbar>
35+
</AppBar>
36+
);
37+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.login-wrapper {
2+
display: flex;
3+
flex-direction: column;
4+
align-items: center;
5+
}

0 commit comments

Comments
 (0)