Skip to content
This repository has been archived by the owner on Jan 20, 2025. It is now read-only.

Commit

Permalink
Merge pull request #114 from ls1intum/custom-mail-contents
Browse files Browse the repository at this point in the history
Add richt text editor with preview functionality
  • Loading branch information
airelawaleria authored Oct 8, 2023
2 parents 4b42699 + 7b61e46 commit 66dab5e
Show file tree
Hide file tree
Showing 16 changed files with 2,935 additions and 777 deletions.
1,724 changes: 1,617 additions & 107 deletions client/package-lock.json

Large diffs are not rendered by default.

26 changes: 19 additions & 7 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,26 @@
"dependencies": {
"@emotion/react": "^11.10.6",
"@formkit/auto-animate": "^0.7.0",
"@mantine/core": "6.0.19",
"@mantine/dates": "6.0.19",
"@mantine/dropzone": "^6.0.19",
"@mantine/form": "6.0.19",
"@mantine/hooks": "6.0.19",
"@mantine/notifications": "6.0.19",
"@mantine/core": "6.0.21",
"@mantine/dates": "6.0.21",
"@mantine/dropzone": "6.0.21",
"@mantine/form": "6.0.21",
"@mantine/hooks": "6.0.21",
"@mantine/notifications": "6.0.21",
"@mantine/tiptap": "^6.0.21",
"@reduxjs/toolkit": "^1.9.3",
"@tabler/icons-react": "^2.20.0",
"@tiptap/extension-color": "^2.1.11",
"@tiptap/extension-highlight": "^2.1.11",
"@tiptap/extension-link": "^2.1.11",
"@tiptap/extension-subscript": "^2.1.11",
"@tiptap/extension-superscript": "^2.1.11",
"@tiptap/extension-text-align": "^2.1.11",
"@tiptap/extension-text-style": "^2.1.11",
"@tiptap/extension-underline": "^2.1.11",
"@tiptap/pm": "^2.1.11",
"@tiptap/react": "^2.1.11",
"@tiptap/starter-kit": "^2.1.11",
"@types/node": "^16.18.12",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
Expand All @@ -21,7 +33,7 @@
"jwt-decode": "^3.1.2",
"keycloak-js": "^21.1.1",
"lodash.sortby": "^4.7.0",
"mantine-datatable": "2.9.0",
"mantine-datatable": "6.0.0",
"moment": "^2.29.4",
"papaparse": "^5.4.1",
"react": "^18.2.0",
Expand Down
11 changes: 11 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { IntroCourseConsole } from './instructor/IntroCourse/IntroCourseConsole'
import type Keycloak from 'keycloak-js'
import { ThesisApplicationsManagementConsole } from './instructor/ThesisApplicationsManagement/ThesisApplicationsManagementConsole'
import { StudentTechnicalDetailsSubmissionPage } from './student/StudentPostKickoffSubmissionPage/StudentTechnicalDetailsSubmissionPage'
import { MailingManagementConsole } from './instructor/MailingManagement/MailingManagementConsole'

export const App = (): JSX.Element => {
const [keycloakValue, setKeycloakValue] = useState<Keycloak>()
Expand Down Expand Up @@ -91,6 +92,16 @@ export const App = (): JSX.Element => {
/>
}
/>
<Route
path='/management/mailing'
element={
<ManagementConsole
child={<MailingManagementConsole />}
permission={['ipraktikum-pm', 'chair-member']}
onKeycloakValueChange={setKeycloakValue}
/>
}
/>
<Route
path='/management'
element={
Expand Down
5 changes: 5 additions & 0 deletions client/src/declaration.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ declare module 'react-beautiful-dnd'
declare module 'react-csv'
declare module 'papaparse'
declare module 'lodash/sortBy'
declare module '@tiptap/extension-highlight'
declare module '@tiptap/extension-underline'
declare module '@tiptap/extension-text-align'
declare module '@tiptap/extension-superscript'
declare module '@tiptap/extension-subscript'
316 changes: 316 additions & 0 deletions client/src/instructor/MailingManagement/MailingManagementConsole.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
import { RichTextEditor, Link } from '@mantine/tiptap'
import { useEditor, BubbleMenu } from '@tiptap/react'
import Highlight from '@tiptap/extension-highlight'
import StarterKit from '@tiptap/starter-kit'
import Underline from '@tiptap/extension-underline'
import TextAlign from '@tiptap/extension-text-align'
import Superscript from '@tiptap/extension-superscript'
import SubScript from '@tiptap/extension-subscript'
import {
ActionIcon,
Button,
Card,
Collapse,
ColorPicker,
Group,
Select,
Stack,
Tabs,
Text,
} from '@mantine/core'
import { MailTemplate, fetchMailTemplate, updateMailTemplate } from '../../service/mailingService'
import { useEffect, useState } from 'react'
import { IconEyeCheck, IconHtml, IconInfoCircle } from '@tabler/icons-react'
import {
StudentAndCourseIterationInstructions,
StudentAndCourseIterationAndDeveloperApplicationInstructions,
ThesisApplicationAndStudentAndThesisAdvisorInstructions,
ThesisApplicationAndStudentInstructions,
StudentAndCourseIterationAndCoachApplicationInstructions,
StudentAndCourseIterationAndTutorApplicationInstructions,
fillMockStudentPlaceholders,
fillMockCourseIterationPlaceholders,
fillMockThesisAdvisorPlaceholders,
fillMockThesisApplicationPlaceholders,
fillDeveloperApplicationPlaceholders,
fillMockCoachApplicationPlaceholders,
fillMockTutorApplicationPlaceholders,
} from './components/TemplateInstructions'

export const MailingManagementConsole = (): JSX.Element => {
const [activeTemplate, setActiveTemplate] = useState<string | null>(null)
const [instructionsOpened, setInstructionsOpened] = useState(false)
const [activeTab, setActiveTab] = useState<string | null>('editor')
const [previewContent, setPreviewContent] = useState<string>('')
const editor = useEditor({
extensions: [
StarterKit,
Underline,
Link,
Superscript,
SubScript,
Highlight,
ColorPicker,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
content: '',
})

useEffect(() => {
void (async () => {
if (activeTemplate) {
const response = await fetchMailTemplate(activeTemplate)
if (response) {
editor?.chain().setContent(response).run()
}
}
})()
}, [editor, activeTemplate])

useEffect(() => {
if (activeTab === 'preview') {
let preview = fillMockStudentPlaceholders(editor?.getHTML() ?? '')
preview = fillMockCourseIterationPlaceholders(preview)
if (
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.THESIS_APPLICATION_CONFIRMATION ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.THESIS_APPLICATION_CREATED ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.THESIS_APPLICATION_ACCEPTANCE ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.THESIS_APPLICATION_REJECTION ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.THESIS_APPLICATION_ACCEPTANCE_NO_ADVISOR
) {
preview = fillMockThesisAdvisorPlaceholders(preview)
preview = fillMockThesisApplicationPlaceholders(preview)
}
if (
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.DEVELOPER_APPLICATION_CONFIRMATION ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.TECHNICAL_DETAILS_SUBMISSION_INVIATION ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.KICK_OFF_SUBMISSION_INVITATION
) {
preview = fillDeveloperApplicationPlaceholders(preview)
}
if (
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.COACH_APPLICATION_ACCEPTANCE ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.COACH_APPLICATION_REJECTION ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.COACH_APPLICATION_CONFIRMATION ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.COACH_INTERVIEW_INVITATION
) {
preview = fillMockCoachApplicationPlaceholders(preview)
}
if (
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.TUTOR_APPLICATION_ACCEPTANCE ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.TUTOR_APPLICATION_REJECTION ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.TUTOR_APPLICATION_CONFIRMATION ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.TUTOR_INTERVIEW_INVITATION
) {
preview = fillMockTutorApplicationPlaceholders(preview)
}
setPreviewContent(preview)
}
}, [activeTab])

return (
<Stack>
<Group style={{ alignItems: 'end' }}>
<Select
style={{ width: '60vw' }}
label='Template'
placeholder='Template'
data={Object.keys(MailTemplate).map((key) => {
return {
label: MailTemplate[key as keyof typeof MailTemplate],
value: key,
}
})}
value={activeTemplate}
onChange={(value) => {
setActiveTab('editor')
setActiveTemplate(value as MailTemplate)
}}
/>
<ActionIcon
size={36}
color='blue'
variant='filled'
onClick={() => {
setInstructionsOpened(!instructionsOpened)
}}
>
<IconInfoCircle />
</ActionIcon>
</Group>
<Collapse in={instructionsOpened}>
<Card>
<Text fz='xs'>
For the below template you can use the following placeholders. Please wrap each accessor
with double curly brackets.
</Text>
{(MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.TECHNICAL_DETAILS_SUBMISSION_INVIATION ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.KICK_OFF_SUBMISSION_INVITATION ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.COACH_INTERVIEW_INVITATION ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.TUTOR_INTERVIEW_INVITATION ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.COACH_APPLICATION_ACCEPTANCE ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.TUTOR_APPLICATION_ACCEPTANCE ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.COACH_APPLICATION_REJECTION ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.TUTOR_APPLICATION_REJECTION) && (
<StudentAndCourseIterationInstructions />
)}
{MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.DEVELOPER_APPLICATION_CONFIRMATION && (
<StudentAndCourseIterationAndDeveloperApplicationInstructions />
)}
{MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.COACH_APPLICATION_CONFIRMATION && (
<StudentAndCourseIterationAndCoachApplicationInstructions />
)}
{MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.TUTOR_APPLICATION_CONFIRMATION && (
<StudentAndCourseIterationAndTutorApplicationInstructions />
)}
{(MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.THESIS_APPLICATION_ACCEPTANCE_NO_ADVISOR ||
MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.THESIS_APPLICATION_REJECTION) && (
<ThesisApplicationAndStudentInstructions />
)}
{MailTemplate[activeTemplate as keyof typeof MailTemplate] ===
MailTemplate.THESIS_APPLICATION_ACCEPTANCE && (
<ThesisApplicationAndStudentAndThesisAdvisorInstructions />
)}
</Card>
</Collapse>
<Tabs
defaultValue='editor'
value={activeTab}
onTabChange={(value) => {
setActiveTab(value)
}}
>
<Tabs.List>
<Tabs.Tab value='editor' icon={<IconHtml size='0.8rem' />}>
Editor
</Tabs.Tab>
<Tabs.Tab value='preview' icon={<IconEyeCheck size='0.8rem' />}>
Preview
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value='editor' pt='xs'>
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
<RichTextEditor.Strikethrough />
<RichTextEditor.ClearFormatting />
<RichTextEditor.Highlight />
<RichTextEditor.Code />
</RichTextEditor.ControlsGroup>

<RichTextEditor.ControlsGroup>
<RichTextEditor.H1 />
<RichTextEditor.H2 />
<RichTextEditor.H3 />
<RichTextEditor.H4 />
<RichTextEditor.H5 />
<RichTextEditor.H6 />
</RichTextEditor.ControlsGroup>

<RichTextEditor.ControlsGroup>
<RichTextEditor.Blockquote />
<RichTextEditor.Hr />
<RichTextEditor.BulletList />
<RichTextEditor.OrderedList />
<RichTextEditor.Subscript />
<RichTextEditor.Superscript />
</RichTextEditor.ControlsGroup>

<RichTextEditor.ControlsGroup>
<RichTextEditor.Link />
<RichTextEditor.Unlink />
</RichTextEditor.ControlsGroup>

<RichTextEditor.ControlsGroup>
<RichTextEditor.AlignLeft />
<RichTextEditor.AlignCenter />
<RichTextEditor.AlignJustify />
<RichTextEditor.AlignRight />
</RichTextEditor.ControlsGroup>

<RichTextEditor.ControlsGroup>
<RichTextEditor.ColorPicker
colors={[
'#25262b',
'#868e96',
'#fa5252',
'#e64980',
'#be4bdb',
'#7950f2',
'#4c6ef5',
'#228be6',
'#15aabf',
'#12b886',
'#40c057',
'#82c91e',
'#fab005',
'#fd7e14',
]}
/>
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
{editor && (
<BubbleMenu editor={editor}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Link />
</RichTextEditor.ControlsGroup>
</BubbleMenu>
)}
<RichTextEditor.Content />
</RichTextEditor>
</Tabs.Panel>
<Tabs.Panel value='preview' pt='xs'>
<Card>
<div dangerouslySetInnerHTML={{ __html: previewContent }} />
</Card>
</Tabs.Panel>
</Tabs>
<Group
position='right'
onClick={() => {
void (async () => {
if (activeTemplate) {
await updateMailTemplate(activeTemplate, editor?.getHTML() ?? '')
}
})()
}}
>
<Button>Save</Button>
</Group>
</Stack>
)
}
Loading

0 comments on commit 66dab5e

Please sign in to comment.