diff --git a/myconext-gui/docker/conf/000-default.conf b/myconext-gui/docker/conf/000-default.conf
index 1c8ca85e..75cc4b64 100644
--- a/myconext-gui/docker/conf/000-default.conf
+++ b/myconext-gui/docker/conf/000-default.conf
@@ -63,7 +63,7 @@ DocumentRoot "/var/www"
Require all granted
-
+
Require all granted
diff --git a/myconext-server/src/main/java/myconext/api/DefaultErrorController.java b/myconext-server/src/main/java/myconext/api/DefaultErrorController.java
index 9d46dff0..7163746a 100644
--- a/myconext-server/src/main/java/myconext/api/DefaultErrorController.java
+++ b/myconext-server/src/main/java/myconext/api/DefaultErrorController.java
@@ -3,6 +3,7 @@
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.servlet.http.HttpServletRequest;
+import myconext.exceptions.RemoteException;
import myconext.exceptions.UserNotFoundException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -76,6 +77,9 @@ public ResponseEntity error(HttpServletRequest request) throws URISyntaxExceptio
} else {
statusCode = BAD_REQUEST;
}
+ if (error instanceof RemoteException) {
+ result.put("reference", ((RemoteException)error).getReference());
+ }
}
result.put("status", statusCode.value());
return ResponseEntity.status(statusCode).body(result);
diff --git a/myconext-server/src/main/java/myconext/api/LoginController.java b/myconext-server/src/main/java/myconext/api/LoginController.java
index 6de2bdfd..6dc2ea7e 100644
--- a/myconext-server/src/main/java/myconext/api/LoginController.java
+++ b/myconext-server/src/main/java/myconext/api/LoginController.java
@@ -110,6 +110,7 @@ public LoginController(UserRepository userRepository,
public Map config() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Map copyConfig = new HashMap<>(config);
+ //Can be an AnonymousAuthenticationToken with Principal=anonymousUser
boolean authenticated = authentication != null && authentication.isAuthenticated() && authentication.getPrincipal() instanceof User;
copyConfig.put("authenticated", authenticated);
if (authenticated ) {
diff --git a/myconext-server/src/main/java/myconext/api/ServiceDeskController.java b/myconext-server/src/main/java/myconext/api/ServiceDeskController.java
index ab0a078c..1397bd61 100644
--- a/myconext-server/src/main/java/myconext/api/ServiceDeskController.java
+++ b/myconext-server/src/main/java/myconext/api/ServiceDeskController.java
@@ -2,6 +2,7 @@
import lombok.Getter;
import myconext.exceptions.ForbiddenException;
+import myconext.exceptions.RemoteException;
import myconext.exceptions.UserNotFoundException;
import myconext.model.ControlCode;
import myconext.model.ExternalLinkedAccount;
@@ -11,6 +12,7 @@
import myconext.verify.AttributeMapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
@@ -56,8 +58,19 @@ public ResponseEntity validateDate(@RequestParam("dayOfBirth") String d
}
@PutMapping("/approve")
- public ResponseEntity convertUserControlCode(Authentication authentication,
+ public ResponseEntity convertUserControlCode(Authentication authentication,
@RequestBody ControlCode controlCode) {
+ try {
+ return doConvertUserControlCode(authentication, controlCode);
+ } catch (RuntimeException e) {
+ ResponseStatus annotation = AnnotationUtils.getAnnotation(e.getClass(), ResponseStatus.class);
+ HttpStatus status = annotation != null ? annotation.value() : HttpStatus.BAD_REQUEST;
+ //This will log the reference to correlate to the user mail
+ throw new RemoteException(status, e.getMessage(), e);
+ }
+ }
+
+ private ResponseEntity doConvertUserControlCode(Authentication authentication, ControlCode controlCode) throws RuntimeException{
String code = controlCode.getCode();
User user = userRepository.findByControlCode_Code(code)
.orElseThrow(() -> new UserNotFoundException(String.format("No user found with controlCode %s", code)));
@@ -69,16 +82,20 @@ public ResponseEntity convertUserControlCode(Authentication authentication
throw new ForbiddenException("User UID's do not match");
}
- User serviceDeskMember = userFromAuthentication(authentication);
+ String userUid = ((User) authentication.getPrincipal()).getUid();
+ User serviceDeskMember = getUserRepository().findUserByUid(userUid).orElseThrow(() -> new UserNotFoundException(userUid));
LOG.info(String.format("Adding external linked account for service desk for user %s by user %s",
user.getEmail(), serviceDeskMember.getEmail()));
ExternalLinkedAccount externalLinkedAccount = attributeMapper.createFromControlCode(controlCode);
user.getExternalLinkedAccounts().add(externalLinkedAccount);
+ //The controlCode is now invalid as already used
+ user.setControlCode(null);
+
userRepository.save(user);
- return ResponseEntity.status(HttpStatus.CREATED).build();
+ return ResponseEntity.status(HttpStatus.CREATED).body(externalLinkedAccount);
}
}
diff --git a/myconext-server/src/main/java/myconext/exceptions/RemoteException.java b/myconext-server/src/main/java/myconext/exceptions/RemoteException.java
new file mode 100644
index 00000000..19e77a7e
--- /dev/null
+++ b/myconext-server/src/main/java/myconext/exceptions/RemoteException.java
@@ -0,0 +1,23 @@
+package myconext.exceptions;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.server.ResponseStatusException;
+
+public class RemoteException extends ResponseStatusException {
+
+ private final String reference;
+
+ public RemoteException(HttpStatus status, String reason, Throwable cause) {
+ super(status, reason, cause);
+ this.reference = String.valueOf(Math.round(Math.random() * 10000));
+ }
+
+ public String getReference() {
+ return reference;
+ }
+
+ @Override
+ public String toString() {
+ return "reference='" + reference + "' " + super.toString();
+ }
+}
diff --git a/myconext-server/src/main/java/myconext/model/ControlCode.java b/myconext-server/src/main/java/myconext/model/ControlCode.java
index 9b18ece0..359118b8 100644
--- a/myconext-server/src/main/java/myconext/model/ControlCode.java
+++ b/myconext-server/src/main/java/myconext/model/ControlCode.java
@@ -20,6 +20,8 @@ public class ControlCode implements Serializable {
@Setter
@Indexed
private String code;
+
+ private String documentId;
@Setter
private long createdAt;
diff --git a/myconext-server/src/test/java/myconext/api/ServiceDeskControllerTest.java b/myconext-server/src/test/java/myconext/api/ServiceDeskControllerTest.java
index 67a757db..fd379b35 100644
--- a/myconext-server/src/test/java/myconext/api/ServiceDeskControllerTest.java
+++ b/myconext-server/src/test/java/myconext/api/ServiceDeskControllerTest.java
@@ -84,6 +84,7 @@ void convertUserControlCode() {
User user = userRepository.findOneUserByEmail("mdoe@example.com");
assertEquals(1, user.getExternalLinkedAccounts().size());
+ assertNull(user.getControlCode());
ExternalLinkedAccount externalLinkedAccount = user.getExternalLinkedAccounts().getFirst();
DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder().appendPattern("dd-MM-yyyy").toFormatter();
diff --git a/servicedesk-gui/docker/Dockerfile b/servicedesk-gui/docker/Dockerfile
new file mode 100644
index 00000000..3d8cdf52
--- /dev/null
+++ b/servicedesk-gui/docker/Dockerfile
@@ -0,0 +1,4 @@
+FROM ghcr.io/openconext/openconext-basecontainers/apache2-shibboleth:latest
+COPY ./dist/ /var/www/
+COPY ./docker/conf/000-default.conf /etc/apache2/sites-enabled/000-default.conf
+
diff --git a/servicedesk-gui/docker/conf/000-default.conf b/servicedesk-gui/docker/conf/000-default.conf
new file mode 100644
index 00000000..b958914c
--- /dev/null
+++ b/servicedesk-gui/docker/conf/000-default.conf
@@ -0,0 +1,88 @@
+ServerName https://${HTTPD_SERVERNAME}
+RewriteEngine on
+
+RewriteCond %{REQUEST_URI} !\.html$
+RewriteCond %{REQUEST_URI} !\.(js|css)(\.map)?$
+RewriteCond %{REQUEST_URI} !\.svg$
+RewriteCond %{REQUEST_URI} !\.png$
+RewriteCond %{REQUEST_URI} !\.ico$
+RewriteCond %{REQUEST_URI} !\.woff$
+RewriteCond %{REQUEST_URI} !\.woff2$
+RewriteCond %{REQUEST_URI} !\.ttf$
+RewriteCond %{REQUEST_URI} !\.eot$
+RewriteCond %{REQUEST_URI} !^/(asset-)?manifest.json$
+RewriteCond %{REQUEST_URI} !^/myconext
+RewriteCond %{REQUEST_URI} !^/tiqr
+RewriteCond %{REQUEST_URI} !^/actuator
+RewriteCond %{REQUEST_URI} !^/internal
+RewriteCond %{REQUEST_URI} !^/robots.txt
+RewriteCond %{REQUEST_URI} !^/config
+RewriteCond %{REQUEST_URI} !^/login
+RewriteCond %{REQUEST_URI} !^/startSSO
+RewriteCond %{REQUEST_URI} !^/fonts
+RewriteCond %{REQUEST_URI} !^/.well-known
+RewriteRule (.*) /index.html [L]
+
+ProxyPreserveHost On
+ProxyPass /Shibboleth.sso !
+
+ProxyPass /myconext/api http://myconextserver:8080/myconext/api
+ProxyPassReverse /myconext/api http://myconextserver:8080/myconext/api
+
+ProxyPass /tiqr http://myconextserver:8080/tiqr
+ProxyPassReverse /tiqr http://myconextserver:8080/tiqr
+
+ProxyPass /internal http://myconextserver:8080/internal
+ProxyPass /actuator http://myconextserver:8080/internal
+ProxyPass /robots.txt http://myconextserver:8080/robots.txt
+ProxyPass /login http://myconextserver:8080/login
+ProxyPass /startSSO http://myconextserver:8080/startSSO
+ProxyPass /config http://myconextserver:8080/config
+
+
+ AuthType shibboleth
+ ShibUseHeaders On
+ ShibRequireSession On
+ Require valid-user
+
+
+DocumentRoot "/var/www"
+
+
+ Require all granted
+ Options -Indexes
+
+
+# Public endpoints
+
+ Require all granted
+
+
+
+ Require all granted
+
+
+
+ Require all granted
+
+
+
+ Require all granted
+
+
+# React resources
+
+ Require all granted
+
+
+
+ Require all granted
+
+
+
+ Require all granted
+
+
+Header always set X-Frame-Options "DENY"
+Header always set Referrer-Policy "same-origin"
+Header always set X-Content-Type-Options "nosniff"
diff --git a/servicedesk-gui/eslint.config.js b/servicedesk-gui/eslint.config.js
index 238d2e4e..cde8cd53 100644
--- a/servicedesk-gui/eslint.config.js
+++ b/servicedesk-gui/eslint.config.js
@@ -5,34 +5,39 @@ import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
export default [
- { ignores: ['dist'] },
- {
- files: ['**/*.{js,jsx}'],
- languageOptions: {
- ecmaVersion: 2020,
- globals: globals.browser,
- parserOptions: {
- ecmaVersion: 'latest',
- ecmaFeatures: { jsx: true },
- sourceType: 'module',
- },
+ {
+ 'extends': [
+ 'plugin:react/recommended'
+ ]
},
- settings: { react: { version: '18.3' } },
- plugins: {
- react,
- 'react-hooks': reactHooks,
- 'react-refresh': reactRefresh,
+ {ignores: ['dist']},
+ {
+ files: ['**/*.{js,jsx}'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ ecmaFeatures: {jsx: true},
+ sourceType: 'module',
+ },
+ },
+ settings: {react: {version: '18.3'}},
+ plugins: {
+ react,
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...js.configs.recommended.rules,
+ ...react.configs.recommended.rules,
+ ...react.configs['jsx-runtime'].rules,
+ ...reactHooks.configs.recommended.rules,
+ 'react/jsx-no-target-blank': 'off',
+ 'react-refresh/only-export-components': [
+ 'warn',
+ {allowConstantExport: true},
+ ],
+ },
},
- rules: {
- ...js.configs.recommended.rules,
- ...react.configs.recommended.rules,
- ...react.configs['jsx-runtime'].rules,
- ...reactHooks.configs.recommended.rules,
- 'react/jsx-no-target-blank': 'off',
- 'react-refresh/only-export-components': [
- 'warn',
- { allowConstantExport: true },
- ],
- },
- },
]
diff --git a/servicedesk-gui/index.html b/servicedesk-gui/index.html
index dd04490c..5df1829e 100644
--- a/servicedesk-gui/index.html
+++ b/servicedesk-gui/index.html
@@ -6,7 +6,7 @@
-
Boem
+
+
+
+
+ {isAuthenticated && }
+ {isAuthenticated &&
+
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+ }
+ {!isAuthenticated &&
+
+ }/>
+ }/>
+ }/>
+ }/>
+ }
- >
+ {
}
+
);
}
diff --git a/servicedesk-gui/src/App.scss b/servicedesk-gui/src/App.scss
index e69de29b..4743b390 100644
--- a/servicedesk-gui/src/App.scss
+++ b/servicedesk-gui/src/App.scss
@@ -0,0 +1,15 @@
+.service-desk {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ position: relative;
+ width: 100%;
+}
+
+.sds--modal.sds--backdrop {
+ z-index: 99;
+}
+
+.react-datepicker-popper {
+ z-index: 3 !important;
+}
\ No newline at end of file
diff --git a/servicedesk-gui/src/__tests__/locale/en.test.js b/servicedesk-gui/src/__tests__/locale/en.test.js
index 401c4d4a..01aad748 100644
--- a/servicedesk-gui/src/__tests__/locale/en.test.js
+++ b/servicedesk-gui/src/__tests__/locale/en.test.js
@@ -1,3 +1,5 @@
+import { assert, expect, test } from 'vitest'
+
import en from "../../locale/en";
import nl from "../../locale/nl";
diff --git a/servicedesk-gui/src/__tests__/stores/AppStore.test.js b/servicedesk-gui/src/__tests__/stores/AppStore.test.js
index 0ddf5533..3e43a7f1 100644
--- a/servicedesk-gui/src/__tests__/stores/AppStore.test.js
+++ b/servicedesk-gui/src/__tests__/stores/AppStore.test.js
@@ -1,8 +1,9 @@
+import { assert, expect, test } from 'vitest'
import {useAppStore} from "../../stores/AppStore";
test("Store outside functional component", () => {
const csrfToken = useAppStore.getState().csrfToken;
- expect(csrfToken).toBeNull();
+ expect(csrfToken).toBeUndefined();
useAppStore.setState({csrfToken: "test"});
diff --git a/servicedesk-gui/src/__tests__/utils/Manage.test.js b/servicedesk-gui/src/__tests__/utils/Manage.test.js
deleted file mode 100644
index 6737ad61..00000000
--- a/servicedesk-gui/src/__tests__/utils/Manage.test.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import {mergeProvidersProvisioningsRoles, reduceApplicationFromUserRoles} from "../../utils/Manage";
-import applications from "./applications.json";
-import roles from "./roles.json";
-import userRoles from "./userRoles.json";
-
-test("mergeProvidersProvisioningsRoles", () => {
- const results = mergeProvidersProvisioningsRoles(applications.providers, applications.provisionings, roles);
- expect(results.length).toEqual(6);
-});
-
-test("reduceApplicationFromUserRoles", () => {
- const results = reduceApplicationFromUserRoles(userRoles, "en");
- const applicationNames = results.map(app => app.applicationName);
- //Sorting alphabetically on applicationName
- expect(applicationNames).toEqual([
- "Calendar EN (SURF bv)",
- "Research EN (SURF bv)",
- "Research EN (SURF bv)",
- "Wiki EN (SURF bv)",
- "Wiki EN (SURF bv)",
- "Wiki EN (SURF bv)"
- ]);
- const roleNames = results
- .filter(app => app.applicationName.startsWith("Wiki"))
- .map(app => app.roleName);
- //Sub-sorting alphabetically on roleName
- expect(roleNames).toEqual([
- "Wiki 1 Role",
- "Wiki 2 Role",
- "Wiki Another Role (3) - Calendar (1)"
- ]);
-});
-
diff --git a/servicedesk-gui/src/__tests__/utils/Pagination.test.js b/servicedesk-gui/src/__tests__/utils/Pagination.test.js
deleted file mode 100644
index 70a82311..00000000
--- a/servicedesk-gui/src/__tests__/utils/Pagination.test.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import {defaultPagination, paginationQueryParams} from "../../utils/Pagination";
-
-test("paginationQueryParams defaults", () => {
- const page = defaultPagination("desc", "DESC");
- const queryParams = paginationQueryParams(page, {custom: "val"});
- expect(queryParams).toEqual("custom=val&pageNumber=0&pageSize=10&sort=desc&sortDirection=DESC&");
-});
-
-test("paginationQueryParams empty", () => {
- const queryParams = paginationQueryParams({});
- expect(queryParams).toEqual("");
-});
diff --git a/servicedesk-gui/src/__tests__/utils/UserRole.test.js b/servicedesk-gui/src/__tests__/utils/UserRole.test.js
deleted file mode 100644
index 8e55b5f6..00000000
--- a/servicedesk-gui/src/__tests__/utils/UserRole.test.js
+++ /dev/null
@@ -1,158 +0,0 @@
-import {
- allowedAuthoritiesForInvitation,
- allowedToDeleteInvitation,
- allowedToEditRole,
- allowedToRenewUserRole,
- AUTHORITIES,
- highestAuthority,
- isUserAllowed
-} from "../../utils/UserRole";
-
-const applicationUsagesForManageId = manageId => {
- return [{application: {manageId: manageId}}];
-}
-
-test("Test isUserAllowed", () => {
- let user = {userRoles: [{authority: AUTHORITIES.GUEST}]}
- expect(isUserAllowed(AUTHORITIES.INVITER, user)).toBeFalsy();
-
- user = {superUser: true}
- expect(isUserAllowed(AUTHORITIES.SUPER_USER, user)).toBeTruthy();
-
- user = {userRoles: [{authority: AUTHORITIES.GUEST}, {authority: AUTHORITIES.MANAGER}]}
- expect(isUserAllowed(AUTHORITIES.SUPER_USER, user)).toBeFalsy();
- expect(isUserAllowed(AUTHORITIES.MANAGER, user)).toBeTruthy();
-
- expect(() => isUserAllowed("nope", user)).toThrow(Error);
-});
-
-test("Allowed authorities for invitation - superUser", () => {
- const user = {superUser: true}
-
- const authorities = allowedAuthoritiesForInvitation(user, []);
- expect(authorities).toEqual([AUTHORITIES.SUPER_USER, AUTHORITIES.MANAGER, AUTHORITIES.INVITER, AUTHORITIES.GUEST]);
-});
-
-test("Allowed authorities for invitation - manager", () => {
- const researchUserRole = {authority: AUTHORITIES.MANAGER, role: {id: "1", manageId: "2"}};
- const wikiRole = {id: "2", manageId: "2"};
- const mailUserRole = {authority: AUTHORITIES.INVITER, role: {id: "3", manageId: "9"}};
- const user = {userRoles: [researchUserRole, mailUserRole]}
-
- let authorities = allowedAuthoritiesForInvitation(user, []);
- expect(authorities).toEqual([AUTHORITIES.INVITER, AUTHORITIES.GUEST]);
-
- authorities = allowedAuthoritiesForInvitation(user, [wikiRole]);
- expect(authorities).toEqual([]);
-
- authorities = allowedAuthoritiesForInvitation(user, [mailUserRole.role]);
- expect(authorities).toEqual([AUTHORITIES.GUEST]);
-
-});
-
-test("Allowed to renew UserRole", () => {
- const research = {
- authority: AUTHORITIES.MANAGER,
- role: {id: "1", applicationUsages: applicationUsagesForManageId("2")}
- };
- const mail = {
- authority: AUTHORITIES.INVITER,
- role: {id: "3", applicationUsages: applicationUsagesForManageId("9")}
- };
- const calendar = {
- authority: AUTHORITIES.GUEST,
- role: {id: "3", applicationUsages: applicationUsagesForManageId("9")}
- };
- let user = {superUser: true}
- expect(allowedToRenewUserRole(user, null)).toBeTruthy();
-
- user = {userRoles: [calendar]}
- expect(allowedToRenewUserRole(user, calendar)).toBeFalsy();
-
- user = {userRoles: [research]}
- expect(allowedToRenewUserRole(user, {authority: AUTHORITIES.SUPER_USER})).toBeFalsy();
- expect(allowedToRenewUserRole(user, {authority: AUTHORITIES.MANAGER})).toBeFalsy();
- expect(allowedToRenewUserRole(user, {
- authority: AUTHORITIES.INVITER,
- role: {id: "9", applicationUsages: applicationUsagesForManageId("9")}
- })).toBeFalsy();
- expect(allowedToRenewUserRole(user, {
- authority: AUTHORITIES.INVITER,
- role: {id: "1", applicationUsages: applicationUsagesForManageId("9")}
- })).toBeTruthy();
- expect(allowedToRenewUserRole(user, {
- authority: AUTHORITIES.INVITER,
- role: {id: "9", applicationUsages: applicationUsagesForManageId("2")}
- })).toBeTruthy();
-
- user = {userRoles: [mail]}
- expect(allowedToRenewUserRole(user, {
- authority: AUTHORITIES.INVITER,
- role: {id: "3", applicationUsages: applicationUsagesForManageId("9")}
- })).toBeFalsy();
- expect(allowedToRenewUserRole(user, {
- authority: AUTHORITIES.GUEST,
- role: {id: "1", applicationUsages: applicationUsagesForManageId("11")}
- })).toBeFalsy();
- expect(allowedToRenewUserRole(user, {
- authority: AUTHORITIES.GUEST,
- role: {id: "3", applicationUsages: applicationUsagesForManageId("11")}
- })).toBeTruthy();
-
- // An institution admin is allowed to CRUD for every role that is owned by the organization of the insitution admin
- user = {institutionAdmin: true, applications: [{id: "2"}]}
- expect(allowedToRenewUserRole(user, research)).toBeTruthy();
- expect(allowedToRenewUserRole(user, mail)).toBeFalsy();
-
-})
-
-test("Allowed to renew guestIncluded UserRole as Inviter", () => {
- const calendar = {
- authority: AUTHORITIES.INVITER,
- role: {id: "1", applicationUsages: applicationUsagesForManageId("1")}
- };
- const user = {userRoles: [calendar]}
- expect(allowedToRenewUserRole(user, {
- authority: AUTHORITIES.MANAGER,
- role: {id: "1", applicationUsages: applicationUsagesForManageId("1")}
- })).toBeFalsy();
-
- expect(allowedToRenewUserRole(user, {
- authority: AUTHORITIES.MANAGER,
- role: {id: "1", applicationUsages: applicationUsagesForManageId("1")}
- }, true, true)).toBeTruthy();
-})
-
-test("Allowed to delete Invitation", () => {
- const mail = {
- authority: AUTHORITIES.INVITER,
- role: {id: "1", applicationUsages: applicationUsagesForManageId("10")}
- };
- const research = {
- authority: AUTHORITIES.INVITER,
- role: {id: "2", applicationUsages: applicationUsagesForManageId("11")}
- };
- const user = {userRoles: [mail, research]}
- const invitation = {intended_authority: AUTHORITIES.GUEST, roles: [mail, research]};
- expect(allowedToDeleteInvitation(user, invitation)).toBeTruthy();
-
- invitation.intended_authority = AUTHORITIES.INVITER;
- expect(allowedToDeleteInvitation(user, invitation)).toBeFalsy();
-});
-
-test("Allowed to edit", () => {
- const role = {id: 1, applicationUsages: applicationUsagesForManageId("1")};
- const user = {
- institutionAdmin: true,
- applications: [{id: "1"}],
- userRoles: [{authority: AUTHORITIES.INVITER, role: role}]
- }
- expect(allowedToEditRole(user, role)).toBeTruthy();
-});
-
-test("Highest authority", () => {
- const user = {
- institutionAdmin: true,
- }
- expect(highestAuthority(user, false)).toEqual(AUTHORITIES.INSTITUTION_ADMIN);
-});
diff --git a/servicedesk-gui/src/__tests__/utils/Utils.test.js b/servicedesk-gui/src/__tests__/utils/Utils.test.js
index 55c3f178..d9e968de 100644
--- a/servicedesk-gui/src/__tests__/utils/Utils.test.js
+++ b/servicedesk-gui/src/__tests__/utils/Utils.test.js
@@ -1,3 +1,4 @@
+import { assert, expect, test } from 'vitest'
import {distinctValues, sanitizeURL} from "../../utils/Utils";
test("Test sanitizeURL", () => {
diff --git a/servicedesk-gui/src/__tests__/utils/applications.json b/servicedesk-gui/src/__tests__/utils/applications.json
deleted file mode 100644
index df630b1e..00000000
--- a/servicedesk-gui/src/__tests__/utils/applications.json
+++ /dev/null
@@ -1,156 +0,0 @@
-{
- "provisionings": [
- {
- "scim_user": "user",
- "scim_update_role_put_method": true,
- "entityid": "https://scim",
- "type": "provisioning",
- "scim_password": "secret",
- "name:en": "Scim",
- "id": "7",
- "scim_url": "http://localhost:8081/api/scim/v2",
- "provisioning_type": "scim",
- "_id": "7",
- "applications": [
- {
- "id": "1",
- "type": "saml20_sp"
- },
- {
- "id": "5",
- "type": "oidc10_rp"
- }
- ]
- },
- {
- "scim_user": "user",
- "scim_update_role_put_method": false,
- "entityid": "https://scim-patch",
- "type": "provisioning",
- "scim_password": "secret",
- "name:en": "Scim-Patch",
- "id": "8",
- "scim_url": "http://localhost:8081/api/scim/v2",
- "scim_user_identifier": "subject_id",
- "provisioning_type": "scim",
- "_id": "8",
- "applications": [
- {
- "id": "4",
- "type": "saml20_sp"
- }
- ]
- },
- {
- "graph_client_id": "client_id",
- "entityid": "https://graph",
- "type": "provisioning",
- "graph_tenant": "tenant",
- "name:en": "graph",
- "id": "9",
- "graph_url": "http://localhost:8081/graph/users",
- "provisioning_type": "graph",
- "graph_secret": "secret",
- "_id": "9",
- "applications": [
- {
- "id": "2",
- "type": "saml20_sp"
- },
- {
- "id": "6",
- "type": "oidc10_rp"
- }
- ]
- },
- {
- "entityid": "https://eva",
- "type": "provisioning",
- "name:en": "EVA",
- "id": "10",
- "eva_guest_account_duration": 30,
- "eva_token": "secret",
- "provisioning_type": "eva",
- "eva_url": "http://localhost:8081/eva",
- "_id": "10",
- "applications": [
- {
- "id": "3",
- "type": "saml20_sp"
- }
- ]
- }
- ],
- "providers": [
- {
- "entityid": "https://calendar",
- "type": "oidc10_rp",
- "url": "https://default-url-calendar.org",
- "name:nl": "Calendar NL",
- "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a",
- "OrganizationName:en": "SURF bv",
- "logo": "https://static.surfconext.nl/media/idp/surfconext.png",
- "name:en": "Calendar EN",
- "id": "5",
- "_id": "5"
- },
- {
- "entityid": "https://cloud",
- "type": "oidc10_rp",
- "url": "https://default-url-cloud.org",
- "name:nl": "Cloud NL",
- "OrganizationName:en": "SURF bv",
- "logo": "https://static.surfconext.nl/media/idp/surfconext.png",
- "name:en": "Cloud EN",
- "id": "6",
- "_id": "6"
- },
- {
- "entityid": "https://wiki",
- "type": "saml20_sp",
- "url": "https://default-url-wiki.org",
- "name:nl": "Wiki NL",
- "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a",
- "OrganizationName:en": "SURF bv",
- "name:en": "Wiki EN",
- "id": "1",
- "_id": "1"
- },
- {
- "entityid": "https://network",
- "type": "saml20_sp",
- "url": "https://default-url-network.org",
- "name:nl": "Network NL",
- "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a",
- "OrganizationName:en": "SURF bv",
- "logo": "https://static.surfconext.nl/media/idp/surfconext.png",
- "name:en": "Network EN",
- "id": "2",
- "_id": "2"
- },
- {
- "entityid": "https://storage",
- "type": "saml20_sp",
- "url": "https://default-url-storage.org",
- "name:nl": "Storage NL",
- "OrganizationName:en": "SURF bv",
- "logo": "https://static.surfconext.nl/media/idp/surfconext.png",
- "name:en": "Storage EN",
- "id": "3",
- "_id": "3"
- },
- {
- "entityid": "https://research",
- "type": "saml20_sp",
- "url": "https://default-url-research.org",
- "name:nl": "Research NL",
- "OrganizationName:en": "SURF bv",
- "logo": "https://static.surfconext.nl/media/idp/surfconext.png",
- "name:en": "Research EN",
- "id": "4",
- "_id": "4"
- }
- ]
-}
-
-
diff --git a/servicedesk-gui/src/__tests__/utils/roles.json b/servicedesk-gui/src/__tests__/utils/roles.json
deleted file mode 100644
index 8b54568a..00000000
--- a/servicedesk-gui/src/__tests__/utils/roles.json
+++ /dev/null
@@ -1,291 +0,0 @@
-[
- {
- "id": 3915,
- "name": "Wiki",
- "shortName": "wiki",
- "description": "Wiki desc",
- "defaultExpiryDays": 365,
- "enforceEmailEquality": false,
- "eduIDOnly": false,
- "blockExpiryDate": false,
- "overrideSettingsAllowed": false,
- "teamsOrigin": false,
- "identifier": "3e267427-e2cd-4b9e-9a78-9248bede1bc4",
- "userRoleCount": 2,
- "applicationUsages": [
- {
- "id": 4574,
- "landingPage": "http://landingpage.com",
- "application": {
- "id": 3901,
- "manageId": "1",
- "manageType": "SAML20_SP",
- "landingPage": "http://landingpage.com"
- }
- }
- ],
- "auditable": {
- "createdAt": 1721314647.000000000,
- "createdBy": "ResourceCleaner"
- },
- "applicationMaps": [
- {
- "landingPage": "http://landingpage.com",
- "entityid": "https://wiki",
- "type": "saml20_sp",
- "url": "https://default-url-wiki.org",
- "name:nl": "Wiki NL",
- "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a",
- "OrganizationName:en": "SURF bv",
- "name:en": "Wiki EN",
- "id": "1",
- "_id": "1"
- }
- ]
- },
- {
- "id": 3916,
- "name": "Network",
- "shortName": "network",
- "description": "Network desc",
- "defaultExpiryDays": 365,
- "enforceEmailEquality": false,
- "eduIDOnly": false,
- "blockExpiryDate": false,
- "overrideSettingsAllowed": false,
- "teamsOrigin": false,
- "identifier": "3cec7986-ca0c-4750-a6f3-86f2ff7fb82f",
- "userRoleCount": 0,
- "applicationUsages": [
- {
- "id": 4575,
- "landingPage": "http://landingpage.com",
- "application": {
- "id": 3902,
- "manageId": "2",
- "manageType": "SAML20_SP",
- "landingPage": "http://landingpage.com"
- }
- }
- ],
- "auditable": {
- "createdAt": 1721314647.000000000,
- "createdBy": "ResourceCleaner"
- },
- "applicationMaps": [
- {
- "landingPage": "http://landingpage.com",
- "entityid": "https://network",
- "type": "saml20_sp",
- "url": "https://default-url-network.org",
- "name:nl": "Network NL",
- "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a",
- "OrganizationName:en": "SURF bv",
- "logo": "https://static.surfconext.nl/media/idp/surfconext.png",
- "name:en": "Network EN",
- "id": "2",
- "_id": "2"
- }
- ]
- },
- {
- "id": 3917,
- "name": "Storage",
- "shortName": "storage",
- "description": "Storage desc",
- "defaultExpiryDays": 365,
- "enforceEmailEquality": false,
- "eduIDOnly": false,
- "blockExpiryDate": false,
- "overrideSettingsAllowed": false,
- "teamsOrigin": false,
- "identifier": "9ac14a11-fd43-4f9c-abeb-8bf811f134df",
- "userRoleCount": 1,
- "applicationUsages": [
- {
- "id": 4576,
- "landingPage": "https://landingpage.com",
- "application": {
- "id": 3903,
- "manageId": "3",
- "manageType": "SAML20_SP",
- "landingPage": "https://landingpage.com"
- }
- },
- {
- "id": 4577,
- "landingPage": "https://landingpage.com",
- "application": {
- "id": 3904,
- "manageId": "6",
- "manageType": "OIDC10_RP",
- "landingPage": "https://landingpage.com"
- }
- }
- ],
- "auditable": {
- "createdAt": 1721314647.000000000,
- "createdBy": "ResourceCleaner"
- },
- "applicationMaps": [
- {
- "landingPage": "https://landingpage.com",
- "entityid": "https://storage",
- "type": "saml20_sp",
- "url": "https://default-url-storage.org",
- "name:nl": "Storage NL",
- "OrganizationName:en": "SURF bv",
- "logo": "https://static.surfconext.nl/media/idp/surfconext.png",
- "name:en": "Storage EN",
- "id": "3",
- "_id": "3"
- },
- {
- "landingPage": "https://landingpage.com",
- "entityid": "https://cloud",
- "type": "oidc10_rp",
- "url": "https://default-url-cloud.org",
- "name:nl": "Cloud NL",
- "OrganizationName:en": "SURF bv",
- "logo": "https://static.surfconext.nl/media/idp/surfconext.png",
- "name:en": "Cloud EN",
- "id": "6",
- "_id": "6"
- }
- ]
- },
- {
- "id": 3918,
- "name": "Research",
- "shortName": "research",
- "description": "Research desc",
- "defaultExpiryDays": 365,
- "enforceEmailEquality": false,
- "eduIDOnly": false,
- "blockExpiryDate": false,
- "overrideSettingsAllowed": false,
- "teamsOrigin": false,
- "identifier": "f2c99710-0cd1-4ea2-a20c-14da698a866b",
- "userRoleCount": 1,
- "applicationUsages": [
- {
- "id": 4578,
- "landingPage": "http://landingpage.com",
- "application": {
- "id": 3905,
- "manageId": "4",
- "manageType": "SAML20_SP",
- "landingPage": "http://landingpage.com"
- }
- }
- ],
- "auditable": {
- "createdAt": 1721314647.000000000,
- "createdBy": "ResourceCleaner"
- },
- "applicationMaps": [
- {
- "landingPage": "http://landingpage.com",
- "entityid": "https://research",
- "type": "saml20_sp",
- "url": "https://default-url-research.org",
- "name:nl": "Research NL",
- "OrganizationName:en": "SURF bv",
- "logo": "https://static.surfconext.nl/media/idp/surfconext.png",
- "name:en": "Research EN",
- "id": "4",
- "_id": "4"
- }
- ]
- },
- {
- "id": 3919,
- "name": "Calendar",
- "shortName": "calendar",
- "description": "Calendar desc",
- "defaultExpiryDays": 365,
- "enforceEmailEquality": false,
- "eduIDOnly": false,
- "blockExpiryDate": false,
- "overrideSettingsAllowed": false,
- "teamsOrigin": false,
- "identifier": "75e4e7b6-5f98-45bc-9d10-9795316e6be0",
- "userRoleCount": 1,
- "applicationUsages": [
- {
- "id": 4579,
- "landingPage": "http://landingpage.com",
- "application": {
- "id": 3906,
- "manageId": "5",
- "manageType": "OIDC10_RP",
- "landingPage": "http://landingpage.com"
- }
- }
- ],
- "auditable": {
- "createdAt": 1721314647.000000000,
- "createdBy": "ResourceCleaner"
- },
- "applicationMaps": [
- {
- "landingPage": "http://landingpage.com",
- "entityid": "https://calendar",
- "type": "oidc10_rp",
- "url": "https://default-url-calendar.org",
- "name:nl": "Calendar NL",
- "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a",
- "OrganizationName:en": "SURF bv",
- "logo": "https://static.surfconext.nl/media/idp/surfconext.png",
- "name:en": "Calendar EN",
- "id": "5",
- "_id": "5"
- }
- ]
- },
- {
- "id": 3920,
- "name": "Mail",
- "shortName": "mail",
- "description": "Mail desc",
- "defaultExpiryDays": 365,
- "enforceEmailEquality": false,
- "eduIDOnly": false,
- "blockExpiryDate": false,
- "overrideSettingsAllowed": false,
- "teamsOrigin": false,
- "identifier": "a2e7d57c-652c-430c-98e6-d5ac7050d979",
- "userRoleCount": 0,
- "applicationUsages": [
- {
- "id": 4580,
- "landingPage": "http://landingpage.com",
- "application": {
- "id": 3906,
- "manageId": "5",
- "manageType": "OIDC10_RP",
- "landingPage": "http://landingpage.com"
- }
- }
- ],
- "auditable": {
- "createdAt": 1721314647.000000000,
- "createdBy": "ResourceCleaner"
- },
- "applicationMaps": [
- {
- "landingPage": "http://landingpage.com",
- "entityid": "https://calendar",
- "type": "oidc10_rp",
- "url": "https://default-url-calendar.org",
- "name:nl": "Calendar NL",
- "institutionGuid": "ad93daef-0911-e511-80d0-005056956c1a",
- "OrganizationName:en": "SURF bv",
- "logo": "https://static.surfconext.nl/media/idp/surfconext.png",
- "name:en": "Calendar EN",
- "id": "5",
- "_id": "5"
- }
- ]
- }
-]
diff --git a/servicedesk-gui/src/__tests__/utils/userRoles.json b/servicedesk-gui/src/__tests__/utils/userRoles.json
deleted file mode 100644
index 61a51d1f..00000000
--- a/servicedesk-gui/src/__tests__/utils/userRoles.json
+++ /dev/null
@@ -1,73 +0,0 @@
-[
- {
- "role": {
- "name": "Wiki Another Role (3) - Calendar (1)",
- "description": "Wiki Another Role (3) - Calendar (1)",
- "applicationMaps": [
- {
- "name:nl": "Wiki NL",
- "OrganizationName:en": "SURF bv",
- "name:en": "Wiki EN"
- },
- {
- "name:nl": "Calendar NL",
- "OrganizationName:en": "SURF bv",
- "name:en": "Calendar EN"
- }
- ]
- }
- },
- {
- "role": {
- "name": "Wiki 2 Role",
- "description": "Wiki 2 Role",
- "applicationMaps": [
- {
- "name:nl": "Wiki NL",
- "OrganizationName:en": "SURF bv",
- "name:en": "Wiki EN"
- }
- ]
- },
- "authority": "INVITER"
- },
- {
- "role": {
- "name": "Research ABC",
- "description": "Research ABC Description",
- "applicationMaps": [
- {
- "name:nl": "Research NL",
- "OrganizationName:en": "SURF bv",
- "name:en": "Research EN"
- }
- ]
- }
- },
- {
- "role": {
- "name": "Wiki 1 Role",
- "description": "Wiki 1 Role",
- "applicationMaps": [
- {
- "name:nl": "Wiki NL",
- "OrganizationName:en": "SURF bv",
- "name:en": "Wiki EN"
- }
- ]
- }
- },
- {
- "role": {
- "name": "Research XYZ",
- "description": "Research XYZ Description",
- "applicationMaps": [
- {
- "name:nl": "Research NL",
- "OrganizationName:en": "SURF bv",
- "name:en": "Research EN"
- }
- ]
- }
- }
-]
\ No newline at end of file
diff --git a/servicedesk-gui/src/__tests__/validations/regExps.test.js b/servicedesk-gui/src/__tests__/validations/regExps.test.js
deleted file mode 100644
index b7a61e6b..00000000
--- a/servicedesk-gui/src/__tests__/validations/regExps.test.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import {constructShortName, validEmailRegExp} from "../../validations/regExps";
-
-test("Sanitize URN", () => {
- const urn = constructShortName(" !@#$%^&*(9IIOO UU plp ")
- expect(urn).toEqual("9iioo_uu_plp")
-});
-
-test("Emails formats", () => {
- expect(validEmailRegExp.test("aa")).toBeFalsy();
- expect(validEmailRegExp.test("a!@a.c")).toBeFalsy();
- expect(validEmailRegExp.test("a!@a.c@")).toBeFalsy();
-
- expect(validEmailRegExp.test("a@a")).toBeTruthy();
- expect(validEmailRegExp.test("a.x@a")).toBeTruthy();
- expect(validEmailRegExp.test("a@a.c")).toBeTruthy();
-})
\ No newline at end of file
diff --git a/servicedesk-gui/src/api/index.js b/servicedesk-gui/src/api/index.js
index 29d07d03..04011e4b 100644
--- a/servicedesk-gui/src/api/index.js
+++ b/servicedesk-gui/src/api/index.js
@@ -54,6 +54,10 @@ function postPutJson(path, body, method, showErrorDialog = true) {
return fetchJson(path, {method: method, body: jsonBody}, {}, showErrorDialog);
}
+function fetchDelete(path) {
+ return validFetch(path, {method: "delete"});
+}
+
//Base
export function configuration() {
return fetchJson("/config");
@@ -64,16 +68,30 @@ export function me() {
return fetchJson("/myconext/api/sp/me");
}
+export function forgetMe() {
+ return fetchDelete("/myconext/api/sp/forget");
+}
+
+export function logout() {
+ const fetchOptions = {
+ credentials: "same-origin",
+ redirect: "manual"
+ };
+ return forgetMe().then(() =>
+ fetchJson("/myconext/api/sp/logout").then(() => fetch("/Shibboleth.sso/Logout", fetchOptions))
+ );
+}
+
//Service Desk
export function getUserControlCode(code) {
return fetchJson(`/myconext/api/servicedesk/user/${code}`);
}
export function validateDate(dayOfBirth) {
- return fetchJson(`/myconext/api/servicedesk/validate?${encodeURIComponent(dayOfBirth)}`);
+ return fetchJson(`/myconext/api/servicedesk/validate?dayOfBirth=${encodeURIComponent(dayOfBirth)}`);
}
-export function convertUserControlCode(firstName, lastName, dayOfBirth, code, userUid) {
- const body = {firstName, lastName, dayOfBirth, code, userUid}
- return postPutJson("/myconext/api/servicedesk/approve", body, "PUT");
+export function convertUserControlCode(firstName, lastName, dayOfBirth, code, documentId , userUid) {
+ const body = {firstName, lastName, dayOfBirth, code, documentId, userUid}
+ return postPutJson("/myconext/api/servicedesk/approve", body, "PUT", false);
}
diff --git a/servicedesk-gui/src/components/BreadCrumb.jsx b/servicedesk-gui/src/components/BreadCrumb.jsx
new file mode 100644
index 00000000..ce057ebd
--- /dev/null
+++ b/servicedesk-gui/src/components/BreadCrumb.jsx
@@ -0,0 +1,41 @@
+import React from "react";
+import "./BreadCrumb.scss";
+import {useAppStore} from "../stores/AppStore";
+import {Link} from "react-router-dom";
+import {isEmpty} from "../utils/Utils";
+import DOMPurify from "dompurify";
+import arrowRight from "../icons/arrow-right.svg";
+
+export const BreadCrumb = () => {
+
+ const paths = useAppStore((state) => state.breadcrumbPath);
+ const clearFlash = useAppStore((state) => state.clearFlash);
+
+ if (isEmpty(paths)) {
+ return null;
+ }
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/servicedesk-gui/src/components/BreadCrumb.scss b/servicedesk-gui/src/components/BreadCrumb.scss
new file mode 100644
index 00000000..9c3fad65
--- /dev/null
+++ b/servicedesk-gui/src/components/BreadCrumb.scss
@@ -0,0 +1,56 @@
+@use "../styles/vars.scss" as *;
+
+.sds--breadcrumb {
+ margin: 0 auto;
+ color: black;
+ background-color: $background;
+ padding-top: 10px;
+
+ @media (max-width: $medium) {
+ padding: 10px 15px 0 15px;
+ }
+
+ ol.sds--breadcrumb--list {
+ min-height: 32px;
+ max-width: $medium;
+ margin: 0 auto 0 auto;
+ position: relative;
+ transition: margin 150ms ease-in-out;
+ display: flex;
+ align-items: center;
+
+ }
+
+ li img {
+ width: calc(5.47em / 14);
+ height: calc(11.25em / 14);
+ }
+
+ span.last {
+ font-weight: 700;
+ }
+
+ .eye-view {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ margin-left: auto;
+ font-weight: bold;
+
+ @media (max-width: $medium) {
+ display: none;
+ }
+
+ svg {
+ width: 32px;
+ margin-right: 10px;
+ }
+
+ color: var(--sds--color--blue--400);
+
+ svg {
+ fill: var(--sds--color--blue--400);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/servicedesk-gui/src/components/Flash.jsx b/servicedesk-gui/src/components/Flash.jsx
new file mode 100644
index 00000000..30ccb98a
--- /dev/null
+++ b/servicedesk-gui/src/components/Flash.jsx
@@ -0,0 +1,30 @@
+import React from "react";
+import {isEmpty} from "../utils/Utils";
+import "./Flash.scss";
+import {Alert, AlertType, Toaster, ToasterContainer, ToasterType} from "@surfnet/sds";
+import {useAppStore} from "../stores/AppStore"
+
+export const Flash = () => {
+
+ const flash = useAppStore(state => state.flash);
+ const clearFlash = useAppStore(state => state.clearFlash);
+
+ if (!isEmpty(flash) && (flash.type === "error" || flash.type === "warning")) {
+ return (
+
+ );
+ }
+ if (!isEmpty(flash) && !isEmpty(flash.msg)) {
+ return (
+
+
+ );
+ }
+ return null;
+}
+
diff --git a/servicedesk-gui/src/components/Flash.scss b/servicedesk-gui/src/components/Flash.scss
new file mode 100644
index 00000000..c62bcd47
--- /dev/null
+++ b/servicedesk-gui/src/components/Flash.scss
@@ -0,0 +1,10 @@
+.sds--alert {
+ display: flex;
+ z-index: 4;
+ min-height: 3.75rem;
+
+ .sds--alert--inner {
+ max-width: 1296px;
+ }
+
+}
\ No newline at end of file
diff --git a/servicedesk-gui/src/components/Footer.jsx b/servicedesk-gui/src/components/Footer.jsx
new file mode 100644
index 00000000..fe45efe3
--- /dev/null
+++ b/servicedesk-gui/src/components/Footer.jsx
@@ -0,0 +1,34 @@
+import React from "react";
+import I18n from "../locale/I18n";
+import "./Footer.scss"
+import {LanguageSelector} from "./LanguageSelector";
+import {Logo, LogoType} from "@surfnet/sds";
+
+export const Footer = () => {
+
+ return (
+
+ );
+}
diff --git a/servicedesk-gui/src/components/Footer.scss b/servicedesk-gui/src/components/Footer.scss
new file mode 100644
index 00000000..6a403ab0
--- /dev/null
+++ b/servicedesk-gui/src/components/Footer.scss
@@ -0,0 +1,3 @@
+footer.sds--footer {
+ margin-top: auto;
+}
diff --git a/servicedesk-gui/src/components/Header.jsx b/servicedesk-gui/src/components/Header.jsx
new file mode 100644
index 00000000..d7576424
--- /dev/null
+++ b/servicedesk-gui/src/components/Header.jsx
@@ -0,0 +1,30 @@
+import React from "react";
+import "./Header.scss";
+import {Logo, LogoColor, LogoType} from "@surfnet/sds";
+import {UserMenu} from "./UserMenu";
+import {Link} from "react-router-dom";
+import {useAppStore} from "../stores/AppStore";
+import I18n from "../locale/I18n";
+
+export const Header = () => {
+ const {user, config} = useAppStore(state => state);
+
+ return (
+
+
+
+
+
+ {user &&
+
+ }
+
+
+ );
+}
+
diff --git a/servicedesk-gui/src/components/Header.scss b/servicedesk-gui/src/components/Header.scss
new file mode 100644
index 00000000..6706fe04
--- /dev/null
+++ b/servicedesk-gui/src/components/Header.scss
@@ -0,0 +1,35 @@
+@use "../styles/vars.scss" as *;
+
+.header-container {
+
+ background-color: var(--sds--palette--main-color--400);
+ color: var(--sds--color--white);
+ margin: 0 auto;
+ padding: 5px 0 10px 0;
+
+ .header-inner {
+ position: relative;
+ transition: margin 150ms ease-in-out;
+ display: flex;
+ align-items: center;
+ width: 100%;
+ margin: 0 auto;
+ max-width: $medium;
+
+ @media (max-width: $medium) {
+ padding: 0 15px;
+ }
+
+ a.logo {
+ margin-top: 10px;
+ text-decoration: none;
+ }
+
+ }
+
+}
+
+
+
+
+
diff --git a/servicedesk-gui/src/components/LanguageSelector.jsx b/servicedesk-gui/src/components/LanguageSelector.jsx
new file mode 100644
index 00000000..e19f8ada
--- /dev/null
+++ b/servicedesk-gui/src/components/LanguageSelector.jsx
@@ -0,0 +1,39 @@
+import React from "react";
+import I18n from "../locale/I18n";
+import Cookies from "js-cookie";
+import {replaceQueryParameter} from "../utils/QueryParameters";
+import {stopEvent} from "../utils/Utils";
+import "./LanguageSelector.scss"
+
+export const LanguageSelector = () => {
+
+ const handleChooseLocale = locale => e => {
+ stopEvent(e);
+ Cookies.set("lang", locale, {expires: 356, secure: document.location.protocol.endsWith("https")});
+ I18n.locale = locale;
+ window.location.search = replaceQueryParameter(window.location.search, "lang", locale);
+ };
+
+ const renderLocaleChooser = locale => {
+ return (
+
+ {I18n.translations[locale].code}
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/servicedesk-gui/src/components/LanguageSelector.scss b/servicedesk-gui/src/components/LanguageSelector.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/servicedesk-gui/src/components/Page.jsx b/servicedesk-gui/src/components/Page.jsx
new file mode 100644
index 00000000..3bb6ae9d
--- /dev/null
+++ b/servicedesk-gui/src/components/Page.jsx
@@ -0,0 +1,11 @@
+import React from "react";
+import "./Page.scss";
+
+export const Page = ({children, className = ""}) => {
+
+ return (
+
+ {children}
+
+ );
+}
\ No newline at end of file
diff --git a/servicedesk-gui/src/components/Page.scss b/servicedesk-gui/src/components/Page.scss
new file mode 100644
index 00000000..859eb6ee
--- /dev/null
+++ b/servicedesk-gui/src/components/Page.scss
@@ -0,0 +1,6 @@
+@use "../styles/mixins" as *;
+
+.page {
+ @include page;
+
+}
\ No newline at end of file
diff --git a/servicedesk-gui/src/components/Tab.jsx b/servicedesk-gui/src/components/Tab.jsx
new file mode 100644
index 00000000..306ee95d
--- /dev/null
+++ b/servicedesk-gui/src/components/Tab.jsx
@@ -0,0 +1,49 @@
+import React from "react";
+import AlertIcon from "@surfnet/sds/icons/functional-icons/alert-circle.svg?react";
+import "./Tab.scss";
+import {BadgeNumber} from "@surfnet/sds";
+
+export default function Tab({
+ name,
+ onClick,
+ activeTab,
+ className = "",
+ label,
+ Icon,
+ notifier,
+ readOnly,
+ busy,
+ }) {
+
+ const onClickInner = () => {
+ if (!readOnly) {
+ onClick(name);
+ }
+ };
+
+
+ className += ` tab ${name}`;
+
+ if (activeTab === name) {
+ className += " active";
+ }
+ if (readOnly) {
+ className += " read-only";
+ }
+ if (busy) {
+ className += " busy";
+ }
+ let chipCount = null;
+ if (label && label.indexOf("(") > -1) {
+ const count = label.substring(label.indexOf("(") + 1, label.indexOf(")"));
+ label = label.substring(0, label.indexOf("(") - 1);
+ chipCount =
+ }
+
+ return (
+
+ {notifier &&
}
+
+
+ );
+}
diff --git a/servicedesk-gui/src/components/Tab.scss b/servicedesk-gui/src/components/Tab.scss
new file mode 100644
index 00000000..dd209edc
--- /dev/null
+++ b/servicedesk-gui/src/components/Tab.scss
@@ -0,0 +1,149 @@
+@use "../styles/vars.scss" as *;
+
+
+.tab {
+ min-width: 110px;
+ display: flex;
+ align-items: center;
+ position: relative;
+ border-bottom: 0.25rem solid transparent;
+ font-weight: 600;
+ border-top: 0.1875rem solid transparent;
+
+ &.busy {
+ min-width: 85px;
+ }
+
+ &:first-child {
+ margin-left: 0;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+
+ @media (max-width: $medium) {
+ margin: 0;
+ }
+
+ svg {
+ width: 20px;
+ font-size: 18px;
+ height: auto;
+ margin-right: 10px;
+ }
+
+ &:not(.read-only):hover {
+ background-color: var(--sds--color--blue--100);
+
+ span.tab-label, svg {
+ color: black;
+ }
+
+ svg {
+ fill: black;
+ }
+ }
+
+ &:not(.active) {
+ span.tab-label, svg {
+ color: var(--sds--color--black);
+ }
+
+ svg {
+ color: var(--sds--color--black);
+ }
+
+
+ &.read-only {
+ cursor: not-allowed;
+
+ span.tab-label, svg {
+ color: var(--sds--color--gray--300);
+ }
+
+ svg {
+ fill: var(--sds--color--gray--300);
+ }
+ }
+ }
+
+ &.active {
+ color: var(--sds--color--black);
+ border-bottom-color: var(--sds--color--blue--400);
+ box-shadow: 0 0.0625rem 0 0 var(--sds--palette--support-color--400);
+ background-color: var(--sds--color--white);
+
+ span.tab-label, svg {
+ color: var(--sds--color--black);
+ }
+
+ svg {
+ fill: var(--sds--color--black);
+
+ }
+ }
+
+ button {
+ padding: var(--sds--space--1-and-a-half) var(--sds--space--2);
+ min-height: 3rem;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ margin: auto;
+ }
+
+ .sds--badge--number {
+ margin-left: 4px;
+ }
+
+ span.notifier {
+ position: absolute;
+ right: -8px;
+ top: 0;
+
+ svg.alert-circle {
+ width: 16px;
+ height: auto;
+ }
+
+ @media (max-width: $medium) {
+ top: 8px;
+ right: 4px;
+ }
+
+ }
+
+ &.collaborations {
+ svg {
+ width: 28px;
+ }
+ }
+
+ &.collaboration_requests {
+ svg {
+ width: 22px;
+ }
+
+ }
+
+ &.serviceConnectionRequests {
+ svg {
+ width: 32px;
+ height: auto;
+ }
+
+ }
+
+ &.apikeys {
+ svg {
+ width: 28px;
+ }
+ }
+
+ span.tab-label {
+ font-weight: 600;
+ }
+}
+
+
diff --git a/servicedesk-gui/src/components/Tabs.jsx b/servicedesk-gui/src/components/Tabs.jsx
new file mode 100644
index 00000000..ab83ed3d
--- /dev/null
+++ b/servicedesk-gui/src/components/Tabs.jsx
@@ -0,0 +1,47 @@
+import React from "react";
+
+import Tab from "./Tab";
+import "./Tabs.scss";
+
+const Tabs = ({children, className, activeTab, tabChanged}) => {
+
+ const filteredChildren = children.filter(child => child);
+ if (!children.some((child => child.props && child.props.name === activeTab))) {
+ activeTab = (children[0] || {props: {name: activeTab}}).props.name
+ }
+
+ return (
+ <>
+
+ {
+
+ {filteredChildren.map(child => {
+ const {label, name, notifier, readOnly} = child.props;
+
+ return (
+ tabChanged(tab)}
+ className={className}
+ />
+ );
+ })}
+
}
+
+ {filteredChildren.map(child => {
+ if (child.props.name !== activeTab) {
+ return undefined;
+ }
+ return child;
+ })}
+
+ >
+ );
+}
+
+export default Tabs;
diff --git a/servicedesk-gui/src/components/Tabs.scss b/servicedesk-gui/src/components/Tabs.scss
new file mode 100644
index 00000000..e1f26b50
--- /dev/null
+++ b/servicedesk-gui/src/components/Tabs.scss
@@ -0,0 +1,21 @@
+@use "../styles/vars.scss" as *;
+
+.tabs-container {
+ width: 100%;
+ background-color: $background;
+
+ .tabs {
+ max-width: $medium;
+ margin: 0 auto;
+ display: flex;
+ gap: var(--sds--space--1);
+
+ @media (max-width: $medium) {
+ flex-direction: column;
+ padding: 0;
+ }
+
+
+ }
+
+}
diff --git a/servicedesk-gui/src/components/UnitHeader.jsx b/servicedesk-gui/src/components/UnitHeader.jsx
new file mode 100644
index 00000000..6ce43520
--- /dev/null
+++ b/servicedesk-gui/src/components/UnitHeader.jsx
@@ -0,0 +1,90 @@
+import React, {useState} from "react";
+import "./UnitHeader.scss";
+import logo from "../icons/frontdesk.svg";
+import {isEmpty, stopEvent} from "../utils/Utils";
+
+import {Button, ButtonType, MenuButton} from "@surfnet/sds";
+import {Link, useNavigate} from "react-router-dom";
+import I18n from "../locale/I18n";
+
+export const UnitHeader = ({
+ obj,
+ history,
+ auditLogPath,
+ name,
+ breadcrumbName,
+ firstTime,
+ actions,
+ children,
+ customAction,
+ displayDescription
+ }) => {
+
+ const [showDropDown, setShowDropDown] = useState(false);
+
+ const navigate = useNavigate();
+ const performAction = action => e => {
+ stopEvent(e);
+ !action.disabled && action.perform();
+ }
+
+ const otherOptions = (chevronActions, firstTime, auditLogPath, history, queryParam) => {
+ return (
+
+ {chevronActions.map((action, index) => -
+ {action.name}
+
)}
+ {(history && auditLogPath) &&
+ - navigate(`/audit-logs/${auditLogPath}?${queryParam}`)}>
+
+ {I18n.t("home.history")}
+
+
}
+ {firstTime &&
+ -
+
+ {I18n.t("home.firstTime")}
+
+
}
+
+ )
+ }
+
+ const queryParam = `name=${encodeURIComponent(breadcrumbName || name)}&back=${encodeURIComponent(window.location.pathname)}`;
+ const nonChevronActions = (actions || []).filter(action => action.buttonType !== ButtonType.Chevron);
+ const chevronActions = (actions || []).filter(action => action.buttonType === ButtonType.Chevron);
+ const showChevronAction = (history && auditLogPath) || firstTime || chevronActions.length > 0;
+ return (
+
+
+
+
+
+
+
{obj.name}
+ {obj.description}
+ {children}
+
+ {!isEmpty(actions) &&
+
+ {nonChevronActions.map((action, index) =>
+
}
+ {customAction && customAction}
+
+
+ )
+}
diff --git a/servicedesk-gui/src/components/UnitHeader.scss b/servicedesk-gui/src/components/UnitHeader.scss
new file mode 100644
index 00000000..0ad3eaf6
--- /dev/null
+++ b/servicedesk-gui/src/components/UnitHeader.scss
@@ -0,0 +1,114 @@
+@use "../styles/vars" as *;
+
+
+.unit-header-container {
+ background-color: $background;
+ padding: 15px 0 25px 0;
+
+ @media (max-width: $medium) {
+ padding: 15px 15px 25px 15px;
+ }
+
+ width: 100%;
+
+ .urn-container {
+ display: flex;
+ align-items: center;
+
+ span {
+ margin-right: 6px;
+ }
+
+ .copy-to-clipboard {
+ margin-top: 6px;
+ }
+ }
+
+ .unit-header {
+ max-width: $medium;
+ margin: 0 auto;
+ width: 100%;
+ display: flex;
+
+ @media (max-width: $medium) {
+ flex-direction: column;
+ }
+
+
+ .image {
+ display: flex;
+ align-items: center;
+ margin-bottom: auto;
+ background-color: var(--sds--color--white);
+ padding: 10px;
+ border-radius: 5px;
+ border: 1px solid var(--sds--color--gray--300);
+
+ img {
+ height: 76px;
+ width: auto;
+ margin-bottom: auto;
+ }
+
+ &.small {
+ svg {
+ width: 28px;
+ height: auto;
+ color: var(--sds--color--green--300);
+ margin-top: 10px;
+ }
+ }
+
+ @media (max-width: $medium) {
+ display: none;
+ }
+
+ }
+
+ .obj-name {
+ margin-left: 26px;
+ flex-grow: 3;
+ display: flex;
+ flex-direction: column;
+ word-break: break-word;
+
+ span.description {
+ margin: 20px 0;
+
+ &.compact {
+ margin: 10px 0;
+ }
+ }
+ }
+
+ }
+
+ .action-menu-container {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ margin-left: 25px;
+
+ @media (max-width: $medium) {
+ margin-top: 20px;
+ }
+
+ button {
+ width: 100%;
+ min-width: 150px;
+
+ &.sds--btn--secondary {
+ min-width: 180px;
+ }
+ }
+ }
+
+ .other-options, .sds--user-info--dropdown {
+ background-color: $background;
+ }
+
+ .sds--menu-button .sds--user-info--dropdown {
+ z-index: 99;
+ }
+
+}
diff --git a/servicedesk-gui/src/components/UserMenu.jsx b/servicedesk-gui/src/components/UserMenu.jsx
new file mode 100644
index 00000000..2a6c849a
--- /dev/null
+++ b/servicedesk-gui/src/components/UserMenu.jsx
@@ -0,0 +1,51 @@
+import I18n from "../locale/I18n";
+import React, {useState} from "react";
+import "./UserMenu.scss";
+import {useNavigate} from "react-router-dom";
+import {stopEvent} from "../utils/Utils";
+import {UserInfo} from "@surfnet/sds";
+import {useAppStore} from "../stores/AppStore";
+import {logout} from "../api";
+
+
+export const UserMenu = ({user}) => {
+ const navigate = useNavigate();
+
+ const [dropDownActive, setDropDownActive] = useState(false);
+
+ const logoutUser = e => {
+ stopEvent(e);
+ logout().then(() => {
+ useAppStore.setState(() => ({breadcrumbPath: []}));
+ navigate("/login", {state: "force"});
+ setTimeout(() =>
+ useAppStore.setState(() => ({user: null, breadcrumbPath: []})), 500);
+ });
+ }
+
+ const renderMenu = () => {
+ return (<>
+
+ >
+ )
+ }
+
+ return (
+ setTimeout(() => setDropDownActive(false), 325)}>
+ setDropDownActive(!dropDownActive)}
+ />
+
+ );
+
+
+}
diff --git a/servicedesk-gui/src/components/UserMenu.scss b/servicedesk-gui/src/components/UserMenu.scss
new file mode 100644
index 00000000..cb60fc0d
--- /dev/null
+++ b/servicedesk-gui/src/components/UserMenu.scss
@@ -0,0 +1,19 @@
+.user-menu {
+ margin-left: auto;
+ z-index: 4;
+
+ color: black;
+
+ button.sds--user-info--button {
+ background-color: white;
+
+ svg {
+ color: var(--sds--palette--main-color--400);
+ }
+ }
+
+ .sds--user-info--textual p.name, .sds--user-info--textual p.role {
+ color: white;
+ }
+}
+
diff --git a/servicedesk-gui/src/icons/alert.svg b/servicedesk-gui/src/icons/alert.svg
new file mode 100644
index 00000000..2df1a531
--- /dev/null
+++ b/servicedesk-gui/src/icons/alert.svg
@@ -0,0 +1,12 @@
+
diff --git a/servicedesk-gui/src/icons/arrow-right.svg b/servicedesk-gui/src/icons/arrow-right.svg
new file mode 100644
index 00000000..a3117d9c
--- /dev/null
+++ b/servicedesk-gui/src/icons/arrow-right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/servicedesk-gui/src/icons/frontdesk.svg b/servicedesk-gui/src/icons/frontdesk.svg
new file mode 100644
index 00000000..7422cc66
--- /dev/null
+++ b/servicedesk-gui/src/icons/frontdesk.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/servicedesk-gui/src/icons/undraw_page_not_found_re_e9o6.svg b/servicedesk-gui/src/icons/undraw_page_not_found_re_e9o6.svg
new file mode 100644
index 00000000..5442a706
--- /dev/null
+++ b/servicedesk-gui/src/icons/undraw_page_not_found_re_e9o6.svg
@@ -0,0 +1,66 @@
+
\ No newline at end of file
diff --git a/servicedesk-gui/src/locale/en.js b/servicedesk-gui/src/locale/en.js
index 07419fcb..1b8d45cc 100644
--- a/servicedesk-gui/src/locale/en.js
+++ b/servicedesk-gui/src/locale/en.js
@@ -9,6 +9,7 @@ const en = {
landing: {
header: {
title: "ServiceDesk",
+ subTitle: "",
login: "Log in",
sup: "EduID ServiceDesk is by invitation only.",
},
@@ -22,7 +23,55 @@ const en = {
},
header: {
title: "SURFconext ServiceDesk",
+ links: {
+ logout: "Logout"
+ }
},
+ serviceDesk: {
+ member: "ServiceDesk member"
+ },
+ tabs: {
+ home: "Home",
+ verify: "Identity control"
+ },
+ verification: {
+ header: "Enter the verification code",
+ proceed: "Continue",
+ error: "No user found for verification code {{code}}",
+ info: "This code is generated by the user in eduID."
+ },
+ control: {
+ header: "Identity control for code: ",
+ restart: "Start again",
+ proceed: "Continue",
+ error: "An unexpected error has occurred. Please contact SURF ServiceDesk",
+ info: "Control the ID of this user",
+ validDocuments: "Valid documents: passports, EEA ID cards, Dutch driving licenses and Dutch residence permits",
+ inValidDocuments: "Copies, public transport tickets and student cards are not valid documents",
+ isValid: "Is this all correct?",
+ validations: {
+ photo: "The person looks like the photo in the document",
+ valid: "The document is still valid",
+ lastName: "The last name in the document: {{lastName}}",
+ firstName: "The first names in the document: {{firstName}}",
+ dayOfBirth: "The date of birth in the document: {{dayOfBirth}} ",
+ },
+ invalidDate: "The day of birth can not be recognized, please select the correct date",
+ idDocument: "Fill in the last 6 characters of the ID document",
+ },
+ confirmation: {
+ header: "The identity of the person has been confirmed",
+ info: "From now on, the person will see a check mark in front of the personal data in eduID",
+ restart: "Back to home"
+ },
+ footer: {
+ terms: "Terms of Use",
+ termsLink: "https://support.surfconext.nl/terms-en",
+ privacy: "Privacy policy",
+ privacyLink: "https://support.surfconext.nl/privacy-en",
+ surfLink: "https://surf.nl",
+ },
+
}
export default en;
diff --git a/servicedesk-gui/src/locale/nl.js b/servicedesk-gui/src/locale/nl.js
index 558cddd6..82fff979 100644
--- a/servicedesk-gui/src/locale/nl.js
+++ b/servicedesk-gui/src/locale/nl.js
@@ -1,14 +1,15 @@
const nl = {
- code: "EN",
- name: "Engels",
+ code: "NL",
+ name: "Nederlands",
languages: {
- language: "Taal",
+ language: "Language",
en: "Engels",
nl: "Nederlands",
},
landing: {
header: {
title: "ServiceDesk",
+ subTitle: "",
login: "Log in",
sup: "EduID ServiceDesk is by invitation only.",
},
@@ -22,7 +23,56 @@ const nl = {
},
header: {
title: "SURFconext ServiceDesk",
+ links: {
+ logout: "Logout"
+ }
},
+ serviceDesk: {
+ member: "ServiceDesk member"
+ },
+ tabs: {
+ home: "Home",
+ verify: "Identity control"
+ },
+ verification: {
+ header: "Enter the verification code",
+ proceed: "Continue",
+ error: "No user found for verification code {{code}}",
+ info: "Deze code heeft de persoon gegenereerd in eduID"
+ },
+ control: {
+ header: "Identity control for code: ",
+ restart: "Start again",
+ proceed: "Continue",
+ error: "An unexpected error has occurred. Please contact SURF ServiceDesk",
+ info: "Controleer het identiteitsbewijs van deze persoon",
+ validDocuments: "Geldige documenten: paspoorten, EER ID-kaarten, Nederlandse rijbewijzen en Nederlandse verblijfsvergunningen",
+ inValidDocuments: "Kopieën, OV-kaarten en studentenkaarten zijn geen geldige documenten",
+ isValid: "Klopt dit allemaal?",
+ validations: {
+ photo: "De persoon lijkt op de foto in het document",
+ valid: "Het document is nog geldig",
+ lastName: "De achternaam in het document: {{lastName}}",
+ firstName: "De voornamen in het document: {{firstName}}",
+ dayOfBirth: "De geboortedatum in het document: {{dayOfBirth}}",
+ },
+ invalidDate: "De geboortedatum wordt niet herkent, selecteer de juiste datum",
+ idDocument: "Vul de laatste 6 karakters van het document in",
+ },
+ confirmation: {
+ header: "De identiteit van de persoon is bevestigd",
+ info: "De persoon ziet vanaf nu in eduID een vinkje voor de persoonsgegevens",
+ restart: "Terug naar home"
+ },
+ footer: {
+ terms: "Gebruiksvoorwaarden",
+ termsLink: "https://support.surfconext.nl/terms-nl",
+ privacy: "Privacybeleid",
+ privacyLink: "https://support.surfconext.nl/privacy-nl",
+ surfLink: "https://surf.nl",
+ },
+
+
}
export default nl;
diff --git a/servicedesk-gui/src/pages/App.js b/servicedesk-gui/src/pages/App.js
deleted file mode 100644
index a60d10e4..00000000
--- a/servicedesk-gui/src/pages/App.js
+++ /dev/null
@@ -1,125 +0,0 @@
-import './App.scss';
-import {Navigate, Route, Routes, useNavigate} from "react-router-dom";
-import {useEffect, useState} from "react";
-import {Loader} from "@surfnet/sds";
-import {useAppStore} from "../stores/AppStore";
-import {configuration, csrf, me} from "../api";
-import {Login} from "./Login";
-import {Home} from "./Home";
-import {Flash} from "../components/Flash";
-import {Header} from "../components/Header";
-import {Footer} from "../components/Footer";
-import {BreadCrumb} from "../components/BreadCrumb";
-import {Invitation} from "./Invitation";
-import {login} from "../utils/Login";
-import NotFound from "./NotFound";
-import {Impersonating} from "../components/Impersonating";
-import RefreshRoute from "./RefreshRoute";
-import {InviteOnly} from "./InviteOnly";
-import {Profile} from "./Profile";
-import {Role} from "./Role";
-import {RoleForm} from "./RoleForm";
-import {InvitationForm} from "./InvitationForm";
-import {isEmpty} from "../utils/Utils";
-import {MissingAttributes} from "./MissingAttributes";
-import {Inviter} from "./Inviter";
-import {Application} from "./Application";
-import {System} from "./System";
-
-
-export const App = () => {
-
-
- useEffect(() => {
- setLoading(true);
- csrf().then(token => {
- useAppStore.setState(() => ({csrfToken: token.token}));
- configuration()
- .then(res => {
- useAppStore.setState(() => ({config: res}));
- if (!res.authenticated) {
- if (!res.name) {
- const direction = window.location.pathname + window.location.search;
- localStorage.setItem("location", direction);
- } else if (!isEmpty(res.missingAttributes)) {
- setLoading(false);
- navigate("/missingAttributes");
- return;
- }
- const pathname = localStorage.getItem("location") || window.location.pathname;
- const isInvitationAcceptFlow = window.location.pathname.startsWith("/invitation/accept");
- if (res.name && !pathname.startsWith("/invitation/accept") && !isInvitationAcceptFlow) {
- setLoading(false);
- navigate("/deadend");
- } else if (pathname === "/" || pathname.startsWith("/login") || pathname.startsWith("/invitation/accept") || isInvitationAcceptFlow) {
- setLoading(false);
- navigate(isInvitationAcceptFlow ? (window.location.pathname + window.location.search) : pathname);
- } else {
- //Bookmarked URL's trigger a direct login and skip the landing page
- login(res);
- }
- } else {
- me()
- .then(res => {
- useAppStore.setState(() => ({user: res, authenticated: true}));
- setLoading(false);
- const location = localStorage.getItem("location") || window.location.pathname + window.location.search;
- const newLocation = location.startsWith("/login") ? "/home" : location;
- localStorage.removeItem("location");
- navigate(newLocation);
- });
- }
- })
- .catch(() => {
- setLoading(false);
- navigate("/deadend");
- })
- })
- }, [reload, impersonator]); // eslint-disable-line react-hooks/exhaustive-deps
-
- if (loading) {
- return
- }
- return (
-
-
-
-
- {impersonator && }
-
- {authenticated && }
- {authenticated &&
-
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
- {(user && user.superUser) &&
- }/>
- }
- }/>
- }
- {!authenticated &&
-
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
- }
-
- {
}
-
- );
-}
diff --git a/servicedesk-gui/src/pages/App.scss b/servicedesk-gui/src/pages/App.scss
deleted file mode 100644
index 30f8b6f4..00000000
--- a/servicedesk-gui/src/pages/App.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-.access {
- display: flex;
- flex-direction: column;
- min-height: 100vh;
- position: relative;
- width: 100%;
-}
-
-.sds--modal.sds--backdrop {
- z-index: 99;
-}
-
-.react-datepicker-popper {
- z-index: 3 !important;
-}
\ No newline at end of file
diff --git a/servicedesk-gui/src/pages/Home.jsx b/servicedesk-gui/src/pages/Home.jsx
new file mode 100644
index 00000000..509bbaa4
--- /dev/null
+++ b/servicedesk-gui/src/pages/Home.jsx
@@ -0,0 +1,65 @@
+import {useAppStore} from "../stores/AppStore";
+import I18n from "../locale/I18n";
+import React, {useEffect, useState} from "react";
+
+import Tabs from "../components/Tabs";
+import "./Home.scss";
+import {UnitHeader} from "../components/UnitHeader";
+import {useNavigate, useParams} from "react-router-dom";
+import {Page} from "../components/Page";
+import {Loader} from "@surfnet/sds";
+import VerifyWizard from "../tabs/VerifyWizard.jsx";
+
+export const Home = () => {
+
+ const {tab = "verify"} = useParams();
+ const [currentTab, setCurrentTab] = useState(tab);
+ const [tabs, setTabs] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ const navigate = useNavigate();
+
+ const user = useAppStore((state) => state.user)
+
+ useEffect(() => {
+ if (user) {
+ useAppStore.setState({
+ breadcrumbPath: [
+ {path: "/home", value: I18n.t("tabs.home")},
+ {value: I18n.t(`tabs.${currentTab}`)}
+ ]
+ });
+ }
+ const newTabs = [
+
+
+
+ ];
+ setTabs(newTabs);
+ setLoading(false);
+ }, [currentTab, user]);// eslint-disable-line react-hooks/exhaustive-deps
+
+ const tabChanged = (name) => {
+ setCurrentTab(name);
+ navigate(`/home/${name}`);
+ }
+
+ if (loading) {
+ return
+ }
+ return (
+
+
+
+ {I18n.t("landing.header.subTitle")}
+
+
+ {tabs}
+
+
+
+ );
+}
diff --git a/servicedesk-gui/src/pages/Home.scss b/servicedesk-gui/src/pages/Home.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/servicedesk-gui/src/pages/Login.js b/servicedesk-gui/src/pages/Login.jsx
similarity index 54%
rename from servicedesk-gui/src/pages/Login.js
rename to servicedesk-gui/src/pages/Login.jsx
index 26b1d7cd..0ee67e30 100644
--- a/servicedesk-gui/src/pages/Login.js
+++ b/servicedesk-gui/src/pages/Login.jsx
@@ -1,30 +1,12 @@
+import React from "react";
import {Button, ButtonSize, ButtonType} from "@surfnet/sds";
import './Login.scss';
import I18n from "../locale/I18n";
import DOMPurify from "dompurify";
-import {LandingInfo} from "../components/LandingInfo";
-import HappyLogo from "../icons/landing/undraw_startled_-8-p0r.svg";
-import {login} from "../utils/Login";
-import {useAppStore} from "../stores/AppStore";
-import {isEmpty} from "../utils/Utils";
-import {useLocation} from "react-router-dom";
-import {useState} from "react";
+import IdCard from "../icons/landing/idcard.svg";
+import LandingPage from "../icons/landing/landingpage.svg";
export const Login = () => {
- const location = useLocation();
- const [spin, setSpin] = useState(false);
-
- const config = useAppStore((state) => state.config);
-
- const doLogin = () => {
- const force = location.state === "force";
- login(config, !isEmpty(force));
- }
-
- const toggleSpin = () => {
- setSpin(true);
- setTimeout(() => setSpin(false), 725);
- }
return (
@@ -33,7 +15,7 @@ export const Login = () => {
-
-
toggleSpin()} src={HappyLogo} alt="logo"/>
+
-