Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [
Expand Down Expand Up @@ -54,15 +53,6 @@ const applicationRoutesObjects: ExtendedRouteObject[] = [
),
requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER],
},
{
path: '/mailing',
element: (
<ApplicationDataWrapper>
<ApplicationMailingSettings />
</ApplicationDataWrapper>
),
requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER],
},
]

export const ApplicationRoutes = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -29,6 +30,7 @@ export const ApplicationConfiguration = () => {

{applicationMetaData && <ApplicationGeneralSettings initialData={applicationMetaData} />}
{applicationMetaData && <ApplicationSettingsCustomScores initialData={applicationMetaData} />}
<ApplicationMailingSettings />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { type UpdateCoursePhase, useGetMailingIsConfigured } from '@tumaet/prompt-shared-state'
import {
Button,
Card,
CardContent,
EmailTemplateEditor,
ManagementPageHeader,
MissingConfig,
type MissingConfigItem,
Tabs,
Expand Down Expand Up @@ -109,72 +110,74 @@ export const ApplicationMailingSettings = () => {
}, [courseId, courseMailingIsConfigured])

return (
<div className='space-y-6'>
<ManagementPageHeader>Application Mailing Settings</ManagementPageHeader>
<MissingConfig elements={missingConfigs} />
<SettingsCard
applicationMailingMetaData={applicationMailingMetaData}
handleSwitchChange={handleSwitchChange}
isModified={isModified}
/>
<h2 className='text-2xl font-bold'>Mailing Templates </h2>
<Card className='w-full'>
<CardContent className='space-y-6 pt-6'>
<h3 className='text-lg font-semibold'>Mailing</h3>
<MissingConfig elements={missingConfigs} />
<SettingsCard
applicationMailingMetaData={applicationMailingMetaData}
handleSwitchChange={handleSwitchChange}
isModified={isModified}
/>
<h3 className='text-lg font-semibold'>Mailing Templates</h3>

<CustomApplicationPlaceHolder />
{/* ensures that tiptap editor is only loaded after receiving meta data */}
{initialMetaData && (
<Tabs defaultValue='confirmation' className='w-full'>
<TabsList className='grid w-full grid-cols-3'>
<TabsTrigger value='confirmation'>1. Confirmation</TabsTrigger>
<TabsTrigger value='acceptance'>2. Acceptance</TabsTrigger>
<TabsTrigger value='rejection'>3. Rejection</TabsTrigger>
</TabsList>
<TabsContent value='confirmation'>
<EmailTemplateEditor
subject={applicationMailingMetaData.confirmationMailSubject}
content={applicationMailingMetaData.confirmationMailContent}
onInputChange={handleInputChange}
label='Confirmation'
subjectHTMLLabel='confirmationMailSubject'
contentHTMLLabel='confirmationMailContent'
placeholders={applicationMailingPlaceholders.map(
(placeholder) => placeholder.placeholder,
)}
/>
</TabsContent>
<TabsContent value='acceptance'>
<EmailTemplateEditor
subject={applicationMailingMetaData.passedMailSubject}
content={applicationMailingMetaData.passedMailContent}
onInputChange={handleInputChange}
label='Acceptance'
subjectHTMLLabel='passedMailSubject'
contentHTMLLabel='passedMailContent'
placeholders={applicationMailingPlaceholders.map(
(placeholder) => placeholder.placeholder,
)}
/>
</TabsContent>
<TabsContent value='rejection'>
<EmailTemplateEditor
subject={applicationMailingMetaData.failedMailSubject}
content={applicationMailingMetaData.failedMailContent}
onInputChange={handleInputChange}
label='Rejection'
subjectHTMLLabel='failedMailSubject'
contentHTMLLabel='failedMailContent'
placeholders={applicationMailingPlaceholders.map(
(placeholder) => placeholder.placeholder,
)}
/>
</TabsContent>
</Tabs>
)}
<CustomApplicationPlaceHolder />
{/* ensures that tiptap editor is only loaded after receiving meta data */}
{initialMetaData && (
<Tabs defaultValue='confirmation' className='w-full'>
<TabsList className='grid w-full grid-cols-3'>
<TabsTrigger value='confirmation'>1. Confirmation</TabsTrigger>
<TabsTrigger value='acceptance'>2. Acceptance</TabsTrigger>
<TabsTrigger value='rejection'>3. Rejection</TabsTrigger>
</TabsList>
<TabsContent value='confirmation'>
<EmailTemplateEditor
subject={applicationMailingMetaData.confirmationMailSubject}
content={applicationMailingMetaData.confirmationMailContent}
onInputChange={handleInputChange}
label='Confirmation'
subjectHTMLLabel='confirmationMailSubject'
contentHTMLLabel='confirmationMailContent'
placeholders={applicationMailingPlaceholders.map(
(placeholder) => placeholder.placeholder,
)}
/>
</TabsContent>
<TabsContent value='acceptance'>
<EmailTemplateEditor
subject={applicationMailingMetaData.passedMailSubject}
content={applicationMailingMetaData.passedMailContent}
onInputChange={handleInputChange}
label='Acceptance'
subjectHTMLLabel='passedMailSubject'
contentHTMLLabel='passedMailContent'
placeholders={applicationMailingPlaceholders.map(
(placeholder) => placeholder.placeholder,
)}
/>
</TabsContent>
<TabsContent value='rejection'>
<EmailTemplateEditor
subject={applicationMailingMetaData.failedMailSubject}
content={applicationMailingMetaData.failedMailContent}
onInputChange={handleInputChange}
label='Rejection'
subjectHTMLLabel='failedMailSubject'
contentHTMLLabel='failedMailContent'
placeholders={applicationMailingPlaceholders.map(
(placeholder) => placeholder.placeholder,
)}
/>
</TabsContent>
</Tabs>
)}

<div className='justify-end flex'>
<Button onClick={handleSubmit} type='submit' className='ml-auto' disabled={!isModified}>
Save Changes
</Button>
</div>
</div>
<div className='justify-end flex'>
<Button onClick={handleSubmit} type='submit' className='ml-auto' disabled={!isModified}>
Save Changes
</Button>
</div>
</CardContent>
</Card>
)
}
14 changes: 2 additions & 12 deletions clients/interview_component/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { type ExtendedRouteObject, Role } from '@tumaet/prompt-shared-state'
import { InterviewDataShell } from '../src/interview/pages/InterviewDataShell'
import { InterviewParticipantsPage } from '../src/interview/pages/InterviewParticipantsPage/InterviewParticipantsPage'
import { MailingPage } from '../src/interview/pages/Mailing/MailingPage'
import OverviewPage from '../src/interview/pages/Overview/OverviewPage'
import { ProfileDetailPage } from '../src/interview/pages/ProfileDetail/ProfileDetailPage'
import { InterviewScheduleManagement } from '../src/interview/pages/ScheduleManagement/InterviewScheduleManagement'
import { QuestionConfiguration } from '../src/interview/pages/Settings/QuestionConfiguration'
import { SettingsPage } from '../src/interview/pages/Settings/SettingsPage'
import { StudentInterviewPage } from '../src/interview/pages/StudentInterview/StudentInterviewPage'

const interviewRoutes: ExtendedRouteObject[] = [
Expand Down Expand Up @@ -42,15 +41,6 @@ const interviewRoutes: ExtendedRouteObject[] = [
element: <InterviewParticipantsPage />,
requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER],
},
{
path: '/mailing',
element: (
<InterviewDataShell>
<MailingPage />
</InterviewDataShell>
),
requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER],
},
{
path: '/schedule',
element: <InterviewScheduleManagement />,
Expand All @@ -60,7 +50,7 @@ const interviewRoutes: ExtendedRouteObject[] = [
path: '/settings',
element: (
<InterviewDataShell>
<QuestionConfiguration />
<SettingsPage />
</InterviewDataShell>
),
requiredPermissions: [Role.PROMPT_ADMIN, Role.COURSE_LECTURER],
Expand Down
5 changes: 0 additions & 5 deletions clients/interview_component/sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -99,10 +94,9 @@ export const QuestionConfiguration = () => {

return (
<div className='flex flex-col'>
{/* Sticky header */}
<div className='sticky top-0 bg-background p-4 z-10 border-b'>
<div className='pb-4'>
<header className='mb-4'>
<ManagementPageHeader>Interview Question Configuration</ManagementPageHeader>
<h2 className='text-2xl font-bold'>Interview Questions</h2>
Comment thread
rappm marked this conversation as resolved.
<p className='text-muted-foreground'>
These questions will be used as a template during interviews. Deleting a question will
make any associated notes or responses inaccessible.
Expand Down Expand Up @@ -130,8 +124,7 @@ export const QuestionConfiguration = () => {
</div>
</div>

{/* Scrollable content */}
<div className='grow overflow-auto h-[calc(100vh-300px)] p-4'>
<div className='grow'>
{/* Saving indicator */}
{isPending && (
<div className='mb-2 text-xs text-muted-foreground flex items-center gap-1'>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<div className='flex flex-col gap-8 p-4'>
<ManagementPageHeader>Settings</ManagementPageHeader>
<QuestionConfiguration />
<section className='space-y-4'>
<h2 className='text-2xl font-bold'>Mailing</h2>
<CoursePhaseMailing coursePhase={coursePhase} />
</section>
</div>
)
}
10 changes: 8 additions & 2 deletions e2e/src/data/permissionMatrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export interface Surface {
browser?: {
path: (courseId: string) => string
heading: string // the page <h1> rendered when authorized
// extra text asserted on the allowed side when the <h1> is a generic page title
// (e.g. a section header) so the surface still proves its own content rendered
assertText?: string
allowed: Role[]
}
api?: {
Expand Down Expand Up @@ -105,8 +108,11 @@ 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; the <h1> 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',
Comment thread
rappm marked this conversation as resolved.
assertText: 'E-Mail Settings',
allowed: ['admin', 'lecturer', 'course-lecturer'],
},
api: {
Expand Down
10 changes: 6 additions & 4 deletions e2e/src/pages/ApplicationAdminPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down
7 changes: 5 additions & 2 deletions e2e/tests/permissions/browser-matrix.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
})
}
Expand Down
Loading