From eea8e65e095fb8bf292070d316576eabdfb93985 Mon Sep 17 00:00:00 2001
From: Bjarke Madsen <bjarke@nordu.net>
Date: Tue, 25 Jul 2023 13:24:47 +0200
Subject: [PATCH 1/8] update eslint config to allow empty default functions

---
 compendium-frontend/.eslintrc.json | 3 ++-
 survey-frontend/.eslintrc.json     | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/compendium-frontend/.eslintrc.json b/compendium-frontend/.eslintrc.json
index 5378a5f9..32eef4da 100644
--- a/compendium-frontend/.eslintrc.json
+++ b/compendium-frontend/.eslintrc.json
@@ -19,7 +19,8 @@
       "react/prop-types": "off",
       "@typescript-eslint/no-unused-vars": [
         "warn", { "argsIgnorePattern": "^_" }
-      ]
+      ],
+      "@typescript-eslint/no-empty-function": "off"
     },
     "settings": {
       "react": {
diff --git a/survey-frontend/.eslintrc.json b/survey-frontend/.eslintrc.json
index 5378a5f9..32eef4da 100644
--- a/survey-frontend/.eslintrc.json
+++ b/survey-frontend/.eslintrc.json
@@ -19,7 +19,8 @@
       "react/prop-types": "off",
       "@typescript-eslint/no-unused-vars": [
         "warn", { "argsIgnorePattern": "^_" }
-      ]
+      ],
+      "@typescript-eslint/no-empty-function": "off"
     },
     "settings": {
       "react": {
-- 
GitLab


From 181300313e714889bfcfb390f16553fc9de61d20 Mon Sep 17 00:00:00 2001
From: Bjarke Madsen <bjarke@nordu.net>
Date: Tue, 25 Jul 2023 13:25:04 +0200
Subject: [PATCH 2/8] update webpack configs to allow shared code

---
 compendium-frontend/webpack.config.ts | 4 ++++
 survey-frontend/webpack.config.ts     | 3 +++
 2 files changed, 7 insertions(+)

diff --git a/compendium-frontend/webpack.config.ts b/compendium-frontend/webpack.config.ts
index 4658af21..73cfd59a 100644
--- a/compendium-frontend/webpack.config.ts
+++ b/compendium-frontend/webpack.config.ts
@@ -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: [
diff --git a/survey-frontend/webpack.config.ts b/survey-frontend/webpack.config.ts
index a00d00c9..c0a0f579 100644
--- a/survey-frontend/webpack.config.ts
+++ b/survey-frontend/webpack.config.ts
@@ -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"),
-- 
GitLab


From b59a02e59562481d6d3bb63ef62c43a56a96949e Mon Sep 17 00:00:00 2001
From: Bjarke Madsen <bjarke@nordu.net>
Date: Tue, 25 Jul 2023 13:25:19 +0200
Subject: [PATCH 3/8] add  typescript path alias for shared code

---
 survey-frontend/tsconfig.json | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/survey-frontend/tsconfig.json b/survey-frontend/tsconfig.json
index fdc02371..ff50d2d9 100644
--- a/survey-frontend/tsconfig.json
+++ b/survey-frontend/tsconfig.json
@@ -16,6 +16,9 @@
     "declaration": true,
     "declarationDir": "dist/types",
     "noImplicitAny": false,
+    "paths": {
+      "shared/*": ["../compendium-frontend/src/shared/*"]
+    }
   },
   "include": ["src"]
 }
-- 
GitLab


From b7445f31d88a31466dbe381e4dd7b2a1ca079756 Mon Sep 17 00:00:00 2001
From: Bjarke Madsen <bjarke@nordu.net>
Date: Tue, 25 Jul 2023 13:28:35 +0200
Subject: [PATCH 4/8] update redirect URL to be the compendium frontpage

---
 compendium_v2/routes/authentication.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/compendium_v2/routes/authentication.py b/compendium_v2/routes/authentication.py
index 1c50f619..f66d26bc 100644
--- a/compendium_v2/routes/authentication.py
+++ b/compendium_v2/routes/authentication.py
@@ -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")
-- 
GitLab


From 07a07147fe2933bbc227ee9c42ccff2a1e22d011 Mon Sep 17 00:00:00 2001
From: Bjarke Madsen <bjarke@nordu.net>
Date: Tue, 25 Jul 2023 13:28:53 +0200
Subject: [PATCH 5/8] update user data returned by API

---
 compendium_v2/routes/user.py | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

diff --git a/compendium_v2/routes/user.py b/compendium_v2/routes/user.py
index 00e0886a..96dca333 100644
--- a/compendium_v2/routes/user.py
+++ b/compendium_v2/routes/user.py
@@ -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))
-- 
GitLab


From 681b40bea3ea395603309a46bcd1052250cda9a7 Mon Sep 17 00:00:00 2001
From: Bjarke Madsen <bjarke@nordu.net>
Date: Tue, 25 Jul 2023 13:29:21 +0200
Subject: [PATCH 6/8] remove faint border on cards

---
 compendium-frontend/src/scss/layout/_components.scss | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/compendium-frontend/src/scss/layout/_components.scss b/compendium-frontend/src/scss/layout/_components.scss
index 9c3178d2..cfd1ad3f 100644
--- a/compendium-frontend/src/scss/layout/_components.scss
+++ b/compendium-frontend/src/scss/layout/_components.scss
@@ -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
+}
+
-- 
GitLab


From 8e1c52302df78179e210aff780911160c2bb9bb8 Mon Sep 17 00:00:00 2001
From: Bjarke Madsen <bjarke@nordu.net>
Date: Tue, 25 Jul 2023 13:30:23 +0200
Subject: [PATCH 7/8] Add UserProvider and put providers in single component

---
 compendium-frontend/src/App.tsx               |  9 +--
 compendium-frontend/src/Providers.tsx         | 17 ++++++
 .../src/helpers/SidebarProvider.tsx           |  2 +-
 .../src/shared/UserProvider.tsx               | 55 +++++++++++++++++++
 4 files changed, 78 insertions(+), 5 deletions(-)
 create mode 100644 compendium-frontend/src/Providers.tsx
 create mode 100644 compendium-frontend/src/shared/UserProvider.tsx

diff --git a/compendium-frontend/src/App.tsx b/compendium-frontend/src/App.tsx
index 246535e5..8c8fbe33 100644
--- a/compendium-frontend/src/App.tsx
+++ b/compendium-frontend/src/App.tsx
@@ -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>
diff --git a/compendium-frontend/src/Providers.tsx b/compendium-frontend/src/Providers.tsx
new file mode 100644
index 00000000..072f5ced
--- /dev/null
+++ b/compendium-frontend/src/Providers.tsx
@@ -0,0 +1,17 @@
+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;
diff --git a/compendium-frontend/src/helpers/SidebarProvider.tsx b/compendium-frontend/src/helpers/SidebarProvider.tsx
index fdf631d1..02302f16 100644
--- a/compendium-frontend/src/helpers/SidebarProvider.tsx
+++ b/compendium-frontend/src/helpers/SidebarProvider.tsx
@@ -6,7 +6,7 @@ interface Props {
 
 const sidebarContext = createContext<{
     show: boolean;
-    toggle: React.Dispatch<any>;
+    toggle: () => void;
 }>({
     show: false,
     toggle: () => { }
diff --git a/compendium-frontend/src/shared/UserProvider.tsx b/compendium-frontend/src/shared/UserProvider.tsx
new file mode 100644
index 00000000..ff3a6e64
--- /dev/null
+++ b/compendium-frontend/src/shared/UserProvider.tsx
@@ -0,0 +1,55 @@
+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
-- 
GitLab


From 5014b1ea17258b5106301bace670ffb648fe4bd9 Mon Sep 17 00:00:00 2001
From: Bjarke Madsen <bjarke@nordu.net>
Date: Tue, 25 Jul 2023 13:30:55 +0200
Subject: [PATCH 8/8] Add login and navigation to Survey depending on user
 attributes

---
 .../components/global/ExternalPageNavBar.tsx  | 47 ++++++++++-------
 compendium-frontend/src/main.scss             | 50 +++++++++++++++++--
 .../src/scss/layout/Login.scss                | 24 +++++++++
 compendium-frontend/src/shared/Login.tsx      | 23 +++++++++
 4 files changed, 122 insertions(+), 22 deletions(-)
 create mode 100644 compendium-frontend/src/scss/layout/Login.scss
 create mode 100644 compendium-frontend/src/shared/Login.tsx

diff --git a/compendium-frontend/src/components/global/ExternalPageNavBar.tsx b/compendium-frontend/src/components/global/ExternalPageNavBar.tsx
index 701d7f10..bb2ac21e 100644
--- a/compendium-frontend/src/components/global/ExternalPageNavBar.tsx
+++ b/compendium-frontend/src/components/global/ExternalPageNavBar.tsx
@@ -1,5 +1,6 @@
 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 >
     );
 }
 
diff --git a/compendium-frontend/src/main.scss b/compendium-frontend/src/main.scss
index 7df1e65b..dff9a81c 100644
--- a/compendium-frontend/src/main.scss
+++ b/compendium-frontend/src/main.scss
@@ -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
diff --git a/compendium-frontend/src/scss/layout/Login.scss b/compendium-frontend/src/scss/layout/Login.scss
new file mode 100644
index 00000000..0d2dcb0c
--- /dev/null
+++ b/compendium-frontend/src/scss/layout/Login.scss
@@ -0,0 +1,24 @@
+.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
diff --git a/compendium-frontend/src/shared/Login.tsx b/compendium-frontend/src/shared/Login.tsx
new file mode 100644
index 00000000..baa75200
--- /dev/null
+++ b/compendium-frontend/src/shared/Login.tsx
@@ -0,0 +1,23 @@
+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
-- 
GitLab