Skip to content
Snippets Groups Projects
Commit 865f1ed4 authored by Bjarke Madsen's avatar Bjarke Madsen
Browse files

Merge branch 'feature/COMP-223-Login-Flow' into 'develop'

Feature/comp 223 login flow

See merge request !53
parents 9cdb938f 5014b1ea
No related branches found
No related tags found
1 merge request!53Feature/comp 223 login flow
Showing
with 246 additions and 35 deletions
......@@ -19,7 +19,8 @@
"react/prop-types": "off",
"@typescript-eslint/no-unused-vars": [
"warn", { "argsIgnorePattern": "^_" }
]
],
"@typescript-eslint/no-empty-function": "off"
},
"settings": {
"react": {
......
......@@ -12,7 +12,7 @@ import { FilterSelection } from "./Schema";
import SubOrganisation from "./pages/SubOrganisation";
import ParentOrganisation from "./pages/ParentOrganisation";
import ECProjects from "./pages/ECProjects";
import SidebarProvider from "./helpers/SidebarProvider";
import Providers from "./Providers";
import PolicyPage from "./pages/Policy";
......@@ -25,8 +25,8 @@ function App(): ReactElement {
return (
<div className="app">
<Router>
<ExternalPageNavBar />
<SidebarProvider>
<Providers>
<ExternalPageNavBar />
<Routes>
<Route path="/budget" element={<BudgetPage filterSelection={filterSelection} setFilterSelection={setFilterSelection} />} />
<Route path="/funding" element={<FundingSourcePage filterSelection={filterSelection} setFilterSelection={setFilterSelection} />} />
......@@ -40,7 +40,8 @@ function App(): ReactElement {
<Route path="/data" element={<CompendiumData />} />
<Route path="*" element={<Landing />} />
</Routes>
</SidebarProvider>
</Providers>
<GeantFooter />
</Router>
......
import React, { ReactElement } from "react";
import SidebarProvider from "./helpers/SidebarProvider";
import UserProvider from "./shared/UserProvider";
function Providers({ children }): ReactElement {
return (
<SidebarProvider>
<UserProvider>
{children}
</UserProvider>
</SidebarProvider>
);
}
export default Providers;
import React, { ReactElement } from "react";
import { Container, Row } from "react-bootstrap";
import { Col, Container, Row } from "react-bootstrap";
import Login from "../../shared/Login";
import GeantLogo from "../../images/geant_logo_f2020_new.svg";
/**
......@@ -14,26 +15,36 @@ function ExternalPageNavBar(): ReactElement {
<Container>
<Row>
<nav>
<a href="https://geant.org/"><img src={GeantLogo} /></a>
<Col xs={10}>
<div className="nav-wrapper">
<nav className="header-nav">
<a href="https://geant.org/"><img src={GeantLogo} /></a>
<ul>
<li><a href="https://network.geant.org/">NETWORK</a></li>
<li><a href="https://geant.org/services/">SERVICES</a></li>
<li><a href="https://community.geant.org/">COMMUNITY</a></li>
<li><a href="https://tnc23.geant.org/">TNC</a></li>
<li><a href="https://geant.org/projects/">PROJECTS</a></li>
<li><a href="https://connect.geant.org/">CONNECT</a></li>
<li><a href="https://impact.geant.org/">IMPACT</a></li>
<li><a href="https://careers.geant.org/">CAREERS</a></li>
<li><a href="https://about.geant.org/">ABOUT</a></li>
<li><a href="https://connect.geant.org/community-news">NEWS</a></li>
<li><a href="https://resources.geant.org/">RESOURCES</a></li>
</ul>
</nav>
<ul>
<li><a className="nav-link-entry" href="https://network.geant.org/">NETWORK</a></li>
<li><a className="nav-link-entry" href="https://geant.org/services/">SERVICES</a></li>
<li><a className="nav-link-entry" href="https://community.geant.org/">COMMUNITY</a></li>
<li><a className="nav-link-entry" href="https://tnc23.geant.org/">TNC</a></li>
<li><a className="nav-link-entry" href="https://geant.org/projects/">PROJECTS</a></li>
<li><a className="nav-link-entry" href="https://connect.geant.org/">CONNECT</a></li>
<li><a className="nav-link-entry" href="https://impact.geant.org/">IMPACT</a></li>
<li><a className="nav-link-entry" href="https://careers.geant.org/">CAREERS</a></li>
<li><a className="nav-link-entry" href="https://about.geant.org/">ABOUT</a></li>
<li><a className="nav-link-entry" href="https://connect.geant.org/community-news">NEWS</a></li>
<li><a className="nav-link-entry" href="https://resources.geant.org/">RESOURCES</a></li>
</ul>
</nav>
</div>
</Col>
<Col align="right">
<Login />
</Col>
</Row>
</Container>
</div>
</div >
);
}
......
......@@ -6,7 +6,7 @@ interface Props {
const sidebarContext = createContext<{
show: boolean;
toggle: React.Dispatch<any>;
toggle: () => void;
}>({
show: false,
toggle: () => { }
......
......@@ -2,13 +2,49 @@
@import 'scss/base/text';
@import 'scss/layout/components';
.external-page-nav-bar {
background-color: #003753;
.nav-link-entry {
border-radius: 2px;
font-family: "Open Sans", sans-serif;
font-size: 0.9rem;
font-weight: 600;
text-decoration: none;
color: #b0cde1;
padding: 10px;
}
.nav-link {
display: flex;
-webkit-box-align: center;
align-items: center;
height: 60px;
.nav-link-entry:hover {
color: #003753;
background-color: #b0cde1;
}
ul {
line-height: 1.3;
text-transform: uppercase;
list-style: none;
li {
float: left;
}
}
}
.nav-wrapper {
display: flex;
-webkit-box-align: center;
align-items: center;
height: 60px;
}
.header-nav {
width: 100%;
img {
float: left;
......@@ -42,8 +78,14 @@
}
}
.external-page-nav-bar {
background-color: #003753;
color: #b0cde1;
height: 60px;
}
.app {
display: flex;
// display: flex;
flex-direction: column;
min-height: 100vh;
}
}
\ No newline at end of file
.btn-login {
--bs-btn-color: #fff;
--bs-btn-border-color: #6c757d;
--bs-btn-border-radius: none;
// active
--bs-btn-active-color: #fff;
// see https://stackoverflow.com/a/76213205 for why this syntax is necessary
--bs-btn-active-bg: #{$yellow-orange};
--bs-btn-active-border-color: #{$yellow-orange};
// hover
--bs-btn-hover-color: rgb(0, 63, 95);
--bs-btn-hover-bg: #{$yellow-orange};
--bs-btn-hover-border-color: #{$yellow-orange};
// disabled
--bs-btn-disabled-color: #6c757d;
--bs-btn-disabled-bg: transparent;
// --bs-btn-disabled-border-color: #6c757d;
border: 2px solid $yellow-orange;
}
\ No newline at end of file
......@@ -3,12 +3,17 @@
@import '../abstracts/variables';
@import '../layout/Sidebar';
@import '../layout/SectionNavigation';
@import '../layout/Login';
.rounded-border {
border-radius: 25px;
border: 1px solid $light-ash-grey
}
.card {
--bs-card-border-color: "";
}
.grow {
display: flex;
flex-direction: column;
......@@ -288,4 +293,5 @@ $funding-source-colors: (
.color-of-badge-blank {
background-color: rgb(0, 0, 0, 0);
}
\ No newline at end of file
}
import React, { useContext, useEffect } from 'react';
import { Button } from 'react-bootstrap';
import { userContext } from './UserProvider';
const Login = () => {
const { user, logout } = useContext(userContext);
if (!user.name) {
return (
<a href='/login'><Button variant='login' active={false} style={{ maxWidth: '6rem' }}><span>Login</span></Button></a>
)
}
if (user.permissions.active) {
return <div className='nav-link' style={{ float: 'right' }}><a className='nav-link-entry' href="/survey">Go to Survey</a></div>
}
return <div className='nav-link' style={{ float: 'right' }}>
<span>{'<'}{user.name}{'>'}</span>
</div>
}
export default Login
\ No newline at end of file
import React, { createContext, useState, useEffect } from 'react';
interface Props {
children: React.ReactNode;
}
interface User {
name: string,
email?: string,
permissions: {
admin: boolean,
active: boolean,
}
}
async function fetchUser(): Promise<User> {
const response = await fetch('/api/user');
const user = await response.json();
return user
}
const anonymousUser: User = { 'name': '', email: '', permissions: { admin: false, active: false } };
const userContext = createContext<{
user: User;
logout: () => void;
}>({
user: anonymousUser,
logout: () => { }
});
const UserProvider: React.FC<Props> = ({ children }) => {
const [user, setUser] = useState<User>(anonymousUser);
async function logoutUser() {
await fetch('/logout');
setUser(anonymousUser);
}
useEffect(() => {
fetchUser().then(user => {
setUser(user)
});
}, []);
return (
<userContext.Provider value={{ user, logout: logoutUser }}>
{children}
</userContext.Provider>
);
};
export { userContext };
export default UserProvider;
\ No newline at end of file
......@@ -85,6 +85,10 @@ const config: Configuration = {
historyApiFallback: true,
proxy: {
"/api": "http://127.0.0.1:5000",
"/login": "http://127.0.0.1:5000",
"/logout": "http://127.0.0.1:5000",
"/authorize": "http://127.0.0.1:5000",
"/survey/*": "http://127.0.0.1:4001"
},
},
plugins: [
......
......@@ -32,8 +32,8 @@ def authorize():
user = create_user(profile['email'], profile['name'], profile['sub'])
login_user(user)
# redirect to /survey since we only require login for this part of the app
return redirect(url_for('compendium-v2-default.survey_index'))
# redirect to /
return redirect(url_for('compendium-v2-default.index'))
@routes.route("/logout")
......
......@@ -6,7 +6,7 @@ from flask_login import current_user, AnonymousUserMixin # type: ignore
from sqlalchemy import select
from compendium_v2.db import db
from compendium_v2.db.auth_model import User
from compendium_v2.db.auth_model import User, ROLES
from compendium_v2.routes import common
......@@ -17,13 +17,23 @@ USER_RESPONSE_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'definitions': {
'permissions': {
'type': 'object',
'properties': {
'admin': {'type': 'boolean'},
'active': {'type': 'boolean'},
},
'required': ['admin', 'active'],
'additionalProperties': False
},
'user': {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'email': {'type': ['string', 'null']},
'permissions': {'$ref': '#/definitions/permissions'},
},
'required': ['name'],
'required': ['name', 'email', 'permissions'],
'additionalProperties': False
}
},
......@@ -50,10 +60,20 @@ def current_user_view() -> Any:
def _extract_data(entry: Union[User, AnonymousUserMixin]):
if isinstance(entry, AnonymousUserMixin):
return {
'name': 'Anonymous User',
'name': '',
'email': None,
'permissions': {
'admin': False,
'active': False,
}
}
return {
'name': entry.fullname,
'email': entry.email,
'permissions': {
'admin': entry.roles == ROLES.admin,
'active': entry.active,
}
}
return jsonify(_extract_data(current_user))
......
......@@ -19,7 +19,8 @@
"react/prop-types": "off",
"@typescript-eslint/no-unused-vars": [
"warn", { "argsIgnorePattern": "^_" }
]
],
"@typescript-eslint/no-empty-function": "off"
},
"settings": {
"react": {
......
......@@ -16,6 +16,9 @@
"declaration": true,
"declarationDir": "dist/types",
"noImplicitAny": false,
"paths": {
"shared/*": ["../compendium-frontend/src/shared/*"]
}
},
"include": ["src"]
}
......@@ -72,6 +72,9 @@ const config: Configuration = {
},
resolve: {
extensions: [".tsx", ".ts", ".js", ".html"],
alias: {
shared: path.resolve(__dirname, '../compendium-frontend/src/shared/'),
},
},
output: {
path: path.resolve(__dirname, "..", "compendium_v2", "static"),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment