From 16a757e3eceaa3002398b22679214be7096676fd Mon Sep 17 00:00:00 2001 From: Maximilian Rapp Date: Mon, 13 Jul 2026 21:04:22 +0200 Subject: [PATCH 1/4] `Core`: Move phase mailing into settings for application and interview --- .../ExternalRoutes/ApplicationRoutes.tsx | 10 ---------- .../ExternalSidebars/ApplicationSidebar.tsx | 5 ----- .../hooks/useMissingConfig.ts | 2 +- .../ApplicationSettings.tsx | 2 ++ .../Mailing/ApplicationMailingSettings.tsx | 3 +-- clients/interview_component/routes/index.tsx | 14 ++------------ clients/interview_component/sidebar/index.tsx | 5 ----- .../src/interview/pages/Mailing/MailingPage.tsx | 12 ------------ .../pages/Settings/QuestionConfiguration.tsx | 14 ++++---------- .../interview/pages/Settings/SettingsPage.tsx | 17 +++++++++++++++++ 10 files changed, 27 insertions(+), 57 deletions(-) delete mode 100644 clients/interview_component/src/interview/pages/Mailing/MailingPage.tsx create mode 100644 clients/interview_component/src/interview/pages/Settings/SettingsPage.tsx diff --git a/clients/core/src/managementConsole/PhaseMapping/ExternalRoutes/ApplicationRoutes.tsx b/clients/core/src/managementConsole/PhaseMapping/ExternalRoutes/ApplicationRoutes.tsx index 1a7139c87..306a03f83 100644 --- a/clients/core/src/managementConsole/PhaseMapping/ExternalRoutes/ApplicationRoutes.tsx +++ b/clients/core/src/managementConsole/PhaseMapping/ExternalRoutes/ApplicationRoutes.tsx @@ -5,7 +5,6 @@ import { ApplicationLandingPage } from '../../applicationAdministration/pages/Ap import { ApplicationParticipantsPage } from '../../applicationAdministration/pages/ApplicationParticipantsPage/ApplicationParticipantsPage' import { ApplicationQuestionConfig } from '../../applicationAdministration/pages/ApplicationQuestionConfigPage/ApplicationQuestionConfig' import { ApplicationConfiguration } from '../../applicationAdministration/pages/ApplicationSettingsPage/ApplicationSettings' -import { ApplicationMailingSettings } from '../../applicationAdministration/pages/Mailing/ApplicationMailingSettings' import { ExternalRoutes } from './ExternalRoutes' const applicationRoutesObjects: ExtendedRouteObject[] = [ @@ -54,15 +53,6 @@ const applicationRoutesObjects: ExtendedRouteObject[] = [ ), requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER], }, - { - path: '/mailing', - element: ( - - - - ), - requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER], - }, ] export const ApplicationRoutes = () => { diff --git a/clients/core/src/managementConsole/PhaseMapping/ExternalSidebars/ApplicationSidebar.tsx b/clients/core/src/managementConsole/PhaseMapping/ExternalSidebars/ApplicationSidebar.tsx index 48f191f81..04f59ff33 100644 --- a/clients/core/src/managementConsole/PhaseMapping/ExternalSidebars/ApplicationSidebar.tsx +++ b/clients/core/src/managementConsole/PhaseMapping/ExternalSidebars/ApplicationSidebar.tsx @@ -27,11 +27,6 @@ export const ApplicationSidebar = ({ goToPath: '/questions', requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER], }, - { - title: 'Mailing', - goToPath: '/mailing', - requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER], - }, { title: 'Settings', goToPath: '/settings', diff --git a/clients/core/src/managementConsole/applicationAdministration/pages/ApplicationLandingPage/hooks/useMissingConfig.ts b/clients/core/src/managementConsole/applicationAdministration/pages/ApplicationLandingPage/hooks/useMissingConfig.ts index a7ad05c10..bb097b352 100644 --- a/clients/core/src/managementConsole/applicationAdministration/pages/ApplicationLandingPage/hooks/useMissingConfig.ts +++ b/clients/core/src/managementConsole/applicationAdministration/pages/ApplicationLandingPage/hooks/useMissingConfig.ts @@ -30,7 +30,7 @@ export const useMissingConfigs = ( description: `This application phase has no mailing settings configured. If you do not want to send mails, you can hide this warning.`, icon: MailWarningIcon, - link: `${path}/mailing`, + link: `${path}/settings`, hide: hideMailingWarning, }) } diff --git a/clients/core/src/managementConsole/applicationAdministration/pages/ApplicationSettingsPage/ApplicationSettings.tsx b/clients/core/src/managementConsole/applicationAdministration/pages/ApplicationSettingsPage/ApplicationSettings.tsx index 3fc89b616..399fe63d6 100644 --- a/clients/core/src/managementConsole/applicationAdministration/pages/ApplicationSettingsPage/ApplicationSettings.tsx +++ b/clients/core/src/managementConsole/applicationAdministration/pages/ApplicationSettingsPage/ApplicationSettings.tsx @@ -4,6 +4,7 @@ import type { ApplicationMetaData } from '../../interfaces/applicationMetaData' import { getIsApplicationConfigured } from '../../utils/getApplicationIsConfigured' import { getApplicationStatus } from '../../utils/getApplicationStatus' import { useApplicationStore } from '../../zustand/useApplicationStore' +import { ApplicationMailingSettings } from '../Mailing/ApplicationMailingSettings' import ApplicationOverview from './components/Overview/ApplicationSettingsOverview' import { ApplicationSettingsCustomScores } from './components/SettingsCustomScores/ApplicationSettingsCustomScores' import { ApplicationGeneralSettings } from './components/SettingsGeneral/ApplicationSettingsGeneral' @@ -29,6 +30,7 @@ export const ApplicationConfiguration = () => { {applicationMetaData && } {applicationMetaData && } + ) } diff --git a/clients/core/src/managementConsole/applicationAdministration/pages/Mailing/ApplicationMailingSettings.tsx b/clients/core/src/managementConsole/applicationAdministration/pages/Mailing/ApplicationMailingSettings.tsx index b932b5889..d187c16a4 100644 --- a/clients/core/src/managementConsole/applicationAdministration/pages/Mailing/ApplicationMailingSettings.tsx +++ b/clients/core/src/managementConsole/applicationAdministration/pages/Mailing/ApplicationMailingSettings.tsx @@ -2,7 +2,6 @@ import { type UpdateCoursePhase, useGetMailingIsConfigured } from '@tumaet/promp import { Button, EmailTemplateEditor, - ManagementPageHeader, MissingConfig, type MissingConfigItem, Tabs, @@ -110,7 +109,7 @@ export const ApplicationMailingSettings = () => { return (
- Application Mailing Settings +

Mailing

, requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER], }, - { - path: '/mailing', - element: ( - - - - ), - requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER], - }, { path: '/schedule', element: , @@ -60,7 +50,7 @@ const interviewRoutes: ExtendedRouteObject[] = [ path: '/settings', element: ( - + ), requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER], diff --git a/clients/interview_component/sidebar/index.tsx b/clients/interview_component/sidebar/index.tsx index 5d3ed4ea2..5c1ac9535 100644 --- a/clients/interview_component/sidebar/index.tsx +++ b/clients/interview_component/sidebar/index.tsx @@ -27,11 +27,6 @@ const interviewSidebarItems: SidebarMenuItemProps = { goToPath: '/schedule', requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER], }, - { - title: 'Mailing', - goToPath: '/mailing', - requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER], - }, { title: 'Settings', goToPath: '/settings', diff --git a/clients/interview_component/src/interview/pages/Mailing/MailingPage.tsx b/clients/interview_component/src/interview/pages/Mailing/MailingPage.tsx deleted file mode 100644 index 313feb24f..000000000 --- a/clients/interview_component/src/interview/pages/Mailing/MailingPage.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { CoursePhaseMailing, ManagementPageHeader } from '@tumaet/prompt-ui-components' -import { useCoursePhaseStore } from '../../zustand/useCoursePhaseStore' - -export const MailingPage = () => { - const { coursePhase } = useCoursePhaseStore() - return ( -
- Mailing - -
- ) -} diff --git a/clients/interview_component/src/interview/pages/Settings/QuestionConfiguration.tsx b/clients/interview_component/src/interview/pages/Settings/QuestionConfiguration.tsx index 1202348b9..1417fea78 100644 --- a/clients/interview_component/src/interview/pages/Settings/QuestionConfiguration.tsx +++ b/clients/interview_component/src/interview/pages/Settings/QuestionConfiguration.tsx @@ -1,11 +1,6 @@ import { DragDropContext, Draggable, Droppable, type DropResult } from '@hello-pangea/dnd' import { useUpdateCoursePhaseMetaData } from '@tumaet/prompt-shared-state' -import { - Button, - DeleteConfirmation, - Input, - ManagementPageHeader, -} from '@tumaet/prompt-ui-components' +import { Button, DeleteConfirmation, Input } from '@tumaet/prompt-ui-components' import { GripVertical, Loader2, Plus, Trash2 } from 'lucide-react' import { useEffect, useRef, useState } from 'react' import { useDebouncedCallback } from 'use-debounce' @@ -99,10 +94,9 @@ export const QuestionConfiguration = () => { return (
- {/* Sticky header */} -
+
- Interview Question Configuration +

Interview Questions

These questions will be used as a template during interviews. Deleting a question will make any associated notes or responses inaccessible. @@ -131,7 +125,7 @@ export const QuestionConfiguration = () => {

{/* Scrollable content */} -
+
{/* Saving indicator */} {isPending && (
diff --git a/clients/interview_component/src/interview/pages/Settings/SettingsPage.tsx b/clients/interview_component/src/interview/pages/Settings/SettingsPage.tsx new file mode 100644 index 000000000..a494cdfa2 --- /dev/null +++ b/clients/interview_component/src/interview/pages/Settings/SettingsPage.tsx @@ -0,0 +1,17 @@ +import { CoursePhaseMailing, ManagementPageHeader } from '@tumaet/prompt-ui-components' +import { useCoursePhaseStore } from '../../zustand/useCoursePhaseStore' +import { QuestionConfiguration } from './QuestionConfiguration' + +export const SettingsPage = () => { + const { coursePhase } = useCoursePhaseStore() + return ( +
+ Settings + +
+

Mailing

+ +
+
+ ) +} From 2bd031aba399dfd9ca2b02b3961ba242f7caa1f7 Mon Sep 17 00:00:00 2001 From: Maximilian Rapp Date: Wed, 15 Jul 2026 12:13:15 +0200 Subject: [PATCH 2/4] `Testing`: Point mailing permission surface at Application settings page --- e2e/src/data/permissionMatrix.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/e2e/src/data/permissionMatrix.ts b/e2e/src/data/permissionMatrix.ts index c42fa0e3e..d4caf399b 100644 --- a/e2e/src/data/permissionMatrix.ts +++ b/e2e/src/data/permissionMatrix.ts @@ -105,8 +105,9 @@ export const SURFACES: Surface[] = [ { name: 'mailing', browser: { - path: (courseId) => `/management/course/${courseId}/${MAILING_PHASE_ID}/mailing`, - heading: 'Application Mailing Settings', + // Mailing config moved into the Application settings page. + path: (courseId) => `/management/course/${courseId}/${MAILING_PHASE_ID}/settings`, + heading: 'Application Settings', allowed: ['admin', 'lecturer', 'course-lecturer'], }, api: { From 2f2952cf121afdf5257815fe89a41ac6eaffbfde Mon Sep 17 00:00:00 2001 From: Maximilian Rapp Date: Thu, 16 Jul 2026 17:47:48 +0200 Subject: [PATCH 3/4] Core: align phase settings sections and mailing e2e coverage - Wrap the application mailing section in a Card with h3 headers so it matches the sibling settings sections and restores sequential heading order - Drop the nested scroll region in interview question configuration so the section uses the settings page's single scroll instead of a nested scrollbar - Assert a mailing-specific control in the permission matrix so the mailing surface exercises its own content, not just the generic page title --- .../Mailing/ApplicationMailingSettings.tsx | 134 +++++++++--------- .../pages/Settings/QuestionConfiguration.tsx | 3 +- e2e/src/data/permissionMatrix.ts | 7 +- e2e/tests/permissions/browser-matrix.spec.ts | 7 +- 4 files changed, 81 insertions(+), 70 deletions(-) diff --git a/clients/core/src/managementConsole/applicationAdministration/pages/Mailing/ApplicationMailingSettings.tsx b/clients/core/src/managementConsole/applicationAdministration/pages/Mailing/ApplicationMailingSettings.tsx index d187c16a4..5b7398e69 100644 --- a/clients/core/src/managementConsole/applicationAdministration/pages/Mailing/ApplicationMailingSettings.tsx +++ b/clients/core/src/managementConsole/applicationAdministration/pages/Mailing/ApplicationMailingSettings.tsx @@ -1,6 +1,8 @@ import { type UpdateCoursePhase, useGetMailingIsConfigured } from '@tumaet/prompt-shared-state' import { Button, + Card, + CardContent, EmailTemplateEditor, MissingConfig, type MissingConfigItem, @@ -108,72 +110,74 @@ export const ApplicationMailingSettings = () => { }, [courseId, courseMailingIsConfigured]) return ( -
-

Mailing

- - -

Mailing Templates

+ + +

Mailing

+ + +

Mailing Templates

- - {/* ensures that tiptap editor is only loaded after receiving meta data */} - {initialMetaData && ( - - - 1. Confirmation - 2. Acceptance - 3. Rejection - - - placeholder.placeholder, - )} - /> - - - placeholder.placeholder, - )} - /> - - - placeholder.placeholder, - )} - /> - - - )} + + {/* ensures that tiptap editor is only loaded after receiving meta data */} + {initialMetaData && ( + + + 1. Confirmation + 2. Acceptance + 3. Rejection + + + placeholder.placeholder, + )} + /> + + + placeholder.placeholder, + )} + /> + + + placeholder.placeholder, + )} + /> + + + )} -
- -
-
+
+ +
+ + ) } diff --git a/clients/interview_component/src/interview/pages/Settings/QuestionConfiguration.tsx b/clients/interview_component/src/interview/pages/Settings/QuestionConfiguration.tsx index 1417fea78..83a7d30b9 100644 --- a/clients/interview_component/src/interview/pages/Settings/QuestionConfiguration.tsx +++ b/clients/interview_component/src/interview/pages/Settings/QuestionConfiguration.tsx @@ -124,8 +124,7 @@ export const QuestionConfiguration = () => {
- {/* Scrollable content */} -
+
{/* Saving indicator */} {isPending && (
diff --git a/e2e/src/data/permissionMatrix.ts b/e2e/src/data/permissionMatrix.ts index d4caf399b..3723a9364 100644 --- a/e2e/src/data/permissionMatrix.ts +++ b/e2e/src/data/permissionMatrix.ts @@ -36,6 +36,9 @@ export interface Surface { browser?: { path: (courseId: string) => string heading: string // the page

rendered when authorized + // extra text asserted on the allowed side when the

is a generic page title + // (e.g. a section header) so the surface still proves its own content rendered + assertText?: string allowed: Role[] } api?: { @@ -105,9 +108,11 @@ export const SURFACES: Surface[] = [ { name: 'mailing', browser: { - // Mailing config moved into the Application settings page. + // Mailing config moved into the Application settings page; the

is now the + // generic page title, so assert the mailing section's own control too. path: (courseId) => `/management/course/${courseId}/${MAILING_PHASE_ID}/settings`, heading: 'Application Settings', + assertText: 'E-Mail Settings', allowed: ['admin', 'lecturer', 'course-lecturer'], }, api: { diff --git a/e2e/tests/permissions/browser-matrix.spec.ts b/e2e/tests/permissions/browser-matrix.spec.ts index 690709d76..e688bd1be 100644 --- a/e2e/tests/permissions/browser-matrix.spec.ts +++ b/e2e/tests/permissions/browser-matrix.spec.ts @@ -9,8 +9,11 @@ import { const browserSurfaces = SURFACES.filter((s) => s.browser) -async function expectAllowed(page: Page, heading: string) { +async function expectAllowed(page: Page, heading: string, assertText?: string) { await expect(page.getByRole('heading', { level: 1, name: heading })).toBeVisible() + if (assertText) { + await expect(page.getByText(assertText).first()).toBeVisible() + } } // Blocked pages render UnauthorizedPage ("Access Denied"). Asserting that overlay @@ -32,7 +35,7 @@ test.describe('permission matrix (browser)', () => { test(`${allowed ? 'sees' : 'is blocked from'} ${surface.name}`, async ({ page }) => { await page.goto(browser.path(PRIMARY_COURSE.id)) - if (allowed) await expectAllowed(page, browser.heading) + if (allowed) await expectAllowed(page, browser.heading, browser.assertText) else await expectBlocked(page, browser.heading) }) } From 836cf040250599ce945715aca24ec8d61b8ed472 Mon Sep 17 00:00:00 2001 From: Maximilian Rapp Date: Fri, 17 Jul 2026 23:55:02 +0200 Subject: [PATCH 4/4] Testing: Point application mailing e2e at the settings page Mailing configuration moved into the Application settings page, so the mailing e2e now navigates to /settings and asserts the mailing section's own controls instead of the removed standalone page heading. --- e2e/src/pages/ApplicationAdminPage.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/e2e/src/pages/ApplicationAdminPage.ts b/e2e/src/pages/ApplicationAdminPage.ts index 481335ce0..f203f6c67 100644 --- a/e2e/src/pages/ApplicationAdminPage.ts +++ b/e2e/src/pages/ApplicationAdminPage.ts @@ -47,14 +47,16 @@ export class ApplicationAdminPage { await expect(dialog.getByRole('heading', { name: 'Personal Information' })).toBeVisible() } + // Mailing configuration moved into the Application settings page. async gotoMailing(courseId: string, phaseId: string) { - await this.page.goto(`/management/course/${courseId}/${phaseId}/mailing`) + await this.page.goto(`/management/course/${courseId}/${phaseId}/settings`) + await expect( + this.page.getByRole('heading', { name: 'Application Settings' }), + ).toBeVisible({ timeout: 15_000 }) } async expectMailingLoaded() { - await expect( - this.page.getByRole('heading', { name: 'Application Mailing Settings' }), - ).toBeVisible({ timeout: 15_000 }) + await expect(this.page.getByText('E-Mail Settings').first()).toBeVisible({ timeout: 15_000 }) await expect(this.page.getByRole('heading', { name: 'Mailing Templates' })).toBeVisible() }