From 47d14895a5730f42182ffc2691dc769dfcf5bb89 Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Mon, 20 Jan 2025 16:56:15 +0100 Subject: [PATCH] Finished client side service-desk #587 --- myconext-gui/docker/conf/000-default.conf | 2 +- .../myconext/api/DefaultErrorController.java | 4 + .../java/myconext/api/LoginController.java | 1 + .../myconext/api/ServiceDeskController.java | 23 +- .../myconext/exceptions/RemoteException.java | 23 + .../main/java/myconext/model/ControlCode.java | 2 + .../api/ServiceDeskControllerTest.java | 1 + servicedesk-gui/docker/Dockerfile | 4 + servicedesk-gui/docker/conf/000-default.conf | 88 ++++ servicedesk-gui/eslint.config.js | 61 +-- servicedesk-gui/index.html | 2 +- servicedesk-gui/package.json | 6 +- servicedesk-gui/src/App.jsx | 51 ++- servicedesk-gui/src/App.scss | 15 + .../src/__tests__/locale/en.test.js | 2 + .../src/__tests__/stores/AppStore.test.js | 3 +- .../src/__tests__/utils/Manage.test.js | 33 -- .../src/__tests__/utils/Pagination.test.js | 12 - .../src/__tests__/utils/UserRole.test.js | 158 ------- .../src/__tests__/utils/Utils.test.js | 1 + .../src/__tests__/utils/applications.json | 156 ------- .../src/__tests__/utils/roles.json | 291 ------------- .../src/__tests__/utils/userRoles.json | 73 ---- .../src/__tests__/validations/regExps.test.js | 16 - servicedesk-gui/src/api/index.js | 26 +- servicedesk-gui/src/components/BreadCrumb.jsx | 41 ++ .../src/components/BreadCrumb.scss | 56 +++ servicedesk-gui/src/components/Flash.jsx | 30 ++ servicedesk-gui/src/components/Flash.scss | 10 + servicedesk-gui/src/components/Footer.jsx | 34 ++ servicedesk-gui/src/components/Footer.scss | 3 + servicedesk-gui/src/components/Header.jsx | 30 ++ servicedesk-gui/src/components/Header.scss | 35 ++ .../src/components/LanguageSelector.jsx | 39 ++ .../src/components/LanguageSelector.scss | 0 servicedesk-gui/src/components/Page.jsx | 11 + servicedesk-gui/src/components/Page.scss | 6 + servicedesk-gui/src/components/Tab.jsx | 49 +++ servicedesk-gui/src/components/Tab.scss | 149 +++++++ servicedesk-gui/src/components/Tabs.jsx | 47 +++ servicedesk-gui/src/components/Tabs.scss | 21 + servicedesk-gui/src/components/UnitHeader.jsx | 90 ++++ .../src/components/UnitHeader.scss | 114 +++++ servicedesk-gui/src/components/UserMenu.jsx | 51 +++ servicedesk-gui/src/components/UserMenu.scss | 19 + servicedesk-gui/src/icons/alert.svg | 12 + servicedesk-gui/src/icons/arrow-right.svg | 1 + servicedesk-gui/src/icons/frontdesk.svg | 1 + .../icons/undraw_page_not_found_re_e9o6.svg | 66 +++ servicedesk-gui/src/locale/en.js | 49 +++ servicedesk-gui/src/locale/nl.js | 56 ++- servicedesk-gui/src/pages/App.js | 125 ------ servicedesk-gui/src/pages/App.scss | 15 - servicedesk-gui/src/pages/Home.jsx | 65 +++ servicedesk-gui/src/pages/Home.scss | 0 .../src/pages/{Login.js => Login.jsx} | 30 +- .../src/pages/{NotFound.js => NotFound.jsx} | 0 servicedesk-gui/src/pages/RefreshRoute.jsx | 21 + servicedesk-gui/src/stores/AppStore.js | 1 + servicedesk-gui/src/styles/vars.scss | 2 +- servicedesk-gui/src/tabs/Confirmation.jsx | 31 ++ servicedesk-gui/src/tabs/Confirmation.scss | 41 ++ servicedesk-gui/src/tabs/Control.jsx | 103 +++++ servicedesk-gui/src/tabs/Control.scss | 77 ++++ servicedesk-gui/src/tabs/Verification.jsx | 58 +++ servicedesk-gui/src/tabs/Verification.scss | 37 ++ servicedesk-gui/src/tabs/VerifyWizard.jsx | 21 + servicedesk-gui/src/tabs/VerifyWizard.scss | 17 + servicedesk-gui/src/utils/Utils.js | 8 - servicedesk-gui/yarn.lock | 395 +++++++++++++++++- 70 files changed, 2152 insertions(+), 969 deletions(-) create mode 100644 myconext-server/src/main/java/myconext/exceptions/RemoteException.java create mode 100644 servicedesk-gui/docker/Dockerfile create mode 100644 servicedesk-gui/docker/conf/000-default.conf delete mode 100644 servicedesk-gui/src/__tests__/utils/Manage.test.js delete mode 100644 servicedesk-gui/src/__tests__/utils/Pagination.test.js delete mode 100644 servicedesk-gui/src/__tests__/utils/UserRole.test.js delete mode 100644 servicedesk-gui/src/__tests__/utils/applications.json delete mode 100644 servicedesk-gui/src/__tests__/utils/roles.json delete mode 100644 servicedesk-gui/src/__tests__/utils/userRoles.json delete mode 100644 servicedesk-gui/src/__tests__/validations/regExps.test.js create mode 100644 servicedesk-gui/src/components/BreadCrumb.jsx create mode 100644 servicedesk-gui/src/components/BreadCrumb.scss create mode 100644 servicedesk-gui/src/components/Flash.jsx create mode 100644 servicedesk-gui/src/components/Flash.scss create mode 100644 servicedesk-gui/src/components/Footer.jsx create mode 100644 servicedesk-gui/src/components/Footer.scss create mode 100644 servicedesk-gui/src/components/Header.jsx create mode 100644 servicedesk-gui/src/components/Header.scss create mode 100644 servicedesk-gui/src/components/LanguageSelector.jsx create mode 100644 servicedesk-gui/src/components/LanguageSelector.scss create mode 100644 servicedesk-gui/src/components/Page.jsx create mode 100644 servicedesk-gui/src/components/Page.scss create mode 100644 servicedesk-gui/src/components/Tab.jsx create mode 100644 servicedesk-gui/src/components/Tab.scss create mode 100644 servicedesk-gui/src/components/Tabs.jsx create mode 100644 servicedesk-gui/src/components/Tabs.scss create mode 100644 servicedesk-gui/src/components/UnitHeader.jsx create mode 100644 servicedesk-gui/src/components/UnitHeader.scss create mode 100644 servicedesk-gui/src/components/UserMenu.jsx create mode 100644 servicedesk-gui/src/components/UserMenu.scss create mode 100644 servicedesk-gui/src/icons/alert.svg create mode 100644 servicedesk-gui/src/icons/arrow-right.svg create mode 100644 servicedesk-gui/src/icons/frontdesk.svg create mode 100644 servicedesk-gui/src/icons/undraw_page_not_found_re_e9o6.svg delete mode 100644 servicedesk-gui/src/pages/App.js delete mode 100644 servicedesk-gui/src/pages/App.scss create mode 100644 servicedesk-gui/src/pages/Home.jsx create mode 100644 servicedesk-gui/src/pages/Home.scss rename servicedesk-gui/src/pages/{Login.js => Login.jsx} (54%) rename servicedesk-gui/src/pages/{NotFound.js => NotFound.jsx} (100%) create mode 100644 servicedesk-gui/src/pages/RefreshRoute.jsx create mode 100644 servicedesk-gui/src/tabs/Confirmation.jsx create mode 100644 servicedesk-gui/src/tabs/Confirmation.scss create mode 100644 servicedesk-gui/src/tabs/Control.jsx create mode 100644 servicedesk-gui/src/tabs/Control.scss create mode 100644 servicedesk-gui/src/tabs/Verification.jsx create mode 100644 servicedesk-gui/src/tabs/Verification.scss create mode 100644 servicedesk-gui/src/tabs/VerifyWizard.jsx create mode 100644 servicedesk-gui/src/tabs/VerifyWizard.scss 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 @@ ServiceDesk - +
diff --git a/servicedesk-gui/package.json b/servicedesk-gui/package.json index cda943ba..0d011a5a 100644 --- a/servicedesk-gui/package.json +++ b/servicedesk-gui/package.json @@ -7,10 +7,11 @@ "dev": "vite", "build": "vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "test": "vitest" }, "dependencies": { - "@surfnet/sds": "^0.0.120", + "@surfnet/sds": "^0.0.126", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "7.1.0", @@ -34,6 +35,7 @@ "globals": "^15.14.0", "sass": "^1.83.0", "vite": "^6.0.4", + "vitest": "^3.0.2", "http-proxy-middleware": "^3.0.3" } } diff --git a/servicedesk-gui/src/App.jsx b/servicedesk-gui/src/App.jsx index 533483be..52b56104 100644 --- a/servicedesk-gui/src/App.jsx +++ b/servicedesk-gui/src/App.jsx @@ -1,19 +1,38 @@ -import {useEffect, useState} from 'react' +import React, {useEffect, useState} from 'react' import {Loader} from "@surfnet/sds"; -import './App.scss' +import './App.scss'; +import {Navigate, Route, Routes, useNavigate} from "react-router-dom"; // import {useNavigate} from "react-router-dom"; -// import {useAppStore} from "./stores/AppStore.js"; import {configuration} from "./api/index.js"; import {useAppStore} from "./stores/AppStore.js"; +import {Flash} from "./components/Flash.jsx"; +import {Header} from "./components/Header.jsx"; +import {BreadCrumb} from "./components/BreadCrumb.jsx"; +import NotFound from "./pages/NotFound.jsx"; +import RefreshRoute from "./pages/RefreshRoute.jsx"; +import {Login} from "./pages/Login.jsx"; +import {Home} from "./pages/Home.jsx"; +import {Footer} from "./components/Footer.jsx"; const App = () => { const [loading, setLoading] = useState(true); + const [isAuthenticated, setIsAuthenticated] = useState(false); + + const navigate = useNavigate(); useEffect(() => { configuration().then(res => { useAppStore.setState(() => ({config: res, authenticated: res.authenticated, user: res.user})); setLoading(false); + setIsAuthenticated(res.authenticated); + if (res.authenticated && res.user?.serviceDeskMember) { + navigate("/home") + } else if (res.authenticated && !res.user) + navigate("/not-found") + else { + navigate("/login") + } }); }, []); @@ -23,11 +42,29 @@ const App = () => { } return ( - <> -
-

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 ( +
+
+
+ Vite logo +
+
+

{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"/> + logo
- + logo ); diff --git a/servicedesk-gui/src/pages/NotFound.js b/servicedesk-gui/src/pages/NotFound.jsx similarity index 100% rename from servicedesk-gui/src/pages/NotFound.js rename to servicedesk-gui/src/pages/NotFound.jsx diff --git a/servicedesk-gui/src/pages/RefreshRoute.jsx b/servicedesk-gui/src/pages/RefreshRoute.jsx new file mode 100644 index 00000000..82c43304 --- /dev/null +++ b/servicedesk-gui/src/pages/RefreshRoute.jsx @@ -0,0 +1,21 @@ +import React, {useEffect} from "react"; +import {useNavigate, useParams} from "react-router-dom"; +import {Loader} from "@surfnet/sds"; + +const RefreshRoute = () => { + + const {path} = useParams(); + + const navigate = useNavigate(); + + useEffect(() => { + const decodedPath = decodeURIComponent(path); + navigate(decodedPath); + }, [path, navigate]); + + return ( + + ); + +} +export default RefreshRoute; diff --git a/servicedesk-gui/src/stores/AppStore.js b/servicedesk-gui/src/stores/AppStore.js index 59975b97..858aed79 100644 --- a/servicedesk-gui/src/stores/AppStore.js +++ b/servicedesk-gui/src/stores/AppStore.js @@ -5,6 +5,7 @@ export const useAppStore = create(set => ({ reload: false, config: {}, user: {}, + controlCode: {}, flash: {msg: "", className: "hide", type: "info"}, setFlash: (message, type) => { set({flash: {msg: message, type: type || "info"}}); diff --git a/servicedesk-gui/src/styles/vars.scss b/servicedesk-gui/src/styles/vars.scss index 7cf678b0..738e73cc 100644 --- a/servicedesk-gui/src/styles/vars.scss +++ b/servicedesk-gui/src/styles/vars.scss @@ -2,4 +2,4 @@ $background: #eaebf0; $br: 6px; // Screen $medium: 1280px; -$tablet-max: 824px; +$verification-width: 680px; diff --git a/servicedesk-gui/src/tabs/Confirmation.jsx b/servicedesk-gui/src/tabs/Confirmation.jsx new file mode 100644 index 00000000..dee04757 --- /dev/null +++ b/servicedesk-gui/src/tabs/Confirmation.jsx @@ -0,0 +1,31 @@ +import "./Confirmation.scss"; +import React from "react"; +import I18n from "../locale/I18n"; +import {Button, ButtonType, Toaster, ToasterType} from "@surfnet/sds"; +import highFive from "../icons/undraw_High_five.svg"; +import {useAppStore} from "../stores/AppStore.js"; + +const Confirmation = ({restart}) => { + + const {controlCode} = useAppStore(state => state); + + return ( +
+

{I18n.t("confirmation.header")}

+ +
+ high-five +
+
+
+
+ ); + +}; + +export default Confirmation; \ No newline at end of file diff --git a/servicedesk-gui/src/tabs/Confirmation.scss b/servicedesk-gui/src/tabs/Confirmation.scss new file mode 100644 index 00000000..5262bc4c --- /dev/null +++ b/servicedesk-gui/src/tabs/Confirmation.scss @@ -0,0 +1,41 @@ +@use "../styles/vars.scss" as *; + + +div.confirmation { + margin: 40px 0 25px 0; + border: 1px solid var(--sds--color--gray--300); + border-radius: 8px; + max-width: $verification-width; + display: flex; + flex-direction: column; + + h4 { + margin: 25px 25px 0 25px; + } + + .sds--toaster { + max-width: max-content; + width: 100%; + margin: 20px auto; + } + .img-container { + display: flex; + padding: 20px; + img { + margin: auto; + width: 460px; + height: auto; + } + } + .button-container { + display: flex; + padding: 20px; + button { + margin: auto; + max-width: 360px; + } + } + + +} + diff --git a/servicedesk-gui/src/tabs/Control.jsx b/servicedesk-gui/src/tabs/Control.jsx new file mode 100644 index 00000000..af4ff7fa --- /dev/null +++ b/servicedesk-gui/src/tabs/Control.jsx @@ -0,0 +1,103 @@ +import "./Control.scss"; +import React, {useEffect, useState} from "react"; +import I18n from "../locale/I18n"; +import {Button, ButtonType, CodeValidation, Switch, Toaster, ToasterType} from "@surfnet/sds"; +import {convertUserControlCode, validateDate} from "../api/index.js"; +import {useAppStore} from "../stores/AppStore.js"; +import {useNavigate} from "react-router-dom"; +import DOMPurify from "dompurify"; +import {isEmpty} from "../utils/Utils.js"; + +const Control = ({restart, proceed}) => { + + const {controlCode} = useAppStore(state => state); + const [validDayOfBirth, setValidDayOfBirth] = useState(true); + const [loading, setLoading] = useState(false); + const [confirmations, setConfirmations] = useState(Array(5).fill(false)); + const [documentId, setDocumentId] = useState(""); + const [error, setError] = useState({}); + + const confirmationItems = ["photo", "valid", "lastName", "firstName", "dayOfBirth"]; + + const navigate = useNavigate(); + + useEffect(() => { + validateDate(controlCode.dayOfBirth).then(res => { + setValidDayOfBirth(res); + }); + }, []); + + const confirm = (index, value) => { + const newConfirmations = [...confirmations]; + newConfirmations[index] = value; + setConfirmations(newConfirmations); + } + + const doConvertUserControlCode = () => { + const {firstName, lastName, dayOfBirth, code, userUid} = controlCode; + setLoading(true); + convertUserControlCode(firstName, lastName, dayOfBirth, code, documentId, userUid) + .then(() => { + setError({}); + setLoading(false); + proceed(); + }) + .catch(e => { + setLoading(false); + e.response.json().then(j => { + setError(j); + }); + + }); + } + + return ( +
+
+

{I18n.t("control.header")}{controlCode.code}

+
+ {!isEmpty(error) && +
+

+

} + +
+

{I18n.t("control.info")}

+

{I18n.t("control.validDocuments")}

+ +

{I18n.t("control.isValid")}

+ {confirmationItems.map((name, index) =>
+

+ confirm(index, val)}/> +

)} +
+

+

+ setDocumentId(val)} + size={6} + focusFirst={false} + validate={() => true} + /> +
+
+
+ +
+ ) +}; + +export default Control; \ No newline at end of file diff --git a/servicedesk-gui/src/tabs/Control.scss b/servicedesk-gui/src/tabs/Control.scss new file mode 100644 index 00000000..3b5c32b8 --- /dev/null +++ b/servicedesk-gui/src/tabs/Control.scss @@ -0,0 +1,77 @@ +@use "../styles/vars.scss" as *; + + +div.control { + margin: 25px 0 25px 0; + + .control-header { + display: flex; + align-items: center; + margin-bottom: 25px; + max-width: $verification-width; + + span.code { + color: var(--sds--color--blue--400); + } + + button { + margin-left: auto; + } + + } + + p.inner-html span { + font-weight: 600; + } + + .control-validation { + border: 1px solid var(--sds--color--gray--300); + border-radius: 8px; + max-width: $verification-width; + padding: 25px; + display: flex; + flex-direction: column; + gap: 20px; + + .sds--toaster { + max-width: max-content; + } + } + + .validation-item { + display: flex; + align-items: center; + border: 1px solid var(--sds--color--gray--200); + padding: 10px; + + &.column { + flex-direction: column; + align-items: normal; + } + + .sds--tooltip-parent { + margin-left: auto; + } + + } + + .sds--toaster { + margin-bottom: 10px; + font-weight: 600; + } + + .code-validation { + padding: 15px 0 0 0; + + button { + margin-left: auto; + } + } + + .error { + margin: 15px 0; + font-weight: 600; + color: var(--sds--color--red--400); + } + +} diff --git a/servicedesk-gui/src/tabs/Verification.jsx b/servicedesk-gui/src/tabs/Verification.jsx new file mode 100644 index 00000000..26d59795 --- /dev/null +++ b/servicedesk-gui/src/tabs/Verification.jsx @@ -0,0 +1,58 @@ +import "./Verification.scss"; +import React, {useState} from "react"; +import I18n from "../locale/I18n"; +import {Button, CodeValidation, ErrorIndicator} from "@surfnet/sds"; +import alertIcon from "../icons/alert.svg"; +import {getUserControlCode} from "../api/index.js"; +import {useAppStore} from "../stores/AppStore.js"; + +const Verification = ({proceed}) => { + + const [clear, setClear] = useState(false); + const [error, setError] = useState(false); + const [code, setCode] = useState(""); + + const doGetUserControlCode = val => { + setCode(val); + getUserControlCode(val) + .then(controlCode => { + useAppStore.setState(() => ({controlCode: controlCode})); + setClear(false); + setCode(""); + setError(false); + proceed(); + }) + .catch(() => { + setClear(true); + setTimeout(() => setClear(false), 20); + setError(true); + }); + } + + return ( +
+

{I18n.t("verification.header")}

+
+ doGetUserControlCode(val)} + size={5} + clear={clear} + intermediateCallback={() => setError(false)} + validate={val => !isNaN(val)} + /> +
+ {error && } +
+ +

{I18n.t("verification.info")}

+
+ +
+ ); + +}; + +export default Verification; \ No newline at end of file diff --git a/servicedesk-gui/src/tabs/Verification.scss b/servicedesk-gui/src/tabs/Verification.scss new file mode 100644 index 00000000..edc9624a --- /dev/null +++ b/servicedesk-gui/src/tabs/Verification.scss @@ -0,0 +1,37 @@ +@use "../styles/vars.scss" as *; + + +div.verification { + margin: 40px 0 25px 0; + border: 1px solid var(--sds--color--gray--300); + border-radius: 8px; + max-width: $verification-width; + + h4 { + margin: 25px 25px 0 25px; + + } + + .disclaimer { + display: flex; + align-content: center; + padding: 15px; + margin-top: 25px; + background-color: var(--sds--color--blue--200); + border: 1px solid var(--sds--color--blue--200); + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + + p { + font-weight: 600; + margin-left: 15px; + } + } + + .sds--error-indicator { + margin-left: 25px; + font-weight: 600; + } + +} + diff --git a/servicedesk-gui/src/tabs/VerifyWizard.jsx b/servicedesk-gui/src/tabs/VerifyWizard.jsx new file mode 100644 index 00000000..952bbf99 --- /dev/null +++ b/servicedesk-gui/src/tabs/VerifyWizard.jsx @@ -0,0 +1,21 @@ +import "./VerifyWizard.scss"; +import React, {useState} from "react"; +import Verification from "./Verification.jsx"; +import Control from "./Control.jsx"; +import Confirmation from "./Confirmation.jsx"; + +const VerifyWizard = () => { + + const [step, setStep] = useState(1); + if (step === 1) { + return setStep(2)}/>; + } + if (step === 2) { + return setStep(1)} proceed={() => setStep(3)}/>; + } + if (step === 3) { + return setStep(1)} />; + } +}; + +export default VerifyWizard; \ No newline at end of file diff --git a/servicedesk-gui/src/tabs/VerifyWizard.scss b/servicedesk-gui/src/tabs/VerifyWizard.scss new file mode 100644 index 00000000..9de5b49c --- /dev/null +++ b/servicedesk-gui/src/tabs/VerifyWizard.scss @@ -0,0 +1,17 @@ +.code-validation { + padding: 25px; + display: flex; + gap: 20px; + + .sds--code-validation-container { + margin-right: 25px; + + .sds--code-validation input.value { + width: 48px; + height: 48px; + padding-left: 16px; + font-size: 24px; + } + } + +} diff --git a/servicedesk-gui/src/utils/Utils.js b/servicedesk-gui/src/utils/Utils.js index 042a43c7..17c5511b 100644 --- a/servicedesk-gui/src/utils/Utils.js +++ b/servicedesk-gui/src/utils/Utils.js @@ -27,14 +27,6 @@ export function isEmpty(obj) { return false; } -export function pseudoGuid() { - return (crypto.randomUUID && crypto.randomUUID()) || Math.round((new Date().getTime() * Math.random() * 1000)).toString() -} - -export const splitListSemantically = (arr, lastSeparator) => { - return [arr.slice(0, -1).join(", "), arr.slice(-1)[0]].join(arr.length < 2 ? "" : ` ${lastSeparator} `); -} - export const sanitizeURL = url => { const protocol = new URL(url).protocol; return ["https:", "http:"].includes(protocol) ? url : "about:blank"; diff --git a/servicedesk-gui/yarn.lock b/servicedesk-gui/yarn.lock index 2773323c..f8e56ca4 100644 --- a/servicedesk-gui/yarn.lock +++ b/servicedesk-gui/yarn.lock @@ -263,121 +263,246 @@ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz#b57697945b50e99007b4c2521507dc613d4a648c" integrity sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw== +"@esbuild/aix-ppc64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz#38848d3e25afe842a7943643cbcd387cc6e13461" + integrity sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA== + "@esbuild/android-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz#1add7e0af67acefd556e407f8497e81fddad79c0" integrity sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w== +"@esbuild/android-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz#f592957ae8b5643129fa889c79e69cd8669bb894" + integrity sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg== + "@esbuild/android-arm@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz#ab7263045fa8e090833a8e3c393b60d59a789810" integrity sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew== +"@esbuild/android-arm@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.2.tgz#72d8a2063aa630308af486a7e5cbcd1e134335b3" + integrity sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q== + "@esbuild/android-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz#e8f8b196cfdfdd5aeaebbdb0110983460440e705" integrity sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ== +"@esbuild/android-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.2.tgz#9a7713504d5f04792f33be9c197a882b2d88febb" + integrity sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw== + "@esbuild/darwin-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz#2d0d9414f2acbffd2d86e98253914fca603a53dd" integrity sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw== +"@esbuild/darwin-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz#02ae04ad8ebffd6e2ea096181b3366816b2b5936" + integrity sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA== + "@esbuild/darwin-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz#33087aab31a1eb64c89daf3d2cf8ce1775656107" integrity sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA== +"@esbuild/darwin-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz#9ec312bc29c60e1b6cecadc82bd504d8adaa19e9" + integrity sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA== + "@esbuild/freebsd-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz#bb76e5ea9e97fa3c753472f19421075d3a33e8a7" integrity sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA== +"@esbuild/freebsd-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz#5e82f44cb4906d6aebf24497d6a068cfc152fa00" + integrity sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg== + "@esbuild/freebsd-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz#e0e2ce9249fdf6ee29e5dc3d420c7007fa579b93" integrity sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ== +"@esbuild/freebsd-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz#3fb1ce92f276168b75074b4e51aa0d8141ecce7f" + integrity sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q== + "@esbuild/linux-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz#d1b2aa58085f73ecf45533c07c82d81235388e75" integrity sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g== +"@esbuild/linux-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz#856b632d79eb80aec0864381efd29de8fd0b1f43" + integrity sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg== + "@esbuild/linux-arm@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz#8e4915df8ea3e12b690a057e77a47b1d5935ef6d" integrity sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw== +"@esbuild/linux-arm@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz#c846b4694dc5a75d1444f52257ccc5659021b736" + integrity sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA== + "@esbuild/linux-ia32@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz#8200b1110666c39ab316572324b7af63d82013fb" integrity sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA== +"@esbuild/linux-ia32@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz#f8a16615a78826ccbb6566fab9a9606cfd4a37d5" + integrity sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw== + "@esbuild/linux-loong64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz#6ff0c99cf647504df321d0640f0d32e557da745c" integrity sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g== +"@esbuild/linux-loong64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz#1c451538c765bf14913512c76ed8a351e18b09fc" + integrity sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ== + "@esbuild/linux-mips64el@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz#3f720ccd4d59bfeb4c2ce276a46b77ad380fa1f3" integrity sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA== +"@esbuild/linux-mips64el@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz#0846edeefbc3d8d50645c51869cc64401d9239cb" + integrity sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw== + "@esbuild/linux-ppc64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz#9d6b188b15c25afd2e213474bf5f31e42e3aa09e" integrity sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ== +"@esbuild/linux-ppc64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz#8e3fc54505671d193337a36dfd4c1a23b8a41412" + integrity sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw== + "@esbuild/linux-riscv64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz#f989fdc9752dfda286c9cd87c46248e4dfecbc25" integrity sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw== +"@esbuild/linux-riscv64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz#6a1e92096d5e68f7bb10a0d64bb5b6d1daf9a694" + integrity sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q== + "@esbuild/linux-s390x@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz#29ebf87e4132ea659c1489fce63cd8509d1c7319" integrity sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g== +"@esbuild/linux-s390x@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz#ab18e56e66f7a3c49cb97d337cd0a6fea28a8577" + integrity sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw== + "@esbuild/linux-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz#4af48c5c0479569b1f359ffbce22d15f261c0cef" integrity sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA== +"@esbuild/linux-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz#8140c9b40da634d380b0b29c837a0b4267aff38f" + integrity sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q== + +"@esbuild/netbsd-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz#65f19161432bafb3981f5f20a7ff45abb2e708e6" + integrity sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw== + "@esbuild/netbsd-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz#1ae73d23cc044a0ebd4f198334416fb26c31366c" integrity sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg== +"@esbuild/netbsd-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz#7a3a97d77abfd11765a72f1c6f9b18f5396bcc40" + integrity sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw== + "@esbuild/openbsd-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz#5d904a4f5158c89859fd902c427f96d6a9e632e2" integrity sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg== +"@esbuild/openbsd-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz#58b00238dd8f123bfff68d3acc53a6ee369af89f" + integrity sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A== + "@esbuild/openbsd-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz#4c8aa88c49187c601bae2971e71c6dc5e0ad1cdf" integrity sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q== +"@esbuild/openbsd-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz#0ac843fda0feb85a93e288842936c21a00a8a205" + integrity sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA== + "@esbuild/sunos-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz#8ddc35a0ea38575fa44eda30a5ee01ae2fa54dd4" integrity sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA== +"@esbuild/sunos-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz#8b7aa895e07828d36c422a4404cc2ecf27fb15c6" + integrity sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig== + "@esbuild/win32-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz#6e79c8543f282c4539db684a207ae0e174a9007b" integrity sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA== +"@esbuild/win32-arm64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz#c023afb647cabf0c3ed13f0eddfc4f1d61c66a85" + integrity sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ== + "@esbuild/win32-ia32@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz#057af345da256b7192d18b676a02e95d0fa39103" integrity sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw== +"@esbuild/win32-ia32@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz#96c356132d2dda990098c8b8b951209c3cd743c2" + integrity sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA== + "@esbuild/win32-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz#168ab1c7e1c318b922637fad8f339d48b01e1244" integrity sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA== +"@esbuild/win32-x64@0.24.2": + version "0.24.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz#34aa0b52d0fbb1a654b596acfa595f0c7b77a77b" + integrity sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg== + "@eslint-community/eslint-utils@^4.2.0": version "4.4.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" @@ -503,7 +628,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== @@ -689,10 +814,10 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.0.tgz#cb313feef9ac6e3737067fdf34f42804ac65a6f2" integrity sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ== -"@surfnet/sds@^0.0.120": - version "0.0.120" - resolved "https://registry.yarnpkg.com/@surfnet/sds/-/sds-0.0.120.tgz#aafc94933fbf55f0f25ea7b3878ff4b21f506d3c" - integrity sha512-MgofTmsO0hX9CPc1jxWoNuptoqbcdZJ0fet6O8ftQ2i0qr4FFBAS/tQtB/Awguq9NQdY/oEYDnw50cDbM13g5A== +"@surfnet/sds@^0.0.126": + version "0.0.126" + resolved "https://registry.yarnpkg.com/@surfnet/sds/-/sds-0.0.126.tgz#8b44ea5e89ca01236584c1da89e0cab93f21cf07" + integrity sha512-JBB2GzrgjJcNOUxWPz6TEoC5yDNk/Zc31mdHipnYaAmTG82rVfHJSHSp5Ntaf2pKt5m4F8F+hnk6m1Cw3vE8OA== "@types/babel__core@^7.20.5": version "7.20.5" @@ -732,7 +857,7 @@ resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== -"@types/estree@1.0.6", "@types/estree@^1.0.6": +"@types/estree@1.0.6", "@types/estree@^1.0.0", "@types/estree@^1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== @@ -794,6 +919,65 @@ "@types/babel__core" "^7.20.5" react-refresh "^0.14.2" +"@vitest/expect@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.0.2.tgz#26a480597a80402f972b1ac8478071b5fd66a698" + integrity sha512-dKSHLBcoZI+3pmP5hiZ7I5grNru2HRtEW8Z5Zp4IXog8QYcxhlox7JUPyIIFWfN53+3HW3KPLIl6nSzUGgKSuQ== + dependencies: + "@vitest/spy" "3.0.2" + "@vitest/utils" "3.0.2" + chai "^5.1.2" + tinyrainbow "^2.0.0" + +"@vitest/mocker@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-3.0.2.tgz#b49987b7293fdca74bb859f45ad84dbe521171d8" + integrity sha512-Hr09FoBf0jlwwSyzIF4Xw31OntpO3XtZjkccpcBf8FeVW3tpiyKlkeUzxS/txzHqpUCNIX157NaTySxedyZLvA== + dependencies: + "@vitest/spy" "3.0.2" + estree-walker "^3.0.3" + magic-string "^0.30.17" + +"@vitest/pretty-format@3.0.2", "@vitest/pretty-format@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-3.0.2.tgz#d1d0ac72a7b9ed6e8421aa54686b925b72a91fab" + integrity sha512-yBohcBw/T/p0/JRgYD+IYcjCmuHzjC3WLAKsVE4/LwiubzZkE8N49/xIQ/KGQwDRA8PaviF8IRO8JMWMngdVVQ== + dependencies: + tinyrainbow "^2.0.0" + +"@vitest/runner@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-3.0.2.tgz#bea5a177e8ca278c9eaaa68a80dda038a33a0081" + integrity sha512-GHEsWoncrGxWuW8s405fVoDfSLk6RF2LCXp6XhevbtDjdDme1WV/eNmUueDfpY1IX3MJaCRelVCEXsT9cArfEg== + dependencies: + "@vitest/utils" "3.0.2" + pathe "^2.0.1" + +"@vitest/snapshot@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-3.0.2.tgz#c3029d298c905f40ec3f238cab1130c87175a47e" + integrity sha512-h9s67yD4+g+JoYG0zPCo/cLTabpDqzqNdzMawmNPzDStTiwxwkyYM1v5lWE8gmGv3SVJ2DcxA2NpQJZJv9ym3g== + dependencies: + "@vitest/pretty-format" "3.0.2" + magic-string "^0.30.17" + pathe "^2.0.1" + +"@vitest/spy@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-3.0.2.tgz#0e306746cc56943db75b69449747483ba466f74a" + integrity sha512-8mI2iUn+PJFMT44e3ISA1R+K6ALVs47W6eriDTfXe6lFqlflID05MB4+rIFhmDSLBj8iBsZkzBYlgSkinxLzSQ== + dependencies: + tinyspy "^3.0.2" + +"@vitest/utils@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-3.0.2.tgz#dff19c1d05890c93a2c4d0ff861e65fb81224d52" + integrity sha512-Qu01ZYZlgHvDP02JnMBRpX43nRaZtNpIzw3C1clDXmn8eakgX6iQVGzTQ/NjkIr64WD8ioqOjkaYRVvHQI5qiw== + dependencies: + "@vitest/pretty-format" "3.0.2" + loupe "^3.1.2" + tinyrainbow "^2.0.0" + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -908,6 +1092,11 @@ arraybuffer.prototype.slice@^1.0.3: is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -964,6 +1153,11 @@ browserslist@^4.24.0: node-releases "^2.0.18" update-browserslist-db "^1.1.1" +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -985,6 +1179,17 @@ caniuse-lite@^1.0.30001669: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz#27c2e2c637e007cfa864a16f7dfe7cde66b38b5f" integrity sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog== +chai@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.2.tgz#3afbc340b994ae3610ca519a6c70ace77ad4378d" + integrity sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + chalk@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -993,6 +1198,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + chokidar@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.1.tgz#4a6dff66798fb0f72a94f616abbd7e1a19f31d41" @@ -1111,7 +1321,7 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" -debug@4, debug@^4.3.4, debug@^4.3.6: +debug@4, debug@^4.3.4, debug@^4.3.6, debug@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -1130,6 +1340,11 @@ decimal.js@^10.4.3: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -1286,6 +1501,11 @@ es-iterator-helpers@^1.1.0: iterator.prototype "^1.1.3" safe-array-concat "^1.1.2" +es-module-lexer@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.6.0.tgz#da49f587fd9e68ee2404fe4e256c0c7d3a81be21" + integrity sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ== + es-object-atoms@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" @@ -1348,6 +1568,37 @@ esbuild@^0.24.0: "@esbuild/win32-ia32" "0.24.0" "@esbuild/win32-x64" "0.24.0" +esbuild@^0.24.2: + version "0.24.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.2.tgz#b5b55bee7de017bff5fb8a4e3e44f2ebe2c3567d" + integrity sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA== + optionalDependencies: + "@esbuild/aix-ppc64" "0.24.2" + "@esbuild/android-arm" "0.24.2" + "@esbuild/android-arm64" "0.24.2" + "@esbuild/android-x64" "0.24.2" + "@esbuild/darwin-arm64" "0.24.2" + "@esbuild/darwin-x64" "0.24.2" + "@esbuild/freebsd-arm64" "0.24.2" + "@esbuild/freebsd-x64" "0.24.2" + "@esbuild/linux-arm" "0.24.2" + "@esbuild/linux-arm64" "0.24.2" + "@esbuild/linux-ia32" "0.24.2" + "@esbuild/linux-loong64" "0.24.2" + "@esbuild/linux-mips64el" "0.24.2" + "@esbuild/linux-ppc64" "0.24.2" + "@esbuild/linux-riscv64" "0.24.2" + "@esbuild/linux-s390x" "0.24.2" + "@esbuild/linux-x64" "0.24.2" + "@esbuild/netbsd-arm64" "0.24.2" + "@esbuild/netbsd-x64" "0.24.2" + "@esbuild/openbsd-arm64" "0.24.2" + "@esbuild/openbsd-x64" "0.24.2" + "@esbuild/sunos-x64" "0.24.2" + "@esbuild/win32-arm64" "0.24.2" + "@esbuild/win32-ia32" "0.24.2" + "@esbuild/win32-x64" "0.24.2" + escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" @@ -1478,6 +1729,13 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -1488,6 +1746,11 @@ eventemitter3@^4.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +expect-type@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.1.0.tgz#a146e414250d13dfc49eafcfd1344a4060fa4c75" + integrity sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -2135,6 +2398,11 @@ loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +loupe@^3.1.0, loupe@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.2.tgz#c86e0696804a02218f2206124c45d8b15291a240" + integrity sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -2142,6 +2410,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +magic-string@^0.30.17: + version "0.30.17" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" + integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + make-plural@*: version "7.4.0" resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-7.4.0.tgz#fa6990dd550dea4de6b20163f74e5ed83d8a8d6d" @@ -2332,6 +2607,16 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathe@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.2.tgz#5ed86644376915b3c7ee4d00ac8c348d671da3a5" + integrity sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w== + +pathval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" + integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== + picocolors@^1.0.0, picocolors@^1.1.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -2641,6 +2926,11 @@ side-channel@^1.0.4, side-channel@^1.0.6: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + "source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" @@ -2651,6 +2941,16 @@ source-map@^0.5.7: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5" + integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w== + string.prototype.matchall@^4.0.11: version "4.0.11" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" @@ -2732,6 +3032,31 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +tinypool@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.2.tgz#706193cc532f4c100f66aa00b01c42173d9051b2" + integrity sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA== + +tinyrainbow@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz#9509b2162436315e80e3eee0fcce4474d2444294" + integrity sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw== + +tinyspy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== + tldts-core@^6.1.69: version "6.1.69" resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.69.tgz#079ffcac8a4407bc74567e292aecf30b943674e1" @@ -2856,6 +3181,28 @@ use-isomorphic-layout-effect@^1.2.0: resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz#afb292eb284c39219e8cb8d3d62d71999361a21d" integrity sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w== +vite-node@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-3.0.2.tgz#0839794dfc9bcc847d8be5529cb6a240ae7a067c" + integrity sha512-hsEQerBAHvVAbv40m3TFQe/lTEbOp7yDpyqMJqr2Tnd+W58+DEYOt+fluQgekOePcsNBmR77lpVAnIU2Xu4SvQ== + dependencies: + cac "^6.7.14" + debug "^4.4.0" + es-module-lexer "^1.6.0" + pathe "^2.0.1" + vite "^5.0.0 || ^6.0.0" + +"vite@^5.0.0 || ^6.0.0": + version "6.0.9" + resolved "https://registry.yarnpkg.com/vite/-/vite-6.0.9.tgz#0a830b767ef7aa762360b56bdef955c1395dc1ee" + integrity sha512-MSgUxHcaXLtnBPktkbUSoQUANApKYuxZ6DrbVENlIorbhL2dZydTLaZ01tjUoE3szeFzlFk9ANOKk0xurh4MKA== + dependencies: + esbuild "^0.24.2" + postcss "^8.4.49" + rollup "^4.23.0" + optionalDependencies: + fsevents "~2.3.3" + vite@^6.0.4: version "6.0.4" resolved "https://registry.yarnpkg.com/vite/-/vite-6.0.4.tgz#fe7cfaedff7c701d5582be5c4ed6a2150538ea9d" @@ -2867,6 +3214,32 @@ vite@^6.0.4: optionalDependencies: fsevents "~2.3.3" +vitest@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-3.0.2.tgz#2ec1f9a2515b13bbfc810cc8e53ab1aa13472823" + integrity sha512-5bzaHakQ0hmVVKLhfh/jXf6oETDBtgPo8tQCHYB+wftNgFJ+Hah67IsWc8ivx4vFL025Ow8UiuTf4W57z4izvQ== + dependencies: + "@vitest/expect" "3.0.2" + "@vitest/mocker" "3.0.2" + "@vitest/pretty-format" "^3.0.2" + "@vitest/runner" "3.0.2" + "@vitest/snapshot" "3.0.2" + "@vitest/spy" "3.0.2" + "@vitest/utils" "3.0.2" + chai "^5.1.2" + debug "^4.4.0" + expect-type "^1.1.0" + magic-string "^0.30.17" + pathe "^2.0.1" + std-env "^3.8.0" + tinybench "^2.9.0" + tinyexec "^0.3.2" + tinypool "^1.0.2" + tinyrainbow "^2.0.0" + vite "^5.0.0 || ^6.0.0" + vite-node "3.0.2" + why-is-node-running "^2.3.0" + w3c-xmlserializer@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" @@ -2956,6 +3329,14 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"