diff --git a/cypress/e2e/Course_statistics.js b/cypress/e2e/Course_statistics.js
index 3ac1dc5f3d..f6d5424122 100644
--- a/cypress/e2e/Course_statistics.js
+++ b/cypress/e2e/Course_statistics.js
@@ -912,7 +912,7 @@ describe('Course Statistics tests', () => {
it('Some features of Course Statistics are hidden for courseStatistics-users without other rights', () => {
cy.init('/coursestatistics', 'onlycoursestatistics')
- cy.get('[data-cy=navbar-courseStatistics]').click()
+ cy.get('[data-cy=nav-bar-button-courseStatistics]').click()
cy.get('[data-cy=course-code-input]').type('TKT10002')
cy.contains('tr', 'TKT10002').click()
cy.contains('Filter statistics by study programmes').should('not.exist')
diff --git a/cypress/e2e/Users.js b/cypress/e2e/Users.js
index 6113b86693..5191ba40f1 100644
--- a/cypress/e2e/Users.js
+++ b/cypress/e2e/Users.js
@@ -1,47 +1,60 @@
///
const visibleLinks = {
- norights: ['University', 'Faculties', 'Special populations', 'Give feedback', 'Logout'],
- onlycoursestatistics: ['University', 'Courses', 'Special populations', 'Give feedback', 'Logout'],
+ norights: ['University', 'Faculties', 'Special populations', 'Feedback'],
+ onlycoursestatistics: ['University', 'Courses', 'Special populations', 'Feedback'],
}
visibleLinks.basic = [...visibleLinks.onlycoursestatistics, 'Faculties', 'Programmes', 'Students']
-visibleLinks.admin = [...visibleLinks.basic, 'Teachers', 'Users', 'Updater']
+visibleLinks.admin = [...visibleLinks.basic, 'Teachers', 'Admin']
+
+const hasVisibleItems = visibleItems => {
+ cy.get('[data-cy=nav-bar]').children().should('have.length', visibleItems)
+}
+
+const containsLinks = links => {
+ cy.get('[data-cy=nav-bar]').within(() => {
+ for (const link of links) {
+ cy.contains(link)
+ }
+ })
+}
+
+const userButtonWorks = (username, mocking = false) => {
+ cy.get('[data-cy=nav-bar-user-button]').click()
+ cy.contains(mocking ? `Mocking as ${username}` : `Logged in as ${username}`)
+ cy.contains('Language')
+ cy.contains('suomi')
+ cy.contains('English')
+ cy.contains('svenska')
+ cy.contains(mocking ? 'Stop mocking' : 'Log out')
+}
describe('Users tests', () => {
- describe('Using user with just grp-oodikone-user, no other rights', () => {
+ describe('Using as user with just grp-oodikone-user, no other rights', () => {
it('shows correct tabs', () => {
cy.init('', 'norights')
- cy.get('[data-cy=navBar]').children().should('have.length', 7)
- cy.get('[data-cy=navBar]').within(() => {
- for (const link of visibleLinks.norights) {
- cy.contains(link)
- }
- })
+ hasVisibleItems(7)
+ containsLinks(visibleLinks.norights)
+ userButtonWorks('norights')
})
})
describe('Using as coursestatistics user', () => {
it('shows correct tabs', () => {
cy.init('', 'onlycoursestatistics')
- cy.get('[data-cy=navBar]').children().should('have.length', 7)
- cy.get('[data-cy=navBar]').within(() => {
- for (const link of visibleLinks.onlycoursestatistics) {
- cy.contains(link)
- }
- })
+ hasVisibleItems(7)
+ containsLinks(visibleLinks.onlycoursestatistics)
+ userButtonWorks('onlycoursestatistics')
})
})
describe('Using as basic user', () => {
it('shows correct tabs', () => {
cy.init('')
- cy.get('[data-cy=navBar]').children().should('have.length', 10)
- cy.get('[data-cy=navBar]').within(() => {
- for (const link of visibleLinks.basic) {
- cy.contains(link)
- }
- })
+ hasVisibleItems(10)
+ containsLinks(visibleLinks.basic)
+ userButtonWorks('basic')
})
})
@@ -51,27 +64,32 @@ describe('Users tests', () => {
})
it('should see more stuff than others', () => {
- cy.get('[data-cy=navBar]').children().should('have.length', 13)
- cy.get('[data-cy=navBar]').within(() => {
- for (const link of visibleLinks.admin) {
- cy.contains(link)
- }
- })
+ hasVisibleItems(13)
+ containsLinks(visibleLinks.admin)
+ userButtonWorks('admin')
})
- it("mocking normal user shows only the mocked user's programmes", () => {
- cy.contains('mocking').should('not.exist')
- cy.cs('user-edit-button-basic').click()
- cy.get('i.spy').click()
- cy.contains('mocking as basic')
- cy.contains('Programmes').click().siblings().contains('Class statistics').click()
- cy.contains('label', 'Study programme')
- .siblings()
- .within(() => {
- cy.get("div[role='option']").should('have.length', 2)
- cy.get("div[role='option']").eq(0).contains('Matemaattisten tieteiden kandiohjelma')
- cy.get("div[role='option']").eq(1).contains('Matematiikan ja tilastotieteen maisteriohjelma')
- })
+ describe('can mock as other users', () => {
+ beforeEach(() => {
+ cy.cs('user-edit-button-basic').click()
+ cy.get('i.spy').click()
+ })
+
+ it('user button shows mocked user', () => {
+ userButtonWorks('basic', true)
+ })
+
+ it("only the mocked user's programmes are visible", () => {
+ cy.get('[data-cy=nav-bar-button-studyProgramme]').click()
+ cy.get('[data-cy=nav-bar-button-class]').click()
+ cy.contains('label', 'Study programme')
+ .siblings()
+ .within(() => {
+ cy.get("div[role='option']").should('have.length', 2)
+ cy.get("div[role='option']").eq(0).contains('Matemaattisten tieteiden kandiohjelma')
+ cy.get("div[role='option']").eq(1).contains('Matematiikan ja tilastotieteen maisteriohjelma')
+ })
+ })
})
})
})
diff --git a/services/frontend/package-lock.json b/services/frontend/package-lock.json
index b2cee3ba6a..bf312c326c 100644
--- a/services/frontend/package-lock.json
+++ b/services/frontend/package-lock.json
@@ -6,6 +6,10 @@
"": {
"name": "frontend",
"dependencies": {
+ "@emotion/react": "^11.13.3",
+ "@emotion/styled": "^11.13.0",
+ "@mui/icons-material": "^6.1.6",
+ "@mui/material": "^6.1.6",
"@reduxjs/toolkit": "^2.2.3",
"@sentry/browser": "^8.33.1",
"axios": "^0.28.1",
@@ -39,10 +43,87 @@
"vite": "^5.2.10"
}
},
+ "node_modules/@babel/code-frame": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
+ "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz",
+ "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.26.2",
+ "@babel/types": "^7.26.0",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
+ "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
+ "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.26.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/@babel/runtime": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz",
- "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==",
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
+ "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+ "license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -50,6 +131,197 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/template": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
+ "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.25.9",
+ "@babel/parser": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz",
+ "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.25.9",
+ "@babel/generator": "^7.25.9",
+ "@babel/parser": "^7.25.9",
+ "@babel/template": "^7.25.9",
+ "@babel/types": "^7.25.9",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
+ "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emotion/babel-plugin": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz",
+ "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/serialize": "^1.2.0",
+ "babel-plugin-macros": "^3.1.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/cache": {
+ "version": "11.13.1",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz",
+ "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/sheet": "^1.4.0",
+ "@emotion/utils": "^1.4.0",
+ "@emotion/weak-memoize": "^0.4.0",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
+ "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/react": {
+ "version": "11.13.3",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz",
+ "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.12.0",
+ "@emotion/cache": "^11.13.0",
+ "@emotion/serialize": "^1.3.1",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
+ "@emotion/utils": "^1.4.0",
+ "@emotion/weak-memoize": "^0.4.0",
+ "hoist-non-react-statics": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/serialize": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz",
+ "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/unitless": "^0.10.0",
+ "@emotion/utils": "^1.4.1",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@emotion/sheet": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
+ "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/styled": {
+ "version": "11.13.0",
+ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz",
+ "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.12.0",
+ "@emotion/is-prop-valid": "^1.3.0",
+ "@emotion/serialize": "^1.3.0",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
+ "@emotion/utils": "^1.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.0.0-rc.0",
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
+ "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz",
+ "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@emotion/utils": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz",
+ "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/weak-memoize": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
+ "license": "MIT"
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -453,6 +725,323 @@
"react-dom": "^16.8.0 || ^17 || ^18"
}
},
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mui/core-downloads-tracker": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.6.tgz",
+ "integrity": "sha512-nz1SlR9TdBYYPz4qKoNasMPRiGb4PaIHFkzLzhju0YVYS5QSuFF2+n7CsiHMIDcHv3piPu/xDWI53ruhOqvZwQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ }
+ },
+ "node_modules/@mui/icons-material": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.6.tgz",
+ "integrity": "sha512-5r9urIL2lxXb/sPN3LFfFYEibsXJUb986HhhIeu1gOcte460pwdSiEhBSxkAuyT8Dj7jvu9MjqSBmSumQELo8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@mui/material": "^6.1.6",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/material": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz",
+ "integrity": "sha512-1yvejiQ/601l5AK3uIdUlAVElyCxoqKnl7QA+2oFB/2qYPWfRwDgavW/MoywS5Y2gZEslcJKhe0s2F3IthgFgw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@mui/core-downloads-tracker": "^6.1.6",
+ "@mui/system": "^6.1.6",
+ "@mui/types": "^7.2.19",
+ "@mui/utils": "^6.1.6",
+ "@popperjs/core": "^2.11.8",
+ "@types/react-transition-group": "^4.4.11",
+ "clsx": "^2.1.1",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1",
+ "react-is": "^18.3.1",
+ "react-transition-group": "^4.4.5"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@mui/material-pigment-css": "^6.1.6",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@mui/material-pigment-css": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/material/node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@mui/material/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
+ "node_modules/@mui/private-theming": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.6.tgz",
+ "integrity": "sha512-ioAiFckaD/fJSnTrUMWgjl9HYBWt7ixCh7zZw7gDZ+Tae7NuprNV6QJK95EidDT7K0GetR2rU3kAeIR61Myttw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@mui/utils": "^6.1.6",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/styled-engine": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.6.tgz",
+ "integrity": "sha512-I+yS1cSuSvHnZDBO7e7VHxTWpj+R7XlSZvTC4lS/OIbUNJOMMSd3UDP6V2sfwzAdmdDNBi7NGCRv2SZ6O9hGDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@emotion/cache": "^11.13.1",
+ "@emotion/serialize": "^1.3.2",
+ "@emotion/sheet": "^1.4.0",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.4.1",
+ "@emotion/styled": "^11.3.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/system": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.6.tgz",
+ "integrity": "sha512-qOf1VUE9wK8syiB0BBCp82oNBAVPYdj4Trh+G1s+L+ImYiKlubWhhqlnvWt3xqMevR+D2h1CXzA1vhX2FvA+VQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@mui/private-theming": "^6.1.6",
+ "@mui/styled-engine": "^6.1.6",
+ "@mui/types": "^7.2.19",
+ "@mui/utils": "^6.1.6",
+ "clsx": "^2.1.1",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/system/node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@mui/types": {
+ "version": "7.2.19",
+ "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz",
+ "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/utils": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.6.tgz",
+ "integrity": "sha512-sBS6D9mJECtELASLM+18WUcXF6RH3zNxBRFeyCRg8wad6NbyNrdxLuwK+Ikvc38sTZwBzAz691HmSofLqHd9sQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@mui/types": "^7.2.19",
+ "@types/prop-types": "^15.7.13",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/utils/node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@mui/utils/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -1125,10 +1714,17 @@
"undici-types": "~6.13.0"
}
},
+ "node_modules/@types/parse-json": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
+ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
+ "license": "MIT"
+ },
"node_modules/@types/prop-types": {
- "version": "15.7.5",
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
- "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
+ "version": "15.7.13",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
+ "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
+ "license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.12",
@@ -1150,6 +1746,15 @@
"@types/react": "*"
}
},
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.11",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz",
+ "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/unist": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz",
@@ -1195,6 +1800,21 @@
"proxy-from-env": "^1.1.0"
}
},
+ "node_modules/babel-plugin-macros": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10",
+ "npm": ">=6"
+ }
+ },
"node_modules/bail": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
@@ -1205,6 +1825,15 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/ccount": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
@@ -1284,10 +1913,33 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "license": "MIT"
+ },
+ "node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/csstype": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
- "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
},
"node_modules/debug": {
"version": "4.3.5",
@@ -1358,6 +2010,25 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
"node_modules/esbuild": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
@@ -1397,6 +2068,18 @@
"@esbuild/win32-x64": "0.21.5"
}
},
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/estree-util-is-identifier-name": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
@@ -1430,6 +2113,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
+ "license": "MIT"
+ },
"node_modules/follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
@@ -1476,6 +2165,36 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/hast-util-to-jsx-runtime": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz",
@@ -1563,6 +2282,22 @@
"url": "https://opencollective.com/immer"
}
},
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/inline-style-parser": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz",
@@ -1593,6 +2328,27 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "license": "MIT"
+ },
+ "node_modules/is-core-module": {
+ "version": "2.15.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
+ "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-decimal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
@@ -1640,11 +2396,35 @@
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
+ "node_modules/jsesc": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
+ "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT"
+ },
"node_modules/keyboard-key": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.1.0.tgz",
"integrity": "sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ=="
},
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -2330,6 +3110,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/parse-entities": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz",
@@ -2356,6 +3148,30 @@
"integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==",
"license": "MIT"
},
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "license": "MIT"
+ },
"node_modules/path-to-regexp": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz",
@@ -2365,11 +3181,19 @@
"isarray": "0.0.1"
}
},
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
- "dev": true,
"license": "ISC"
},
"node_modules/postcss": {
@@ -2611,6 +3435,22 @@
"react": ">=15"
}
},
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
@@ -2669,6 +3509,32 @@
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz",
"integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg=="
},
+ "node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
@@ -2755,6 +3621,15 @@
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
},
+ "node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
@@ -2809,6 +3684,24 @@
"inline-style-parser": "0.2.3"
}
},
+ "node_modules/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
+ "license": "MIT"
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/tiny-invariant": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
@@ -3103,6 +3996,15 @@
"node": ">=0.8"
}
},
+ "node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
diff --git a/services/frontend/package.json b/services/frontend/package.json
index af7f5f14da..3822569744 100644
--- a/services/frontend/package.json
+++ b/services/frontend/package.json
@@ -9,6 +9,10 @@
"tsc": "tsc"
},
"dependencies": {
+ "@emotion/react": "^11.13.3",
+ "@emotion/styled": "^11.13.0",
+ "@mui/icons-material": "^6.1.6",
+ "@mui/material": "^6.1.6",
"@reduxjs/toolkit": "^2.2.3",
"@sentry/browser": "^8.33.1",
"axios": "^0.28.1",
diff --git a/services/frontend/src/components/App/index.jsx b/services/frontend/src/components/App/index.jsx
index 63753d3f75..e7e9d4485a 100644
--- a/services/frontend/src/components/App/index.jsx
+++ b/services/frontend/src/components/App/index.jsx
@@ -1,9 +1,10 @@
+import { createTheme, ThemeProvider } from '@mui/material/styles'
import * as Sentry from '@sentry/browser'
import { useEffect } from 'react'
import { initShibbolethPinger } from 'unfuck-spa-shibboleth-session'
import { AccessDenied } from '@/components/AccessDenied'
-import { NavigationBar } from '@/components/NavigationBar'
+import { NavigationBar } from '@/components/material/NavigationBar'
import { Routes } from '@/components/Routes'
import { SegmentDimmer } from '@/components/SegmentDimmer'
import { isProduction } from '@/conf'
@@ -11,17 +12,21 @@ import { useGetAuthorizedUserQuery } from '@/redux/auth'
import './app.css'
const addUserDetailsToLoggers = ({ id, username, mockedBy }) => {
- if (!isProduction || !id || !username) return
+ if (!isProduction || !id || !username) {
+ return
+ }
Sentry.setUser({ id, username, mockedBy })
}
+const theme = createTheme({})
+
const Layout = ({ children }) => (
-
- {children}
+ {children}
+
)
@@ -39,7 +44,9 @@ export const App = () => {
addUserDetailsToLoggers({ id, username, mockedBy })
}, [id, username, mockedBy])
- if (error) return
+ if (error) {
+ return
+ }
return {isLoading ? : }
}
diff --git a/services/frontend/src/components/NavigationBar/index.jsx b/services/frontend/src/components/NavigationBar/index.jsx
deleted file mode 100644
index 169796a92d..0000000000
--- a/services/frontend/src/components/NavigationBar/index.jsx
+++ /dev/null
@@ -1,212 +0,0 @@
-import { Link, NavLink, useLocation } from 'react-router-dom'
-import { Button, Dropdown, Label, Menu } from 'semantic-ui-react'
-
-import { checkUserAccess, getFullStudyProgrammeRights, isDefaultServiceProvider } from '@/common'
-import { LanguagePicker } from '@/components/LanguagePicker'
-import { adminerUrls, isDev, languageCenterViewEnabled } from '@/conf'
-import { useGetAuthorizedUserQuery, useLogoutMutation, useShowAsUser } from '@/redux/auth'
-import './navigationBar.css'
-
-const allNavigationItems = {
- university: { key: 'university', label: 'University', path: '/university' },
- faculty: { key: 'faculties', label: 'Faculties', path: '/faculties' },
- populations: {
- key: 'studyProgramme',
- items: [
- { key: 'class', label: 'Class statistics', path: '/populations' },
- { key: 'overview', label: 'Overview', path: '/study-programme' },
- ],
- label: 'Programmes',
- },
- courseStatistics: { key: 'courseStatistics', label: 'Courses', path: '/coursestatistics' },
- students: { key: 'students', label: 'Students', path: '/students' },
- teachers: { key: 'teachers', label: 'Teachers', path: '/teachers', reqRights: ['teachers'] },
- users: { key: 'users', label: 'Users', path: '/users', reqRights: ['admin'] },
- studyGuidanceGroups: {
- key: 'studyGuidanceGroups',
- label: 'Guidance groups',
- path: '/studyguidancegroups',
- reqRights: ['studyGuidanceGroups'],
- },
- customPopulations: {
- key: 'customPopulation',
- items: [
- { key: 'customSearch', label: 'Custom population', path: '/custompopulation' },
- { key: 'openUniSearch', label: 'Fetch open uni students by courses', path: '/openunipopulation' },
- { key: 'completedCoursesSearch', label: 'Completed courses of students', path: '/completedcoursessearch' },
- { key: 'languageCenterView', label: 'Language center view', path: '/languagecenterview' },
- { key: 'closeToGraduation', label: 'Students close to graduation', path: '/close-to-graduation' },
- ],
- label: 'Special populations',
- },
- updater: { key: 'updater', label: 'Updater', path: '/updater', reqRights: ['admin'] },
- feedback: { key: 'feedback', label: 'Give feedback', path: '/feedback' },
-}
-
-export const NavigationBar = () => {
- const { isLoading, iamGroups, mockedBy, username, roles, isAdmin, programmeRights, fullAccessToStudentData } =
- useGetAuthorizedUserQuery()
- const fullStudyProgrammeRights = getFullStudyProgrammeRights(programmeRights)
- const location = useLocation()
- const showAsUser = useShowAsUser()
- const [logout] = useLogoutMutation()
-
- const refreshNavigationRoutes = () => {
- const visibleNavigationItems = {}
- if (isLoading) {
- return visibleNavigationItems
- }
- Object.keys(allNavigationItems).forEach(key => {
- if (key === 'populations') {
- if (!fullAccessToStudentData && programmeRights.length === 0) return
- }
- if (key === 'students') {
- if (
- !checkUserAccess(['admin', 'fullSisuAccess', 'studyGuidanceGroups'], roles) &&
- fullStudyProgrammeRights.length === 0
- )
- return
- } else if (key === 'courseStatistics') {
- if (
- !checkUserAccess(['admin', 'fullSisuAccess', 'courseStatistics'], roles) &&
- fullStudyProgrammeRights.length === 0
- )
- return
- } else if (key === 'faculty') {
- if (!checkUserAccess(['admin', 'fullSisuAccess', 'facultyStatistics'], roles)) return
- } else if (key === 'feedback') {
- if (!isDefaultServiceProvider()) return
- }
- const { reqRights } = allNavigationItems[key]
- if (!reqRights || reqRights.every(r => roles.includes(r) || (key === 'teachers' && isAdmin))) {
- visibleNavigationItems[key] = allNavigationItems[key]
- }
- })
- return { ...visibleNavigationItems }
- }
-
- const renderHome = () => (
-
-
- oodikone
-
- {isDev && }
-
- )
-
- const showSearch = item => {
- if (item.key === 'class' || item.key === 'overview') return true
- if (
- checkUserAccess(['admin', 'openUniSearch'], roles) &&
- item.key === 'openUniSearch' &&
- isDefaultServiceProvider()
- )
- return true
- if (
- (checkUserAccess(['admin', 'fullSisuAccess', 'studyGuidanceGroups'], roles) ||
- fullStudyProgrammeRights.length > 0) &&
- item.key === 'customSearch'
- )
- return true
- if (item.key === 'completedCoursesSearch') return true
- if (
- (checkUserAccess(['admin'], roles) || iamGroups.includes('grp-kielikeskus-esihenkilot')) &&
- item.key === 'languageCenterView' &&
- languageCenterViewEnabled
- )
- return true
- if (item.key === 'closeToGraduation' && checkUserAccess(['admin', 'fullSisuAccess', 'studyGuidanceGroups'], roles))
- return true
- return false
- }
-
- const visibleNavigationItems = refreshNavigationRoutes()
-
- const renderNavigationRoutes = () =>
- Object.values(visibleNavigationItems).map(({ items, key, label, path, tag }) =>
- items ? (
- location.pathname.includes(item.path))}
- as={Dropdown}
- data-cy={`navbar-${key}`}
- key={`menu-item-drop-${key}`}
- tabIndex="-1"
- text={label}
- >
-
- {items.map(
- item =>
- showSearch(item) && (
-
- {item.label}
-
- )
- )}
-
-
- ) : (
-
- {label}
- {tag && (
-
-
-
- )}
-
- )
- )
-
- const renderUserMenu = () =>
- isDev ? (
-
-
- {adminerUrls.map(({ url, text }) => (
- {
- const win = window.open(url, '_blank')
- win.focus()
- }}
- text={text}
- />
- ))}
- logout()} text="Logout" />
-
-
- ) : (
- logout()} tabIndex="-1">
- Logout
-
- )
-
- const renderLanguagePicker = () => (
-
-
-
- )
-
- const renderStopMockingButton = () => (
-
-
-
- )
-
- return (
-
- )
-}
diff --git a/services/frontend/src/components/NavigationBar/navigationBar.css b/services/frontend/src/components/NavigationBar/navigationBar.css
deleted file mode 100644
index 56e4b90297..0000000000
--- a/services/frontend/src/components/NavigationBar/navigationBar.css
+++ /dev/null
@@ -1,17 +0,0 @@
-.navBar {
- display: grid !important;
- grid-auto-flow: column !important;
- border-radius: 0 !important;
- border-top: none !important;
-}
-
-.navBar::after {
- content: none !important;
-}
-
-.navBar .item {
- display: flex;
- justify-content: center;
- align-items: center;
- background-color: pink;
-}
diff --git a/services/frontend/src/components/material/NavigationBar/Logo.tsx b/services/frontend/src/components/material/NavigationBar/Logo.tsx
new file mode 100644
index 0000000000..770e0948f9
--- /dev/null
+++ b/services/frontend/src/components/material/NavigationBar/Logo.tsx
@@ -0,0 +1,28 @@
+import { Chip, Stack, Typography } from '@mui/material'
+import { Link } from 'react-router-dom'
+
+import { isDev } from '@/conf'
+
+export const Logo = () => {
+ return (
+
+
+ oodikone
+
+ {isDev && }
+
+ )
+}
diff --git a/services/frontend/src/components/material/NavigationBar/NavigationButton.tsx b/services/frontend/src/components/material/NavigationBar/NavigationButton.tsx
new file mode 100644
index 0000000000..d8b63e5b15
--- /dev/null
+++ b/services/frontend/src/components/material/NavigationBar/NavigationButton.tsx
@@ -0,0 +1,118 @@
+import { ArrowDropDown } from '@mui/icons-material'
+import { Button, Menu, MenuItem } from '@mui/material'
+import { useState } from 'react'
+import { Link, useLocation } from 'react-router-dom'
+
+import { checkUserAccess, getFullStudyProgrammeRights, isDefaultServiceProvider } from '@/common'
+import { languageCenterViewEnabled } from '@/conf'
+import { useGetAuthorizedUserQuery } from '@/redux/auth'
+import { NavigationItem } from './navigationItems'
+
+export const NavigationButton = ({ item }: { item: NavigationItem }) => {
+ const { iamGroups, programmeRights, roles } = useGetAuthorizedUserQuery()
+ const fullStudyProgrammeRights = getFullStudyProgrammeRights(programmeRights)
+ const location = useLocation()
+ const [anchorEl, setAnchorEl] = useState(null)
+
+ const { key, label, path, items } = item
+
+ const showItem = (subItemKey: string) => {
+ if (['class', 'completedCoursesSearch', 'overview'].includes(subItemKey)) {
+ return true
+ }
+
+ if (
+ checkUserAccess(['admin', 'openUniSearch'], roles) &&
+ subItemKey === 'openUniSearch' &&
+ isDefaultServiceProvider()
+ ) {
+ return true
+ }
+
+ if (
+ (checkUserAccess(['admin', 'fullSisuAccess', 'studyGuidanceGroups'], roles) ||
+ fullStudyProgrammeRights.length > 0) &&
+ subItemKey === 'customSearch'
+ ) {
+ return true
+ }
+
+ if (
+ (checkUserAccess(['admin'], roles) || iamGroups.includes('grp-kielikeskus-esihenkilot')) &&
+ subItemKey === 'languageCenterView' &&
+ languageCenterViewEnabled
+ ) {
+ return true
+ }
+
+ if (
+ subItemKey === 'closeToGraduation' &&
+ checkUserAccess(['admin', 'fullSisuAccess', 'studyGuidanceGroups'], roles)
+ ) {
+ return true
+ }
+
+ if (['users', 'updater'].includes(subItemKey) && checkUserAccess(['admin'], roles)) {
+ return true
+ }
+
+ return false
+ }
+
+ const isActivePath = (mainPath: string | undefined, subPaths: (string | undefined)[] = []) => {
+ const allPaths = [mainPath, ...subPaths].filter(Boolean)
+ return allPaths.some(currentPath => location.pathname.includes(currentPath!))
+ }
+
+ const subItemPaths = items ? items.map(subItem => subItem.path) : []
+ const isActive = isActivePath(path, subItemPaths)
+
+ const buttonStyle = {
+ color: 'inherit',
+ fontWeight: isActive ? 'bold' : 'normal',
+ textTransform: 'none',
+ whiteSpace: 'nowrap',
+ '&:hover': {
+ color: 'inherit',
+ textDecoration: 'underline',
+ },
+ }
+
+ if (items) {
+ return (
+ <>
+ }
+ onClick={event => setAnchorEl(event.currentTarget)}
+ sx={buttonStyle}
+ >
+ {label}
+
+
+ >
+ )
+ }
+
+ return (
+
+ )
+}
diff --git a/services/frontend/src/components/material/NavigationBar/NavigationDivider.tsx b/services/frontend/src/components/material/NavigationBar/NavigationDivider.tsx
new file mode 100644
index 0000000000..40dece6e0d
--- /dev/null
+++ b/services/frontend/src/components/material/NavigationBar/NavigationDivider.tsx
@@ -0,0 +1,5 @@
+import { Divider } from '@mui/material'
+
+export const NavigationDivider = () => {
+ return
+}
diff --git a/services/frontend/src/components/material/NavigationBar/UserButton/LogOutButton.jsx b/services/frontend/src/components/material/NavigationBar/UserButton/LogOutButton.jsx
new file mode 100644
index 0000000000..104252b33e
--- /dev/null
+++ b/services/frontend/src/components/material/NavigationBar/UserButton/LogOutButton.jsx
@@ -0,0 +1,18 @@
+import { Logout } from '@mui/icons-material'
+import { ListItemIcon, MenuItem, Typography } from '@mui/material'
+
+import { isDev } from '@/conf'
+import { useLogoutMutation } from '@/redux/auth'
+
+export const LogOutButton = () => {
+ const [logout] = useLogoutMutation()
+
+ return (
+
+ )
+}
diff --git a/services/frontend/src/components/material/NavigationBar/UserButton/StopMockingButton.tsx b/services/frontend/src/components/material/NavigationBar/UserButton/StopMockingButton.tsx
new file mode 100644
index 0000000000..349de29a33
--- /dev/null
+++ b/services/frontend/src/components/material/NavigationBar/UserButton/StopMockingButton.tsx
@@ -0,0 +1,17 @@
+import { ExitToApp } from '@mui/icons-material'
+import { ListItemIcon, MenuItem, Typography } from '@mui/material'
+
+import { useShowAsUser } from '@/redux/auth'
+
+export const StopMockingButton = () => {
+ const showAsUser = useShowAsUser()
+
+ return (
+
+ )
+}
diff --git a/services/frontend/src/components/material/NavigationBar/UserButton/index.tsx b/services/frontend/src/components/material/NavigationBar/UserButton/index.tsx
new file mode 100644
index 0000000000..13cafeaa23
--- /dev/null
+++ b/services/frontend/src/components/material/NavigationBar/UserButton/index.tsx
@@ -0,0 +1,75 @@
+import { AccountCircle, Check, Language } from '@mui/icons-material'
+import { Box, Divider, IconButton, ListItemIcon, Menu, MenuItem, Typography } from '@mui/material'
+import { useState } from 'react'
+
+import { useLanguage } from '@/components/LanguagePicker/useLanguage'
+import { useGetAuthorizedUserQuery } from '@/redux/auth'
+import { LANGUAGE_CODES, LANGUAGE_TEXTS } from '@/shared/language'
+import { LogOutButton } from './LogOutButton'
+import { StopMockingButton } from './StopMockingButton'
+
+export const UserButton = () => {
+ const { language, setLanguage } = useLanguage()
+ const { isLoading, mockedBy, username } = useGetAuthorizedUserQuery()
+ const [anchorEl, setAnchorEl] = useState(null)
+
+ const currentLanguage: string = language as unknown as string // TODO: Fix the type in the origin
+
+ const languageOptions = LANGUAGE_CODES.map(code => ({
+ key: code,
+ text: LANGUAGE_TEXTS[code],
+ value: code,
+ }))
+
+ return (
+
+ setAnchorEl(event.currentTarget)}
+ sx={{ p: 0 }}
+ >
+
+
+
+
+ )
+}
diff --git a/services/frontend/src/components/material/NavigationBar/index.tsx b/services/frontend/src/components/material/NavigationBar/index.tsx
new file mode 100644
index 0000000000..ea67b1951d
--- /dev/null
+++ b/services/frontend/src/components/material/NavigationBar/index.tsx
@@ -0,0 +1,80 @@
+import { AppBar, Box, Container, Toolbar } from '@mui/material'
+import { Fragment } from 'react'
+
+import { checkUserAccess, getFullStudyProgrammeRights, isDefaultServiceProvider } from '@/common'
+import { useGetAuthorizedUserQuery } from '@/redux/auth'
+import { Logo } from './Logo'
+import { NavigationButton } from './NavigationButton'
+import { NavigationDivider } from './NavigationDivider'
+import { NavigationItem, navigationItems } from './navigationItems'
+import { UserButton } from './UserButton'
+
+export const NavigationBar = () => {
+ const { fullAccessToStudentData, isAdmin, isLoading, programmeRights, roles } = useGetAuthorizedUserQuery()
+ const fullStudyProgrammeRights = getFullStudyProgrammeRights(programmeRights)
+
+ const getVisibleNavigationItems = () => {
+ const visibleNavigationItems: Record = {}
+ if (isLoading) {
+ return visibleNavigationItems
+ }
+ Object.keys(navigationItems).forEach(key => {
+ if (key === 'populations') {
+ if (!fullAccessToStudentData && programmeRights.length === 0) return
+ }
+ if (key === 'students') {
+ if (
+ !checkUserAccess(['admin', 'fullSisuAccess', 'studyGuidanceGroups'], roles) &&
+ fullStudyProgrammeRights.length === 0
+ ) {
+ return
+ }
+ } else if (key === 'courseStatistics') {
+ if (
+ !checkUserAccess(['admin', 'fullSisuAccess', 'courseStatistics'], roles) &&
+ fullStudyProgrammeRights.length === 0
+ ) {
+ return
+ }
+ } else if (key === 'faculty') {
+ if (!checkUserAccess(['admin', 'fullSisuAccess', 'facultyStatistics'], roles)) return
+ } else if (key === 'feedback') {
+ if (!isDefaultServiceProvider()) return
+ } else if (key === 'admin') {
+ if (!isAdmin) return
+ }
+ const { reqRights } = navigationItems[key]
+ if (!reqRights || reqRights.every(r => roles.includes(r) || (key === 'teachers' && isAdmin))) {
+ visibleNavigationItems[key] = navigationItems[key]
+ }
+ })
+ return { ...visibleNavigationItems }
+ }
+
+ const visibleNavigationItems = getVisibleNavigationItems()
+
+ return (
+
+
+
+
+ {!isLoading && (
+ <>
+
+
+ {Object.values(visibleNavigationItems).map(item => (
+
+ {['feedback', 'admin'].includes(item.key) && }
+
+
+ ))}
+
+
+
+ >
+ )}
+
+
+
+ )
+}
diff --git a/services/frontend/src/components/material/NavigationBar/navigationItems.ts b/services/frontend/src/components/material/NavigationBar/navigationItems.ts
new file mode 100644
index 0000000000..0007b760cc
--- /dev/null
+++ b/services/frontend/src/components/material/NavigationBar/navigationItems.ts
@@ -0,0 +1,49 @@
+export type NavigationItem = {
+ key: string
+ label: string
+ path?: string
+ reqRights?: string[]
+ items?: NavigationItem[]
+}
+
+export const navigationItems: Record = {
+ university: { key: 'university', label: 'University', path: '/university' },
+ faculty: { key: 'faculties', label: 'Faculties', path: '/faculties' },
+ populations: {
+ key: 'studyProgramme',
+ items: [
+ { key: 'class', label: 'Class statistics', path: '/populations' },
+ { key: 'overview', label: 'Overview', path: '/study-programme' },
+ ],
+ label: 'Programmes',
+ },
+ courseStatistics: { key: 'courseStatistics', label: 'Courses', path: '/coursestatistics' },
+ students: { key: 'students', label: 'Students', path: '/students' },
+ teachers: { key: 'teachers', label: 'Teachers', path: '/teachers', reqRights: ['teachers'] },
+ studyGuidanceGroups: {
+ key: 'studyGuidanceGroups',
+ label: 'Guidance groups',
+ path: '/studyguidancegroups',
+ reqRights: ['studyGuidanceGroups'],
+ },
+ customPopulations: {
+ key: 'customPopulation',
+ items: [
+ { key: 'customSearch', label: 'Custom population', path: '/custompopulation' },
+ { key: 'openUniSearch', label: 'Fetch open uni students by courses', path: '/openunipopulation' },
+ { key: 'completedCoursesSearch', label: 'Completed courses of students', path: '/completedcoursessearch' },
+ { key: 'languageCenterView', label: 'Language center view', path: '/languagecenterview' },
+ { key: 'closeToGraduation', label: 'Students close to graduation', path: '/close-to-graduation' },
+ ],
+ label: 'Special populations',
+ },
+ feedback: { key: 'feedback', label: 'Feedback', path: '/feedback' },
+ admin: {
+ key: 'admin',
+ items: [
+ { key: 'users', label: 'Users', path: '/users', reqRights: ['admin'] },
+ { key: 'updater', label: 'Updater', path: '/updater', reqRights: ['admin'] },
+ ],
+ label: 'Admin',
+ },
+}
diff --git a/services/shared/language.ts b/services/shared/language.ts
index 8f61d41731..146962890d 100644
--- a/services/shared/language.ts
+++ b/services/shared/language.ts
@@ -1,2 +1,3 @@
export const DEFAULT_LANG = 'fi'
export const LANGUAGE_CODES = ['fi', 'en', 'sv'] as const
+export const LANGUAGE_TEXTS = { fi: 'suomi', en: 'English', sv: 'svenska' } as const